@basetime/a2w-api-ts
Version:
Client library that communicates with the addtowallet API.
776 lines (765 loc) • 19.8 kB
JavaScript
// src/NoopLogger.ts
var NoopLogger = class {
/**
* @inheritDoc
*/
debug(message, meta) {
}
/**
* @inheritDoc
*/
info(message, meta) {
}
/**
* @inheritDoc
*/
error(message, meta) {
}
};
// src/constants.ts
var baseUrl = void 0;
var getBaseUrl = () => {
if (baseUrl) {
return baseUrl;
}
return "https://app.addtowallet.io/api/v1";
};
var setBaseUrl = (url) => {
baseUrl = url;
};
// src/endpoint/Endpoint.ts
var Endpoint = class {
/**
* Constructor.
*
* @param req The object to use to make requests.
*/
constructor(req) {
this.req = req;
}
/**
* Makes a GET request.
*
* @param url The url to fetch.
*/
doGet = async (url, authenticate = true) => {
return await this.req.fetch(
url,
{
method: "GET"
},
authenticate
);
};
/**
* Makes a POST request.
*
* @param url The url to fetch.
* @param body The body to send.
*/
doPost = async (url, body, authenticate = true) => {
const options = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(body)
};
return await this.req.fetch(url, options, authenticate);
};
/**
* Makes a DELETE request.
*
* @param url The url to fetch.
*/
doDelete = async (url, authenticate = true) => {
return await this.req.fetch(
url,
{
method: "DELETE"
},
authenticate
);
};
};
// src/endpoint/CampaignsEndpoint.ts
var endpoint = "/campaigns";
var enrollmentEndpoint = "/e";
var CampaignsEndpoint = class extends Endpoint {
/**
* A function to encode the data into a jwt. Used by the enrollment endpoint.
*/
jwtEncode;
/**
* Returns all of the campaigns for authenticated organization.
*
* @returns The campaigns.
*/
getAll = async () => {
return await this.doGet(endpoint);
};
/**
* Returns the details of a campaign.
*
* @param id The ID of the campaign.
*/
getById = async (id) => {
return await this.doGet(`${endpoint}/${id}`);
};
/**
* Returns the passes for a campaign.
*
* @param campaignId The ID of the campaign.
* @returns The passes.
*/
getPasses = async (campaignId) => {
return await this.doGet(`${endpoint}/${campaignId}/passes`);
};
/**
* Returns the details for a pass.
*
* @param campaignId The campaign the pass belongs to.
* @param passId The ID of the pass.
* @param scanner Only used by scanners. The scanner that's being used to request the pass.
*/
getPass = async (campaignId, passId, scanner = "") => {
const scannerStr = encodeURIComponent(JSON.stringify(scanner));
const url = `${endpoint}/${campaignId}/passes/details/${passId}?scanner=${scannerStr}`;
return await this.doGet(url);
};
/**
* Updates the details of a pass.
*
* This method also updates the wallets that contain the pass.
*
* @param campaignId The ID of the campaign the pass belongs to.
* @param passId The ID of the pass.
* @param body The new pass values.
*/
updatePass = async (campaignId, passId, body) => {
const url = `${endpoint}/${campaignId}/passes/details/${passId}`;
return await this.doPost(url, body);
};
/**
* Appends a log to a pass.
*
* @param campaignId The ID of the campaign the pass belongs to.
* @param passId The ID of the pass.
* @param log The message to append to the log.
*/
appendLog = async (campaignId, passId, log) => {
const url = `${endpoint}/${campaignId}/passes/${passId}/logs`;
return await this.doPost(url, { log });
};
/**
* Creates a pass bundle and returns the URL to the claims page.
*
* Example:
* ```ts
* const client = new Client(auth, console);
* const link = await client.campaigns.createBundle('123');
* console.log(link);
* ```
*
* @param campaignId The campaign the pass belongs to.
* @param metaValues The meta values to set.
* @param formValues The form values to set.
* @param utm The UTM values to pass along to the api.
*/
createBundle = async (campaignId, metaValues = {}, formValues = {}, utm = {}) => {
const url = `${endpoint}/${campaignId}/passes/bundle`;
return await this.doPost(url, {
metaValues,
formValues,
utm
});
};
/**
* Creates an enrollment for a campaign, and returns the bundle ID and any errors.
*
* This method needs to encode the data into a jwt. The jwt is used to authenticate
* with the site. This method requires the jwtEncode function to be set.
*
* @param campaignId The ID of the campaign.
* @param metaValues The meta values to set.
* @param formValues The form values to set.
*/
createEnrollment = async (campaignId, metaValues = {}, formValues = {}) => {
if (!this.jwtEncode) {
throw new Error(
"CampaignsEndpoint.createEnrollment() requires the jwtEncode function to be set."
);
}
const body = {
d: await this.jwtEncode({
metaValues,
formValues
})
};
const url = `${enrollmentEndpoint}/campaign/${campaignId}`;
return await this.doPost(url, body);
};
/**
* Returns the passes for a job.
*
* @param campaignId The ID of the campaign.
* @param jobId The ID of the job.
* @returns The passes.
*/
getPassesByJob = async (campaignId, jobId) => {
return await this.doGet(`${endpoint}/${campaignId}/passes/${jobId}`);
};
/**
* Returns the claims for a campaign.
*
* @param campaignId The ID of the campaign.
* @returns The claims.
*/
getClaims = async (campaignId) => {
return await this.doGet(`${endpoint}/${campaignId}/claims`);
};
/**
* Returns the jobs for a campaign.
*
* @param campaignId The ID of the campaign.
* @returns The jobs.
*/
getJobs = async (campaignId) => {
return await this.doGet(`${endpoint}/${campaignId}/jobs`);
};
/**
* Returns statistics for a campaign.
*
* @param campaignId The ID of the campaign.
* @returns The statistics.
*/
getStats = async (campaignId) => {
return await this.doGet(`${endpoint}/${campaignId}/stats`);
};
/**
* Returns the enrollments for a campaign.
*
* @param campaignId The ID of the campaign.
* @returns The enrollments.
*/
getEnrollments = async (campaignId) => {
return await this.doGet(`${endpoint}/${campaignId}/enrollments`);
};
/**
* Sets the redeemed status of a pass to true.
*
* @param campaignId The ID of the campaign.
* @param passId The ID of the pass.
* @returns True if the pass was redeemed, false if it was already redeemed.
*/
redeemPass = async (campaignId, passId) => {
const url = `${endpoint}/${campaignId}/passes/${passId}/redeemed`;
return await this.doPost(url, {});
};
/**
* Returns the redeemed status of a pass.
*
* @param campaignId The ID of the campaign.
* @param passId The ID of the pass.
* @returns The redeemed status.
*/
getRedeemedStatus = async (campaignId, passId) => {
const url = `${endpoint}/${campaignId}/passes/${passId}/redeemed`;
return await this.doGet(url);
};
};
// src/endpoint/ClaimsEndpoint.ts
var endpoint2 = "/claim";
var ClaimsEndpoint = class extends Endpoint {
/**
* Returns the pkpass file for a campaign and pass.
*
* @param campaignId The ID of the campaign.
* @param passId The ID of the pass.
* @returns The pkpass file.
*/
getPkpass = async (campaignId, passId) => {
const url = `${endpoint2}/${campaignId}/${passId}.pkpass`;
return await this.req.fetch(url, {
method: "GET",
headers: {
Accept: "application/vnd.apple.pkpass"
}
});
};
};
// src/endpoint/OrganizationsEndpoint.ts
var endpoint3 = "/organization";
var OrganizationsEndpoint = class extends Endpoint {
/**
* Returns the authenticated organization.
*
* @returns The organization.
*/
getMine = async () => {
return await this.doGet(endpoint3);
};
/**
* Returns a scanner invite by code.
*
* @param code The invite code.
*/
getScannerInvite = async (code) => {
return await this.doGet(`${endpoint3}/scanners/invites/${code}`, false);
};
/**
* Begins the scanner exchange.
*
* @param code The invite code.
*/
startScannerExchange = async (code) => {
return await this.doGet(`${endpoint3}/scanners/invites/${code}/start`, false);
};
/**
* Accepts an scanner app invite code and returns api keys.
*
* @param code The invite code.
* @param pushToken The push token.
* @param scannerDeviceInfo The scanner device info.
*/
finishScannerExchange = async (code, pushToken, scannerDeviceInfo) => {
return await this.doPost(
`${endpoint3}/scanners/invites`,
{
code,
pushToken,
scannerDeviceInfo
},
false
);
};
/**
* Returns the API keys for the authenticated organization.
*/
getApiKeys = async () => {
return await this.doGet(`${endpoint3}/apiKeys`);
};
/**
* Returns an API key by ID.
*
* @param id The ID of the API key.
*/
getApiKey = async (id, scanner = "") => {
const scannerStr = encodeURIComponent(JSON.stringify(scanner));
const url = `${endpoint3}/apiKeys/${id}?scanner=${scannerStr}`;
return await this.doGet(url);
};
/**
* Deletes an API key.
*
* @param id The ID of the API key.
*/
deleteApiKey = async (id) => {
return await this.doDelete(`${endpoint3}/apiKeys/${id}`);
};
};
// src/endpoint/TemplatesEndpoint.ts
var endpoint4 = "/templates";
var TemplatesEndpoint = class extends Endpoint {
/**
* Returns a template by ID.
*
* @param id The ID of the template.
*/
getById = async (id) => {
const url = `${endpoint4}/simple/${id}`;
return await this.doGet(url);
};
/**
* Returns all of the templates for authenticated organization.
*
* @returns The templates.
*/
getAll = async () => {
return await this.doGet(`${endpoint4}/organization`);
};
/**
* Returns all of the templates for a specific tag.
*
* @param tag The tag.
* @returns The templates.
*/
getByTag = async (tag) => {
return await this.doGet(`${endpoint4}/tagged/${tag}`);
};
};
// src/Client.ts
var Client = class {
/**
* The authentication object.
*/
auth;
/**
* The logger.
*/
logger;
/**
* The campaigns endpoint.
*/
_campaigns;
/**
* The claims endpoint.
*/
_claims;
/**
* The templates endpoint.
*/
_templates;
/**
* The organizations endpoint.
*/
_organizations;
/**
* Constructor.
*
* @param auth The authentication provider.
* @param logger The logger to use.
*/
constructor(auth, logger) {
this.logger = logger || new NoopLogger();
if (auth) {
this.setAuth(auth);
}
}
/**
* Sets the base URL for all requests to the API.
*
* @param url The base URL for all requests to the API.
*/
setBaseUrl = (url) => {
setBaseUrl(url);
};
/**
* Sets the auth provider to use.
*
* @param auth The auth provider to use.
*/
setAuth = (auth) => {
this.auth = auth;
this.auth.setLogger(this.logger);
};
/**
* Returns the campaigns endpoint.
*
* @returns {CampaignsEndpoint} The campaigns endoint.
*/
get campaigns() {
if (!this._campaigns) {
this._campaigns = new CampaignsEndpoint(this);
}
return this._campaigns;
}
/**
* Returns the claims endpoint.
*
* @returns {ClaimsEndpoint} The claims endpoint.
*/
get claims() {
if (!this._claims) {
this._claims = new ClaimsEndpoint(this);
}
return this._claims;
}
/**
* Returns the templates endpoint.
*/
get templates() {
if (!this._templates) {
this._templates = new TemplatesEndpoint(this);
}
return this._templates;
}
/**
* Returns the organizations endpoint.
*/
get organizations() {
if (!this._organizations) {
this._organizations = new OrganizationsEndpoint(this);
}
return this._organizations;
}
/**
* Sends a request using the fetcher and returns the response.
*
* Adds the bearer token to the headers and catches errors.
*
* @param url The url to send the request to.
* @param options The fetch options.
* @param authenticate Whether to authenticate the request.
* @returns The response from the endpoint.
*/
fetch = async (url, options = {}, authenticate = true) => {
const sep = url.includes("?") ? "&" : "?";
url = `${getBaseUrl()}${url}${sep}api=true`;
const headers = options.headers ? new Headers(options.headers) : new Headers();
if (!headers.has("Accept")) {
headers.set("Accept", "application/json");
}
if (!headers.has("Content-Type")) {
headers.set("Content-Type", "application/json");
}
if (authenticate && this.auth) {
const bearerToken = await this.auth.authenticate();
headers.set("Authorization", `Bearer ${bearerToken}`);
}
this.logger.debug(
`${(options == null ? void 0 : options.method) || "GET"} ${url}, ${authenticate ? "authenticate" : "no authenticate"}`
);
const opts = {
...options,
headers
};
return await fetch(url, opts).then(async (resp) => {
if (resp.ok) {
if (headers.get("Accept") === "application/json") {
return resp.json();
}
return resp.text();
}
const body = await resp.text();
let json = body;
try {
json = JSON.parse(body);
} catch (err) {
}
if (typeof json === "string") {
throw new Error(`Response failed: ${resp.status} ${body}`);
}
if (json.error) {
throw new Error(`${resp.status} ${json.error}`);
}
throw new Error(`Response failed: ${resp.status} ${resp.statusText}`);
}).catch((err) => {
throw new Error(`Response failed: ${err.toString()}`);
});
};
};
// src/provider/KeysProvider.ts
var KeysProvider = class {
/**
* Constructor.
*
* @param key The API key.
* @param secret The API secret.
* @param logger The logger to use.
*/
constructor(key, secret, logger) {
this.key = key;
this.secret = secret;
this.logger = logger || new NoopLogger();
}
/**
* The successful last authentication.
*/
authed;
/**
* The logger.
*/
logger;
/**
* @inheritdoc
*/
setLogger = (logger) => {
this.logger = logger;
};
/**
* @inheritdoc
*/
getAuthed = () => {
return this.authed;
};
/**
* @inheritdoc
*/
authenticate = async () => {
if (this.authed && this.authed.expiresAt > Date.now() / 1e3) {
return this.authed.idToken;
}
const opts = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({
key: this.key,
secret: this.secret
})
};
const baseUrl2 = getBaseUrl();
this.logger.debug(`Sending request to ${baseUrl2}/auth/apiGrant`);
this.authed = await fetch(`${baseUrl2}/auth/apiGrant`, opts).then(async (resp) => {
if (resp.ok) {
return resp.json();
}
throw new Error(
`Authentication returned non-ok response: ${resp.status} ${resp.statusText}`
);
}).then((json) => {
if (typeof json !== "object") {
throw new Error("Invalid object from /auth/apiGrant endpoint.");
}
if (typeof json.idToken !== "string") {
throw new Error("Invalid idToken from /auth/apiGrant endpoint.");
}
if (typeof json.refreshToken !== "string") {
throw new Error("Invalid refreshToken from /auth/apiGrant endpoint.");
}
if (typeof json.expiresAt !== "number") {
throw new Error("Invalid expiresAt from /auth/apiGrant endpoint.");
}
return json;
}).catch((err) => {
throw new Error(`Failed to authenticate: ${err.toString()}`);
});
return this.authed.idToken;
};
};
// src/provider/OAuthProvider.ts
var e = encodeURIComponent;
var OAuthProvider = class {
/**
* Constructor.
*
* @param app The ID of the app requesting authentication.
* @param code The code that was received from the oauth.
* @param logger The logger to use.
*/
constructor(app, code = "", logger) {
this.app = app;
this.code = code;
this.logger = logger || new NoopLogger();
}
/**
* The successful last authentication.
*/
authed;
/**
* The logger.
*/
logger;
/**
* @inheritdoc
*/
setLogger = (logger) => {
this.logger = logger;
};
/**
* @inheritdoc
*/
getAuthed = () => {
return this.authed;
};
/**
* Returns a URL to get an oauth code.
*
* @param redirectUrl The URL to redirect to after the oauth code is received.
* @param scopes The requested scopes.
* @param state Any value, it will be returned in the redirect.
*/
getCodeUrl = (redirectUrl, scopes, state) => {
this.logger.debug("OAuth.getCodeUrl", { redirectUrl, scopes, state });
return `${getBaseUrl()}/auth/oauth/code?app=${e(this.app)}&redirectUrl=${e(redirectUrl)}&scope=${e(scopes.join(" "))}&state=${e(state)}`;
};
/**
* @inheritdoc
*/
authenticate = async () => {
if (this.authed && this.authed.expiresAt > Date.now() / 1e3) {
return this.authed.idToken;
}
const opts = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({
app: this.app,
code: this.code
})
};
const baseUrl2 = getBaseUrl();
this.logger.debug(`Sending request to ${baseUrl2}/auth/oauth/token`);
this.authed = await fetch(`${baseUrl2}/auth/oauth/token`, opts).then((resp) => {
if (resp.ok) {
return resp.json();
}
throw new Error(
`Authentication returned non-ok response: ${resp.status} ${resp.statusText}`
);
}).then((json) => {
if (typeof json !== "object") {
throw new Error("Invalid object from /oauth/token endpoint.");
}
if (typeof json.idToken !== "string") {
throw new Error("Invalid idToken from /oauth/token endpoint.");
}
if (typeof json.refreshToken !== "string") {
throw new Error("Invalid refreshToken from /oauth/token endpoint.");
}
if (typeof json.expiresAt !== "number") {
throw new Error("Invalid expiresAt from /oauth/token endpoint.");
}
return json;
}).catch((err) => {
throw new Error(`Failed to authenticate: ${err.toString()}`);
});
return this.authed.idToken;
};
};
// src/provider/StoredProvider.ts
var StoredProvider = class {
/**
* Constructor.
*
* @param authed The auth credentials.
* @param logger The logger to use.
*/
constructor(authed, logger) {
this.authed = authed;
this.logger = logger || new NoopLogger();
}
/**
* The logger.
*/
logger;
/**
* @inheritdoc
*/
setLogger = (logger) => {
this.logger = logger;
};
/**
* @inheritdoc
*/
getAuthed = () => {
return this.authed;
};
/**
* @inheritdoc
*/
authenticate = async () => {
if (this.authed && this.authed.expiresAt > Date.now() / 1e3) {
return this.authed.idToken;
}
this.logger.error("StoredProvider: No valid authed found");
throw new Error("StoredProvider: No valid authed found");
};
};
export {
CampaignsEndpoint,
ClaimsEndpoint,
Client,
KeysProvider,
OAuthProvider,
OrganizationsEndpoint,
StoredProvider,
TemplatesEndpoint
};