Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,42 @@ class Template {
}
}

// HIDOrg model class
class HIDOrg {
constructor(data = {}) {
this.id = data.id;
this.name = data.name;
this.slug = data.slug;
this.firstName = data.first_name;
this.lastName = data.last_name;
this.phone = data.phone;
this.fullAddress = data.full_address;
this.status = data.status;
this.createdAt = data.created_at;
}
}

// LedgerItem model class
class LedgerItem {
constructor(data = {}) {
this.id = data.id;
this.amount = data.amount;
this.kind = data.kind;
this.createdAt = data.created_at;

if (data.access_pass) {
this.accessPass = {
exId: data.access_pass.ex_id,
passTemplate: data.access_pass.pass_template
? { exId: data.access_pass.pass_template.ex_id }
: null,
};
} else {
this.accessPass = null;
}
}
}

// PassTemplatePair model class
class PassTemplatePair {
constructor(data = {}) {
Expand Down Expand Up @@ -395,6 +431,9 @@ class AccessCardsApi extends BaseApi {
class ConsoleApi extends BaseApi {
constructor(accountId, secretKey, baseUrl) {
super(accountId, secretKey, baseUrl);
this.hid = {
orgs: new HIDOrgsApi(accountId, secretKey, baseUrl),
};
}

_buildTemplateBody(params) {
Expand Down Expand Up @@ -476,6 +515,50 @@ class ConsoleApi extends BaseApi {
return this.getEventLogs(params);
}

async iosPreflight(params) {
const body = {};
if (params.accessPassExId) body.access_pass_ex_id = params.accessPassExId;

const response = await this.request(
`/v1/console/card-templates/${params.cardTemplateId}/ios_preflight`,
{ method: "POST", body },
);

return {
provisioningCredentialIdentifier:
response.provisioning_credential_identifier,
sharingInstanceIdentifier: response.sharing_instance_identifier,
cardTemplateIdentifier: response.card_template_identifier,
environmentIdentifier: response.environment_identifier,
};
}

async ledgerItems(params = {}) {
const queryParams = new URLSearchParams();
if (params.page) queryParams.append("page", params.page);
if (params.perPage) queryParams.append("per_page", params.perPage);
if (params.startDate) queryParams.append("start_date", params.startDate);
if (params.endDate) queryParams.append("end_date", params.endDate);

const queryString = queryParams.toString();
const path = queryString
? `/v1/console/ledger-items?${queryString}`
: "/v1/console/ledger-items";

const response = await this.request(path);

const result = {};
if (response.ledger_items) {
result.ledgerItems = response.ledger_items.map(
(item) => new LedgerItem(item),
);
}
if (response.pagination) {
result.pagination = response.pagination;
}
return result;
}

async listPassTemplatePairs(params = {}) {
const queryParams = new URLSearchParams();
if (params.page) queryParams.append("page", params.page);
Expand All @@ -499,6 +582,45 @@ class ConsoleApi extends BaseApi {
}
}

// HID Orgs API handling
class HIDOrgsApi extends BaseApi {
constructor(accountId, secretKey, baseUrl) {
super(accountId, secretKey, baseUrl);
}

async create(params) {
const body = {
name: params.name,
full_address: params.fullAddress,
phone: params.phone,
first_name: params.firstName,
last_name: params.lastName,
};

const response = await this.request("/v1/console/hid/orgs", {
method: "POST",
body,
});
return new HIDOrg(response);
}

async list() {
const response = await this.request("/v1/console/hid/orgs");
return (response.hid_orgs || []).map((org) => new HIDOrg(org));
}

async activate(params) {
const response = await this.request("/v1/console/hid/orgs/activate", {
method: "POST",
body: {
email: params.email,
password: params.password,
},
});
return new HIDOrg(response);
}
}

// Main AccessGrid class
class AccessGrid {
constructor(accountId, secretKey, options = {}) {
Expand All @@ -521,6 +643,8 @@ export {
Template,
PassTemplatePair,
TemplateInfo,
HIDOrg,
LedgerItem,
};

// Default export
Expand Down
220 changes: 219 additions & 1 deletion test/index.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import AccessGrid, { AccessGridError, AuthenticationError, AccessCard, Template, PassTemplatePair, TemplateInfo } from '../src/index';
import AccessGrid, { AccessGridError, AuthenticationError, AccessCard, Template, PassTemplatePair, TemplateInfo, HIDOrg, LedgerItem } from '../src/index';

// ══════════════════════════════════════════════════════════════════════════════
// Global mocks
Expand Down Expand Up @@ -756,6 +756,50 @@ describe('AccessGrid SDK', () => {
expect(info.platform).toBe('apple');
});

test('HIDOrg should have correct properties', () => {
const org = new HIDOrg({
id: 'org-1',
name: 'My Org',
slug: 'my-org',
first_name: 'Ada',
last_name: 'Lovelace',
phone: '+1-555-0000',
full_address: '1 Main St, NY NY',
status: 'active',
created_at: '2025-01-01T00:00:00Z'
});

expect(org.id).toBe('org-1');
expect(org.name).toBe('My Org');
expect(org.slug).toBe('my-org');
expect(org.firstName).toBe('Ada');
expect(org.lastName).toBe('Lovelace');
expect(org.phone).toBe('+1-555-0000');
expect(org.fullAddress).toBe('1 Main St, NY NY');
expect(org.status).toBe('active');
expect(org.createdAt).toBe('2025-01-01T00:00:00Z');
});

test('LedgerItem should have correct properties', () => {
const item = new LedgerItem({
id: 'li-1',
amount: '1.50',
kind: 'issuance',
created_at: '2025-01-01T00:00:00Z',
access_pass: {
ex_id: 'pass-1',
pass_template: { ex_id: 'tmpl-1' }
}
});

expect(item.id).toBe('li-1');
expect(item.amount).toBe('1.50');
expect(item.kind).toBe('issuance');
expect(item.createdAt).toBe('2025-01-01T00:00:00Z');
expect(item.accessPass.exId).toBe('pass-1');
expect(item.accessPass.passTemplate.exId).toBe('tmpl-1');
});

test('Template should have metadata property', () => {
const template = new Template({
id: 'template-123',
Expand Down Expand Up @@ -794,6 +838,180 @@ describe('AccessGrid SDK', () => {
});
});

// ════════════════════════════════════════════════════════════════════════════
// HID Orgs API
// ════════════════════════════════════════════════════════════════════════════

describe('HID Orgs API', () => {
describe('create', () => {
test('should make correct API call', async () => {
const mockResponse = {
id: 'org-1', name: 'My Org', slug: 'my-org',
first_name: 'Ada', last_name: 'Lovelace',
phone: '+1-555-0000', full_address: '1 Main St, NY NY',
status: 'pending', created_at: '2025-01-01T00:00:00Z'
};
global.fetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(mockResponse)
});

const org = await client.console.hid.orgs.create({
name: 'My Org',
fullAddress: '1 Main St, NY NY',
phone: '+1-555-0000',
firstName: 'Ada',
lastName: 'Lovelace'
});

expect(fetch).toHaveBeenCalledWith(
expect.stringContaining('/v1/console/hid/orgs'),
expect.objectContaining({ method: 'POST' })
);
expect(org).toBeInstanceOf(HIDOrg);
expect(org.name).toBe('My Org');
expect(org.slug).toBe('my-org');
});

test('should send snake_case params', async () => {
global.fetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ id: 'org-1' })
});

await client.console.hid.orgs.create({
name: 'My Org',
fullAddress: '1 Main St',
phone: '+1-555-0000',
firstName: 'Ada',
lastName: 'Lovelace'
});

const callBody = JSON.parse(fetch.mock.calls[0][1].body);
expect(callBody.name).toBe('My Org');
expect(callBody.full_address).toBe('1 Main St');
expect(callBody.first_name).toBe('Ada');
expect(callBody.last_name).toBe('Lovelace');
});
});

describe('list', () => {
test('should return array of HIDOrg instances', async () => {
global.fetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({
hid_orgs: [
{ id: 'org-1', name: 'Org 1', slug: 'org-1' },
{ id: 'org-2', name: 'Org 2', slug: 'org-2' }
]
})
});

const orgs = await client.console.hid.orgs.list();

expect(fetch).toHaveBeenCalledWith(
expect.stringContaining('/v1/console/hid/orgs'),
expect.objectContaining({ method: 'GET' })
);
expect(orgs).toHaveLength(2);
expect(orgs[0]).toBeInstanceOf(HIDOrg);
});
});

describe('activate', () => {
test('should make correct API call', async () => {
global.fetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ id: 'org-1', name: 'My Org', status: 'active' })
});

const result = await client.console.hid.orgs.activate({
email: 'admin@example.com',
password: 'hid-password-123'
});

expect(fetch).toHaveBeenCalledWith(
expect.stringContaining('/v1/console/hid/orgs/activate'),
expect.objectContaining({ method: 'POST' })
);
expect(result).toBeInstanceOf(HIDOrg);
expect(result.status).toBe('active');
});
});
});

