@wallethero/sdk
Version:
TypeScript SDK for WalletHero API - manage pass templates and passes for Apple Wallet and Google Pay
1,650 lines (1,640 loc) • 43.2 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
FilesService: () => FilesService,
HttpClient: () => HttpClient,
PassTemplatesService: () => PassTemplatesService,
PassesService: () => PassesService,
ProjectsService: () => ProjectsService,
QRCodeService: () => QRCodeService,
WalletHero: () => WalletHero,
WalletHeroError: () => WalletHeroError,
WorkspacesService: () => WorkspacesService,
default: () => WalletHero
});
module.exports = __toCommonJS(index_exports);
// src/http-client.ts
var WalletHeroError = class extends Error {
constructor(message, code, status, details) {
super(message);
this.code = code;
this.status = status;
this.details = details;
this.name = "WalletHeroError";
}
};
var HttpClient = class {
constructor(apiUrl, apiToken, timeout = 3e4) {
this.baseUrl = apiUrl.replace(/\/$/, "");
this.token = apiToken;
this.timeout = timeout;
}
// Public getters for use in other services
get apiBaseUrl() {
return this.baseUrl;
}
get apiToken() {
return this.token;
}
buildUrl(endpoint, options) {
const url = new URL(`${this.baseUrl}${endpoint}`);
if (options) {
if (options.fields) {
url.searchParams.set("fields", options.fields.join(","));
}
if (options.filter) {
url.searchParams.set("filter", JSON.stringify(options.filter));
}
if (options.sort) {
url.searchParams.set("sort", options.sort.join(","));
}
if (options.limit) {
url.searchParams.set("limit", options.limit.toString());
}
if (options.offset) {
url.searchParams.set("offset", options.offset.toString());
}
if (options.search) {
url.searchParams.set("search", options.search);
}
}
return url.toString();
}
async handleResponse(response) {
if (!response.ok) {
let errorData;
try {
errorData = await response.json();
} catch {
errorData = { message: response.statusText };
}
if (errorData.errors && Array.isArray(errorData.errors)) {
const error = errorData.errors[0];
throw new WalletHeroError(
error.message || "Unknown error",
error.extensions?.code || void 0,
response.status,
errorData
);
}
throw new WalletHeroError(
errorData.message || `HTTP ${response.status}: ${response.statusText}`,
"HTTP_ERROR",
response.status,
errorData
);
}
const contentType = response.headers.get("content-type");
if (contentType?.includes("application/json")) {
return response.json();
}
return response.text();
}
async get(endpoint, options) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
try {
const response = await fetch(this.buildUrl(endpoint, options), {
method: "GET",
headers: {
Authorization: `Bearer ${this.token}`,
"Content-Type": "application/json"
},
signal: controller.signal
});
return this.handleResponse(response);
} catch (error) {
if (error instanceof Error && error.name === "AbortError") {
throw new WalletHeroError("Request timeout", "TIMEOUT", 0);
}
throw error;
} finally {
clearTimeout(timeoutId);
}
}
async post(endpoint, data) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
try {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
method: "POST",
headers: {
Authorization: `Bearer ${this.token}`,
"Content-Type": "application/json"
},
body: data ? JSON.stringify(data) : void 0,
signal: controller.signal
});
return this.handleResponse(response);
} catch (error) {
if (error instanceof Error && error.name === "AbortError") {
throw new WalletHeroError("Request timeout", "TIMEOUT", 0);
}
throw error;
} finally {
clearTimeout(timeoutId);
}
}
async patch(endpoint, data) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
try {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
method: "PATCH",
headers: {
Authorization: `Bearer ${this.token}`,
"Content-Type": "application/json"
},
body: data ? JSON.stringify(data) : void 0,
signal: controller.signal
});
return this.handleResponse(response);
} catch (error) {
if (error instanceof Error && error.name === "AbortError") {
throw new WalletHeroError("Request timeout", "TIMEOUT", 0);
}
throw error;
} finally {
clearTimeout(timeoutId);
}
}
async delete(endpoint) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
try {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
method: "DELETE",
headers: {
Authorization: `Bearer ${this.token}`,
"Content-Type": "application/json"
},
signal: controller.signal
});
return this.handleResponse(response);
} catch (error) {
if (error instanceof Error && error.name === "AbortError") {
throw new WalletHeroError("Request timeout", "TIMEOUT", 0);
}
throw error;
} finally {
clearTimeout(timeoutId);
}
}
};
// src/files.ts
var FilesService = class {
constructor(httpClient) {
this.http = httpClient;
}
/**
* Upload a file
*/
async upload(file, options) {
const formData = new FormData();
if (file instanceof File) {
formData.append("file", file);
} else {
formData.append("file", file, options?.title || "upload");
}
if (options?.title) {
formData.append("title", options.title);
}
if (options?.description) {
formData.append("description", options.description);
}
if (options?.folder) {
formData.append("folder", options.folder);
}
if (options?.tags) {
formData.append("tags", JSON.stringify(options.tags));
}
const response = await fetch(`${this.http.apiBaseUrl}/files`, {
method: "POST",
headers: {
Authorization: `Bearer ${this.http.apiToken}`
},
body: formData
});
if (!response.ok) {
throw new Error(`File upload failed: ${response.statusText}`);
}
return response.json();
}
/**
* Get file information by ID
*/
async get(id, options) {
return this.http.get(`/files/${id}`, options);
}
/**
* List files
*/
async list(options) {
return this.http.get("/files", options);
}
/**
* Delete a file
*/
async delete(id) {
await this.http.delete(`/files/${id}`);
}
/**
* Update file metadata
*/
async update(id, data) {
return this.http.patch(`/files/${id}`, data);
}
/**
* Get file URL for download/display
*/
getFileUrl(id, options) {
const baseUrl = this.http.apiBaseUrl;
let url = `${baseUrl}/assets/${id}`;
if (options) {
const params = new URLSearchParams();
if (options.width) params.set("width", options.width.toString());
if (options.height) params.set("height", options.height.toString());
if (options.fit) params.set("fit", options.fit);
if (options.quality) params.set("quality", options.quality.toString());
if (options.format) params.set("format", options.format);
const queryString = params.toString();
if (queryString) {
url += `?${queryString}`;
}
}
return url;
}
/**
* Upload image with automatic optimization for pass templates
*/
async uploadPassImage(file, type, options) {
const folderMap = {
icon: "166196bd-e7f2-46f4-aee8-02f1ad8a6b4d",
logo: "6c49ee61-a3c6-44c1-8d1e-49d755bd4bf7",
cover_image: "5e7dc33a-16ee-4cf3-bc41-2c8617e15500"
};
return this.upload(file, {
...options,
folder: folderMap[type],
title: options?.title || `Pass ${type} image`
});
}
};
// src/pass-templates.ts
var PassTemplatesService = class {
constructor(httpClient) {
this.http = httpClient;
this.files = new FilesService(httpClient);
}
/**
* Filter out fields that shouldn't be sent in update requests
*/
filterUpdateData(data) {
const {
id,
user_created,
date_created,
user_updated,
date_updated,
statistics,
uuid,
...filteredData
} = data;
return filteredData;
}
/**
* Get a list of pass templates
*/
async list(options) {
return this.http.get(
"/items/pass_templates",
options
);
}
/**
* Get a specific pass template by ID (UUID)
*/
async get(id, options) {
return this.http.get(
`/items/pass_templates/${id}`,
options
);
}
/**
* Create a new pass template
*/
async create(data) {
const filteredData = this.filterUpdateData(data);
return this.http.post(
"/items/pass_templates",
filteredData
);
}
/**
* Update an existing pass template
*/
async update(id, data) {
const filteredData = this.filterUpdateData(data);
return this.http.patch(
`/items/pass_templates/${id}`,
filteredData
);
}
/**
* Delete a pass template
*/
async delete(id) {
await this.http.delete(`/items/pass_templates/${id}`);
}
/**
* Get pass templates by workspace
*/
async getByWorkspace(workspaceId, options) {
const filterOptions = {
...options,
filter: {
...options?.filter,
workspace_id: { _eq: workspaceId }
}
};
return this.list(filterOptions);
}
/**
* Get pass templates by project
*/
async getByProject(projectId, options) {
const filterOptions = {
...options,
filter: {
...options?.filter,
project_id: { _eq: projectId }
}
};
return this.list(filterOptions);
}
/**
* Search pass templates by name
*/
async search(query, options) {
const searchOptions = {
...options,
search: query
};
return this.list(searchOptions);
}
/**
* Get pass templates by type
*/
async getByType(passType, options) {
const filterOptions = {
...options,
filter: {
...options?.filter,
pass_type: { _eq: passType }
}
};
return this.list(filterOptions);
}
/**
* Duplicate a pass template
*/
async duplicate(id) {
const template = await this.get(id);
const filteredData = this.filterUpdateData(template.data);
return this.create({
...filteredData,
name: `${template.data.name} (Copy)`,
uuid: crypto.randomUUID()
// Generate new UUID for the copy
});
}
/**
* Upload and set template images
*/
async updateImages(id, images) {
const updateData = {};
for (const [key, value] of Object.entries(images)) {
if (value instanceof File || value instanceof Blob) {
const uploadResult = await this.files.uploadPassImage(
value,
key
);
updateData[key] = uploadResult.data.id;
} else if (typeof value === "string") {
updateData[key] = value;
}
}
return this.update(id, updateData);
}
/**
* Get image URLs for a template
*/
getImageUrls(template) {
const urls = {};
if (template.icon) {
urls.icon = this.files.getFileUrl(template.icon);
}
if (template.logo) {
urls.logo = this.files.getFileUrl(template.logo);
}
if (template.cover_image) {
urls.cover_image = this.files.getFileUrl(template.cover_image);
}
return urls;
}
/**
* Get optimized image URLs for different uses
*/
getOptimizedImageUrls(template) {
const urls = {};
if (template.icon) {
urls.icon = {
small: this.files.getFileUrl(template.icon, {
width: 50,
height: 50,
fit: "cover"
}),
medium: this.files.getFileUrl(template.icon, {
width: 100,
height: 100,
fit: "cover"
}),
large: this.files.getFileUrl(template.icon, {
width: 200,
height: 200,
fit: "cover"
})
};
}
if (template.logo) {
urls.logo = {
small: this.files.getFileUrl(template.logo, {
width: 100,
height: 50,
fit: "contain"
}),
medium: this.files.getFileUrl(template.logo, {
width: 200,
height: 100,
fit: "contain"
}),
large: this.files.getFileUrl(template.logo, {
width: 400,
height: 200,
fit: "contain"
})
};
}
if (template.cover_image) {
urls.cover_image = {
small: this.files.getFileUrl(template.cover_image, {
width: 300,
height: 200,
fit: "cover"
}),
medium: this.files.getFileUrl(template.cover_image, {
width: 600,
height: 400,
fit: "cover"
}),
large: this.files.getFileUrl(template.cover_image, {
width: 1200,
height: 800,
fit: "cover"
})
};
}
return urls;
}
/**
* Remove images from a template
*/
async removeImages(id, imageTypes) {
const updateData = {};
for (const type of imageTypes) {
updateData[type] = null;
}
return this.update(id, updateData);
}
/**
* Upload icon image
*/
async uploadIcon(id, file, _options) {
return this.updateImages(id, { icon: file });
}
/**
* Upload logo image
*/
async uploadLogo(id, file, _options) {
return this.updateImages(id, { logo: file });
}
/**
* Upload cover image
*/
async uploadCoverImage(id, file, _options) {
return this.updateImages(id, { cover_image: file });
}
/**
* Add a custom field to a pass template
*/
async addCustomField(id, customField) {
const template = await this.get(id);
const existingFields = template.data.custom_fields || [];
if (existingFields.some((field) => field.name === customField.name)) {
throw new Error(
`Custom field with name '${customField.name}' already exists`
);
}
const updatedFields = [...existingFields, customField];
return this.update(id, { custom_fields: updatedFields });
}
/**
* Update a custom field in a pass template
*/
async updateCustomField(id, fieldName, updatedField) {
const template = await this.get(id);
const existingFields = template.data.custom_fields || [];
const fieldIndex = existingFields.findIndex(
(field) => field.name === fieldName
);
if (fieldIndex === -1) {
throw new Error(`Custom field with name '${fieldName}' not found`);
}
if (updatedField.name && updatedField.name !== fieldName) {
if (existingFields.some((field) => field.name === updatedField.name)) {
throw new Error(
`Custom field with name '${updatedField.name}' already exists`
);
}
}
const updatedFields = [...existingFields];
updatedFields[fieldIndex] = {
...updatedFields[fieldIndex],
...updatedField
};
return this.update(id, { custom_fields: updatedFields });
}
/**
* Remove a custom field from a pass template
*/
async removeCustomField(id, fieldName) {
const template = await this.get(id);
const existingFields = template.data.custom_fields || [];
const updatedFields = existingFields.filter(
(field) => field.name !== fieldName
);
if (updatedFields.length === existingFields.length) {
throw new Error(`Custom field with name '${fieldName}' not found`);
}
return this.update(id, { custom_fields: updatedFields });
}
/**
* Get all custom fields for a pass template
*/
async getCustomFields(id) {
const template = await this.get(id);
return template.data.custom_fields || [];
}
/**
* Set all custom fields for a pass template (replaces existing)
*/
async setCustomFields(id, customFields) {
const fieldNames = customFields.map((field) => field.name);
const uniqueNames = new Set(fieldNames);
if (fieldNames.length !== uniqueNames.size) {
throw new Error("Duplicate field names are not allowed");
}
return this.update(id, { custom_fields: customFields });
}
/**
* Set iOS deeplink configuration for a pass template
*/
async setIOSDeeplink(id, deeplinkConfig) {
return this.update(id, deeplinkConfig);
}
/**
* Remove iOS deeplink configuration from a pass template
*/
async removeIOSDeeplink(id) {
return this.update(id, {
ios_deeplink_id: void 0,
ios_deeplink_url: void 0
});
}
/**
* Get iOS deeplink configuration for a pass template
*/
async getIOSDeeplink(id) {
const template = await this.get(id);
return {
ios_deeplink_id: template.data.ios_deeplink_id,
ios_deeplink_url: template.data.ios_deeplink_url
};
}
/**
* Get main templates only
*/
async getMainTemplates(options) {
const filterOptions = {
...options,
filter: {
...options?.filter,
is_main_template: { _eq: true }
}
};
return this.list(filterOptions);
}
/**
* Get non-main templates only
*/
async getCurrentTemplates(options) {
const filterOptions = {
...options,
filter: {
...options?.filter,
is_main_template: { _neq: true }
}
};
return this.list(filterOptions);
}
/**
* Get the main template for a project
*/
async getMainTemplateForProject(projectId, options) {
const filterOptions = {
...options,
filter: {
...options?.filter,
project_id: { _eq: projectId },
is_main_template: { _eq: true }
},
limit: 1
};
const response = await this.list(filterOptions);
return {
data: response.data.length > 0 ? response.data[0] : null
};
}
/**
* Set template as main template for its project
*/
async setAsMainTemplate(id) {
const template = await this.get(id);
const projectId = template.data.project_id;
const existingMainResponse = await this.getMainTemplateForProject(
projectId
);
if (existingMainResponse.data) {
await this.update(existingMainResponse.data.id, {
is_main_template: false
});
}
return this.update(id, { is_main_template: true });
}
/**
* Unset template as main template
*/
async unsetAsMainTemplate(id) {
return this.update(id, { is_main_template: false });
}
/**
* Update template custom fields (new template-level custom fields)
*/
async updateTemplateCustomFields(id, customFields) {
return this.update(id, { template_custom_fields: customFields });
}
/**
* Get template custom fields
*/
async getTemplateCustomFields(id) {
const template = await this.get(id);
return template.data.template_custom_fields || {};
}
/**
* Merge template custom fields with existing ones
*/
async mergeTemplateCustomFields(id, customFields) {
const existingFields = await this.getTemplateCustomFields(id);
const mergedFields = { ...existingFields, ...customFields };
return this.updateTemplateCustomFields(id, mergedFields);
}
/**
* Remove specific template custom fields
*/
async removeTemplateCustomFields(id, fieldNames) {
const existingFields = await this.getTemplateCustomFields(id);
const updatedFields = { ...existingFields };
for (const fieldName of fieldNames) {
delete updatedFields[fieldName];
}
return this.updateTemplateCustomFields(id, updatedFields);
}
/**
* Get templates by tier
*/
async getByTier(tierId, options) {
const filterOptions = {
...options,
filter: {
...options?.filter,
tier_id: { _eq: tierId }
}
};
return this.list(filterOptions);
}
/**
* Set template tier
*/
async setTier(id, tierId, tierLabel) {
return this.update(id, {
tier_id: tierId,
tier_label: tierLabel || void 0
});
}
/**
* Remove template tier
*/
async removeTier(id) {
return this.update(id, {
tier_id: void 0,
tier_label: void 0
});
}
};
// src/passes.ts
var PassesService = class {
constructor(httpClient) {
this.http = httpClient;
}
/**
* Get a list of passes
*/
async list(options) {
return this.http.get("/items/passes", options);
}
/**
* Get a specific pass by ID (UUID)
*/
async get(id, options) {
return this.http.get(`/items/passes/${id}`, options);
}
/**
* Create a new pass
*/
async create(data) {
return this.http.post("/items/passes", data);
}
/**
* Update an existing pass
*/
async update(id, data) {
return this.http.patch(`/items/passes/${id}`, data);
}
/**
* Delete a pass
*/
async delete(id) {
await this.http.delete(`/items/passes/${id}`);
}
/**
* Get passes by workspace
*/
async getByWorkspace(workspaceId, options) {
const filterOptions = {
...options,
filter: {
...options?.filter,
workspace_id: { _eq: workspaceId }
}
};
return this.list(filterOptions);
}
/**
* Get passes by template
*/
async getByTemplate(templateId, options) {
const filterOptions = {
...options,
filter: {
...options?.filter,
pass_template_id: { _eq: templateId }
}
};
return this.list(filterOptions);
}
/**
* Get passes by project
*/
async getByProject(projectId, options) {
const filterOptions = {
...options,
filter: {
...options?.filter,
project_id: { _eq: projectId }
}
};
return this.list(filterOptions);
}
/**
* Get passes by current template (for template inheritance)
*/
async getByCurrentTemplate(currentTemplateId, options) {
const filterOptions = {
...options,
filter: {
...options?.filter,
current_template_id: { _eq: currentTemplateId }
}
};
return this.list(filterOptions);
}
/**
* Search passes by email or name
*/
async search(query, options) {
const searchOptions = {
...options,
search: query
};
return this.list(searchOptions);
}
/**
* Send push notification to update a pass
*/
async sendNotification(id, message) {
const updateData = {};
if (message) {
updateData.notification = message;
}
await this.update(id, updateData);
}
/**
* Bulk create passes from template
*/
async bulkCreateFromTemplate(templateId, passes) {
const passesData = passes.map((pass) => ({
...pass,
pass_template_id: templateId
// ID will be auto-generated as UUID by the database
}));
return this.http.post("/items/passes", passesData);
}
// ===== CUSTOM FIELDS MANAGEMENT =====
/**
* Update pass custom fields (replaces the old updatePayload method)
*/
async updateCustomFields(id, customFields) {
return this.update(id, { custom_fields: customFields });
}
/**
* Get custom fields for a specific pass
*/
async getCustomFields(id) {
const response = await this.get(id, { fields: ["custom_fields"] });
return response.data.custom_fields || {};
}
/**
* Set a single custom field value
*/
async setCustomField(id, fieldName, value) {
const currentFields = await this.getCustomFields(id);
const updatedFields = {
...currentFields,
[fieldName]: value
};
return this.updateCustomFields(id, updatedFields);
}
/**
* Get a single custom field value
*/
async getCustomField(id, fieldName) {
const customFields = await this.getCustomFields(id);
return customFields[fieldName];
}
/**
* Remove a custom field
*/
async removeCustomField(id, fieldName) {
const currentFields = await this.getCustomFields(id);
const { [fieldName]: _removed, ...remainingFields } = currentFields;
return this.updateCustomFields(id, remainingFields);
}
/**
* Merge custom fields (adds new fields without removing existing ones)
*/
async mergeCustomFields(id, fieldsToMerge) {
const currentFields = await this.getCustomFields(id);
const mergedFields = {
...currentFields,
...fieldsToMerge
};
return this.updateCustomFields(id, mergedFields);
}
/**
* Clear all custom fields
*/
async clearCustomFields(id) {
return this.updateCustomFields(id, {});
}
/**
* Check if a custom field exists
*/
async hasCustomField(id, fieldName) {
const customFields = await this.getCustomFields(id);
return fieldName in customFields;
}
/**
* Get all custom field names for a pass
*/
async getCustomFieldNames(id) {
const customFields = await this.getCustomFields(id);
return Object.keys(customFields);
}
/**
* Bulk update custom fields for multiple passes
*/
async bulkUpdateCustomFields(updates) {
const updatePromises = updates.map(
({ passId, customFields }) => this.updateCustomFields(passId, customFields)
);
const results = await Promise.all(updatePromises);
return {
data: results.map((result) => result.data),
meta: {
total_count: results.length,
filter_count: results.length
}
};
}
/**
* Search passes by custom field value
*/
async searchByCustomField(fieldName, value, options) {
const searchOptions = {
...options,
filter: {
...options?.filter,
[`custom_fields.${fieldName}`]: { _eq: value }
}
};
return this.list(searchOptions);
}
/**
* Filter passes by multiple custom field criteria
*/
async filterByCustomFields(criteria, options) {
const customFieldFilters = {};
for (const [fieldName, value] of Object.entries(criteria)) {
customFieldFilters[`custom_fields.${fieldName}`] = { _eq: value };
}
const filterOptions = {
...options,
filter: {
...options?.filter,
...customFieldFilters
}
};
return this.list(filterOptions);
}
// ===== LEGACY SUPPORT =====
/**
* @deprecated Use updateCustomFields instead
* Update pass payload (custom data) - kept for backward compatibility
*/
async updatePayload(id, payload) {
console.warn(
"updatePayload is deprecated. Use updateCustomFields instead."
);
return this.updateCustomFields(id, payload);
}
// ===== TEMPLATE INHERITANCE SUPPORT =====
/**
* Switch pass to a different template within the same project
*/
async switchTemplate(id, newTemplateId) {
return this.update(id, { current_template_id: newTemplateId });
}
/**
* Reset pass to use its original template (unset current_template_id)
*/
async resetToOriginalTemplate(id) {
return this.update(id, { current_template_id: void 0 });
}
/**
* Get complete pass data with template inheritance
*/
async getCompleteData(id) {
return this.http.get(`/passes/${id}/complete-data`);
}
/**
* Bulk switch passes to a different template within the same project
*/
async bulkSwitchTemplate(passIds, newTemplateId) {
const updatePromises = passIds.map(
(passId) => this.switchTemplate(passId, newTemplateId)
);
const results = await Promise.all(updatePromises);
return {
data: results.map((result) => result.data),
meta: {
total_count: results.length,
filter_count: results.length
}
};
}
/**
* Get passes that are using template inheritance (have current_template_id)
*/
async getPassesWithTemplateInheritance(options) {
const filterOptions = {
...options,
filter: {
...options?.filter,
current_template_id: { _nnull: true }
}
};
return this.list(filterOptions);
}
/**
* Get passes that are NOT using template inheritance (no current_template_id)
*/
async getPassesWithoutTemplateInheritance(options) {
const filterOptions = {
...options,
filter: {
...options?.filter,
current_template_id: { _null: true }
}
};
return this.list(filterOptions);
}
/**
* Update pass with template inheritance context
*/
async updateWithTemplateInheritance(id, data) {
return this.update(id, data);
}
};
// src/projects.ts
var ProjectsService = class {
constructor(httpClient) {
this.http = httpClient;
}
/**
* Get a list of projects
*/
async list(options) {
const listOptions = {
...options,
fields: options?.fields || ["*"]
};
return this.http.get(
"/items/projects",
listOptions
);
}
/**
* Get a specific project by ID (UUID)
*/
async get(id, options) {
const getOptions = {
...options,
fields: options?.fields || ["*"]
};
return this.http.get(
`/items/projects/${id}`,
getOptions
);
}
/**
* Create a new project
*/
async create(data) {
return this.http.post("/items/projects", data);
}
/**
* Update an existing project
*/
async update(id, data) {
return this.http.patch(`/items/projects/${id}`, data);
}
/**
* Delete a project
*/
async delete(id) {
await this.http.delete(`/items/projects/${id}`);
}
/**
* Get projects by workspace
*/
async getByWorkspace(workspaceId, options) {
const filterOptions = {
...options,
fields: options?.fields || ["*"],
filter: {
...options?.filter,
workspace_id: { _eq: workspaceId }
}
};
return this.list(filterOptions);
}
/**
* Search projects by name
*/
async search(query, options) {
const searchOptions = {
...options,
search: query
};
return this.list(searchOptions);
}
/**
* Get the main template for a project
*/
async getMainTemplate(projectId) {
const response = await this.http.get(
"/items/pass_templates",
{
filter: {
project_id: { _eq: projectId },
is_main_template: { _eq: true }
},
limit: 1
}
);
return {
data: response.data.length > 0 ? response.data[0] : null
};
}
/**
* Set a template as the main template for a project
*/
async setMainTemplate(projectId, templateId) {
const existingMainResponse = await this.getMainTemplate(projectId);
if (existingMainResponse.data) {
await this.http.patch(
`/items/pass_templates/${existingMainResponse.data.id}`,
{ is_main_template: false }
);
}
return this.http.patch(
`/items/pass_templates/${templateId}`,
{ is_main_template: true }
);
}
/**
* Unset the main template for a project
*/
async unsetMainTemplate(projectId) {
const existingMainResponse = await this.getMainTemplate(projectId);
if (existingMainResponse.data) {
await this.http.patch(
`/items/pass_templates/${existingMainResponse.data.id}`,
{ is_main_template: false }
);
}
}
/**
* Get all templates for a project
*/
async getTemplates(projectId, options) {
const filterOptions = {
...options,
filter: {
...options?.filter,
project_id: { _eq: projectId }
}
};
return this.http.get(
"/items/pass_templates",
filterOptions
);
}
/**
* Get all passes for a project
*/
async getPasses(projectId, options) {
const filterOptions = {
...options,
filter: {
...options?.filter,
project_id: { _eq: projectId }
}
};
return this.http.get("/items/passes", filterOptions);
}
/**
* Get complete pass data with template inheritance for a pass
*/
async getCompletePassData(passId) {
return this.http.get(
`/passes/${passId}/complete-data`
);
}
/**
* Merge templates using inheritance logic (main template + current template)
*/
async mergeTemplates(mainTemplateId, currentTemplateId) {
return this.http.post("/templates/merge", {
mainTemplateId,
currentTemplateId
});
}
/**
* Update project custom fields
*/
async updateCustomFields(id, customFields) {
return this.update(id, { custom_fields: customFields });
}
/**
* Get project custom fields
*/
async getCustomFields(id) {
const project = await this.get(id);
return project.data.custom_fields || {};
}
/**
* Merge custom fields with existing ones
*/
async mergeCustomFields(id, customFields) {
const existingFields = await this.getCustomFields(id);
const mergedFields = { ...existingFields, ...customFields };
return this.updateCustomFields(id, mergedFields);
}
/**
* Remove specific custom fields
*/
async removeCustomFields(id, fieldNames) {
const existingFields = await this.getCustomFields(id);
const updatedFields = { ...existingFields };
for (const fieldName of fieldNames) {
delete updatedFields[fieldName];
}
return this.updateCustomFields(id, updatedFields);
}
/**
* Get project statistics
*/
async getStats(id) {
const [templatesResponse, passesResponse, mainTemplateResponse] = await Promise.all([
this.getTemplates(id, { limit: 0 }),
// Only need count
this.getPasses(id, { limit: 0 }),
// Only need count
this.getMainTemplate(id)
]);
return {
templateCount: templatesResponse.meta?.total_count || 0,
passCount: passesResponse.meta?.total_count || 0,
mainTemplate: mainTemplateResponse.data
};
}
/**
* Duplicate a project with all its templates
*/
async duplicate(id, newName) {
const project = await this.get(id);
const {
id: _,
user_created: _uc,
date_created: _dc,
user_updated: _uu,
date_updated: _du,
...projectData
} = project.data;
return this.create({
...projectData,
name: newName || `${projectData.name} (Copy)`
});
}
};
// src/qrcode.ts
var QRCodeService = class {
constructor(httpClient) {
this.http = httpClient;
}
/**
* Get pass data to retrieve QR code URLs
*/
async getPassData(passId) {
const response = await this.http.get(
`/items/passes/${passId}`
);
return response.data;
}
/**
* Private helper method to build QR code URL with query parameters
*/
async getQRCodeUrlForPassType(passType, passId, options) {
const passData = await this.getPassData(passId);
const qrCodeUrl = passType === "apple" ? passData.apple_qrcode_url : passData.google_qrcode_url;
if (!qrCodeUrl) {
throw new Error(`${passType} QR code URL not found for pass ${passId}`);
}
const params = new URLSearchParams();
if (options?.width) params.append("width", options.width.toString());
if (options?.margin) params.append("margin", options.margin.toString());
if (options?.dark) params.append("dark", options.dark);
if (options?.light) params.append("light", options.light);
if (options?.format) params.append("format", options.format);
return `${qrCodeUrl}${params.toString() ? `?${params.toString()}` : ""}`;
}
/**
* Get QR code URL for Apple Pass
* Returns URL that can be used directly in img src or for JSON format
*/
async getApplePassQRCode(passId, options) {
const url = await this.getQRCodeUrlForPassType("apple", passId, options);
if (options?.format === "json") {
return {
passId,
url: await this.getApplePassURL(passId),
qrCode: url,
format: "url"
};
}
return url;
}
/**
* Get QR code URL for Google Wallet Pass
* Returns URL that can be used directly in img src or for JSON format
*/
async getGooglePassQRCode(passId, options) {
const url = await this.getQRCodeUrlForPassType("google", passId, options);
if (options?.format === "json") {
return {
passId,
url: await this.getGooglePassURL(passId),
qrCode: url,
format: "url"
};
}
return url;
}
/**
* Get Apple Pass URL for QR code generation from pass data
*/
async getApplePassURL(passId) {
const passData = await this.getPassData(passId);
if (!passData.apple_pass_url) {
throw new Error(`Apple pass URL not found for pass ${passId}`);
}
return passData.apple_pass_url;
}
/**
* Get Google Wallet Pass URL for QR code generation from pass data
*/
async getGooglePassURL(passId) {
const passData = await this.getPassData(passId);
if (!passData.google_pass_url) {
throw new Error(`Google pass URL not found for pass ${passId}`);
}
return passData.google_pass_url;
}
};
// src/workspaces.ts
var WorkspacesService = class {
constructor(httpClient) {
this.http = httpClient;
}
/**
* Get a list of workspaces
*/
async list(options) {
return this.http.get(
"/items/workspaces",
options
);
}
/**
* Get a specific workspace by ID (UUID)
*/
async get(id, options) {
return this.http.get(
`/items/workspaces/${id}`,
options
);
}
/**
* Create a new workspace
*/
async create(data) {
return this.http.post("/items/workspaces", data);
}
/**
* Update an existing workspace
*/
async update(id, data) {
return this.http.patch(
`/items/workspaces/${id}`,
data
);
}
/**
* Delete a workspace
*/
async delete(id) {
await this.http.delete(`/items/workspaces/${id}`);
}
/**
* Search workspaces by name
*/
async search(query, options) {
const searchOptions = {
...options,
search: query
};
return this.list(searchOptions);
}
/**
* Get workspaces by name (exact match)
*/
async getByName(name, options) {
const filterOptions = {
...options,
filter: {
...options?.filter,
name: { _eq: name }
}
};
return this.list(filterOptions);
}
/**
* Get workspace with related data (passes, templates, etc.)
*/
async getWithRelations(id, includeFields) {
const defaultFields = [
"*",
"passes.id",
"passes.full_name",
"passes.email",
"passes.date_created",
"apple_registrations.id",
"apple_registrations.device_library_identifier",
"google_registrations.id",
"google_registrations.class_id"
];
const fields = includeFields || defaultFields;
return this.get(id, { fields });
}
/**
* Get workspace statistics
*/
async getStats(id) {
const workspace = await this.get(id, {
fields: [
"*",
"passes_func.count",
"apple_registrations_func.count",
"google_registrations_func.count"
]
});
const templatesResponse = await this.http.get(
"/items/pass_templates",
{
filter: { workspace_id: { _eq: id } },
limit: 0
// We only need the count from meta
}
);
return {
passCount: workspace.data.passes_func?.count || 0,
templateCount: templatesResponse.meta?.total_count || 0,
appleRegistrationCount: workspace.data.apple_registrations_func?.count || 0,
googleRegistrationCount: workspace.data.google_registrations_func?.count || 0
};
}
/**
* Get current user's workspaces
*/
async getMy(options) {
return this.list(options);
}
/**
* Check if workspace name is available
*/
async isNameAvailable(name) {
const response = await this.getByName(name, { limit: 1 });
return response.data.length === 0;
}
/**
* Get workspace members (requires junction table access)
*/
async getMembers(id) {
return this.http.get(
"/items/junction_directus_users_workspaces",
{
filter: { workspaces_id: { _eq: id } },
fields: [
"*",
"directus_users_id.id",
"directus_users_id.first_name",
"directus_users_id.last_name",
"directus_users_id.email"
]
}
);
}
/**
* Add member to workspace
*/
async addMember(workspaceId, userId) {
return this.http.post(
"/items/junction_directus_users_workspaces",
{
workspaces_id: workspaceId,
directus_users_id: userId
}
);
}
/**
* Remove member from workspace
*/
async removeMember(workspaceId, userId) {
const junctionResponse = await this.http.get(
"/items/junction_directus_users_workspaces",
{
filter: {
workspaces_id: { _eq: workspaceId },
directus_users_id: { _eq: userId }
},
limit: 1
}
);
if (junctionResponse.data.length > 0) {
const junctionId = junctionResponse.data[0].id;
await this.http.delete(
`/items/junction_directus_users_workspaces/${junctionId}`
);
}
}
};
// src/client.ts
var WalletHero = class {
constructor(config) {
if (!config.apiToken) {
throw new Error("API token is required");
}
const apiUrl = config.apiUrl || "https://api.wallethero.app";
this.http = new HttpClient(apiUrl, config.apiToken, config.timeout);
this.passTemplates = new PassTemplatesService(this.http);
this.passes = new PassesService(this.http);
this.projects = new ProjectsService(this.http);
this.files = new FilesService(this.http);
this.qrCodes = new QRCodeService(this.http);
this.workspaces = new WorkspacesService(this.http);
}
/**
* Test the connection to the API
*/
async ping() {
try {
await this.http.get("/server/ping");
return true;
} catch {
return false;
}
}
/**
* Get current user information
*/
async me() {
return this.http.get("/users/me");
}
/**
* Get API server information
*/
async serverInfo() {
return this.http.get("/server/info");
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
FilesService,
HttpClient,
PassTemplatesService,
PassesService,
ProjectsService,
QRCodeService,
WalletHero,
WalletHeroError,
WorkspacesService
});