UNPKG

bam-ticketing-sdk

Version:

SDK for B.A.M Ticketing API

415 lines (368 loc) 12 kB
import jwtDecode from 'jwt-decode' import queryString from 'query-string' import { AxiosError, AxiosInstance } from 'axios' import { AxiosCacheInstance, CacheOptions } from 'axios-cache-interceptor' import { EventService, MyEventsEvent } from './event' import { OrderService } from './order' import { TicketService } from './ticket' import { AccountService } from './account' import { PaymentService } from './payment' import { BlockchainService } from './blockchain' import { ValidatorService } from './validator' import { BaseUrl, buildClient, createBaseUrl, IClientBuildOptions, setAxiosUpdateTokenFunction, } from './common' import { ImportData, ImportDataRequest, MyEventsQuery } from '.' import { AuthService } from './auth' import { VenueService } from './venue' import { ClusterHealth } from './common/types' import { Jwt, RefreshableJwt } from './auth/types' import { CredentialFactory } from './credential/credential-factory' import { ICredential, GuestCredentials, ICredentialData } from './credential' import { UploadService } from './upload/service' import { OrderbookService } from './orderbook' import { NftService } from './nft' import { NotificationService } from './notification' import { PdfService } from './pdf/service' import { BAMgregatorService } from './bamgregator' export type ApiVersion = 'v1' // | 'v2' ... some day export interface RetryOptions { retries?: number retryCondition?: (error: AxiosError<any>) => boolean | Promise<boolean> shouldResetTimeout?: boolean retryDelay?: (retryCount: number, error: any) => number } export interface BamOptions { baseUrl: string | BaseUrl version?: ApiVersion organizer?: string credentials?: ICredential | ICredentialData retry?: RetryOptions cache?: CacheOptions camelCaseResponse?: boolean } /** * BAM services class. The BAM instance holds the API URLs, the authorization token * and keeps track of the API version used. */ export class BAM { // Services auth: AuthService account: AccountService venue: VenueService upload: UploadService validator: ValidatorService nft: NftService pdf: PdfService bamgregator: BAMgregatorService event: EventService order: OrderService payment: PaymentService blockchain: BlockchainService ticket: TicketService orderbook: OrderbookService notification: NotificationService client: AxiosInstance | AxiosCacheInstance tenantClient: AxiosInstance | AxiosCacheInstance // User/service/guest credentials private _credentials?: ICredential private _tenant?: string private _organizer?: string private _deviceId?: string constructor( readonly baseUrl: string | BaseUrl, readonly version: ApiVersion = 'v1', retryOptions?: RetryOptions, cacheOptions?: CacheOptions, camelCaseResponse = true ) { let orgDefault: string switch (baseUrl) { case BaseUrl.Dev: orgDefault = 'org1' break case BaseUrl.Prod: orgDefault = 'catapult' break case BaseUrl.QA: orgDefault = 'qa' break case BaseUrl.ThProd: case BaseUrl.ThStaging: orgDefault = 'tickethead' break } const options: IClientBuildOptions = { baseUrl, retryOptions, cacheOptions, camelCaseResponse, } this.client = buildClient(options) this.tenantClient = buildClient({ ...options, tenantName: orgDefault, }) // Global services this.auth = new AuthService(this.client, version) this.account = new AccountService(this.client, version) this.venue = new VenueService(this.client, version) this.upload = new UploadService(this.client, version) this.validator = new ValidatorService(this.client, version) this.nft = new NftService(this.client, version) this.pdf = new PdfService(this.client, version) this.bamgregator = new BAMgregatorService(this.client, version) // Per-tenant services this.event = new EventService(this.tenantClient, version) this.order = new OrderService(this.tenantClient, version) this.payment = new PaymentService(this.tenantClient, version) this.blockchain = new BlockchainService(this.tenantClient, version) this.ticket = new TicketService(this.tenantClient, version) this.orderbook = new OrderbookService(this.tenantClient, version) // Hybrids this.notification = new NotificationService( this.client, this.tenantClient, version ) } public get credentials() { return this._credentials } public get tenant(): string { return this._tenant } /** * Sets the tenant used by tenant-specific services. * * @param tenantName Name of a tenant to be set */ public set tenant(tenantName: string) { this._tenant = tenantName // Organizer name is reset to tenant name, as in the general case it no longer corresponds this._organizer = tenantName this.tenantClient.defaults.baseURL = createBaseUrl(this.baseUrl, tenantName) } public get organizer(): string { return this._organizer } public get deviceId() { return this._deviceId } public set deviceId(deviceId: string) { this._deviceId = deviceId this.client.defaults.headers['x-device-id'] = deviceId this.tenantClient.defaults.headers['x-device-id'] = deviceId } /** * You can use the object in the response to construct JwtCredentials. */ public getToken(): Jwt | RefreshableJwt { return this.credentials?.getToken() } public static getTokenPayload(token: string): Record<string, any> { return jwtDecode(token) } /** * Used to store the state of the SDK. * Can be used via the build method to restore state. */ serialize(): BamOptions { return { version: this.version, baseUrl: this.baseUrl, organizer: this.organizer, credentials: this.credentials, } } /** * Factory method for initializing the SDK. * Can be used in conjunction with the serialize method to restore state. */ static async build(params: BamOptions): Promise<BAM> { const { baseUrl, version = 'v1', organizer, retry, cache, camelCaseResponse, } = params const bam = new BAM(baseUrl, version, retry, cache, camelCaseResponse) if (params.organizer) await bam.useOrganizer(organizer) if (params.credentials) { const creds = isCredential(params.credentials) ? params.credentials : CredentialFactory.build(params.credentials) await bam.authorize(creds) } return bam } /** * Sets the organizer used on the instance. Any event, payment, reporting or websocket API call * will be made to the organizer's tenant API. * * @param organizerName Name of an organizer to be set on the instance */ async useOrganizer(organizerName: string): Promise<void> { if (organizerName === this._organizer) { // No need to refresh the token if it is for the selected organizer return } const organizer = await this.account.getOrganizer({ id: organizerName }) // Second term is to support the no case conversion use case this.tenant = organizer.organizationName || (organizer as any).organization_name // Ordering is important as using the tenant setter resets the organizer name this._organizer = organizer.name // Refresh the token, fetching the permissions for the specified organizer // For guest and service tokens, this does not change anything await this.authorize(this._credentials, this._organizer) } /** * Authorizes the SDK instance with a JWT obtained from the API. * For user or organizer based login supply `PasswordCredentials`. * For internal service authentication use `ServiceCredentials` with the appropriate organizer ID. * For logging in with a wallet, use `WalletCredentials` aka the private key and certificate. * For guest login use the parameterless version of `authorize`. * * @param credentials User, organizer, service or wallet credentials. In case of `undefined`, log in as guest. * @param organizer The organizer name of the organizer for which the token is requested. */ async authorize( credentials: ICredential = new GuestCredentials(), organizer?: string ) { this._credentials = credentials const jwt = await credentials.authorize(this.auth, organizer) // Set the auth headers this.client.defaults.headers['Authorization'] = `Bearer ${jwt.token}` this.tenantClient.defaults.headers['Authorization'] = `Bearer ${jwt.token}` // This is an instance of jjs programming const refreshFunction = async () => { const newJwt = await this.credentials.refreshToken(this.auth, organizer) return newJwt.token } setAxiosUpdateTokenFunction(this.client, refreshFunction) setAxiosUpdateTokenFunction(this.tenantClient, refreshFunction) return jwt } /** * Sets the Bearer header of the calls to the specified token. * You can also use the authorize method with JwtCredentials. * * @param jwt JWT to use for calls to the BAM services */ setAuthorization(jwt: Jwt) { // Set the auth headers this.client.defaults.headers['Authorization'] = `Bearer ${jwt.token}` this.tenantClient.defaults.headers['Authorization'] = `Bearer ${jwt.token}` } /** * Returns the status of currently used BAM services. * This includes global services and the services for the current tenant. */ async health(): Promise<ClusterHealth> { const accHealth = this.account.health() const eventHealth = this.event.health() const venueHealth = this.venue.health() const paymentHealth = this.payment.health() const blockchainHealth = this.blockchain.health() // Await at the same time so its parallel const [account, event, payment, blockchain, venue] = await Promise.all([ accHealth, eventHealth, paymentHealth, blockchainHealth, venueHealth, ]) // This needs to be changed if a service is extracted from another service // This is mapped so there are no redundant calls to services return { auth: account, account: account, payment: payment, event: event, order: event, blockchain: blockchain, venue: venue, } } /** * Returns events with secure tickets for the authorized enrolled user. * * @param req.date Filter events by `end_at` date comparing with `midnight`. Expected values are `future`, `past` and `all`. * @returns */ async getMyEvents(req: MyEventsQuery): Promise<MyEventsEvent[]> { const query = queryString.stringify({ date: req.date, }) const res = await this.client.get(`${this.version}/my_events?${query}`) return res.data.data } /** * Returns data that needs to be imported to the local validation server. */ async getImportData(request: ImportDataRequest): Promise<ImportData> { const event = await this.event.getEvent({ id: request.eventId, organizer_id: request.organizerId, }) const [venue, qrCodes, validators] = await Promise.all([ this.venue.getVenue({ id: parseInt(event.venueId), }), this.event.getQrCodes({ id: request.eventId, organizer_id: request.organizerId, }), this.validator.getExportedValidators( { id: request.organizerId }, { event_id: request.eventId, } ), ]) const importedValidators = validators.map((v) => { // Casing issue regarding the blockchain service... const wallet = { ...v.wallet, private_key: v.wallet.privateKey, privateKey: undefined as string, } return { ...v, wallet } }) return { event: { event, qr_codes: qrCodes, }, venue: { venue, }, account: { validators: importedValidators, }, } } /** * Enable sending the current version to the backend and checking against the configured minimum version * * @param version Version which is sent to the backend and checked against the minimum version. Format 'X.Y.Z' */ enableMinimumVersionCheck(version: string) { // set x-app-version headers this.client.defaults.headers['x-app-version'] = version this.tenantClient.defaults.headers['x-app-version'] = version } } function isCredential( credential: ICredential | ICredentialData ): credential is ICredential { return (<ICredential>credential)?.authorize !== undefined }