// ════════════════════════════════════════════════════════════════════════════
// iOS Preflight
// ════════════════════════════════════════════════════════════════════════════

describe('iOS Preflight', () => {
test('should make correct API call', async () => {
const mockResponse = {
provisioning_credential_identifier: 'pci-123',
sharing_instance_identifier: 'sii-456',
card_template_identifier: 'cti-789',
environment_identifier: 'env-abc'
};
global.fetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(mockResponse)
});

const result = await client.console.iosPreflight({
cardTemplateId: '0xt3mp14t3',
accessPassExId: '0xp455'
});

expect(fetch).toHaveBeenCalledWith(
expect.stringContaining('/v1/console/card-templates/0xt3mp14t3/ios_preflight'),
expect.objectContaining({ method: 'POST' })
);
expect(result.provisioningCredentialIdentifier).toBe('pci-123');
expect(result.sharingInstanceIdentifier).toBe('sii-456');
expect(result.cardTemplateIdentifier).toBe('cti-789');
expect(result.environmentIdentifier).toBe('env-abc');
});
});

// ════════════════════════════════════════════════════════════════════════════
// Ledger Items
// ════════════════════════════════════════════════════════════════════════════

describe('Ledger Items', () => {
test('should make correct API call with pagination', async () => {
const mockResponse = {
ledger_items: [
{ id: 'li-1', amount: '1.50', kind: 'issuance', created_at: '2025-01-01T00:00:00Z' },
{ id: 'li-2', amount: '0.50', kind: 'activation', created_at: '2025-01-02T00:00:00Z' }
],
pagination: { current_page: 1, total_pages: 3, total_count: 50 }
};
global.fetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(mockResponse)
});

const result = await client.console.ledgerItems({
page: 1,
perPage: 50,
startDate: '2025-01-01T00:00:00Z',
endDate: '2025-02-01T00:00:00Z'
});

expect(fetch).toHaveBeenCalledWith(
expect.stringContaining('/v1/console/ledger-items'),
expect.objectContaining({ method: 'GET' })
);
expect(fetch).toHaveBeenCalledWith(
expect.stringContaining('page=1'),
expect.anything()
);
expect(result.ledgerItems).toHaveLength(2);
expect(result.ledgerItems[0]).toBeInstanceOf(LedgerItem);
expect(result.pagination).toBeDefined();
});
});

// ════════════════════════════════════════════════════════════════════════════
// Provision with new fields
// ════════════════════════════════════════════════════════════════════════════
Expand Down
Loading