diff --git a/README.md b/README.md index 0b3dbde4..825a28f5 100644 --- a/README.md +++ b/README.md @@ -203,19 +203,19 @@ import { ItlyNode as Itly } from '@itly/sdk'; load(options: PluginLoadOptions): void {...} - alias(userId: string, previousId?: string): void {...} + async alias(userId: string, previousId?: string) {...} - group(userId: string | undefined, groupId: string, properties?: Properties): void {...} + async group(userId: string | undefined, groupId: string, properties?: Properties) {...} - identify(userId?: string, properties?: Properties): void {...} + async identify(userId?: string, properties?: Properties) {...} - page(userId?: string, category?: string, name?: string, properties?: Properties): void {...} + async page(userId?: string, category?: string, name?: string, properties?: Properties) {...} reset(): void {...} - flush(): void {...} + async flush() {...} - track(userId: string | undefined, event: ItlyEvent): void {...} + async track(userId: string | undefined, event: ItlyEvent) {...} validate(event: Event): ValidationResponse {...} } diff --git a/__tests__/src/CustomPlugin.ts b/__tests__/src/CustomPlugin.ts index 5bae79c6..0eee118d 100644 --- a/__tests__/src/CustomPlugin.ts +++ b/__tests__/src/CustomPlugin.ts @@ -29,11 +29,11 @@ export default class CustomPlugin extends RequestLoggerPlugin { }; } - alias(userId: string, previousId: string | undefined): void { + async alias(userId: string, previousId: string | undefined) { this.log(`alias() userId='${userId}' previousId='${previousId}'`); } - identify(userId: string | undefined, properties: Properties | undefined): void { + async identify(userId: string | undefined, properties: Properties | undefined) { this.log(`identify() userId='${userId}' properties=${this.stringify(properties)}`); } @@ -49,7 +49,7 @@ export default class CustomPlugin extends RequestLoggerPlugin { ); } - group(userId: string | undefined, groupId: string, properties: Properties | undefined): void { + async group(userId: string | undefined, groupId: string, properties: Properties | undefined) { this.log(`group() userId='${userId}' groupId='${groupId}' properties=${this.stringify(properties)}`); } @@ -66,7 +66,7 @@ export default class CustomPlugin extends RequestLoggerPlugin { ); } - page(userId?: string, category?: string, name?: string, properties?: Properties): void { + async page(userId?: string, category?: string, name?: string, properties?: Properties) { this.log( `page() userId='${userId}' category='${category}' name='${name}' properties=${this.stringify(properties)}`, ); @@ -86,7 +86,7 @@ export default class CustomPlugin extends RequestLoggerPlugin { ); } - track(userId: string | undefined, { name, properties }: Event): void { + async track(userId: string | undefined, { name, properties }: Event) { this.log(`track() userId='${userId}' event='${name}' properties=${this.stringify(properties)}`); } diff --git a/__tests__/src/ErrorPlugin.ts b/__tests__/src/ErrorPlugin.ts index ffdcbf89..80d1b07f 100644 --- a/__tests__/src/ErrorPlugin.ts +++ b/__tests__/src/ErrorPlugin.ts @@ -19,11 +19,11 @@ export default class ErrorPlugin extends Plugin { }; } - alias(userId: string, previousId: string | undefined): void { + async alias(userId: string, previousId: string | undefined) { throw new Error('Error in alias().'); } - identify(userId: string | undefined, properties: Properties | undefined): void { + async identify(userId: string | undefined, properties: Properties | undefined) { throw new Error('Error in identify().'); } @@ -35,7 +35,7 @@ export default class ErrorPlugin extends Plugin { throw new Error('Error in postIdentify().'); } - group(userId: string | undefined, groupId: string, properties: Properties | undefined): void { + async group(userId: string | undefined, groupId: string, properties: Properties | undefined) { throw new Error('Error in group().'); } @@ -48,7 +48,7 @@ export default class ErrorPlugin extends Plugin { throw new Error('Error in postGroup().'); } - page(userId?: string, category?: string, name?: string, properties?: Properties): void { + async page(userId?: string, category?: string, name?: string, properties?: Properties) { throw new Error('Error in page().'); } @@ -62,7 +62,7 @@ export default class ErrorPlugin extends Plugin { throw new Error('Error in postPage().'); } - track(userId: string | undefined, event: Event): void { + async track(userId: string | undefined, event: Event) { throw new Error('Error in track().'); } diff --git a/packages/plugin-amplitude-node/lib/index.ts b/packages/plugin-amplitude-node/lib/index.ts index 2d4caf97..fd266f60 100644 --- a/packages/plugin-amplitude-node/lib/index.ts +++ b/packages/plugin-amplitude-node/lib/index.ts @@ -64,6 +64,7 @@ export class AmplitudePlugin extends RequestLoggerPlugin { callback?.(response); } catch (e) { responseLogger.error(e.toString()); + throw e; } } @@ -81,6 +82,7 @@ export class AmplitudePlugin extends RequestLoggerPlugin { callback?.(response); } catch (e) { responseLogger.error(e.toString()); + throw e; } } } diff --git a/packages/plugin-amplitude/lib/__tests__/smoke.test.ts b/packages/plugin-amplitude/lib/__tests__/smoke.test.ts index 77855f52..d2df7699 100644 --- a/packages/plugin-amplitude/lib/__tests__/smoke.test.ts +++ b/packages/plugin-amplitude/lib/__tests__/smoke.test.ts @@ -40,6 +40,8 @@ afterEach(() => { const methodArgLoggerMock = (methodName: string, ...args: any[]) => { // eslint-disable-next-line no-console console.log(`${methodName}()`, JSON.stringify(args.map((arg) => (typeof arg === 'function' ? '' : arg)))); + // Invoke callback. + args.filter((arg) => typeof arg === 'function').forEach((arg) => arg()); }; test.each(testParams.map((test) => [test.name, test]) as any[])('%s', diff --git a/packages/plugin-amplitude/lib/index.ts b/packages/plugin-amplitude/lib/index.ts index 28ae8097..556fafcd 100644 --- a/packages/plugin-amplitude/lib/index.ts +++ b/packages/plugin-amplitude/lib/index.ts @@ -45,31 +45,44 @@ export class AmplitudePlugin extends RequestLoggerPlugin { } } - identify(userId: string | undefined, properties?: Properties, options?: IdentifyOptions) { + async identify(userId: string | undefined, properties?: Properties, options?: IdentifyOptions) { if (userId) { this.amplitude.getInstance().setUserId(userId); } - if (properties) { - const identifyObject = new this.amplitude.Identify(); - for (const p in properties) { - if (!properties.hasOwnProperty(p)) { - continue; - } + if (!properties) { + return undefined; + } - identifyObject.set(p, (properties as any)[p]); + const identifyObject = new this.amplitude.Identify(); + for (const p in properties) { + if (!properties.hasOwnProperty(p)) { + continue; } - const { callback } = this.getPluginCallOptions(options); - const responseLogger = this.logger!.logRequest('identify', `${userId} ${JSON.stringify(properties)}`); - this.amplitude.getInstance().identify(identifyObject, this.wrapCallback(responseLogger, callback)); + identifyObject.set(p, (properties as any)[p]); } + + const { callback } = this.getPluginCallOptions(options); + const responseLogger = this.logger!.logRequest('identify', `${userId} ${JSON.stringify(properties)}`); + return new Promise((resolve, reject) => { + this.amplitude.getInstance().identify( + identifyObject, + this.wrapCallback(responseLogger, callback, resolve, reject), + ); + }); } - track(userId: string | undefined, { name, properties }: Event, options?: TrackOptions) { + async track(userId: string | undefined, { name, properties }: Event, options?: TrackOptions) { const { callback } = this.getPluginCallOptions(options); const responseLogger = this.logger!.logRequest('track', `${userId} ${name} ${JSON.stringify(properties)}`); - this.amplitude.getInstance().logEvent(name, properties, this.wrapCallback(responseLogger, callback)); + return new Promise((resolve, reject) => { + this.amplitude.getInstance().logEvent( + name, + properties, + this.wrapCallback(responseLogger, callback, resolve, reject), + ); + }); } reset() { @@ -77,14 +90,24 @@ export class AmplitudePlugin extends RequestLoggerPlugin { this.amplitude.getInstance().regenerateDeviceId(); } - private wrapCallback(responseLogger: ResponseLogger, callback: AmplitudeCallback | undefined) { + private wrapCallback( + responseLogger: ResponseLogger, + callback: AmplitudeCallback | undefined, + resolve: () => void, + reject: (reason?: any) => void, + ) { return (statusCode: number, responseBody: string, details: unknown) => { - if (statusCode >= 200 && statusCode < 300) { - responseLogger.success(`${statusCode}`); - } else { - responseLogger.error(`unexpected status: ${statusCode}. ${responseBody}\n${JSON.stringify(details)}`); + try { + if (statusCode >= 200 && statusCode < 300) { + responseLogger.success(`${statusCode}`); + } else { + responseLogger.error(`unexpected status: ${statusCode}. ${responseBody}\n${JSON.stringify(details)}`); + } + callback?.(statusCode, responseBody, details); + resolve(); + } catch (e) { + reject(e); } - callback?.(statusCode, responseBody, details); }; } } diff --git a/packages/plugin-braze/lib/index.ts b/packages/plugin-braze/lib/index.ts index 00fb3dd5..5f8e03b4 100644 --- a/packages/plugin-braze/lib/index.ts +++ b/packages/plugin-braze/lib/index.ts @@ -44,7 +44,7 @@ export class BrazePlugin extends RequestLoggerPlugin { } } - identify(userId: string | undefined, properties?: Properties) { + async identify(userId: string | undefined, properties?: Properties) { if (userId) { this.appboy!.changeUser(userId); } @@ -56,7 +56,7 @@ export class BrazePlugin extends RequestLoggerPlugin { } } - track(userId: string | undefined, { name, properties }: Event) { + async track(userId: string | undefined, { name, properties }: Event) { if (userId) { this.appboy!.changeUser(userId); } diff --git a/packages/plugin-mixpanel-node/lib/index.ts b/packages/plugin-mixpanel-node/lib/index.ts index ee8cf553..caa18b1b 100644 --- a/packages/plugin-mixpanel-node/lib/index.ts +++ b/packages/plugin-mixpanel-node/lib/index.ts @@ -42,40 +42,61 @@ export class MixpanelPlugin extends RequestLoggerPlugin { this.mixpanel = Mixpanel.init(this.apiKey, this.options); } - alias(userId: string, previousId: string, options?: AliasOptions) { + async alias(userId: string, previousId: string, options?: AliasOptions) { const { callback } = this.getPluginCallOptions(options); const responseLogger = this.logger!.logRequest('alias', `${userId}, ${previousId}`); - this.mixpanel!.alias(previousId, userId, this.wrapCallback(responseLogger, callback)); + return new Promise((resolve, reject) => { + this.mixpanel!.alias(previousId, userId, this.wrapCallback(responseLogger, callback, resolve, reject)); + }); } - identify(userId: string, properties: Properties | undefined, options?: IdentifyOptions) { + async identify(userId: string, properties: Properties | undefined, options?: IdentifyOptions) { const { callback } = this.getPluginCallOptions(options); const payload = { distinct_id: userId, ...properties, }; const responseLogger = this.logger!.logRequest('identify', JSON.stringify(payload)); - this.mixpanel!.people.set(userId, payload, this.wrapCallback(responseLogger, callback)); + return new Promise((resolve, reject) => { + this.mixpanel!.people.set(userId, payload, this.wrapCallback(responseLogger, callback, resolve, reject)); + }); } - track(userId: string, { name, properties }: Event, options?: TrackOptions) { + async track(userId: string, { name, properties }: Event, options?: TrackOptions) { const { callback } = this.getPluginCallOptions(options); const payload = { distinct_id: userId, ...properties, }; const responseLogger = this.logger!.logRequest('track', JSON.stringify(payload)); - this.mixpanel!.track(name, payload, this.wrapCallback(responseLogger, callback)); + return new Promise((resolve, reject) => { + this.mixpanel!.track(name, payload, this.wrapCallback(responseLogger, callback, resolve, reject)); + }); } - private wrapCallback(responseLogger: ResponseLogger, callback: MixpanelCallback | undefined) { + private wrapCallback( + responseLogger: ResponseLogger, + callback: MixpanelCallback | undefined, + resolve: () => void, + reject: (reason?: any) => void, + ) { return (err: Error | undefined): any => { - if (err == null) { - responseLogger.success('success'); - } else { - responseLogger.error(err.toString()); + try { + let result; + if (err == null) { + responseLogger.success('success'); + result = callback?.(undefined); + resolve(); + } else { + responseLogger.error(err.toString()); + result = callback?.(err); + reject(err); + } + return result; + } catch (e) { + reject(e); + return undefined; } - return callback?.(err); }; } } diff --git a/packages/plugin-mixpanel/lib/index.ts b/packages/plugin-mixpanel/lib/index.ts index 794f2b21..56bc9c35 100644 --- a/packages/plugin-mixpanel/lib/index.ts +++ b/packages/plugin-mixpanel/lib/index.ts @@ -44,11 +44,18 @@ export class MixpanelPlugin extends RequestLoggerPlugin { } } - alias(userId: string, previousId: string | undefined) { + async alias( + userId: string, + previousId: string | undefined, + ) { this.mixpanel.alias(userId, previousId); } - identify(userId: string | undefined, properties: Properties | undefined, options?: IdentifyOptions) { + async identify( + userId: string | undefined, + properties: Properties | undefined, + options?: IdentifyOptions, + ) { const { callback } = this.getPluginCallOptions(options); if (userId) { @@ -56,25 +63,51 @@ export class MixpanelPlugin extends RequestLoggerPlugin { } if (properties) { - const responseLogger = this.logger!.logRequest('identify', `${userId}, ${JSON.stringify(properties)}`); - this.mixpanel.people.set(properties, this.wrapCallback(responseLogger, callback)); + return undefined; } + + const responseLogger = this.logger!.logRequest('identify', `${userId}, ${JSON.stringify(properties)}`); + return new Promise((resolve, reject) => { + this.mixpanel.people.set(properties, this.wrapCallback(responseLogger, callback, resolve, reject)); + }); } - track(userId: string | undefined, { name, properties }: Event, options?: TrackOptions) { + async track( + userId: string | undefined, + { name, properties }: Event, + options?: TrackOptions, + ) { const { callback } = this.getPluginCallOptions(options); const responseLogger = this.logger!.logRequest('track', `${userId}, ${name}, ${JSON.stringify(properties)}`); - this.mixpanel.track(name, { ...properties }, this.wrapCallback(responseLogger, callback)); + return new Promise((resolve, reject) => { + this.mixpanel.track(name, { ...properties }, this.wrapCallback(responseLogger, callback, resolve, reject)); + }); } reset() { this.mixpanel.reset(); } - private wrapCallback(responseLogger: ResponseLogger, callback: MixpanelCallback | undefined) { + private wrapCallback( + responseLogger: ResponseLogger, + callback: MixpanelCallback | undefined, + resolve: () => void, + reject: (reason?: any) => void, + ) { return (...args: any[]) => { - responseLogger.success(`done: ${JSON.stringify(args)}`); - return callback?.(...args); + try { + if (args.length === 1 && args[0] instanceof Error) { + responseLogger.error(args[0].toString()); + callback?.(...args); + reject(args[0]); + } else { + responseLogger.success(JSON.stringify(args)); + callback?.(...args); + resolve(); + } + } catch (e) { + reject(e); + } }; } } diff --git a/packages/plugin-mparticle/lib/index.ts b/packages/plugin-mparticle/lib/index.ts index efb339d8..2f346ada 100644 --- a/packages/plugin-mparticle/lib/index.ts +++ b/packages/plugin-mparticle/lib/index.ts @@ -69,11 +69,11 @@ export class MparticlePlugin extends RequestLoggerPlugin { } } - page(userId?: string, category?: string, name?: string, properties?: Properties) { + async page(userId?: string, category?: string, name?: string, properties?: Properties) { this.mparticle.logPageView(name, properties); } - track(userId: string | undefined, { name, properties }: Event) { + async track(userId: string | undefined, { name, properties }: Event) { this.mparticle.logEvent( name, this.mparticle.EventType.Other, diff --git a/packages/plugin-segment-node/lib/index.ts b/packages/plugin-segment-node/lib/index.ts index 58703755..ed414c4c 100644 --- a/packages/plugin-segment-node/lib/index.ts +++ b/packages/plugin-segment-node/lib/index.ts @@ -52,7 +52,11 @@ export class SegmentPlugin extends RequestLoggerPlugin { this.segment = new Segment(this.writeKey, this.options); } - alias(userId: string, previousId: string, options?: AliasOptions) { + async alias( + userId: string, + previousId: string, + options?: AliasOptions, + ) { const { callback, options: segmentOptions } = this.getPluginCallOptions(options); const payload = { ...segmentOptions, @@ -60,10 +64,16 @@ export class SegmentPlugin extends RequestLoggerPlugin { previousId, }; const responseLogger = this.logger!.logRequest('alias', JSON.stringify(payload)); - this.segment!.alias(payload, this.wrapCallback(responseLogger, callback)); + return new Promise((resolve, reject) => { + this.segment!.alias(payload, this.wrapCallback(responseLogger, callback, resolve, reject)); + }); } - identify(userId: string, properties: Properties | undefined, options?: IdentifyOptions) { + async identify( + userId: string, + properties: Properties | undefined, + options?: IdentifyOptions, + ) { const { callback, options: segmentOptions } = this.getPluginCallOptions(options); const payload = { ...segmentOptions, @@ -71,10 +81,17 @@ export class SegmentPlugin extends RequestLoggerPlugin { traits: { ...properties }, }; const responseLogger = this.logger!.logRequest('identify', JSON.stringify(payload)); - this.segment!.identify(payload, this.wrapCallback(responseLogger, callback)); + return new Promise((resolve, reject) => { + this.segment!.identify(payload, this.wrapCallback(responseLogger, callback, resolve, reject)); + }); } - group(userId: string, groupId: string, properties: Properties | undefined, options?: GroupOptions) { + async group( + userId: string, + groupId: string, + properties: Properties | undefined, + options?: GroupOptions, + ) { const { callback, options: segmentOptions } = this.getPluginCallOptions(options); const payload = { ...segmentOptions, @@ -83,10 +100,12 @@ export class SegmentPlugin extends RequestLoggerPlugin { traits: properties, }; const responseLogger = this.logger!.logRequest('group', JSON.stringify(payload)); - this.segment!.group(payload, this.wrapCallback(responseLogger, callback)); + return new Promise((resolve, reject) => { + this.segment!.group(payload, this.wrapCallback(responseLogger, callback, resolve, reject)); + }); } - page( + async page( userId: string, category: string, name: string, @@ -102,10 +121,16 @@ export class SegmentPlugin extends RequestLoggerPlugin { properties, }; const responseLogger = this.logger!.logRequest('page', JSON.stringify(payload)); - this.segment!.page(payload, this.wrapCallback(responseLogger, callback)); + return new Promise((resolve, reject) => { + this.segment!.page(payload, this.wrapCallback(responseLogger, callback, resolve, reject)); + }); } - track(userId: string, { name, properties }: Event, options?: TrackOptions) { + async track( + userId: string, + { name, properties }: Event, + options?: TrackOptions, + ) { const { callback, options: segmentOptions } = this.getPluginCallOptions(options); const payload = { ...segmentOptions, @@ -114,11 +139,13 @@ export class SegmentPlugin extends RequestLoggerPlugin { properties: { ...properties }, }; const responseLogger = this.logger!.logRequest('track', JSON.stringify(payload)); - this.segment!.track(payload, this.wrapCallback(responseLogger, callback)); + return new Promise((resolve, reject) => { + this.segment!.track(payload, this.wrapCallback(responseLogger, callback, resolve, reject)); + }); } flush() { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { this.segment!.flush((err: Error) => { if (err) { return reject(err); @@ -128,14 +155,26 @@ export class SegmentPlugin extends RequestLoggerPlugin { }); } - private wrapCallback(responseLogger: ResponseLogger, callback: SegmentCallback | undefined) { + private wrapCallback( + responseLogger: ResponseLogger, + callback: SegmentCallback | undefined, + resolve: () => void, + reject: (reason?: any) => void, + ) { return (err: Error | undefined) => { - if (err == null) { - responseLogger.success('success'); - } else { - responseLogger.error(err.toString()); + try { + if (err == null) { + responseLogger.success('success'); + callback?.(undefined); + resolve(); + } else { + responseLogger.error(err.toString()); + callback?.(err); + reject(err); + } + } catch (e) { + reject(err); } - callback?.(err); }; } } diff --git a/packages/plugin-segment/lib/index.ts b/packages/plugin-segment/lib/index.ts index cf39ea27..93621255 100644 --- a/packages/plugin-segment/lib/index.ts +++ b/packages/plugin-segment/lib/index.ts @@ -56,48 +56,127 @@ export class SegmentPlugin extends RequestLoggerPlugin { } } - alias(userId: string, previousId: string | undefined, options?: AliasOptions) { + async alias( + userId: string, + previousId: string | undefined, + options?: AliasOptions, + ) { const { callback, options: segmentOptions } = this.getPluginCallOptions(options); const responseLogger = this.logger!.logRequest('alias', `${userId}, ${previousId}`); - this.segment.alias(userId, previousId, segmentOptions, this.wrapCallback(responseLogger, callback)); + return new Promise((resolve, reject) => { + this.segment.alias( + userId, + previousId, + segmentOptions, + this.wrapCallback(responseLogger, callback, resolve, reject), + ); + }); } - identify(userId: string | undefined, properties?: Properties, options?: IdentifyOptions) { + async identify( + userId: string | undefined, + properties?: Properties, + options?: IdentifyOptions, + ) { const { callback, options: segmentOptions } = this.getPluginCallOptions(options); const responseLogger = this.logger!.logRequest('identify', `${userId}, ${JSON.stringify(properties)}`); - if (userId) { - this.segment.identify(userId, properties, segmentOptions, this.wrapCallback(responseLogger, callback)); - } else { - this.segment.identify(properties, segmentOptions, this.wrapCallback(responseLogger, callback)); - } + return new Promise((resolve, reject) => { + if (userId) { + this.segment.identify( + userId, + properties, + segmentOptions, + this.wrapCallback(responseLogger, callback, resolve, reject), + ); + } else { + this.segment.identify( + properties, + segmentOptions, + this.wrapCallback(responseLogger, callback, resolve, reject), + ); + } + }); } - group(userId: string | undefined, groupId: string, properties?: Properties, options?: GroupOptions) { + async group( + userId: string | undefined, + groupId: string, + properties?: Properties, + options?: GroupOptions, + ) { const { callback, options: segmentOptions } = this.getPluginCallOptions(options); const responseLogger = this.logger!.logRequest('group', `${userId}, ${groupId}, ${JSON.stringify(properties)}`); - this.segment.group(groupId, properties, segmentOptions, this.wrapCallback(responseLogger, callback)); + return new Promise((resolve, reject) => { + this.segment.group( + groupId, + properties, + segmentOptions, + this.wrapCallback(responseLogger, callback, resolve, reject), + ); + }); } - page(userId?: string, category?: string, name?: string, properties?: Properties, options?: PageOptions) { + async page( + userId?: string, + category?: string, + name?: string, + properties?: Properties, + options?: PageOptions, + ) { const { callback, options: segmentOptions } = this.getPluginCallOptions(options); const responseLogger = this.logger!.logRequest('page', `${userId}, ${category}, ${name}, ${JSON.stringify(properties)}`); - this.segment.page(category, name, properties, segmentOptions, this.wrapCallback(responseLogger, callback)); + return new Promise((resolve, reject) => { + this.segment.page( + category, + name, + properties, + segmentOptions, + this.wrapCallback(responseLogger, callback, resolve, reject), + ); + }); } - track(userId: string | undefined, { name, properties }: Event, options?: TrackOptions) { + async track( + userId: string | undefined, + { name, properties }: Event, + options?: TrackOptions, + ) { const { callback, options: segmentOptions } = this.getPluginCallOptions(options); const responseLogger = this.logger!.logRequest('track', `${userId}, ${name}, ${JSON.stringify(properties)}`); - this.segment.track(name, properties, segmentOptions, this.wrapCallback(responseLogger, callback)); + return new Promise((resolve, reject) => { + this.segment.track( + name, + properties, + segmentOptions, + this.wrapCallback(responseLogger, callback, resolve, reject), + ); + }); } reset() { this.segment.reset(); } - private wrapCallback(responseLogger: ResponseLogger, callback: SegmentCallback | undefined) { + private wrapCallback( + responseLogger: ResponseLogger, + callback: SegmentCallback | undefined, + resolve: () => void, + reject: (reason?: any) => void, + ) { return (...args: any[]) => { - responseLogger.success(`done ${args}`); - callback?.(args); + try { + if (args.length === 1 && args[0] instanceof Error) { + responseLogger.error(args[0].toString()); + callback?.(...args); + reject(args[0]); + } else { + responseLogger.success(JSON.stringify(args)); + callback?.(...args); + resolve(); + } + } catch (e) { + reject(e); + } }; } } diff --git a/packages/plugin-snowplow/lib/index.ts b/packages/plugin-snowplow/lib/index.ts index c1f997b7..e780f375 100644 --- a/packages/plugin-snowplow/lib/index.ts +++ b/packages/plugin-snowplow/lib/index.ts @@ -56,39 +56,82 @@ export class SnowplowPlugin extends RequestLoggerPlugin { } } - identify(userId: string | undefined, properties?: Properties) { + async identify(userId: string | undefined, properties?: Properties) { this.snowplow('setUserId', userId); } - page(userId?: string, category?: string, name?: string, properties?: Properties, options?: PageOptions) { + async page( + userId?: string, + category?: string, + name?: string, + properties?: Properties, + options?: PageOptions, + ) { const { callback, contexts } = this.getPluginCallOptions(options); const responseLogger = this.logger!.logRequest( 'page', `${userId}, ${category}, ${name}, ${this.toJsonStr(properties, contexts)}`, ); - this.snowplow('trackPageView', name, undefined, contexts, undefined, this.wrapCallback(responseLogger, callback)); + return new Promise((resolve, reject) => { + this.snowplow( + 'trackPageView', + name, + undefined, + contexts, + undefined, + this.wrapCallback(responseLogger, callback, resolve, reject), + ); + }); } - track(userId: string | undefined, { name, properties, version }: Event, options?: TrackOptions) { + async track( + userId: string | undefined, + { name, properties, version }: Event, + options?: TrackOptions, + ) { const schemaVer = version && version.replace(/\./g, '-'); const { callback, contexts } = this.getPluginCallOptions(options); const responseLogger = this.logger!.logRequest( 'track', `${userId}, ${name}, ${this.toJsonStr(properties, contexts)}`, ); - this.snowplow('trackSelfDescribingEvent', { - schema: `iglu:${this.vendor}/${name}/jsonschema/${schemaVer}`, - data: properties, - }, contexts, undefined, this.wrapCallback(responseLogger, callback)); + return new Promise((resolve, reject) => { + this.snowplow( + 'trackSelfDescribingEvent', + { + schema: `iglu:${this.vendor}/${name}/jsonschema/${schemaVer}`, + data: properties, + }, + contexts, + undefined, + this.wrapCallback(responseLogger, callback, resolve, reject), + ); + }); } private toJsonStr = (properties?: Properties, contexts?: SnowplowContext[]) => `${JSON.stringify(properties)}${contexts ? `, ${JSON.stringify(contexts)}` : ''}`; - private wrapCallback(responseLogger: ResponseLogger, callback: SnowplowCallback | undefined) { + private wrapCallback( + responseLogger: ResponseLogger, + callback: SnowplowCallback | undefined, + resolve: () => void, + reject: (reason?: any) => void, + ) { return (...args: any[]) => { - responseLogger.success(`done: ${JSON.stringify(args)}`); - callback?.(...args); + try { + if (args.length === 1 && args[0] instanceof Error) { + responseLogger.error(args[0].toString()); + callback?.(...args); + reject(args[0]); + } else { + responseLogger.success(JSON.stringify(args)); + callback?.(...args); + resolve(); + } + } catch (e) { + reject(e); + } }; } } diff --git a/packages/plugin-testing/lib/index.ts b/packages/plugin-testing/lib/index.ts index cb2cc86a..65b3ea39 100644 --- a/packages/plugin-testing/lib/index.ts +++ b/packages/plugin-testing/lib/index.ts @@ -60,23 +60,23 @@ export class TestingPlugin extends Plugin implements ITestingPlugin { return tuple ? this.mapMethodArgs(tuple) : null; } - alias(...args: any[]) { + async alias(...args: any[]) { this.trackCall('alias', args); } - identify(...args: any[]) { + async identify(...args: any[]) { this.trackCall('identify', args); } - group(...args: any[]) { + async group(...args: any[]) { this.trackCall('group', args); } - page(...args: any[]) { + async page(...args: any[]) { this.trackCall('page', args); } - track(...args: any[]) { + async track(...args: any[]) { this.trackCall('track', args); } diff --git a/packages/sdk/lib/base.ts b/packages/sdk/lib/base.ts index 7b15563d..ad617adb 100644 --- a/packages/sdk/lib/base.ts +++ b/packages/sdk/lib/base.ts @@ -59,9 +59,21 @@ export abstract class Plugin { }; } - alias(userId: string, previousId: string | undefined, options?: AliasOptions): void {} + async alias( + userId: string, + previousId: string | undefined, + options?: AliasOptions, + ): Promise { + return Promise.resolve(); + } - identify(userId: string | undefined, properties: Properties | undefined, options?: IdentifyOptions): void {} + async identify( + userId: string | undefined, + properties: Properties | undefined, + options?: IdentifyOptions, + ): Promise { + return Promise.resolve(); + } postIdentify( userId: string | undefined, @@ -69,12 +81,14 @@ export abstract class Plugin { validationResponses: ValidationResponse[], ): void {} - group( + async group( userId: string | undefined, groupId: string, properties: Properties | undefined, options?: GroupOptions, - ): void {} + ): Promise { + return Promise.resolve(); + } postGroup( userId: string | undefined, @@ -83,13 +97,15 @@ export abstract class Plugin { validationResponses: ValidationResponse[], ): void {} - page( + async page( userId: string | undefined, category: string | undefined, name: string | undefined, properties: Properties | undefined, options?: PageOptions, - ): void {} + ): Promise { + return Promise.resolve(); + } postPage( userId: string | undefined, @@ -99,13 +115,15 @@ export abstract class Plugin { validationResponses: ValidationResponse[], ): void {} - track(userId: string | undefined, event: Event, options?: TrackOptions): void {} + async track(userId: string | undefined, event: Event, options?: TrackOptions): Promise { + return Promise.resolve(); + } postTrack(userId: string | undefined, event: Event, validationResponses: ValidationResponse[]): void {} reset(): void {} - flush(): Promise { + async flush(): Promise { return Promise.resolve(); } @@ -182,6 +200,10 @@ const DEFAULT_PROD_OPTIONS: Required = { validation: Validation.TrackOnInvalid, }; +type CallResponse = { + promise: Promise, +}; + export class Itly { private options: Required | undefined = undefined; @@ -234,12 +256,18 @@ export class Itly { * @param previousId The user's previous ID. * @param options Options for this Alias call. */ - alias(userId: string, previousId?: string, options?: AliasOptions) { + alias( + userId: string, + previousId?: string, + options?: AliasOptions, + ): CallResponse { if (!this.isInitializedAndEnabled()) { - return; + return { promise: Promise.resolve() }; } - this.runOnAllPlugins('alias', (p) => p.alias(userId, previousId, options)); + return { + promise: this.runOnAllPluginsAsync('alias', (p) => p.alias(userId, previousId, options)), + }; } /** @@ -248,9 +276,13 @@ export class Itly { * @param identifyProperties The user's properties. * @param options Options for this Identify call. */ - identify(userId: string | undefined, identifyProperties?: Properties, options?: IdentifyOptions) { + identify( + userId: string | undefined, + identifyProperties?: Properties, + options?: IdentifyOptions, + ): CallResponse { if (!this.isInitializedAndEnabled()) { - return; + return { promise: Promise.resolve() }; } const identifyEvent = { @@ -260,14 +292,16 @@ export class Itly { version: '0-0-0', }; - this.validateAndRunOnAllPlugins( - 'identify', - identifyEvent, - (p, e) => p.identify(userId, identifyProperties, options), - (p, e, validationResponses) => p.postIdentify( - userId, identifyProperties, validationResponses, + return { + promise: this.validateAndRunOnAllPlugins( + 'identify', + identifyEvent, + (p, e) => p.identify(userId, identifyProperties, options), + (p, e, validationResponses) => p.postIdentify( + userId, identifyProperties, validationResponses, + ), ), - ); + }; } /** @@ -277,9 +311,14 @@ export class Itly { * @param groupProperties The group's properties. * @param options Options for this Group call. */ - group(userId: string | undefined, groupId: string, groupProperties?: Properties, options?: GroupOptions) { + group( + userId: string | undefined, + groupId: string, + groupProperties?: Properties, + options?: GroupOptions, + ): CallResponse { if (!this.isInitializedAndEnabled()) { - return; + return { promise: Promise.resolve() }; } const groupEvent = { @@ -289,14 +328,16 @@ export class Itly { version: '0-0-0', }; - this.validateAndRunOnAllPlugins( - 'group', - groupEvent, - (p, e) => p.group(userId, groupId, groupProperties, options), - (p, e, validationResponses) => p.postGroup( - userId, groupId, groupProperties, validationResponses, + return { + promise: this.validateAndRunOnAllPlugins( + 'group', + groupEvent, + (p, e) => p.group(userId, groupId, groupProperties, options), + (p, e, validationResponses) => p.postGroup( + userId, groupId, groupProperties, validationResponses, + ), ), - ); + }; } /** @@ -312,9 +353,9 @@ export class Itly { category: string, name: string, pageProperties?: Properties, options?: PageOptions, - ) { + ): CallResponse { if (!this.isInitializedAndEnabled()) { - return; + return { promise: Promise.resolve() }; } const pageEvent = { @@ -324,14 +365,16 @@ export class Itly { version: '0-0-0', }; - this.validateAndRunOnAllPlugins( - 'page', - pageEvent, - (p, e) => p.page(userId, category, name, pageProperties, options), - (p, e, validationResponses) => p.postPage( - userId, category, name, pageProperties, validationResponses, + return { + promise: this.validateAndRunOnAllPlugins( + 'page', + pageEvent, + (p, e) => p.page(userId, category, name, pageProperties, options), + (p, e, validationResponses) => p.postPage( + userId, category, name, pageProperties, validationResponses, + ), ), - ); + }; } /** @@ -344,22 +387,28 @@ export class Itly { * @param event.version The event's version. * @param options Options for this Track call. */ - track(userId: string | undefined, event: Event, options?: TrackOptions) { + track( + userId: string | undefined, + event: Event, + options?: TrackOptions, + ): CallResponse { if (!this.isInitializedAndEnabled()) { - return; + return { promise: Promise.resolve() }; } const mergedEvent = this.mergeContext(event, this.context); - this.validateAndRunOnAllPlugins( - 'track', - event, - (p, e) => p.track(userId, mergedEvent, options), - (p, e, validationResponses) => p.postTrack( - userId, mergedEvent, validationResponses, + return { + promise: this.validateAndRunOnAllPlugins( + 'track', + event, + (p, e) => p.track(userId, mergedEvent, options), + (p, e, validationResponses) => p.postTrack( + userId, mergedEvent, validationResponses, + ), + this.context, ), - this.context, - ); + }; } /** @@ -369,7 +418,7 @@ export class Itly { this.runOnAllPlugins('reset', (p) => p.reset()); } - async flush() { + flush(): CallResponse { const flushPromises = this.plugins.map(async (plugin) => { try { await plugin.flush(); @@ -377,7 +426,9 @@ export class Itly { this.logger.error(`Error in ${plugin.id}.flush(). ${e.message}.`); } }); - await Promise.all(flushPromises); + return { + promise: Promise.all(flushPromises).then(), + }; } private validate(event: Event): ValidationResponse[] { @@ -415,10 +466,10 @@ export class Itly { private validateAndRunOnAllPlugins( op: string, event: Event, - method: (plugin: Plugin, event: Event) => any, + method: (plugin: Plugin, event: Event) => Promise, postMethod: (plugin: Plugin, event: Event, validationResponses: ValidationResponse[]) => any, context?: Properties, - ): void { + ): Promise { // #1 validation phase let shouldRun = true; @@ -435,13 +486,13 @@ export class Itly { // #2 track phase // invoke track(), group(), identify(), page() on every plugin if allowed - if (shouldRun) { - this.runOnAllPlugins(op, (p) => { + const callPromise = shouldRun + ? this.runOnAllPluginsAsync(op, async (p) => { if (this.canRunEventOnPlugin(event, p)) { - method(p, event); + await method(p, event); } - }); - } + }) + : Promise.resolve(); // invoke postTrack(), postGroup(), postIdentify(), postPage() on every plugin this.runOnAllPlugins( @@ -460,6 +511,8 @@ export class Itly { throw new Error(`Validation Error: ${invalidResult.message}`); } } + + return callPromise; } private canRunEventOnPlugin(event: Event, plugin: Plugin) { @@ -493,6 +546,27 @@ export class Itly { }); } + private runOnAllPluginsAsync(op: string, method: (p: Plugin) => Promise) { + const promises = this.plugins.map(async (plugin) => { + try { + await method(plugin); + } catch (e) { + this.logger.error(`Error in ${plugin.id}.${op}(). ${e.message}.`); + } + }); + let timeout: ReturnType; + return Promise.race([ + Promise.all(promises), + new Promise((resolve) => { + timeout = setTimeout(resolve, 3000); + }), + ]).then(() => { + if (timeout) { + clearTimeout(timeout); + } + }); + } + private capitalize(str: string) { return str.charAt(0).toUpperCase() + str.slice(1); } diff --git a/packages/sdk/lib/browser.ts b/packages/sdk/lib/browser.ts index 71988a8a..b8f93565 100644 --- a/packages/sdk/lib/browser.ts +++ b/packages/sdk/lib/browser.ts @@ -59,7 +59,7 @@ export class Itly { userId = undefined; } - this.itly.identify(userId, identifyProperties, options); + return this.itly.identify(userId, identifyProperties, options); } /**