@purespectrum1/purescore-client 0.7.0 → 0.9.8

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.
Files changed (47) hide show
  1. package/README.md +135 -3
  2. package/lib/application/auto-init.d.ts +6 -3
  3. package/lib/application/auto-init.js +29 -20
  4. package/lib/application/device-identifier.d.ts +18 -1
  5. package/lib/application/device-identifier.js +43 -5
  6. package/lib/domain/errors/index.d.ts +1 -0
  7. package/lib/domain/errors/index.js +17 -0
  8. package/lib/domain/errors/transaction-rejected-error.d.ts +7 -0
  9. package/lib/domain/errors/transaction-rejected-error.js +14 -0
  10. package/lib/domain/models/crosscutting/device-intelligence.d.ts +3 -3
  11. package/lib/domain/models/crosscutting/device-intelligence.js +3 -3
  12. package/lib/domain/models/crosscutting/index.d.ts +1 -0
  13. package/lib/domain/models/crosscutting/index.js +1 -0
  14. package/lib/domain/models/crosscutting/verisoul.d.ts +19 -0
  15. package/lib/domain/models/crosscutting/verisoul.js +25 -0
  16. package/lib/domain/protocols/services/device-fingerprint/navigator.d.ts +1 -0
  17. package/lib/domain/protocols/services/device-identifier.d.ts +6 -6
  18. package/lib/domain/protocols/services/interaction-activity.d.ts +25 -0
  19. package/lib/domain/protocols/services/interaction-activity.js +2 -0
  20. package/lib/domain/protocols/services/single-script-injection/auto-init.d.ts +8 -8
  21. package/lib/domain/protocols/services/verisoul.d.ts +23 -0
  22. package/lib/domain/protocols/services/verisoul.js +2 -0
  23. package/lib/domain/services/device-fingerprint/index.d.ts +1 -0
  24. package/lib/domain/services/device-fingerprint/index.js +1 -0
  25. package/lib/domain/services/device-fingerprint/navigator.d.ts +2 -0
  26. package/lib/domain/services/device-fingerprint/navigator.js +7 -0
  27. package/lib/domain/services/device-fingerprint/webgl.d.ts +5 -0
  28. package/lib/domain/services/device-fingerprint/webgl.js +29 -0
  29. package/lib/domain/services/dom-mutation-tracker.d.ts +19 -0
  30. package/lib/domain/services/dom-mutation-tracker.js +47 -0
  31. package/lib/domain/services/interaction-activity.d.ts +43 -0
  32. package/lib/domain/services/interaction-activity.js +189 -0
  33. package/lib/domain/services/single-script-injection/checkpoint-scheduler.d.ts +1 -1
  34. package/lib/domain/services/single-script-injection/checkpoint-scheduler.js +3 -3
  35. package/lib/domain/services/single-script-injection/response-delivery.d.ts +3 -3
  36. package/lib/domain/services/single-script-injection/response-delivery.js +3 -3
  37. package/lib/domain/services/single-script-injection/schedule-store.d.ts +17 -7
  38. package/lib/domain/services/single-script-injection/schedule-store.js +11 -10
  39. package/lib/domain/services/snapshot-store.d.ts +1 -0
  40. package/lib/domain/services/snapshot-store.js +3 -0
  41. package/lib/domain/services/translation-detector.d.ts +20 -0
  42. package/lib/domain/services/translation-detector.js +119 -0
  43. package/lib/domain/services/verisoul.d.ts +27 -0
  44. package/lib/domain/services/verisoul.js +96 -0
  45. package/lib/index.d.ts +2 -1
  46. package/lib/index.js +2 -2
  47. package/package.json +1 -1
@@ -19,7 +19,7 @@ export interface DeviceIdentifierOptions {
19
19
  canvas?: boolean;
20
20
  /**
21
21
  * Set by the `data-auto-init="true"` bootstrap. Sends
22
- * `single_script_injection: true` on both Device-Intelligence calls.
22
+ * `single_script_injection: true` on both PureScore-Intelligence calls.
23
23
  */
24
24
  singleScriptInjection?: boolean;
25
25
  }
