@wallethero/sdk
Version:
TypeScript SDK for WalletHero API - manage pass templates and passes for Apple Wallet and Google Pay
1,941 lines (1,931 loc) • 63.8 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, {
CampaignsService: () => CampaignsService,
DistributionsService: () => DistributionsService,
EventsService: () => EventsService,
FilesService: () => FilesService,
HttpClient: () => HttpClient,
PassTemplatesService: () => PassTemplatesService,
PassesService: () => PassesService,
ProjectsService: () => ProjectsService,
QRCodeService: () => QRCodeService,
SegmentsService: () => SegmentsService,
WalletHero: () => WalletHero,
WalletHeroError: () => WalletHeroError,
WorkspacesService: () => WorkspacesService,
default: () => WalletHero
});
module.exports = __toCommonJS(index_exports);
// src/campaigns.ts
var CampaignsService = class {
constructor(httpClient) {
this.http = httpClient;
}
// ============================================================================
// CRUD Operations
// ============================================================================
/**
* Get a list of campaigns
*/
async list(options) {
return this.http.get(
"/items/campaigns",
options
);
}
/**
* Get a specific campaign by ID
*/
async get(id, options) {
return this.http.get(
`/items/campaigns/${id}`,
options
);
}
/**
* Create a new campaign
*/
async create(data) {
return this.http.post("/items/campaigns", data);
}
/**
* Update an existing campaign
*/
async update(id, data) {
return this.http.patch(
`/items/campaigns/${id}`,
data
);
}
/**
* Delete a campaign
*/
async delete(id) {
await this.http.delete(`/items/campaigns/${id}`);
}
// ============================================================================
// Query Methods
// ============================================================================
/**
* Get campaigns by workspace
*/
async getByWorkspace(workspaceId, options) {
const filterOptions = {
...options,
filter: {
...options?.filter,
workspace_id: { _eq: workspaceId }
}
};
return this.list(filterOptions);
}
/**
* Get campaigns by status
*/
async getByStatus(status, options) {
const filterOptions = {
...options,
filter: {
...options?.filter,
status: { _eq: status }
}
};
return this.list(filterOptions);
}
/**
* Get campaigns for calendar view
*/
async getCalendar(workspaceId, options) {
const params = {
workspace_id: workspaceId
};
if (options?.status) params.status = options.status;
if (options?.start) params.start = options.start;
if (options?.end) params.end = options.end;
const response = await this.http.get(
"/wallethero-api/campaigns/calendar",
params
);
return response.data;
}
// ============================================================================
// Lifecycle Methods
// ============================================================================
/**
* Schedule a campaign for future execution
*/
async schedule(id, scheduledAt) {
const response = await this.http.post(
`/wallethero-api/campaigns/${id}/schedule`,
{ scheduled_at: scheduledAt }
);
return response.data;
}
/**
* Start a campaign immediately (bypasses scheduling)
*/
async startNow(id) {
const response = await this.http.post(
`/wallethero-api/campaigns/${id}/start-now`
);
return response.data;
}
/**
* Complete a campaign
*/
async complete(id) {
const response = await this.http.post(
`/wallethero-api/campaigns/${id}/complete`
);
return response.data;
}
/**
* Cancel a campaign
*/
async cancel(id) {
const response = await this.http.post(
`/wallethero-api/campaigns/${id}/cancel`
);
return response.data;
}
// ============================================================================
// Audience Methods
// ============================================================================
/**
* Preview campaign audience
*/
async previewAudience(id, limit = 100) {
const response = await this.http.get(`/wallethero-api/campaigns/${id}/preview`, { limit });
return {
passes: response.data,
total: response.meta?.total ?? 0
};
}
/**
* Count campaign audience
*/
async countAudience(id) {
const response = await this.http.get(
`/wallethero-api/campaigns/${id}/count`
);
return response.data;
}
// ============================================================================
// Statistics
// ============================================================================
/**
* Get campaign statistics
*/
async getStatistics(id) {
const response = await this.http.get(
`/wallethero-api/campaigns/${id}/statistics`
);
return response.data;
}
/**
* Get campaign events (events logged during execution)
*/
async getEvents(id, options) {
const params = {};
if (options?.limit) params.limit = options.limit;
if (options?.offset) params.offset = options.offset;
return this.http.get(
`/wallethero-api/campaigns/${id}/events`,
params
);
}
/**
* Get campaign errors (failed passes with error details)
*/
async getErrors(id) {
const response = await this.http.get(
`/wallethero-api/campaigns/${id}/errors`
);
return response.data;
}
/**
* Get all campaign passes (affected users)
*/
async getPasses(id, options) {
const params = {};
if (options?.limit) params.limit = options.limit;
if (options?.offset) params.offset = options.offset;
return this.http.get(
`/wallethero-api/campaigns/${id}/passes`,
params
);
}
// ============================================================================
// Actions Management
// ============================================================================
/**
* Get all actions for a campaign
*/
async getActions(campaignId) {
const response = await this.http.get(
`/wallethero-api/campaigns/${campaignId}/actions`
);
return response.data;
}
/**
* Add an action to a campaign
*/
async addAction(campaignId, action) {
const response = await this.http.post(
`/wallethero-api/campaigns/${campaignId}/actions`,
action
);
return response.data;
}
/**
* Update a campaign action
*/
async updateAction(actionId, data) {
const response = await this.http.patch(
`/wallethero-api/campaign-actions/${actionId}`,
data
);
return response.data;
}
/**
* Delete a campaign action
*/
async deleteAction(actionId) {
await this.http.delete(`/wallethero-api/campaign-actions/${actionId}`);
}
/**
* Execute a single action
*/
async executeAction(actionId) {
const response = await this.http.post(
`/wallethero-api/campaign-actions/${actionId}/execute`
);
return response.data;
}
/**
* Execute all pending actions in a campaign
*/
async executeAllActions(campaignId) {
const response = await this.http.post(
`/wallethero-api/campaigns/${campaignId}/execute-all`
);
return response.data;
}
// ============================================================================
// Action Helpers
// ============================================================================
/**
* Add a push notification action
*/
async addPushNotificationAction(campaignId, message) {
return this.addAction(campaignId, {
action_type: "push_notification",
action_config: { notification_message: message }
});
}
/**
* Add a template switch action
*/
async addTemplateSwitchAction(campaignId, targetTemplateId) {
return this.addAction(campaignId, {
action_type: "template_switch",
action_config: {
target_template_id: targetTemplateId
}
});
}
/**
* Add a custom field update action
*/
async addCustomFieldUpdateAction(campaignId, fieldUpdates) {
return this.addAction(campaignId, {
action_type: "custom_field_update",
action_config: { custom_field_updates: fieldUpdates }
});
}
// ============================================================================
// Utility Methods
// ============================================================================
/**
* Duplicate a campaign
*/
async duplicate(id, options) {
const response = await this.http.post(
`/wallethero-api/campaigns/${id}/duplicate`,
options ?? {}
);
return response.data;
}
/**
* Get draft campaigns
*/
async getDrafts(options) {
return this.getByStatus("draft", options);
}
/**
* Get scheduled campaigns
*/
async getScheduled(options) {
return this.getByStatus("scheduled", options);
}
/**
* Get active campaigns
*/
async getActive(options) {
return this.getByStatus("active", options);
}
/**
* Get completed campaigns
*/
async getCompleted(options) {
return this.getByStatus("completed", options);
}
/**
* Assign a segment to a campaign
*/
async assignSegment(campaignId, segmentId) {
const response = await this.update(campaignId, { segment_id: segmentId });
return response.data;
}
/**
* Remove segment from a campaign
*/
async removeSegment(campaignId) {
const response = await this.http.patch(
`/items/campaigns/${campaignId}`,
{ segment_id: null }
);
return response.data;
}
};
// src/distributions.ts
var DistributionsService = class {
constructor(httpClient) {
this.http = httpClient;
}
/**
* Get a list of distributions
*/
async list(options) {
return this.http.get(
"/items/distributions",
options
);
}
/**
* Get a specific distribution by ID
*/
async get(id, options) {
return this.http.get(
`/items/distributions/${id}`,
options
);
}
/**
* Create a new distribution
*/
async create(data) {
return this.http.post(
"/items/distributions",
data
);
}
/**
* Update an existing distribution
*/
async update(id, data) {
return this.http.patch(
`/items/distributions/${id}`,
data
);
}
/**
* Delete a distribution
*/
async delete(id) {
await this.http.delete(`/items/distributions/${id}`);
}
/**
* Get distributions by workspace
*/
async getByWorkspace(workspaceId, options) {
const filterOptions = {
...options,
filter: {
...options?.filter,
workspace_id: { _eq: workspaceId }
}
};
return this.list(filterOptions);
}
/**
* Get distributions by project
*/
async getByProject(projectId, options) {
const filterOptions = {
...options,
filter: {
...options?.filter,
project_id: { _eq: projectId }
}
};
return this.list(filterOptions);
}
/**
* Get distributions by template
*/
async getByTemplate(templateId, options) {
const filterOptions = {
...options,
filter: {
...options?.filter,
pass_template_id: { _eq: templateId }
}
};
return this.list(filterOptions);
}
/**
* Get distributions by status
*/
async getByStatus(status, options) {
const filterOptions = {
...options,
filter: {
...options?.filter,
status: { _eq: status }
}
};
return this.list(filterOptions);
}
/**
* Get a published distribution by page address
* Only returns published distributions (for public access)
* @param pageAddress - The page address to look up
* @param workspaceFilter - Optional workspace filter (slug or ID)
* @param options - Optional query options
*/
async getByPageAddress(pageAddress, workspaceFilter, options) {
const filter = {
...options?.filter,
page_address: { _eq: pageAddress },
status: { _eq: "published" }
};
if (workspaceFilter?.workspaceSlug) {
filter.workspace_id = {
slug: { _eq: workspaceFilter.workspaceSlug }
};
} else if (workspaceFilter?.workspaceId) {
filter.workspace_id = { _eq: workspaceFilter.workspaceId };
}
const filterOptions = {
...options,
filter,
limit: 1
};
const result = await this.list(filterOptions);
if (result.data.length === 0) {
throw new Error(
`Published distribution with page address '${pageAddress}' not found`
);
}
return { data: result.data[0] };
}
/**
* Search distributions by page address
*/
async search(query, options) {
const searchOptions = {
...options,
search: query
};
return this.list(searchOptions);
}
/**
* Publish a distribution (change status to published)
*/
async publish(id) {
return this.update(id, { status: "published" });
}
/**
* Unpublish a distribution (change status to draft)
*/
async unpublish(id) {
return this.update(id, { status: "draft" });
}
/**
* Duplicate a distribution
* @param id - The ID of the distribution to duplicate
* @param options - Options for duplication
* @param options.newPageAddress - New page address (optional, defaults to same address for versioning)
* @param options.keepSameAddress - If true, keeps the same page_address (for versioning). Defaults to true.
*/
async duplicate(id, options) {
const { newPageAddress, keepSameAddress = true } = options || {};
const distribution = await this.get(id);
const {
id: _,
user_created: _uc,
date_created: _dc,
user_updated: _uu,
date_updated: _du,
page_address,
...distributionData
} = distribution.data;
let finalPageAddress;
if (newPageAddress) {
finalPageAddress = newPageAddress;
} else if (keepSameAddress) {
finalPageAddress = page_address || "";
} else {
finalPageAddress = `${page_address}-copy`;
}
return this.create({
...distributionData,
page_address: finalPageAddress,
status: "draft"
});
}
/**
* Get complete distribution data with related entities
*/
async getComplete(id) {
return this.get(id, {
fields: ["*", "project_id.*", "pass_template_id.*", "workspace_id.*"]
});
}
/**
* Bulk update distributions
*/
async bulkUpdate(updates) {
const updatePromises = updates.map(({ id, data }) => this.update(id, data));
const results = await Promise.all(updatePromises);
return {
data: results.map((result) => result.data),
meta: {
filter_count: results.length,
total_count: results.length
}
};
}
/**
* Bulk publish distributions
*/
async bulkPublish(ids) {
return this.bulkUpdate(
ids.map((id) => ({
data: { status: "published" },
id
}))
);
}
/**
* Bulk unpublish distributions (set status to draft)
*/
async bulkUnpublish(ids) {
return this.bulkUpdate(
ids.map((id) => ({
data: { status: "draft" },
id
}))
);
}
};
// src/events.ts
var EventsService = class {
constructor(httpClient) {
this.http = httpClient;
}
/**
* Create a single event
*/
async create(data) {
return this.http.post(
"/wallethero-api/events",
data
);
}
/**
* Create multiple events in batch (up to 1000)
*/
async createBatch(events) {
return this.http.post(
"/wallethero-api/events/batch",
{ events }
);
}
/**
* Get a single event by ID
*/
async get(id) {
return this.http.get(
`/wallethero-api/events/${id}`
);
}
/**
* Delete multiple events by IDs
*/
async deleteBatch(eventIds, workspaceId) {
return this.http.delete(
"/wallethero-api/events/batch",
{
event_ids: eventIds,
workspace_id: workspaceId
}
);
}
/**
* List events with filters
*/
async list(query) {
const params = new URLSearchParams();
if (query.workspace_id) params.set("workspace_id", query.workspace_id);
if (query.pass_id) params.set("pass_id", query.pass_id);
if (query.project_id) params.set("project_id", query.project_id);
if (query.pass_template_id)
params.set("pass_template_id", query.pass_template_id);
if (query.event_category)
params.set("event_category", query.event_category);
if (query.event_types?.length)
params.set("event_types", query.event_types.join(","));
if (query.field_name) params.set("field_name", query.field_name);
if (query.from_date) params.set("from_date", query.from_date);
if (query.to_date) params.set("to_date", query.to_date);
if (query.source) params.set("source", query.source);
if (query.limit) params.set("limit", String(query.limit));
if (query.offset) params.set("offset", String(query.offset));
return this.http.get(
`/wallethero-api/events?${params.toString()}`
);
}
/**
* Get events for a specific pass
*/
async getByPass(passId, options) {
const params = new URLSearchParams();
if (options?.event_category)
params.set("event_category", options.event_category);
if (options?.limit) params.set("limit", String(options.limit));
if (options?.offset) params.set("offset", String(options.offset));
const queryString = params.toString();
const url = `/wallethero-api/passes/${passId}/events${queryString ? `?${queryString}` : ""}`;
return this.http.get(url);
}
/**
* Aggregate events (count, sum, avg, min, max)
*/
async aggregate(query) {
return this.http.post(
"/wallethero-api/events/aggregate",
query
);
}
/**
* Aggregate events grouped by pass
*/
async aggregateByPass(query) {
return this.http.post(
"/wallethero-api/events/aggregate",
{ ...query, group_by_pass: true }
);
}
/**
* Get distinct event types used in a workspace
*/
async getEventTypes(workspaceId) {
return this.http.get(
`/wallethero-api/events/types?workspace_id=${workspaceId}`
);
}
/**
* Get distinct event sources in a workspace
*/
async getSources(workspaceId) {
return this.http.get(
`/wallethero-api/events/sources?workspace_id=${workspaceId}`
);
}
// ===== CONVENIENCE METHODS =====
/**
* Record a purchase transaction
*/
async recordPurchase(passId, amount, currency, options) {
return this.create({
event_category: "transaction",
pass_id: passId,
event_type: "purchase",
amount,
currency,
...options
});
}
/**
* Record a refund transaction
*/
async recordRefund(passId, amount, currency, options) {
return this.create({
event_category: "transaction",
pass_id: passId,
event_type: "refund",
amount,
currency,
...options
});
}
/**
* Record a custom transaction
*/
async recordTransaction(passId, eventType, amount, currency, options) {
return this.create({
event_category: "transaction",
pass_id: passId,
event_type: eventType,
amount,
currency,
...options
});
}
/**
* Record a check-in activity
*/
async recordCheckIn(passId, options) {
return this.create({
event_category: "activity",
pass_id: passId,
event_type: "check_in",
...options
});
}
/**
* Record a visit activity
*/
async recordVisit(passId, options) {
return this.create({
event_category: "activity",
pass_id: passId,
event_type: "visit",
...options
});
}
/**
* Record a custom activity
*/
async recordActivity(passId, eventType, options) {
return this.create({
event_category: "activity",
pass_id: passId,
event_type: eventType,
...options
});
}
/**
* Record a field change (e.g., points balance update)
*/
async recordFieldChange(passId, fieldName, oldValue, newValue, options) {
return this.create({
event_category: "field_change",
pass_id: passId,
event_type: options?.event_type || "field_updated",
field_name: fieldName,
old_value: oldValue,
new_value: newValue,
event_timestamp: options?.event_timestamp,
external_id: options?.external_id,
source: options?.source,
metadata: options?.metadata
});
}
/**
* Record points earned (increment)
*/
async recordPointsEarned(passId, pointsFieldName, pointsEarned, currentBalance, options) {
const oldBalance = currentBalance - pointsEarned;
return this.recordFieldChange(
passId,
pointsFieldName,
String(oldBalance),
String(currentBalance),
{
event_type: "field_increment",
...options
}
);
}
/**
* Record points redeemed (decrement)
*/
async recordPointsRedeemed(passId, pointsFieldName, pointsRedeemed, currentBalance, options) {
const oldBalance = currentBalance + pointsRedeemed;
return this.recordFieldChange(
passId,
pointsFieldName,
String(oldBalance),
String(currentBalance),
{
event_type: "field_decrement",
...options
}
);
}
// ===== AGGREGATION HELPERS =====
/**
* Get total transaction amount for a workspace
*/
async getTotalTransactionAmount(workspaceId, options) {
const result = await this.aggregate({
workspace_id: workspaceId,
function: "sum",
field: "amount",
event_category: "transaction",
event_types: options?.event_types,
time_window: options?.time_window,
custom_days: options?.custom_days
});
return result.data.value;
}
/**
* Get event count for a workspace
*/
async getEventCount(workspaceId, options) {
const result = await this.aggregate({
workspace_id: workspaceId,
function: "count",
event_category: options?.event_category,
event_types: options?.event_types,
time_window: options?.time_window,
custom_days: options?.custom_days
});
return result.data.value;
}
};
// 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/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 put(endpoint, data) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
try {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
method: "PUT",
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, data) {
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"
},
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);
}
}
};
// 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: _id,
user_created: _user_created,
date_created: _date_created,
user_updated: _user_updated,
date_updated: _date_updated,
statistics: _statistics,
uuid: _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 });
}
/**
* 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 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);
}
/**
* 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);
}
/**
* Resend pass email to user
* @param passId - The pass ID (UUID string)
* @param emailTemplate - Optional custom email HTML template
* @param email - Optional email address override (if not provided, uses pass's email)
* @returns Object with pass URLs
*/
async resendEmail(passId, emailTemplate, email) {
const payload = {
pass_id: passId
};
if (emailTemplate) {
payload.email_template = emailTemplate;
}
if (email) {
payload.email = email;
}
return this.http.post("/wallethero-api/pass/resend", payload);
}
/**
* 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: {
filter_count: results.length,
total_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 SWITCHING SUPPORT =====
/**
* Switch pass to a different template within the same project
*/
async switchTemplate(id, newTemplateId) {
return this.update(id, { pass_template_id: newTemplateId });
}
/**
* 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: {
filter_count: results.length,
total_count: results.length
}
};
}
};
// 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 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`
);
}
/**
* Get project statistics
*/
async getStats(id) {
const [templatesResponse, passesResponse] = await Promise.all([
this.getTemplates(id, { limit: 0 }),
// Only need count
this.getPasses(id, { limit: 0 })
// Only need count
]);
return {
templateCount: templatesResponse.meta?.total_count || 0,
passCount: passesResponse.meta?.total_count || 0
};
}
/**
* 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",