import EnvoyMeta, { EnvoyEventMeta, EnvoyRouteMeta } from './EnvoyMeta'; import EnvoyPluginStorage from './EnvoyPluginStorage'; import EnvoyPluginJob from './EnvoyPluginJob'; import EnvoyJWT from '../util/EnvoyJWT'; import EnvoyUserAPI from './EnvoyUserAPI'; import EnvoyPluginAPI from './EnvoyPluginAPI'; import JSONAPIData from '../util/json-api/JSONAPIData'; import EntryPayload, { normalizeEntryPayload } from '../payloads/EntryPayload'; import InvitePayload, { normalizeInvitePayload } from '../payloads/InvitePayload'; /** * Every Envoy request has a `meta` and `payload`. * @category Request */ export interface EnvoyRequestBody { meta: Meta; payload: Payload; } /** * Sets up all relevant Envoy functions. * Attaches itself to every request object using our {@link middleware}, * to allow for easy access to Envoy functions. * * @category SDK */ export default class EnvoyPluginSDK, Payload = unknown> { /** * Body of the request. * use `meta` or `payload` to access the relevant properties, * instead of this directly. */ private readonly body: EnvoyRequestBody; /** * Verified that the request comes from Envoy. */ private readonly isVerified: boolean; /** * The internally managed plugin access token. * Use the `pluginAPI` instead of this directly. */ private readonly pluginAccessToken: string | null = null; /** * Don't create an instance of this class directly. * * Instead, use {@link middleware} to automatically attach an instance to `req` on every request. * @param body - Request body * @param isVerified - If the request has been verified via {@link EnvoySignatureVerifier} * @param pluginAccessToken - An access token from {@link EnvoyPluginAPI.loginAsPlugin} */ constructor(body: EnvoyRequestBody, isVerified = false, pluginAccessToken: string | null = null) { this.body = body; this.isVerified = isVerified; this.pluginAccessToken = pluginAccessToken; } /** * The metadata for the request. * Either an {@link EnvoyEventMeta}} or {@link EnvoyRouteMeta}. */ get meta(): Meta { if (!this.isVerified) { throw new Error('Could not verify meta.'); } return this.body.meta; } /** * The payload for the request. * For events, it's some Envoy event payload, like an {@link EntryPayload} or {@link InvitePayload}. * For setup step validation URLs, it's the form submitted values for a validation URL. */ get payload(): Payload { if (!this.isVerified) { throw new Error('Could not verify payload.'); } const payload = this.body.payload as unknown as JSONAPIData; switch (payload.type) { case 'entries': return normalizeEntryPayload(payload as unknown as EntryPayload) as unknown as Payload; case 'invites': return normalizeInvitePayload(payload as unknown as InvitePayload) as unknown as Payload; default: return payload as unknown as Payload; } } /** * Envoy API scoped to the user. * Used only in routes. */ get userAPI(): EnvoyUserAPI { const meta = this.meta as unknown as EnvoyRouteMeta; const { auth } = meta; let accessToken: string | undefined = auth?.access_token; if (!accessToken && 'forwarded_bearer_token' in meta) { accessToken = meta.forwarded_bearer_token; } if (!accessToken) { throw new Error("This user's API token was not found. Either no scopes were asked for, or this is a route."); } return new EnvoyUserAPI(accessToken); } /** * Envoy API scoped to the plugin. * Used to perform storage or job operations. */ get pluginAPI(): EnvoyPluginAPI { const { pluginAccessToken } = this; if (!pluginAccessToken) { throw new Error('No API token found. Are your Envoy client ID and secret set?'); } return new EnvoyPluginAPI(pluginAccessToken); } /** * A function that returns an instance of EnvoyPluginStorage, * optionally scoped to an install ID. * * Use {@link installStorage} or {@link globalStorage} instead of this directly. */ private get getStorage(): (installId?: string) => EnvoyPluginStorage { return (installId?) => new EnvoyPluginStorage(this.pluginAPI, installId); } /** * Storage scoped to the install. */ get installStorage(): EnvoyPluginStorage { const { install_id: installId } = this.meta as unknown as EnvoyMeta; if (!installId) { throw new Error('No install ID found in meta.'); } return this.getStorage(installId); } /** * Storage scoped globally (across installs). */ get globalStorage(): EnvoyPluginStorage { return this.getStorage(); } /** * Returns a job based on an ID. */ get getJob(): (jobId: string) => EnvoyPluginJob { return (jobId: string) => new EnvoyPluginJob(this.pluginAPI, jobId); } /** * Returns the current job's ID. */ get jobId(): string | null { const { meta } = this; if ('job' in meta) { return (meta as unknown as EnvoyEventMeta).job.id; } return null; } /** * Perform operations on the current job. */ get job(): EnvoyPluginJob { const { jobId } = this; if (!jobId) { throw new Error('No job found in meta. Are you in a worker?'); } return this.getJob(jobId); } /** * A utility to encode and decode JWTs. * Useful for verifiable communications between plugin endpoints. */ // eslint-disable-next-line class-methods-use-this get jwt(): EnvoyJWT { return new EnvoyJWT(); } }