@@ -28,7 +28,7 @@ export interface DeviceIdentifierConfig {
28
28
  env?: 'staging' | 'production';
29
29
  }
30
30
  /**
31
- * Flushes the collected signals for a transaction to the Device-Intelligence
31
+ * Flushes the collected signals for a transaction to the PureScore-Intelligence
32
32
  * service and resolves with its response.
33
33
  */
34
34
  export interface TrackDetails {
@@ -38,13 +38,13 @@ export interface TrackDetails {
38
38
  * What a `trackDetails` call knows about the transaction beyond its id.
39
39
  */
40
40
  export interface TrackDetailsContext {
41
- surveyId?: string;
41
+ projectId?: string;
42
42
  }
43
43
  /**
44
44
  * Survey details sent once to `POST /v1/transaction` at transaction start.
45
45
  */
46
46
  export interface TransactionDetails {
47
- surveyId: string;
47
+ projectId: string;
48
48
  expectedSurveyLOI: number;
49
49
  surveyCountry: string;
50
50
  surveyLanguage: string;
@@ -58,10 +58,10 @@ export interface TransactionDetails {
58
58
  };
59
59
  }
60
60
  /**
61
- * Registers a transaction with the Device-Intelligence service.
61
+ * Registers a transaction with the PureScore-Intelligence service.
62
62
  */
63
63
  export interface CreateTransaction {
64
- createTransaction: (details: TransactionDetails, config?: DeviceIdentifierConfig) => Promise<string | undefined>;
64
+ createTransaction: (details: TransactionDetails, config?: DeviceIdentifierConfig, metadata?: DeviceIdentifierMetadata) => Promise<string | undefined>;
65
65
  }
66
66
  export interface DeviceIdentifierMetadata {
67
67
  [key: string]: any;
@@ -0,0 +1,25 @@
1
+ export type ClipboardActivityType = 'copy' | 'cut' | 'paste';
2
+ export interface ClipboardActivity {
3
+ type: ClipboardActivityType;
4
+ timestamp: number;
5
+ targetTag: string | null;
6
+ length?: number;
7
+ }
8
+ export interface TabSwitch {
9
+ leftAt: number;
10
+ returnedAt: number | null;
11
+ }
12
+ export interface InteractionActivity {
13
+ copyCount: number;
14
+ cutCount: number;
15
+ pasteCount: number;
16
+ tabSwitchCount: number;
17
+ timeAwayMs: number;
18
+ clipboardEvents: ClipboardActivity[];
19
+ tabSwitches: TabSwitch[];
20
+ trackingStartedAt: number | null;
21
+ totalDurationMs: number;
22
+ translationDetected: boolean;
23
+ translationDetectedAt: number | null;
24
+ translationPhrase: string | null;
25
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,6 +1,6 @@
1
1
  import { DeviceIdentifierMetadata } from '../device-identifier';
2
2
  /**
3
- * A single scheduled Device-Intelligence call.
3
+ * A single scheduled PureScore-Intelligence call.
4
4
  */
5
5
  export interface ScheduleCheckpoint {
6
6
  /**
@@ -14,9 +14,9 @@ export interface ScheduleCheckpoint {
14
14
  dueAt: number;
15
15
  }
16
16
  /**
17
- * The `ps_di_schedule_<surveyId>` record.
17
+ * The `ps_di_schedule_<projectId>_<supplierId>_<supplierMemberId|emailHash>` record.
18
18
  *
19
- * It is keyed by survey id and handed to a storage adapter rather than
19
+ * It is keyed by survey and respondent and handed to a storage adapter rather than
20
20
  * held in page memory, so that a page navigation resumes the schedule instead of
21
21
  * restarting it: `startedAt` is written once, by the very first page load of the
22
22
  * transaction.
@@ -25,7 +25,7 @@ export interface ScheduleRecord {
25
25
  /**
26
26
  * The survey the schedule belongs to.
27
27
  */
28
- surveyId: string;
28
+ projectId: string;
29
29
  /**
30
30
  * Length of the interview, in minutes, from `data-expected-survey-loi`. Null
31
31
  * only between creating the record and applying the LOI to it.
@@ -50,7 +50,7 @@ export interface ScheduleRecord {
50
50
  */
51
51
  expiry: number;
52
52
  /**
53
- * Replay buffer: the most recent Device-Intelligence response, so a page that
53
+ * Replay buffer: the most recent PureScore-Intelligence response, so a page that
54
54
  * registers a listener after the checkpoint fired still receives the verdict.
55
55
  */
56
56
  lastResponse?: Record<string, any> | null;
@@ -64,7 +64,7 @@ export interface ScheduleRecord {
64
64
  */
65
65
  export interface AutoInitConfig {
66
66
  /**
67
- * `data-token` — Device-Intelligence bearer token.
67
+ * `data-token` — PureScore-Intelligence bearer token.
68
68
  */
69
69
  token: string;
70
70
  /**
@@ -80,9 +80,9 @@ export interface AutoInitConfig {
80
80
  */
81
81
  callbackName?: string;
82
82
  /**
83
- * `data-survey-id`.
83
+ * `data-project-id`.
84
84
  */
85
- surveyId: string;
85
+ projectId: string;
86
86
  /**
87
87
  * `data-expected-survey-loi` — length of the interview, in minutes. Drives the
88
88
  * checkpoint schedule.
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The global the Verisoul SDK installs once its bundle has loaded.
3
+ */
4
+ export interface VerisoulSdk {
5
+ session: () => Promise<{
6
+ session_id: string;
7
+ }>;
8
+ reinitialize: () => Promise<void>;
9
+ }
10
+ declare global {
11
+ interface Window {
12
+ Verisoul?: VerisoulSdk;
13
+ }
14
+ }
15
+ /**
16
+ * Supplies the respondent's Verisoul session id. Never throws: a missing or
17
+ * blocked SDK resolves with undefined so it cannot hold up the
18
+ * PureScore-Intelligence call it is attached to.
19
+ */
20
+ export interface VerisoulSessionProvider {
21
+ load: () => void;
22
+ getSessionId: () => Promise<string | undefined>;
23
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -6,3 +6,4 @@ export * from './navigator';
6
6
  export * from './pointer-capability';
7
7
  export * from './storage';
8
8
  export * from './utils';
9
+ export * from './webgl';
@@ -22,3 +22,4 @@ __exportStar(require("./navigator"), exports);
22
22
  __exportStar(require("./pointer-capability"), exports);
23
23
  __exportStar(require("./storage"), exports);
24
24
  __exportStar(require("./utils"), exports);
25
+ __exportStar(require("./webgl"), exports);
@@ -107,4 +107,6 @@ export declare class Navigatior {
107
107
  * @returns {number} The RTT in milliseconds, or -1 if the Network Information API is not supported.
108
108
  */
109
109
  getEstimatedRoundTripTime(): number;
110
+ getCpuCores(): number | null;
111
+ getDeviceMemory(): number | null;
110
112
  }
@@ -209,5 +209,12 @@ class Navigatior {
209
209
  }
210
210
  return -1;
211
211
  }
212
+ getCpuCores() {
213
+ return navigator.hardwareConcurrency || null;
214
+ }
215
+ getDeviceMemory() {
216
+ var _a;
217
+ return (_a = navigator.deviceMemory) !== null && _a !== void 0 ? _a : null;
218
+ }
212
219
  }
213
220
  exports.Navigatior = Navigatior;
@@ -0,0 +1,5 @@
1
+ export declare class WebGL {
2
+ private gpuRenderer?;
3
+ getGpuRenderer(): string | null;
4
+ private _readGpuRenderer;
5
+ }
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebGL = void 0;
4
+ class WebGL {
5
+ getGpuRenderer() {
6
+ if (this.gpuRenderer === undefined) {
7
+ this.gpuRenderer = this._readGpuRenderer();
8
+ }
9
+ return this.gpuRenderer;
10
+ }
11
+ _readGpuRenderer() {
12
+ var _a;
13
+ try {
14
+ const canvas = document.createElement('canvas');
15
+ const gl = (canvas.getContext('webgl') || canvas.getContext('experimental-webgl'));
16
+ if (!gl) {
17
+ return null;
18
+ }
19
+ const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
20
+ const renderer = gl.getParameter(debugInfo ? debugInfo.UNMASKED_RENDERER_WEBGL : gl.RENDERER);
21
+ (_a = gl.getExtension('WEBGL_lose_context')) === null || _a === void 0 ? void 0 : _a.loseContext();
22
+ return typeof renderer === 'string' && renderer ? renderer : null;
23
+ }
24
+ catch (_b) {
25
+ return null;
26
+ }
27
+ }
28
+ }
29
+ exports.WebGL = WebGL;
@@ -0,0 +1,19 @@
1
+ import { GetStorage, SetStorage } from '../protocols/cache';
2
+ /**
3
+ * Counts `style` attribute mutations on the page's elements, a signal of AI
4
+ * agents driving the browser.
5
+ *
6
+ * The count is never reset: it is mirrored into storage on every change and
7
+ * hydrated on start, so it keeps accumulating across page navigations for the
8
+ * whole session instead of restarting on each page.
9
+ */
10
+ export declare class DomMutationTracker {
11
+ private readonly storage;
12
+ private observer;
13
+ private count;
14
+ constructor(storage: GetStorage & SetStorage);
15
+ get domElementsMutationCount(): number;
16
+ start(): void;
17
+ stop(): void;
18
+ private onMutations;
19
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DomMutationTracker = void 0;
4
+ const DOM_MUTATION_PERSIST_KEY = 'ps_deviceIdentifier_domElementsMutationCount';
5
+ /**
6
+ * Counts `style` attribute mutations on the page's elements, a signal of AI
7
+ * agents driving the browser.
8
+ *
9
+ * The count is never reset: it is mirrored into storage on every change and
10
+ * hydrated on start, so it keeps accumulating across page navigations for the
11
+ * whole session instead of restarting on each page.
12
+ */
13
+ class DomMutationTracker {
14
+ constructor(storage) {
15
+ this.storage = storage;
16
+ this.observer = null;
17
+ this.count = 0;
18
+ }
19
+ get domElementsMutationCount() {
20
+ return this.count;
21
+ }
22
+ start() {
23
+ if (this.observer || typeof document === 'undefined' || typeof MutationObserver === 'undefined')
24
+ return;
25
+ const stored = this.storage.get(DOM_MUTATION_PERSIST_KEY);
26
+ this.count = Math.max(this.count, Number(stored === null || stored === void 0 ? void 0 : stored.count) || 0);
27
+ this.observer = new MutationObserver(mutations => this.onMutations(mutations));
28
+ this.observer.observe(document.documentElement, {
29
+ attributes: true,
30
+ attributeFilter: ['style'],
31
+ subtree: true,
32
+ });
33
+ }
34
+ stop() {
35
+ var _a;
36
+ (_a = this.observer) === null || _a === void 0 ? void 0 : _a.disconnect();
37
+ this.observer = null;
38
+ }
39
+ onMutations(mutations) {
40
+ const counted = mutations.filter(mutation => mutation.type === 'attributes' && mutation.target instanceof HTMLElement).length;
41
+ if (!counted)
42
+ return;
43
+ this.count += counted;
44
+ this.storage.set(DOM_MUTATION_PERSIST_KEY, { count: this.count });
45
+ }
46
+ }
47
+ exports.DomMutationTracker = DomMutationTracker;
@@ -0,0 +1,43 @@
1
+ import { GetStorage, RemoveStorage, SetStorage } from '../protocols/cache';
2
+ import { InteractionActivity } from '../protocols/services/interaction-activity';
3
+ export declare const INTERACTION_ACTIVITY_MAX_ENTRIES = 50;
4
+ export declare class InteractionActivityTracker {
5
+ private copyCount;
6
+ private cutCount;
7
+ private pasteCount;
8
+ private tabSwitchCount;
9
+ private timeAwayMs;
10
+ private clipboardEvents;
11
+ private tabSwitches;
12
+ private trackingStartedAt;
13
+ private translationDetected;
14
+ private translationDetectedAt;
15
+ private translationPhrase;
16
+ private isTracking;
17
+ private isUnloading;
18
+ private readonly clipboardHandler;
19
+ private readonly visibilityHandler;
20
+ private readonly blurHandler;
21
+ private readonly focusHandler;
22
+ private readonly pageHideHandler;
23
+ private readonly pageShowHandler;
24
+ private readonly snapshots;
25
+ private readonly translation;
26
+ constructor(storage: GetStorage & SetStorage & RemoveStorage);
27
+ startTracking(): void;
28
+ stopTracking(): void;
29
+ hasActiveListeners(): boolean;
30
+ getState(): InteractionActivity;
31
+ restoreState(state: Partial<InteractionActivity>): void;
32
+ getInteractionActivity(): InteractionActivity;
33
+ private handleClipboard;
34
+ private pastedLength;
35
+ private handleTranslation;
36
+ private handleTranslationSample;
37
+ private handleVisibilityChange;
38
+ private handleLeave;
39
+ private handleReturn;
40
+ private isAway;
41
+ private openTabSwitch;
42
+ private capped;
43
+ }
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InteractionActivityTracker = exports.INTERACTION_ACTIVITY_MAX_ENTRIES = void 0;
4
+ const snapshot_store_1 = require("./snapshot-store");
5
+ const translation_detector_1 = require("./translation-detector");
6
+ exports.INTERACTION_ACTIVITY_MAX_ENTRIES = 50;
7
+ const INTERACTION_ACTIVITY_PERSIST_KEY = 'ps_deviceIdentifier_interactionActivity';
8
+ const CLIPBOARD_EVENTS = ['copy', 'cut', 'paste'];
9
+ class InteractionActivityTracker {
10
+ constructor(storage) {
11
+ this.copyCount = 0;
12
+ this.cutCount = 0;
13
+ this.pasteCount = 0;
14
+ this.tabSwitchCount = 0;
15
+ this.timeAwayMs = 0;
16
+ this.clipboardEvents = [];
17
+ this.tabSwitches = [];
18
+ this.trackingStartedAt = null;
19
+ this.translationDetected = false;
20
+ this.translationDetectedAt = null;
21
+ this.translationPhrase = null;
22
+ this.isTracking = false;
23
+ this.isUnloading = false;
24
+ this.translation = new translation_detector_1.TranslationDetector();
25
+ this.clipboardHandler = this.handleClipboard.bind(this);
26
+ this.visibilityHandler = this.handleVisibilityChange.bind(this);
27
+ this.blurHandler = this.handleLeave.bind(this);
28
+ this.focusHandler = this.handleReturn.bind(this);
29
+ this.pageHideHandler = () => { this.isUnloading = true; };
30
+ this.pageShowHandler = () => { this.isUnloading = false; };
31
+ this.snapshots = new snapshot_store_1.SnapshotStore(INTERACTION_ACTIVITY_PERSIST_KEY, () => this.getState(), storage);
32
+ }
33
+ startTracking() {
34
+ var _a;
35
+ if (this.isTracking || typeof document === 'undefined' || typeof window === 'undefined')
36
+ return;
37
+ const stored = this.snapshots.read();
38
+ if (stored)
39
+ this.restoreState(stored);
40
+ this.isTracking = true;
41
+ this.isUnloading = false;
42
+ (_a = this.trackingStartedAt) !== null && _a !== void 0 ? _a : (this.trackingStartedAt = Date.now());
43
+ for (const type of CLIPBOARD_EVENTS) {
44
+ document.addEventListener(type, this.clipboardHandler);
45
+ }
46
+ document.addEventListener('visibilitychange', this.visibilityHandler);
47
+ window.addEventListener('blur', this.blurHandler);
48
+ window.addEventListener('focus', this.focusHandler);
49
+ window.addEventListener('pagehide', this.pageHideHandler);
50
+ window.addEventListener('pageshow', this.pageShowHandler);
51
+ if (!this.translationDetected)
52
+ this.translation.start({
53
+ onDetected: () => this.handleTranslation(),
54
+ onSample: sample => this.handleTranslationSample(sample),
55
+ });
56
+ if (this.isAway() && document.visibilityState === 'visible' && document.hasFocus()) {
57
+ this.handleReturn();
58
+ }
59
+ }
60
+ stopTracking() {
61
+ if (!this.isTracking)
62
+ return;
63
+ for (const type of CLIPBOARD_EVENTS) {
64
+ document.removeEventListener(type, this.clipboardHandler);
65
+ }
66
+ document.removeEventListener('visibilitychange', this.visibilityHandler);
67
+ window.removeEventListener('blur', this.blurHandler);
68
+ window.removeEventListener('focus', this.focusHandler);
69
+ window.removeEventListener('pagehide', this.pageHideHandler);
70
+ window.removeEventListener('pageshow', this.pageShowHandler);
71
+ this.translation.stop();
72
+ this.isTracking = false;
73
+ }
74
+ hasActiveListeners() {
75
+ return this.isTracking;
76
+ }
77
+ getState() {
78
+ return this.getInteractionActivity();
79
+ }
80
+ restoreState(state) {
81
+ if (typeof state.copyCount === 'number')
82
+ this.copyCount = state.copyCount;
83
+ if (typeof state.cutCount === 'number')
84
+ this.cutCount = state.cutCount;
85
+ if (typeof state.pasteCount === 'number')
86
+ this.pasteCount = state.pasteCount;
87
+ if (typeof state.tabSwitchCount === 'number')
88
+ this.tabSwitchCount = state.tabSwitchCount;
89
+ if (typeof state.timeAwayMs === 'number')
90
+ this.timeAwayMs = state.timeAwayMs;
91
+ if (Array.isArray(state.clipboardEvents))
92
+ this.clipboardEvents = state.clipboardEvents.slice(-exports.INTERACTION_ACTIVITY_MAX_ENTRIES);
93
+ if (Array.isArray(state.tabSwitches))
94
+ this.tabSwitches = state.tabSwitches.slice(-exports.INTERACTION_ACTIVITY_MAX_ENTRIES);
95
+ if (typeof state.trackingStartedAt === 'number')
96
+ this.trackingStartedAt = state.trackingStartedAt;
97
+ if (typeof state.translationDetected === 'boolean')
98
+ this.translationDetected = state.translationDetected;
99
+ if (typeof state.translationDetectedAt === 'number')
100
+ this.translationDetectedAt = state.translationDetectedAt;
101
+ if (typeof state.translationPhrase === 'string')
102
+ this.translationPhrase = state.translationPhrase;
103
+ }
104
+ getInteractionActivity() {
105
+ return {
106
+ copyCount: this.copyCount,
107
+ cutCount: this.cutCount,
108
+ pasteCount: this.pasteCount,
109
+ tabSwitchCount: this.tabSwitchCount,
110
+ timeAwayMs: this.timeAwayMs,
111
+ clipboardEvents: this.clipboardEvents.map(event => (Object.assign({}, event))),
112
+ tabSwitches: this.tabSwitches.map(tabSwitch => (Object.assign({}, tabSwitch))),
113
+ trackingStartedAt: this.trackingStartedAt,
114
+ totalDurationMs: this.trackingStartedAt === null ? 0 : Math.max(0, Date.now() - this.trackingStartedAt),
115
+ translationDetected: this.translationDetected,
116
+ translationDetectedAt: this.translationDetectedAt,
117
+ translationPhrase: this.translationPhrase,
118
+ };
119
+ }
120
+ handleClipboard(event) {
121
+ const type = event.type;
122
+ const entry = {
123
+ type,
124
+ timestamp: Date.now(),
125
+ targetTag: event.target instanceof Element ? event.target.tagName.toLowerCase() : null,
126
+ };
127
+ if (type === 'copy')
128
+ this.copyCount++;
129
+ if (type === 'cut')
130
+ this.cutCount++;
131
+ if (type === 'paste') {
132
+ this.pasteCount++;
133
+ entry.length = this.pastedLength(event);
134
+ }
135
+ this.clipboardEvents = this.capped([...this.clipboardEvents, entry]);
136
+ this.snapshots.save();
137
+ }
138
+ pastedLength(event) {
139
+ const data = event.clipboardData;
140
+ return ((data === null || data === void 0 ? void 0 : data.getData('text/plain')) || (data === null || data === void 0 ? void 0 : data.getData('text')) || '').length;
141
+ }
142
+ handleTranslation() {
143
+ if (this.translationDetected)
144
+ return;
145
+ this.translationDetected = true;
146
+ this.translationDetectedAt = Date.now();
147
+ this.snapshots.save();
148
+ }
149
+ handleTranslationSample(sample) {
150
+ this.translationPhrase = sample;
151
+ this.snapshots.save();
152
+ }
153
+ handleVisibilityChange() {
154
+ if (document.visibilityState === 'hidden') {
155
+ this.handleLeave();
156
+ }
157
+ else {
158
+ this.handleReturn();
159
+ }
160
+ }
161
+ handleLeave() {
162
+ // A navigation within the tab hides the outgoing page after `pagehide`.
163
+ if (this.isUnloading || this.isAway())
164
+ return;
165
+ this.tabSwitchCount++;
166
+ this.tabSwitches = this.capped([...this.tabSwitches, { leftAt: Date.now(), returnedAt: null }]);
167
+ this.snapshots.save();
168
+ }
169
+ handleReturn() {
170
+ const open = this.openTabSwitch();
171
+ if (!open)
172
+ return;
173
+ const now = Date.now();
174
+ open.returnedAt = now;
175
+ this.timeAwayMs += Math.max(0, now - open.leftAt);
176
+ this.snapshots.save();
177
+ }
178
+ isAway() {
179
+ return this.openTabSwitch() !== undefined;
180
+ }
181
+ openTabSwitch() {
182
+ const last = this.tabSwitches[this.tabSwitches.length - 1];
183
+ return (last === null || last === void 0 ? void 0 : last.returnedAt) === null ? last : undefined;
184
+ }
185
+ capped(entries) {
186
+ return entries.slice(-exports.INTERACTION_ACTIVITY_MAX_ENTRIES);
187
+ }
188
+ }
189
+ exports.InteractionActivityTracker = InteractionActivityTracker;
@@ -10,7 +10,7 @@ import { ScheduleStore } from './schedule-store';
10
10
  export declare const TICK_MS = 5000;
11
11
  type CheckpointHandler = (n: number, record: ScheduleRecord) => Promise<void>;
12
12
  /**
13
- * Drives the Device-Intelligence calls off the schedule record.
13
+ * Drives the PureScore-Intelligence calls off the schedule record.
14
14
  *
15
15
  * Every tick it resolves the next outstanding checkpoint and fires it if its due
16
16
  * time has passed; otherwise it keeps waiting. Once every checkpoint of the
@@ -19,7 +19,7 @@ exports.CheckpointScheduler = exports.TICK_MS = void 0;
19
19
  */
20
20
  exports.TICK_MS = 5000;
21
21
  /**
22
- * Drives the Device-Intelligence calls off the schedule record.
22
+ * Drives the PureScore-Intelligence calls off the schedule record.
23
23
  *
24
24
  * Every tick it resolves the next outstanding checkpoint and fires it if its due
25
25
  * time has passed; otherwise it keeps waiting. Once every checkpoint of the
@@ -55,7 +55,7 @@ class CheckpointScheduler {
55
55
  }
56
56
  /** Fires a tick without letting its promise escape as an unhandled rejection. */
57
57
  runTick() {
58
- this.tick().catch(error => console.error('Error in Device-Intelligence checkpoint tick:', error));
58
+ this.tick().catch(error => console.error('Error in PureScore-Intelligence checkpoint tick:', error));
59
59
  }
60
60
  tick() {
61
61
  return __awaiter(this, void 0, void 0, function* () {
@@ -83,7 +83,7 @@ class CheckpointScheduler {
83
83
  catch (error) {
84
84
  // Swallowed on purpose: a rejected checkpoint must not surface as an
85
85
  // unhandled rejection on the buyer's survey page.
86
- console.error(`Error delivering Device-Intelligence checkpoint ${next.n}:`, error);
86
+ console.error(`Error delivering PureScore-Intelligence checkpoint ${next.n}:`, error);
87
87
  }
88
88
  finally {
89
89
  this.inFlight = false;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Name of the custom event carrying the Device-Intelligence response.
2
+ * Name of the custom event carrying the PureScore-Intelligence response.
3
3
  *
4
4
  * The `ps:` prefix namespaces it so it cannot collide with an event the buyer's
5
5
  * own survey platform dispatches.
@@ -11,9 +11,9 @@ export declare const PS_RESULT_EVENT = "ps:purescore:result";
11
11
  */
12
12
  export declare const PS_GLOBAL = "psPureScore";
13
13
  /**
14
- * Hands the Device-Intelligence response back to the buyer.
14
+ * Hands the PureScore-Intelligence response back to the buyer.
15
15
  *
16
- * Once the Device-Identifier owns the call it also owns the response, so it has
16
+ * Once the Purescore-Client owns the call it also owns the response, so it has
17
17
  * to push it out. Three channels carry the same object and the buyer picks
18
18
  * whichever suits their platform:
19
19
  *
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ResponseDeliveryService = exports.PS_GLOBAL = exports.PS_RESULT_EVENT = void 0;
4
4
  const validation_1 = require("../../../validation");
5
5
  /**
6
- * Name of the custom event carrying the Device-Intelligence response.
6
+ * Name of the custom event carrying the PureScore-Intelligence response.
7
7
  *
8
8
  * The `ps:` prefix namespaces it so it cannot collide with an event the buyer's
9
9
  * own survey platform dispatches.
@@ -15,9 +15,9 @@ exports.PS_RESULT_EVENT = 'ps:purescore:result';
15
15
  */
16
16
  exports.PS_GLOBAL = 'psPureScore';
17
17
  /**
18
- * Hands the Device-Intelligence response back to the buyer.
18
+ * Hands the PureScore-Intelligence response back to the buyer.
19
19
  *
20
- * Once the Device-Identifier owns the call it also owns the response, so it has
20
+ * Once the Purescore-Client owns the call it also owns the response, so it has
21
21
  * to push it out. Three channels carry the same object and the buyer picks
22
22
  * whichever suits their platform:
23
23
  *
@@ -12,15 +12,25 @@ export declare const LOI_CHECKPOINT_FRACTIONS: readonly [number, number, 1, 2];
12
12
  /** Most calls a transaction can make: the initial one plus each LOI fraction. */
13
13
  export declare const CHECKPOINT_COUNT: number;
14
14
  /**
15
- * Owns the `ps_di_schedule_<surveyId>` record: which checkpoints are still
16
- * outstanding on a later page load. Storage is injected, so this holds only the
17
- * schedule rules, not where the record lives.
15
+ * Who a schedule belongs to: the survey plus whatever identifies the respondent.
16
+ */
17
+ export interface ScheduleIdentity {
18
+ projectId: string;
19
+ supplierId?: string;
20
+ supplierMemberId?: string;
21
+ emailHash?: string;
22
+ }
23
+ /**
24
+ * Owns the `ps_di_schedule_<projectId>_<supplierId>_<supplierMemberId|emailHash>`
25
+ * record: which checkpoints are still outstanding on a later page load. Parts
26
+ * that were not provided are left out of the key. Storage is injected, so this
27
+ * holds only the schedule rules, not where the record lives.
18
28
  */
19
29
  export declare class ScheduleStore {
20
- private readonly surveyId;
21
30
  private readonly storage;
22
- constructor(surveyId: string, storage: GetStorage & SetStorage & RemoveStorage);
23
- get key(): string;
31
+ private readonly projectId;
32
+ readonly key: string;
33
+ constructor({ projectId, supplierId, supplierMemberId, emailHash }: ScheduleIdentity, storage: GetStorage & SetStorage & RemoveStorage);
24
34
  /**
25
35
  * Checkpoint 1 fires `INITIAL_CHECKPOINT_DELAY_MS` in regardless; the rest are
26
36
  * LOI multiples, so an unknown LOI yields a one-checkpoint schedule rather
@@ -57,7 +67,7 @@ export declare class ScheduleStore {
57
67
  */
58
68
  markSent(n: number): ScheduleRecord | null;
59
69
  /**
60
- * Stores the latest Device-Intelligence response alongside the schedule so it
70
+ * Stores the latest PureScore-Intelligence response alongside the schedule so it
61
71
  * can be replayed on the next page load.
62
72
  */
63
73
  storeResponse(response: Record<string, any>): void;