@workwell-global/toolkit 1.1.49 → 1.1.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analytics/consent.d.ts +33 -0
- package/dist/analytics/consent.d.ts.map +1 -0
- package/dist/analytics/consent.js +53 -0
- package/dist/analytics/consent.js.map +1 -0
- package/dist/analytics/consent.test.d.ts +2 -0
- package/dist/analytics/consent.test.d.ts.map +1 -0
- package/dist/analytics/consent.test.js +51 -0
- package/dist/analytics/consent.test.js.map +1 -0
- package/dist/analytics/events.d.ts +82 -0
- package/dist/analytics/events.d.ts.map +1 -0
- package/dist/analytics/events.js +73 -0
- package/dist/analytics/events.js.map +1 -0
- package/dist/analytics/index.d.ts +4 -0
- package/dist/analytics/index.d.ts.map +1 -0
- package/dist/analytics/index.js +4 -0
- package/dist/analytics/index.js.map +1 -0
- package/dist/analytics/serverTracker.d.ts +36 -0
- package/dist/analytics/serverTracker.d.ts.map +1 -0
- package/dist/analytics/serverTracker.js +139 -0
- package/dist/analytics/serverTracker.js.map +1 -0
- package/dist/analytics/serverTracker.test.d.ts +2 -0
- package/dist/analytics/serverTracker.test.d.ts.map +1 -0
- package/dist/analytics/serverTracker.test.js +139 -0
- package/dist/analytics/serverTracker.test.js.map +1 -0
- package/dist/enums/EntityNames.d.ts +4 -0
- package/dist/enums/EntityNames.d.ts.map +1 -1
- package/dist/enums/EntityNames.js +4 -0
- package/dist/enums/EntityNames.js.map +1 -1
- package/dist/models/schemas/api/_entityTools/CreateEntityTools.d.ts +3 -3
- package/dist/models/schemas/api/_entityTools/EntityRegistry.d.ts +7 -2
- package/dist/models/schemas/api/_entityTools/EntityRegistry.d.ts.map +1 -1
- package/dist/models/schemas/api/_entityTools/EntityRegistry.js.map +1 -1
- package/dist/models/schemas/api/accountManager/TAccountManagerAPI.d.ts +256 -0
- package/dist/models/schemas/api/accountManager/TAccountManagerAPI.d.ts.map +1 -1
- package/dist/models/schemas/api/accountManager/index.d.ts +256 -0
- package/dist/models/schemas/api/accountManager/index.d.ts.map +1 -1
- package/dist/models/schemas/api/activityParty/DActivityPartyAPI.d.ts +7 -0
- package/dist/models/schemas/api/activityParty/DActivityPartyAPI.d.ts.map +1 -1
- package/dist/models/schemas/api/activityParty/DActivityPartyAPI.js +10 -3
- package/dist/models/schemas/api/activityParty/DActivityPartyAPI.js.map +1 -1
- package/dist/models/schemas/api/activityParty/TActivityPartyAPI.d.ts +905 -33
- package/dist/models/schemas/api/activityParty/TActivityPartyAPI.d.ts.map +1 -1
- package/dist/models/schemas/api/activityParty/index.d.ts +919 -33
- package/dist/models/schemas/api/activityParty/index.d.ts.map +1 -1
- package/dist/models/schemas/api/activityParty/index.js +1 -0
- package/dist/models/schemas/api/activityParty/index.js.map +1 -1
- package/dist/models/schemas/api/activityPointer/TActivityPointerAPI.d.ts +876 -18
- package/dist/models/schemas/api/activityPointer/TActivityPointerAPI.d.ts.map +1 -1
- package/dist/models/schemas/api/activityPointer/index.d.ts +876 -18
- package/dist/models/schemas/api/activityPointer/index.d.ts.map +1 -1
- package/dist/models/schemas/api/assignment/TAssignmentAPI.d.ts +1 -1
- package/dist/models/schemas/api/batch/SBatchAPI.d.ts +6 -6
- package/dist/models/schemas/api/caseManagement/TCaseManagementAPI.d.ts +1758 -41
- package/dist/models/schemas/api/caseManagement/TCaseManagementAPI.d.ts.map +1 -1
- package/dist/models/schemas/api/caseManagement/TCaseManagementAPI.js +1 -0
- package/dist/models/schemas/api/caseManagement/TCaseManagementAPI.js.map +1 -1
- package/dist/models/schemas/api/caseManagement/index.d.ts +1757 -41
- package/dist/models/schemas/api/caseManagement/index.d.ts.map +1 -1
- package/dist/models/schemas/api/changeRequest/TChangeRequestAPI.d.ts +4 -4
- package/dist/models/schemas/api/changeRequest/index.d.ts +4 -4
- package/dist/models/schemas/api/customerAddress/TCustomerAddressAPI.d.ts +4 -4
- package/dist/models/schemas/api/customerAddress/index.d.ts +4 -4
- package/dist/models/schemas/api/email/TEmailAPI.d.ts +876 -18
- package/dist/models/schemas/api/email/TEmailAPI.d.ts.map +1 -1
- package/dist/models/schemas/api/email/index.d.ts +876 -18
- package/dist/models/schemas/api/email/index.d.ts.map +1 -1
- package/dist/models/schemas/api/index.d.ts +1 -0
- package/dist/models/schemas/api/index.d.ts.map +1 -1
- package/dist/models/schemas/api/index.js +1 -0
- package/dist/models/schemas/api/index.js.map +1 -1
- package/dist/models/schemas/api/queue/DQueueAPI.d.ts +184 -0
- package/dist/models/schemas/api/queue/DQueueAPI.d.ts.map +1 -0
- package/dist/models/schemas/api/queue/DQueueAPI.js +184 -0
- package/dist/models/schemas/api/queue/DQueueAPI.js.map +1 -0
- package/dist/models/schemas/api/queue/TQueueAPI.d.ts +972 -0
- package/dist/models/schemas/api/queue/TQueueAPI.d.ts.map +1 -0
- package/dist/models/schemas/api/queue/TQueueAPI.js +9 -0
- package/dist/models/schemas/api/queue/TQueueAPI.js.map +1 -0
- package/dist/models/schemas/api/queue/index.d.ts +975 -0
- package/dist/models/schemas/api/queue/index.d.ts.map +1 -0
- package/dist/models/schemas/api/queue/index.js +10 -0
- package/dist/models/schemas/api/queue/index.js.map +1 -0
- package/dist/models/schemas/api/referral/TReferralAPI.d.ts +1 -1
- package/dist/models/schemas/api/systemUser/DSystemUserAPI.d.ts +14 -1
- package/dist/models/schemas/api/systemUser/DSystemUserAPI.d.ts.map +1 -1
- package/dist/models/schemas/api/systemUser/DSystemUserAPI.js +11 -1
- package/dist/models/schemas/api/systemUser/DSystemUserAPI.js.map +1 -1
- package/dist/models/schemas/api/systemUser/TSystemUserAPI.d.ts +418 -12
- package/dist/models/schemas/api/systemUser/TSystemUserAPI.d.ts.map +1 -1
- package/dist/models/schemas/api/systemUser/index.d.ts +424 -12
- package/dist/models/schemas/api/systemUser/index.d.ts.map +1 -1
- package/dist/models/schemas/api/systemUser/index.js +1 -0
- package/dist/models/schemas/api/systemUser/index.js.map +1 -1
- package/dist/models/schemas/api/webMessage/DWebMessageAPI.d.ts +7 -0
- package/dist/models/schemas/api/webMessage/DWebMessageAPI.d.ts.map +1 -1
- package/dist/models/schemas/api/webMessage/TWebMessageAPI.d.ts +7 -0
- package/dist/models/schemas/api/webMessage/TWebMessageAPI.d.ts.map +1 -1
- package/dist/models/schemas/api/webMessage/index.d.ts +7 -0
- package/dist/models/schemas/api/webMessage/index.d.ts.map +1 -1
- package/dist/models/schemas/bff/caseDetail/SGetCaseActivityBFF.d.ts +886 -1
- package/dist/models/schemas/bff/caseDetail/SGetCaseActivityBFF.d.ts.map +1 -1
- package/dist/models/schemas/bff/caseDetail/SGetCaseActivityBFF.js +21 -3
- package/dist/models/schemas/bff/caseDetail/SGetCaseActivityBFF.js.map +1 -1
- package/dist/models/schemas/bff/caseDetail/SGetCaseBFF.d.ts +1953 -5
- package/dist/models/schemas/bff/caseDetail/SGetCaseBFF.d.ts.map +1 -1
- package/dist/models/schemas/bff/caseDetail/SGetCaseEmailBFF.d.ts +7 -0
- package/dist/models/schemas/bff/caseDetail/SGetCaseEmailBFF.d.ts.map +1 -1
- package/dist/models/schemas/bff/caseDetail/SGetCaseEmailPartiesBFF.d.ts +7 -0
- package/dist/models/schemas/bff/caseDetail/SGetCaseEmailPartiesBFF.d.ts.map +1 -1
- package/dist/models/schemas/bff/caseDetail/SGetCasePartiesBFF.d.ts +782 -0
- package/dist/models/schemas/bff/caseDetail/SGetCasePartiesBFF.d.ts.map +1 -0
- package/dist/models/schemas/bff/caseDetail/SGetCasePartiesBFF.js +7 -0
- package/dist/models/schemas/bff/caseDetail/SGetCasePartiesBFF.js.map +1 -0
- package/dist/models/schemas/bff/caseDetail/SGetCaseQueueBFF.d.ts +256 -0
- package/dist/models/schemas/bff/caseDetail/SGetCaseQueueBFF.d.ts.map +1 -0
- package/dist/models/schemas/bff/caseDetail/SGetCaseQueueBFF.js +7 -0
- package/dist/models/schemas/bff/caseDetail/SGetCaseQueueBFF.js.map +1 -0
- package/dist/models/schemas/bff/caseDetail/SGetCaseSystemUserBFF.d.ts +588 -0
- package/dist/models/schemas/bff/caseDetail/SGetCaseSystemUserBFF.d.ts.map +1 -0
- package/dist/models/schemas/bff/caseDetail/SGetCaseSystemUserBFF.js +11 -0
- package/dist/models/schemas/bff/caseDetail/SGetCaseSystemUserBFF.js.map +1 -0
- package/dist/models/schemas/bff/caseDetail/SPostCaseBFF.d.ts.map +1 -1
- package/dist/models/schemas/bff/caseDetail/SPostCaseBFF.js.map +1 -1
- package/dist/models/schemas/bff/casesMetrics/SGetCasesMetricsBFF.d.ts +292 -6
- package/dist/models/schemas/bff/casesMetrics/SGetCasesMetricsBFF.d.ts.map +1 -1
- package/dist/models/schemas/bff/casesSummary/SGetCaseEmailPartiesBFF.d.ts +7 -0
- package/dist/models/schemas/bff/casesSummary/SGetCaseEmailPartiesBFF.d.ts.map +1 -1
- package/dist/models/schemas/bff/casesSummary/SGetCasesSummaryBFF.d.ts +978 -4
- package/dist/models/schemas/bff/casesSummary/SGetCasesSummaryBFF.d.ts.map +1 -1
- package/dist/models/schemas/bff/casesSummary/SGetCasesSummaryEmailBFF.d.ts +7 -0
- package/dist/models/schemas/bff/casesSummary/SGetCasesSummaryEmailBFF.d.ts.map +1 -1
- package/dist/models/schemas/bff/changeRequest/SGetChangeRequestBFF.d.ts +5 -5
- package/dist/models/schemas/bff/helpers/CaseHelper.d.ts +5 -2
- package/dist/models/schemas/bff/helpers/CaseHelper.d.ts.map +1 -1
- package/dist/models/schemas/bff/helpers/CaseHelper.js +6 -4
- package/dist/models/schemas/bff/helpers/CaseHelper.js.map +1 -1
- package/dist/models/schemas/bff/helpers/CaseHelper.test.js +35 -6
- package/dist/models/schemas/bff/helpers/CaseHelper.test.js.map +1 -1
- package/package.json +10 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Cookie set on the parent domain so one accept covers all subdomains. */
|
|
2
|
+
export declare const CONSENT_COOKIE_NAME = "wwl_consent";
|
|
3
|
+
/** Request header web apps forward to the API so server-side events respect consent. */
|
|
4
|
+
export declare const CONSENT_HEADER_NAME = "x-wwl-consent";
|
|
5
|
+
/** Bump when the consent policy text/categories change; a lower stored version re-prompts the banner. */
|
|
6
|
+
export declare const CONSENT_VERSION = 1;
|
|
7
|
+
export interface IConsentCategories {
|
|
8
|
+
/** Always true: auth/session + the consent cookie itself. */
|
|
9
|
+
functional: true;
|
|
10
|
+
/** Product analytics (Mixpanel). */
|
|
11
|
+
statistics: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface IConsentState {
|
|
14
|
+
/** Consent policy version the user responded to. */
|
|
15
|
+
v: number;
|
|
16
|
+
/** Epoch ms of the user's choice. */
|
|
17
|
+
ts: number;
|
|
18
|
+
cat: IConsentCategories;
|
|
19
|
+
}
|
|
20
|
+
export declare const buildConsentState: (statistics: boolean, nowMs: number) => IConsentState;
|
|
21
|
+
/** Serialize for the cookie value / forwarded header. URI-encoded JSON. */
|
|
22
|
+
export declare const serializeConsent: (state: IConsentState) => string;
|
|
23
|
+
/**
|
|
24
|
+
* Parse a cookie/header value. Returns null for anything malformed or absent:
|
|
25
|
+
* callers must treat null as "no consent given" (and the banner re-prompts).
|
|
26
|
+
*/
|
|
27
|
+
export declare const parseConsent: (raw: string | null | undefined) => IConsentState | null;
|
|
28
|
+
/**
|
|
29
|
+
* True only for a well-formed, current-version consent with statistics on.
|
|
30
|
+
* An outdated version means the user has not consented to the CURRENT policy.
|
|
31
|
+
*/
|
|
32
|
+
export declare const hasStatisticsConsent: (raw: string | null | undefined) => boolean;
|
|
33
|
+
//# sourceMappingURL=consent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consent.d.ts","sourceRoot":"","sources":["../../src/analytics/consent.ts"],"names":[],"mappings":"AAKA,2EAA2E;AAC3E,eAAO,MAAM,mBAAmB,gBAAgB,CAAA;AAEhD,wFAAwF;AACxF,eAAO,MAAM,mBAAmB,kBAAkB,CAAA;AAElD,yGAAyG;AACzG,eAAO,MAAM,eAAe,IAAI,CAAA;AAEhC,MAAM,WAAW,kBAAkB;IACjC,6DAA6D;IAC7D,UAAU,EAAE,IAAI,CAAA;IAChB,oCAAoC;IACpC,UAAU,EAAE,OAAO,CAAA;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,oDAAoD;IACpD,CAAC,EAAE,MAAM,CAAA;IACT,qCAAqC;IACrC,EAAE,EAAE,MAAM,CAAA;IACV,GAAG,EAAE,kBAAkB,CAAA;CACxB;AAED,eAAO,MAAM,iBAAiB,GAAI,YAAY,OAAO,EAAE,OAAO,MAAM,KAAG,aAIrE,CAAA;AAEF,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,GAAI,OAAO,aAAa,KAAG,MACb,CAAA;AAE3C;;;GAGG;AACH,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,KAAG,aAAa,GAAG,IAyB7E,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,oBAAoB,GAC/B,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,KAC7B,OAGF,CAAA"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Consent contract shared by the web apps (banner writes / reads the cookie)
|
|
2
|
+
// and the API (parses the forwarded header). Blanket v1: 'statistics' is the
|
|
3
|
+
// only toggleable category; 'functional' is always active. Stored per-category
|
|
4
|
+
// from day one so a tiered model later needs no format change.
|
|
5
|
+
/** Cookie set on the parent domain so one accept covers all subdomains. */
|
|
6
|
+
export const CONSENT_COOKIE_NAME = 'wwl_consent';
|
|
7
|
+
/** Request header web apps forward to the API so server-side events respect consent. */
|
|
8
|
+
export const CONSENT_HEADER_NAME = 'x-wwl-consent';
|
|
9
|
+
/** Bump when the consent policy text/categories change; a lower stored version re-prompts the banner. */
|
|
10
|
+
export const CONSENT_VERSION = 1;
|
|
11
|
+
export const buildConsentState = (statistics, nowMs) => ({
|
|
12
|
+
v: CONSENT_VERSION,
|
|
13
|
+
ts: nowMs,
|
|
14
|
+
cat: { functional: true, statistics },
|
|
15
|
+
});
|
|
16
|
+
/** Serialize for the cookie value / forwarded header. URI-encoded JSON. */
|
|
17
|
+
export const serializeConsent = (state) => encodeURIComponent(JSON.stringify(state));
|
|
18
|
+
/**
|
|
19
|
+
* Parse a cookie/header value. Returns null for anything malformed or absent:
|
|
20
|
+
* callers must treat null as "no consent given" (and the banner re-prompts).
|
|
21
|
+
*/
|
|
22
|
+
export const parseConsent = (raw) => {
|
|
23
|
+
if (!raw)
|
|
24
|
+
return null;
|
|
25
|
+
try {
|
|
26
|
+
const parsed = JSON.parse(decodeURIComponent(raw));
|
|
27
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
28
|
+
return null;
|
|
29
|
+
const candidate = parsed;
|
|
30
|
+
if (typeof candidate.v !== 'number' || typeof candidate.ts !== 'number')
|
|
31
|
+
return null;
|
|
32
|
+
if (candidate.cat?.functional !== true ||
|
|
33
|
+
typeof candidate.cat.statistics !== 'boolean')
|
|
34
|
+
return null;
|
|
35
|
+
return {
|
|
36
|
+
v: candidate.v,
|
|
37
|
+
ts: candidate.ts,
|
|
38
|
+
cat: { functional: true, statistics: candidate.cat.statistics },
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* True only for a well-formed, current-version consent with statistics on.
|
|
47
|
+
* An outdated version means the user has not consented to the CURRENT policy.
|
|
48
|
+
*/
|
|
49
|
+
export const hasStatisticsConsent = (raw) => {
|
|
50
|
+
const state = parseConsent(raw);
|
|
51
|
+
return state !== null && state.v >= CONSENT_VERSION && state.cat.statistics;
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=consent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consent.js","sourceRoot":"","sources":["../../src/analytics/consent.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,6EAA6E;AAC7E,+EAA+E;AAC/E,+DAA+D;AAE/D,2EAA2E;AAC3E,MAAM,CAAC,MAAM,mBAAmB,GAAG,aAAa,CAAA;AAEhD,wFAAwF;AACxF,MAAM,CAAC,MAAM,mBAAmB,GAAG,eAAe,CAAA;AAElD,yGAAyG;AACzG,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAA;AAiBhC,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,UAAmB,EAAE,KAAa,EAAiB,EAAE,CAAC,CAAC;IACvF,CAAC,EAAE,eAAe;IAClB,EAAE,EAAE,KAAK;IACT,GAAG,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE;CACtC,CAAC,CAAA;AAEF,2EAA2E;AAC3E,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAoB,EAAU,EAAE,CAC/D,kBAAkB,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAA;AAE3C;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,GAA8B,EAAwB,EAAE;IACnF,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAA;IACrB,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAA;QAC3D,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,IAAI,CAAA;QAC9D,MAAM,SAAS,GAAG,MAIjB,CAAA;QACD,IAAI,OAAO,SAAS,CAAC,CAAC,KAAK,QAAQ,IAAI,OAAO,SAAS,CAAC,EAAE,KAAK,QAAQ;YACrE,OAAO,IAAI,CAAA;QACb,IACE,SAAS,CAAC,GAAG,EAAE,UAAU,KAAK,IAAI;YAClC,OAAO,SAAS,CAAC,GAAG,CAAC,UAAU,KAAK,SAAS;YAE7C,OAAO,IAAI,CAAA;QACb,OAAO;YACL,CAAC,EAAE,SAAS,CAAC,CAAC;YACd,EAAE,EAAE,SAAS,CAAC,EAAE;YAChB,GAAG,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,CAAC,GAAG,CAAC,UAAU,EAAE;SAChE,CAAA;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC,CAAA;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,GAA8B,EACrB,EAAE;IACX,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;IAC/B,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,IAAI,eAAe,IAAI,KAAK,CAAC,GAAG,CAAC,UAAU,CAAA;AAC7E,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consent.test.d.ts","sourceRoot":"","sources":["../../src/analytics/consent.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { buildConsentState, CONSENT_VERSION, hasStatisticsConsent, parseConsent, serializeConsent, } from './consent.js';
|
|
3
|
+
describe('consent serialization', () => {
|
|
4
|
+
it('round-trips an accept state', () => {
|
|
5
|
+
const state = buildConsentState(true, 1_756_000_000_000);
|
|
6
|
+
const parsed = parseConsent(serializeConsent(state));
|
|
7
|
+
expect(parsed).toEqual({
|
|
8
|
+
v: CONSENT_VERSION,
|
|
9
|
+
ts: 1_756_000_000_000,
|
|
10
|
+
cat: { functional: true, statistics: true },
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
it('round-trips a deny state', () => {
|
|
14
|
+
const state = buildConsentState(false, 1_756_000_000_000);
|
|
15
|
+
const parsed = parseConsent(serializeConsent(state));
|
|
16
|
+
expect(parsed?.cat.statistics).toBe(false);
|
|
17
|
+
expect(parsed?.cat.functional).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
it.each([
|
|
20
|
+
['undefined', undefined],
|
|
21
|
+
['null', null],
|
|
22
|
+
['empty string', ''],
|
|
23
|
+
['not json', 'garbage'],
|
|
24
|
+
['json but wrong shape', encodeURIComponent('{"foo":1}')],
|
|
25
|
+
['missing categories', encodeURIComponent('{"v":1,"ts":2}')],
|
|
26
|
+
[
|
|
27
|
+
'functional not always-active',
|
|
28
|
+
encodeURIComponent('{"v":1,"ts":2,"cat":{"functional":false,"statistics":true}}'),
|
|
29
|
+
],
|
|
30
|
+
])('parseConsent returns null for %s', (_label, raw) => {
|
|
31
|
+
expect(parseConsent(raw)).toBeNull();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
describe('hasStatisticsConsent', () => {
|
|
35
|
+
it('is true only for current-version consent with statistics on', () => {
|
|
36
|
+
const accepted = serializeConsent(buildConsentState(true, 1));
|
|
37
|
+
const denied = serializeConsent(buildConsentState(false, 1));
|
|
38
|
+
expect(hasStatisticsConsent(accepted)).toBe(true);
|
|
39
|
+
expect(hasStatisticsConsent(denied)).toBe(false);
|
|
40
|
+
expect(hasStatisticsConsent(undefined)).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
it('treats consent given under an older policy version as no consent', () => {
|
|
43
|
+
const outdated = serializeConsent({
|
|
44
|
+
v: CONSENT_VERSION - 1,
|
|
45
|
+
ts: 1,
|
|
46
|
+
cat: { functional: true, statistics: true },
|
|
47
|
+
});
|
|
48
|
+
expect(hasStatisticsConsent(outdated)).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
//# sourceMappingURL=consent.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consent.test.js","sourceRoot":"","sources":["../../src/analytics/consent.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** Which deployable emitted the event. Registered as a super property client-side, stamped per event server-side. */
|
|
2
|
+
export type TAnalyticsApp = 'auth' | 'portal' | 'services' | 'api';
|
|
3
|
+
/**
|
|
4
|
+
* Coarse population the signed-in user belongs to, for segmentation. Shared
|
|
5
|
+
* vocabulary so the apps cannot diverge: each derives it from whatever signal it
|
|
6
|
+
* already trusts (an RBAC permission in Services, the CRM customer type in the
|
|
7
|
+
* auth hub) but they must agree on the values.
|
|
8
|
+
*
|
|
9
|
+
* Not set by the Portal, where every user is agency-side and `app: 'portal'`
|
|
10
|
+
* already says so. A role filter therefore excludes Portal events by design:
|
|
11
|
+
* see the tracking plan, section 10.3.
|
|
12
|
+
*/
|
|
13
|
+
export type TAnalyticsRole = 'worker' | 'agency';
|
|
14
|
+
export declare const AnalyticsEvents: {
|
|
15
|
+
readonly pageViewed: "page_viewed";
|
|
16
|
+
readonly panelViewed: "panel_viewed";
|
|
17
|
+
/**
|
|
18
|
+
* A deliberately named UI click, for journey steps that do not warrant their
|
|
19
|
+
* own semantic event. Middle tier: prefer a semantic KPI event where one
|
|
20
|
+
* exists (assignment_accepted, not element_clicked), and leave everything
|
|
21
|
+
* else to autocapture. Carries a stable snake_case `element_name`, never a
|
|
22
|
+
* translated label.
|
|
23
|
+
*/
|
|
24
|
+
readonly elementClicked: "element_clicked";
|
|
25
|
+
readonly formStarted: "form_started";
|
|
26
|
+
readonly formSubmitted: "form_submitted";
|
|
27
|
+
readonly formFailed: "form_failed";
|
|
28
|
+
readonly loginStarted: "login_started";
|
|
29
|
+
readonly loginCompleted: "login_completed";
|
|
30
|
+
readonly loginFailed: "login_failed";
|
|
31
|
+
readonly logoutCompleted: "logout_completed";
|
|
32
|
+
readonly inviteOpened: "invite_opened";
|
|
33
|
+
readonly inviteExpiredViewed: "invite_expired_viewed";
|
|
34
|
+
readonly signupCompleted: "signup_completed";
|
|
35
|
+
readonly emailVerificationRequested: "email_verification_requested";
|
|
36
|
+
readonly emailVerified: "email_verified";
|
|
37
|
+
readonly passwordResetRequested: "password_reset_requested";
|
|
38
|
+
readonly passwordResetCompleted: "password_reset_completed";
|
|
39
|
+
readonly appTileClicked: "app_tile_clicked";
|
|
40
|
+
readonly workerOnboardingStarted: "worker_onboarding_started";
|
|
41
|
+
readonly documentUploaded: "document_uploaded";
|
|
42
|
+
readonly documentViewed: "document_viewed";
|
|
43
|
+
readonly workerViewed: "worker_viewed";
|
|
44
|
+
readonly assignmentViewed: "assignment_viewed";
|
|
45
|
+
readonly onboardingStepViewed: "onboarding_step_viewed";
|
|
46
|
+
readonly onboardingStepCompleted: "onboarding_step_completed";
|
|
47
|
+
readonly kycStarted: "kyc_started";
|
|
48
|
+
readonly insuranceValidationStarted: "insurance_validation_started";
|
|
49
|
+
readonly assignmentAccepted: "assignment_accepted";
|
|
50
|
+
readonly assignmentDeclined: "assignment_declined";
|
|
51
|
+
readonly caseFormSubmitted: "case_form_submitted";
|
|
52
|
+
readonly caseThreadOpened: "case_thread_opened";
|
|
53
|
+
/**
|
|
54
|
+
* The thread's "Did this answer your question?" prompt. `answer` is `yes`
|
|
55
|
+
* (the portal then closes the case, confirmed by the API's `case_closed`) or
|
|
56
|
+
* `no` (prompt dismissed; never reaches the API, so only visible here).
|
|
57
|
+
*/
|
|
58
|
+
readonly caseClosePromptAnswered: "case_close_prompt_answered";
|
|
59
|
+
/** Funnel step only: the authoritative record is the API's `worker_referred`. */
|
|
60
|
+
readonly workerReferralSubmitted: "worker_referral_submitted";
|
|
61
|
+
readonly simulationViewed: "simulation_viewed";
|
|
62
|
+
readonly bankDetailsSubmitted: "bank_details_submitted";
|
|
63
|
+
readonly userInvited: "user_invited";
|
|
64
|
+
readonly userRegistered: "user_registered";
|
|
65
|
+
readonly caseCreated: "case_created";
|
|
66
|
+
readonly caseReplyCreated: "case_reply_created";
|
|
67
|
+
readonly caseClosed: "case_closed";
|
|
68
|
+
readonly assignmentStatusChanged: "assignment_status_changed";
|
|
69
|
+
readonly changeRequestCreated: "change_request_created";
|
|
70
|
+
/** An agency referred a worker (CRM referral created at stage `referred`). */
|
|
71
|
+
readonly workerReferred: "worker_referred";
|
|
72
|
+
readonly changeRequestResponded: "change_request_responded";
|
|
73
|
+
readonly kycVerificationCompleted: "kyc_verification_completed";
|
|
74
|
+
readonly documentValidationCompleted: "document_validation_completed";
|
|
75
|
+
readonly contractorDocumentUploaded: "contractor_document_uploaded";
|
|
76
|
+
};
|
|
77
|
+
export type TAnalyticsEventName = (typeof AnalyticsEvents)[keyof typeof AnalyticsEvents];
|
|
78
|
+
/** Allowed property value types. No objects/arrays: keeps properties queryable and PII-review simple. */
|
|
79
|
+
export type TAnalyticsPropValue = string | number | boolean | null | undefined;
|
|
80
|
+
/** Event properties. Property names must be snake_case; no PII (names, emails, free text). */
|
|
81
|
+
export type TAnalyticsProps = Record<string, TAnalyticsPropValue>;
|
|
82
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/analytics/events.ts"],"names":[],"mappings":"AAKA,qHAAqH;AACrH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,KAAK,CAAA;AAElE;;;;;;;;;GASG;AACH,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAEhD,eAAO,MAAM,eAAe;;;IAI1B;;;;;;OAMG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAoCH;;;;OAIG;;IAEH,iFAAiF;;;;;;;;;;;IAajF,8EAA8E;;;;;;CAMtE,CAAA;AAEV,MAAM,MAAM,mBAAmB,GAC7B,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,OAAO,eAAe,CAAC,CAAA;AAExD,yGAAyG;AACzG,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAA;AAE9E,8FAA8F;AAC9F,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAA"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Mixpanel event dictionary - single source of truth for event names across
|
|
2
|
+
// all apps and the API. Naming convention: snake_case object_verb (past tense).
|
|
3
|
+
// Never encode dynamic data (ids, dates, page names) in an event NAME; use a
|
|
4
|
+
// property. Tracking plan: /context/docs/mixpanel-tracking-plan.md (workspace).
|
|
5
|
+
export const AnalyticsEvents = {
|
|
6
|
+
// Cross-app (identical shape in all web apps)
|
|
7
|
+
pageViewed: 'page_viewed',
|
|
8
|
+
panelViewed: 'panel_viewed',
|
|
9
|
+
/**
|
|
10
|
+
* A deliberately named UI click, for journey steps that do not warrant their
|
|
11
|
+
* own semantic event. Middle tier: prefer a semantic KPI event where one
|
|
12
|
+
* exists (assignment_accepted, not element_clicked), and leave everything
|
|
13
|
+
* else to autocapture. Carries a stable snake_case `element_name`, never a
|
|
14
|
+
* translated label.
|
|
15
|
+
*/
|
|
16
|
+
elementClicked: 'element_clicked',
|
|
17
|
+
formStarted: 'form_started',
|
|
18
|
+
formSubmitted: 'form_submitted',
|
|
19
|
+
formFailed: 'form_failed',
|
|
20
|
+
// wwl-auth (identity moments)
|
|
21
|
+
loginStarted: 'login_started',
|
|
22
|
+
loginCompleted: 'login_completed',
|
|
23
|
+
loginFailed: 'login_failed',
|
|
24
|
+
logoutCompleted: 'logout_completed',
|
|
25
|
+
inviteOpened: 'invite_opened',
|
|
26
|
+
inviteExpiredViewed: 'invite_expired_viewed',
|
|
27
|
+
signupCompleted: 'signup_completed',
|
|
28
|
+
emailVerificationRequested: 'email_verification_requested',
|
|
29
|
+
emailVerified: 'email_verified',
|
|
30
|
+
passwordResetRequested: 'password_reset_requested',
|
|
31
|
+
passwordResetCompleted: 'password_reset_completed',
|
|
32
|
+
appTileClicked: 'app_tile_clicked',
|
|
33
|
+
// Portal (admin/managers)
|
|
34
|
+
workerOnboardingStarted: 'worker_onboarding_started',
|
|
35
|
+
documentUploaded: 'document_uploaded',
|
|
36
|
+
documentViewed: 'document_viewed',
|
|
37
|
+
workerViewed: 'worker_viewed',
|
|
38
|
+
assignmentViewed: 'assignment_viewed',
|
|
39
|
+
// Services (workers/agencies)
|
|
40
|
+
onboardingStepViewed: 'onboarding_step_viewed',
|
|
41
|
+
onboardingStepCompleted: 'onboarding_step_completed',
|
|
42
|
+
kycStarted: 'kyc_started',
|
|
43
|
+
insuranceValidationStarted: 'insurance_validation_started',
|
|
44
|
+
assignmentAccepted: 'assignment_accepted',
|
|
45
|
+
assignmentDeclined: 'assignment_declined',
|
|
46
|
+
caseFormSubmitted: 'case_form_submitted',
|
|
47
|
+
caseThreadOpened: 'case_thread_opened',
|
|
48
|
+
/**
|
|
49
|
+
* The thread's "Did this answer your question?" prompt. `answer` is `yes`
|
|
50
|
+
* (the portal then closes the case, confirmed by the API's `case_closed`) or
|
|
51
|
+
* `no` (prompt dismissed; never reaches the API, so only visible here).
|
|
52
|
+
*/
|
|
53
|
+
caseClosePromptAnswered: 'case_close_prompt_answered',
|
|
54
|
+
/** Funnel step only: the authoritative record is the API's `worker_referred`. */
|
|
55
|
+
workerReferralSubmitted: 'worker_referral_submitted',
|
|
56
|
+
simulationViewed: 'simulation_viewed',
|
|
57
|
+
bankDetailsSubmitted: 'bank_details_submitted',
|
|
58
|
+
// API (server-truthy business outcomes - the authoritative record)
|
|
59
|
+
userInvited: 'user_invited',
|
|
60
|
+
userRegistered: 'user_registered',
|
|
61
|
+
caseCreated: 'case_created',
|
|
62
|
+
caseReplyCreated: 'case_reply_created',
|
|
63
|
+
caseClosed: 'case_closed',
|
|
64
|
+
assignmentStatusChanged: 'assignment_status_changed',
|
|
65
|
+
changeRequestCreated: 'change_request_created',
|
|
66
|
+
/** An agency referred a worker (CRM referral created at stage `referred`). */
|
|
67
|
+
workerReferred: 'worker_referred',
|
|
68
|
+
changeRequestResponded: 'change_request_responded',
|
|
69
|
+
kycVerificationCompleted: 'kyc_verification_completed',
|
|
70
|
+
documentValidationCompleted: 'document_validation_completed',
|
|
71
|
+
contractorDocumentUploaded: 'contractor_document_uploaded',
|
|
72
|
+
};
|
|
73
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/analytics/events.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,gFAAgF;AAChF,6EAA6E;AAC7E,gFAAgF;AAiBhF,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,8CAA8C;IAC9C,UAAU,EAAE,aAAa;IACzB,WAAW,EAAE,cAAc;IAC3B;;;;;;OAMG;IACH,cAAc,EAAE,iBAAiB;IACjC,WAAW,EAAE,cAAc;IAC3B,aAAa,EAAE,gBAAgB;IAC/B,UAAU,EAAE,aAAa;IAEzB,8BAA8B;IAC9B,YAAY,EAAE,eAAe;IAC7B,cAAc,EAAE,iBAAiB;IACjC,WAAW,EAAE,cAAc;IAC3B,eAAe,EAAE,kBAAkB;IACnC,YAAY,EAAE,eAAe;IAC7B,mBAAmB,EAAE,uBAAuB;IAC5C,eAAe,EAAE,kBAAkB;IACnC,0BAA0B,EAAE,8BAA8B;IAC1D,aAAa,EAAE,gBAAgB;IAC/B,sBAAsB,EAAE,0BAA0B;IAClD,sBAAsB,EAAE,0BAA0B;IAClD,cAAc,EAAE,kBAAkB;IAElC,0BAA0B;IAC1B,uBAAuB,EAAE,2BAA2B;IACpD,gBAAgB,EAAE,mBAAmB;IACrC,cAAc,EAAE,iBAAiB;IACjC,YAAY,EAAE,eAAe;IAC7B,gBAAgB,EAAE,mBAAmB;IAErC,8BAA8B;IAC9B,oBAAoB,EAAE,wBAAwB;IAC9C,uBAAuB,EAAE,2BAA2B;IACpD,UAAU,EAAE,aAAa;IACzB,0BAA0B,EAAE,8BAA8B;IAC1D,kBAAkB,EAAE,qBAAqB;IACzC,kBAAkB,EAAE,qBAAqB;IACzC,iBAAiB,EAAE,qBAAqB;IACxC,gBAAgB,EAAE,oBAAoB;IACtC;;;;OAIG;IACH,uBAAuB,EAAE,4BAA4B;IACrD,iFAAiF;IACjF,uBAAuB,EAAE,2BAA2B;IACpD,gBAAgB,EAAE,mBAAmB;IACrC,oBAAoB,EAAE,wBAAwB;IAE9C,mEAAmE;IACnE,WAAW,EAAE,cAAc;IAC3B,cAAc,EAAE,iBAAiB;IACjC,WAAW,EAAE,cAAc;IAC3B,gBAAgB,EAAE,oBAAoB;IACtC,UAAU,EAAE,aAAa;IACzB,uBAAuB,EAAE,2BAA2B;IACpD,oBAAoB,EAAE,wBAAwB;IAC9C,8EAA8E;IAC9E,cAAc,EAAE,iBAAiB;IACjC,sBAAsB,EAAE,0BAA0B;IAClD,wBAAwB,EAAE,4BAA4B;IACtD,2BAA2B,EAAE,+BAA+B;IAC5D,0BAA0B,EAAE,8BAA8B;CAClD,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/analytics/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,oBAAoB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/analytics/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,oBAAoB,CAAA"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { TAnalyticsApp, TAnalyticsEventName, TAnalyticsProps } from './events.js';
|
|
2
|
+
export declare const MIXPANEL_EU_HOST = "https://api-eu.mixpanel.com";
|
|
3
|
+
export interface IServerTrackerOptions {
|
|
4
|
+
/** Mixpanel project token. Missing or an unresolved "$(...)" pipeline macro disables the tracker (no-op, one warning). */
|
|
5
|
+
token: string | undefined;
|
|
6
|
+
/** Stamped on every event. Defaults to 'api'. */
|
|
7
|
+
app?: TAnalyticsApp;
|
|
8
|
+
host?: string;
|
|
9
|
+
/** Events per /import request. Mixpanel caps at 2000; keep batches small. */
|
|
10
|
+
maxBatchSize?: number;
|
|
11
|
+
flushIntervalMs?: number;
|
|
12
|
+
/** Send attempts per batch on 429/5xx/network failure before dropping. */
|
|
13
|
+
maxRetries?: number;
|
|
14
|
+
/** Injectable for tests and future runtimes. Defaults to globalThis.fetch. */
|
|
15
|
+
fetchImpl?: typeof fetch;
|
|
16
|
+
/** Failure reporter. Defaults to console.warn. Must not throw. */
|
|
17
|
+
onError?: (message: string, detail?: unknown) => void;
|
|
18
|
+
}
|
|
19
|
+
export interface ITrackOptions {
|
|
20
|
+
/** The acting user's identity ($user_id, e.g. the Auth0 sub). Required and non-empty. */
|
|
21
|
+
distinctId: string;
|
|
22
|
+
props?: TAnalyticsProps;
|
|
23
|
+
/** Event time, epoch ms. Defaults to now. */
|
|
24
|
+
timeMs?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface IServerTracker {
|
|
27
|
+
/** False when the token was missing/invalid at creation: all calls no-op. */
|
|
28
|
+
readonly enabled: boolean;
|
|
29
|
+
track: (event: TAnalyticsEventName, options: ITrackOptions) => void;
|
|
30
|
+
/** Send everything queued now. Resolves when the queue has drained (or dropped). */
|
|
31
|
+
flush: () => Promise<void>;
|
|
32
|
+
/** Stop the interval timer and flush. Call on process shutdown. */
|
|
33
|
+
shutdown: () => Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
export declare const createServerTracker: (options: IServerTrackerOptions) => IServerTracker;
|
|
36
|
+
//# sourceMappingURL=serverTracker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serverTracker.d.ts","sourceRoot":"","sources":["../../src/analytics/serverTracker.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,aAAa,EACb,mBAAmB,EACnB,eAAe,EAChB,MAAM,aAAa,CAAA;AAEpB,eAAO,MAAM,gBAAgB,gCAAgC,CAAA;AAE7D,MAAM,WAAW,qBAAqB;IACpC,0HAA0H;IAC1H,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IACzB,iDAAiD;IACjD,GAAG,CAAC,EAAE,aAAa,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,8EAA8E;IAC9E,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;IACxB,kEAAkE;IAClE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;CACtD;AAED,MAAM,WAAW,aAAa;IAC5B,yFAAyF;IACzF,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,KAAK,EAAE,CAAC,KAAK,EAAE,mBAAmB,EAAE,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IACnE,oFAAoF;IACpF,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1B,mEAAmE;IACnE,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9B;AAqBD,eAAO,MAAM,mBAAmB,GAC9B,SAAS,qBAAqB,KAC7B,cAmJF,CAAA"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// Server-side Mixpanel tracker over the /import HTTP API. Framework-agnostic
|
|
2
|
+
// by design (no Next.js or node: imports) so the API's planned migration off
|
|
3
|
+
// Next.js changes nothing here. Fire-and-forget: an analytics failure must
|
|
4
|
+
// never fail a business request, so nothing in here throws to the caller.
|
|
5
|
+
export const MIXPANEL_EU_HOST = 'https://api-eu.mixpanel.com';
|
|
6
|
+
const isUsableToken = (token) => typeof token === 'string' && token.length > 0 && !/^\$\(.+\)$/.test(token);
|
|
7
|
+
export const createServerTracker = (options) => {
|
|
8
|
+
const { token, app = 'api', host = MIXPANEL_EU_HOST, maxBatchSize = 200, flushIntervalMs = 10_000, maxRetries = 3, fetchImpl = globalThis.fetch, onError = (message, detail) =>
|
|
9
|
+
// eslint-disable-next-line no-console
|
|
10
|
+
console.warn(`[analytics] ${message}`, detail ?? ''), } = options;
|
|
11
|
+
const enabled = isUsableToken(token);
|
|
12
|
+
if (!enabled) {
|
|
13
|
+
onError('Mixpanel token missing or unresolved macro - tracking disabled');
|
|
14
|
+
return {
|
|
15
|
+
enabled: false,
|
|
16
|
+
track: () => undefined,
|
|
17
|
+
flush: () => Promise.resolve(),
|
|
18
|
+
shutdown: () => Promise.resolve(),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const importUrl = `${host}/import?strict=1`;
|
|
22
|
+
const authHeader = `Basic ${btoa(`${token}:`)}`;
|
|
23
|
+
let queue = [];
|
|
24
|
+
let timer;
|
|
25
|
+
let inFlight;
|
|
26
|
+
const scheduleFlush = () => {
|
|
27
|
+
if (timer !== undefined)
|
|
28
|
+
return;
|
|
29
|
+
timer = setTimeout(() => {
|
|
30
|
+
timer = undefined;
|
|
31
|
+
void flush();
|
|
32
|
+
}, flushIntervalMs);
|
|
33
|
+
// Do not keep the process alive for analytics (Node-only; absent in browsers).
|
|
34
|
+
const unref = timer.unref;
|
|
35
|
+
if (typeof unref === 'function')
|
|
36
|
+
unref.call(timer);
|
|
37
|
+
};
|
|
38
|
+
const sendBatch = async (batch) => {
|
|
39
|
+
try {
|
|
40
|
+
const response = await fetchImpl(importUrl, {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
headers: {
|
|
43
|
+
authorization: authHeader,
|
|
44
|
+
'content-type': 'application/json',
|
|
45
|
+
accept: 'application/json',
|
|
46
|
+
},
|
|
47
|
+
body: JSON.stringify(batch.map(pending => pending.body)),
|
|
48
|
+
});
|
|
49
|
+
if (response.ok)
|
|
50
|
+
return;
|
|
51
|
+
if (response.status === 429 || response.status >= 500) {
|
|
52
|
+
requeue(batch, `Mixpanel /import responded ${response.status}`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// 4xx other than 429: strict validation rejected the payload; retrying cannot help.
|
|
56
|
+
onError(`Mixpanel /import rejected batch (${response.status}) - ${batch.length} event(s) dropped`, await response.text().catch(() => undefined));
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
requeue(batch, 'Mixpanel /import request failed');
|
|
60
|
+
void error;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const requeue = (batch, reason) => {
|
|
64
|
+
const retryable = batch
|
|
65
|
+
.map(pending => ({ ...pending, attempts: pending.attempts + 1 }))
|
|
66
|
+
.filter(pending => pending.attempts < maxRetries);
|
|
67
|
+
const dropped = batch.length - retryable.length;
|
|
68
|
+
if (dropped > 0)
|
|
69
|
+
onError(`${reason} - ${dropped} event(s) dropped after ${maxRetries} attempts`);
|
|
70
|
+
if (retryable.length > 0) {
|
|
71
|
+
queue.push(...retryable);
|
|
72
|
+
scheduleFlush();
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
// One drain pass sends only what was queued when it started; failed events
|
|
76
|
+
// are requeued at the back with attempts+1 for the next pass, so a single
|
|
77
|
+
// pass never resends the same event. Retry volume is bounded by maxRetries.
|
|
78
|
+
// $insert_id makes any duplicate delivery a safe no-op on Mixpanel's side.
|
|
79
|
+
const drain = async () => {
|
|
80
|
+
let remaining = queue.length;
|
|
81
|
+
while (remaining > 0) {
|
|
82
|
+
const batch = queue.slice(0, Math.min(maxBatchSize, remaining));
|
|
83
|
+
queue = queue.slice(batch.length);
|
|
84
|
+
remaining -= batch.length;
|
|
85
|
+
await sendBatch(batch);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const flush = async () => {
|
|
89
|
+
// Loop until the queue is fully drained: picks up events queued while a
|
|
90
|
+
// drain was in flight, and retries requeued failures immediately. This
|
|
91
|
+
// terminates because failures increment attempts and drop at maxRetries.
|
|
92
|
+
while (inFlight !== undefined || queue.length > 0) {
|
|
93
|
+
if (inFlight === undefined)
|
|
94
|
+
inFlight = drain().finally(() => {
|
|
95
|
+
inFlight = undefined;
|
|
96
|
+
});
|
|
97
|
+
await inFlight;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const track = (event, trackOptions) => {
|
|
101
|
+
try {
|
|
102
|
+
const { distinctId, props, timeMs } = trackOptions;
|
|
103
|
+
if (!distinctId) {
|
|
104
|
+
onError(`track('${event}') called without a distinctId - event dropped`);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
queue.push({
|
|
108
|
+
attempts: 0,
|
|
109
|
+
body: {
|
|
110
|
+
event,
|
|
111
|
+
properties: {
|
|
112
|
+
...props,
|
|
113
|
+
time: timeMs ?? Date.now(),
|
|
114
|
+
distinct_id: distinctId,
|
|
115
|
+
$insert_id: globalThis.crypto.randomUUID(),
|
|
116
|
+
ip: '0',
|
|
117
|
+
app,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
if (queue.length >= maxBatchSize)
|
|
122
|
+
void flush();
|
|
123
|
+
else
|
|
124
|
+
scheduleFlush();
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
onError('track() failed', error);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const shutdown = async () => {
|
|
131
|
+
if (timer !== undefined) {
|
|
132
|
+
clearTimeout(timer);
|
|
133
|
+
timer = undefined;
|
|
134
|
+
}
|
|
135
|
+
await flush();
|
|
136
|
+
};
|
|
137
|
+
return { enabled: true, track, flush, shutdown };
|
|
138
|
+
};
|
|
139
|
+
//# sourceMappingURL=serverTracker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serverTracker.js","sourceRoot":"","sources":["../../src/analytics/serverTracker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serverTracker.test.d.ts","sourceRoot":"","sources":["../../src/analytics/serverTracker.test.ts"],"names":[],"mappings":""}
|