@purespectrum1/purescore-client 0.7.0 → 1.0.0

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
@@ -14,17 +14,18 @@ exports.LOI_CHECKPOINT_FRACTIONS = [1 / 3, 2 / 3, 1, 2];
14
14
  /** Most calls a transaction can make: the initial one plus each LOI fraction. */
15
15
  exports.CHECKPOINT_COUNT = 1 + exports.LOI_CHECKPOINT_FRACTIONS.length;
16
16
  /**
17
- * Owns the `ps_di_schedule_<surveyId>` record: which checkpoints are still
18
- * outstanding on a later page load. Storage is injected, so this holds only the
19
- * schedule rules, not where the record lives.
17
+ * Owns the `ps_di_schedule_<projectId>_<supplierId>_<supplierMemberId|emailHash>`
18
+ * record: which checkpoints are still outstanding on a later page load. Parts
19
+ * that were not provided are left out of the key. Storage is injected, so this
20
+ * holds only the schedule rules, not where the record lives.
20
21
  */
21
22
  class ScheduleStore {
22
- constructor(surveyId, storage) {
23
- this.surveyId = surveyId;
23
+ constructor({ projectId, supplierId, supplierMemberId, emailHash }, storage) {
24
24
  this.storage = storage;
25
- }
26
- get key() {
27
- return `ps_di_schedule_${this.surveyId}`;
25
+ this.projectId = projectId;
26
+ this.key = ['ps_di_schedule', projectId, supplierId, supplierMemberId || emailHash]
27
+ .filter(Boolean)
28
+ .join('_');
28
29
  }
29
30
  /**
30
31
  * Checkpoint 1 fires `INITIAL_CHECKPOINT_DELAY_MS` in regardless; the rest are
@@ -78,7 +79,7 @@ class ScheduleStore {
78
79
  const startedAt = this.now();
79
80
  const checkpoints = ScheduleStore.buildCheckpoints(startedAt, null);
80
81
  const record = {
81
- surveyId: this.surveyId,
82
+ projectId: this.projectId,
82
83
  loi: null,
83
84
  startedAt,
84
85
  checkpoints,
@@ -119,7 +120,7 @@ class ScheduleStore {
119
120
  return record;
120
121
  }
121
122
  /**
122
- * Stores the latest Device-Intelligence response alongside the schedule so it
123
+ * Stores the latest PureScore-Intelligence response alongside the schedule so it
123
124
  * can be replayed on the next page load.
124
125
  */
125
126
  storeResponse(response) {
@@ -14,6 +14,7 @@ export declare class SnapshotStore<T extends object> {
14
14
  constructor(key: string, getData: () => T, storage: GetStorage & SetStorage & RemoveStorage, intervalMs?: number);
15
15
  start(): void;
16
16
  stop(): void;
17
+ save(): void;
17
18
  read(): T | null;
18
19
  clear(): void;
19
20
  }
@@ -26,6 +26,9 @@ class SnapshotStore {
26
26
  this.interval = null;
27
27
  }
28
28
  }
29
+ save() {
30
+ this.storage.set(this.key, this.getData());
31
+ }
29
32
  read() {
30
33
  return this.storage.get(this.key);
31
34
  }
@@ -0,0 +1,20 @@
1
+ export interface TranslationListener {
2
+ onDetected: () => void;
3
+ onSample: (sample: string) => void;
4
+ }
5
+ export declare class TranslationDetector {
6
+ private attributeObserver;
7
+ private textObserver;
8
+ private initialLang;
9
+ private markSeen;
10
+ private detected;
11
+ private sample;
12
+ start(listener: TranslationListener): void;
13
+ stop(): void;
14
+ private handleAttributes;
15
+ private isTranslationMark;
16
+ private handleText;
17
+ private translatedLength;
18
+ private extendSample;
19
+ private readableText;
20
+ }
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TranslationDetector = void 0;
4
+ const MIN_TRANSLATED_TEXT_LENGTH = 4;
5
+ const MAX_TRANSLATED_TEXT_LENGTH = 256;
6
+ const GOOGLE_CLASS_PREFIX = 'translate';
7
+ const GOOGLE_CLASS_MARK = 'translated';
8
+ const EDGE_ATTRIBUTES = new Set(['_msthash', '_msttexthash']);
9
+ const NON_CONTENT_PARENTS = new Set(['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEMPLATE']);
10
+ const TAG_LIKE = /<[^>]*>/g;
11
+ const WHITESPACE = /\s+/g;
12
+ class TranslationDetector {
13
+ constructor() {
14
+ this.attributeObserver = null;
15
+ this.textObserver = null;
16
+ this.initialLang = null;
17
+ this.markSeen = false;
18
+ this.detected = false;
19
+ this.sample = '';
20
+ }
21
+ start(listener) {
22
+ if (this.attributeObserver || typeof document === 'undefined' || typeof MutationObserver === 'undefined')
23
+ return;
24
+ const root = document.documentElement;
25
+ if (!root)
26
+ return;
27
+ const targets = [root];
28
+ const title = document.getElementsByTagName('title')[0];
29
+ if (title)
30
+ targets.push(title);
31
+ this.initialLang = root.getAttribute('lang');
32
+ this.markSeen = false;
33
+ this.detected = false;
34
+ this.sample = '';
35
+ this.attributeObserver = new MutationObserver(mutations => this.handleAttributes(mutations));
36
+ this.textObserver = new MutationObserver(mutations => this.handleText(mutations, listener));
37
+ for (const target of targets) {
38
+ this.attributeObserver.observe(target, { attributes: true, attributeOldValue: true });
39
+ this.textObserver.observe(target, { characterData: true, characterDataOldValue: true, subtree: true });
40
+ }
41
+ }
42
+ stop() {
43
+ var _a, _b;
44
+ (_a = this.attributeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
45
+ (_b = this.textObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
46
+ this.attributeObserver = null;
47
+ this.textObserver = null;
48
+ this.markSeen = false;
49
+ }
50
+ handleAttributes(mutations) {
51
+ for (const mutation of mutations) {
52
+ if (mutation.type === 'attributes' && this.isTranslationMark(mutation)) {
53
+ this.markSeen = true;
54
+ return;
55
+ }
56
+ }
57
+ }
58
+ isTranslationMark(mutation) {
59
+ const target = mutation.target;
60
+ if (!(target instanceof HTMLElement))
61
+ return false;
62
+ const name = mutation.attributeName;
63
+ const className = typeof target.className === 'string' ? target.className : '';
64
+ const google = name === 'class' && (className.startsWith(GOOGLE_CLASS_PREFIX) || className.includes(GOOGLE_CLASS_MARK));
65
+ const edge = name !== null && EDGE_ATTRIBUTES.has(name);
66
+ const safari = name === 'lang' && target === document.documentElement && target.getAttribute('lang') !== this.initialLang;
67
+ return google || edge || safari;
68
+ }
69
+ handleText(mutations, listener) {
70
+ var _a;
71
+ if (!this.markSeen)
72
+ return;
73
+ const changed = mutations.filter(mutation => mutation.target instanceof CharacterData && mutation.oldValue !== mutation.target.data);
74
+ if (!this.detected && this.translatedLength(changed) > MIN_TRANSLATED_TEXT_LENGTH) {
75
+ this.detected = true;
76
+ (_a = this.attributeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
77
+ listener.onDetected();
78
+ }
79
+ if (!this.detected)
80
+ return;
81
+ const sample = this.extendSample(changed);
82
+ if (sample !== this.sample) {
83
+ this.sample = sample;
84
+ listener.onSample(sample);
85
+ }
86
+ if (this.sample.length >= MAX_TRANSLATED_TEXT_LENGTH)
87
+ this.stop();
88
+ }
89
+ translatedLength(changed) {
90
+ const texts = [];
91
+ let length = 0;
92
+ for (const mutation of changed) {
93
+ const data = mutation.target.data;
94
+ texts.push(data);
95
+ length += data.length;
96
+ if (length >= MAX_TRANSLATED_TEXT_LENGTH)
97
+ break;
98
+ }
99
+ return texts.join(' ').length;
100
+ }
101
+ extendSample(changed) {
102
+ const parts = this.sample ? [this.sample] : [];
103
+ for (const mutation of changed) {
104
+ const text = this.readableText(mutation.target);
105
+ if (text)
106
+ parts.push(text);
107
+ }
108
+ return parts.join(' ').slice(0, MAX_TRANSLATED_TEXT_LENGTH);
109
+ }
110
+ readableText(node) {
111
+ var _a, _b, _c;
112
+ if (node.nodeType !== Node.TEXT_NODE)
113
+ return '';
114
+ if (NON_CONTENT_PARENTS.has((_b = (_a = node.parentElement) === null || _a === void 0 ? void 0 : _a.tagName) !== null && _b !== void 0 ? _b : ''))
115
+ return '';
116
+ return ((_c = node.textContent) !== null && _c !== void 0 ? _c : '').replace(TAG_LIKE, ' ').replace(WHITESPACE, ' ').trim();
117
+ }
118
+ }
119
+ exports.TranslationDetector = TranslationDetector;
@@ -0,0 +1,27 @@
1
+ import { VerisoulEnvConfig } from '../models/crosscutting';
2
+ import { VerisoulSessionProvider } from '../protocols/services/verisoul';
3
+ /**
4
+ * How long to wait for the SDK to load, and then for it to hand back a session,
5
+ * before giving up. Bounded so an ad-blocked or slow SDK only costs the first
6
+ * checkpoint a few seconds instead of stalling it.
7
+ */
8
+ export declare const VERISOUL_TIMEOUT_MS = 5000;
9
+ /**
10
+ * Loads the Verisoul SDK into the page and reads the session id off it.
11
+ *
12
+ * The script is injected once per page: a tag already carrying the same `src`
13
+ * (from an earlier bundle instance, or the page itself) is reused rather than
14
+ * duplicated.
15
+ */
16
+ export declare class VerisoulService implements VerisoulSessionProvider {
17
+ private readonly config;
18
+ private readonly timeoutMs;
19
+ private ready?;
20
+ constructor(config: VerisoulEnvConfig, timeoutMs?: number);
21
+ /** Starts loading the SDK. Safe to call more than once. */
22
+ load(): void;
23
+ getSessionId(): Promise<string | undefined>;
24
+ private sdk;
25
+ private inject;
26
+ private withTimeout;
27
+ }
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.VerisoulService = exports.VERISOUL_TIMEOUT_MS = void 0;
13
+ /**
14
+ * How long to wait for the SDK to load, and then for it to hand back a session,
15
+ * before giving up. Bounded so an ad-blocked or slow SDK only costs the first
16
+ * checkpoint a few seconds instead of stalling it.
17
+ */
18
+ exports.VERISOUL_TIMEOUT_MS = 5000;
19
+ /**
20
+ * Loads the Verisoul SDK into the page and reads the session id off it.
21
+ *
22
+ * The script is injected once per page: a tag already carrying the same `src`
23
+ * (from an earlier bundle instance, or the page itself) is reused rather than
24
+ * duplicated.
25
+ */
26
+ class VerisoulService {
27
+ constructor(config, timeoutMs = exports.VERISOUL_TIMEOUT_MS) {
28
+ this.config = config;
29
+ this.timeoutMs = timeoutMs;
30
+ }
31
+ /** Starts loading the SDK. Safe to call more than once. */
32
+ load() {
33
+ this.sdk();
34
+ }
35
+ getSessionId() {
36
+ return __awaiter(this, void 0, void 0, function* () {
37
+ try {
38
+ const verisoul = yield this.sdk();
39
+ if (!verisoul)
40
+ return undefined;
41
+ const session = yield this.withTimeout(verisoul.session());
42
+ const sessionId = session === null || session === void 0 ? void 0 : session.session_id;
43
+ return typeof sessionId === 'string' && sessionId.length > 0 ? sessionId : undefined;
44
+ }
45
+ catch (error) {
46
+ console.error('Error getting Verisoul session id:', error);
47
+ return undefined;
48
+ }
49
+ });
50
+ }
51
+ sdk() {
52
+ if (!this.ready) {
53
+ this.ready = this.withTimeout(this.inject()).catch(error => {
54
+ console.error('Error loading Verisoul SDK:', error);
55
+ // Let a later call try again rather than caching the failure for the page.
56
+ this.ready = undefined;
57
+ return undefined;
58
+ });
59
+ }
60
+ return this.ready;
61
+ }
62
+ inject() {
63
+ return new Promise((resolve, reject) => {
64
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
65
+ reject(new Error('Verisoul needs a browser environment'));
66
+ return;
67
+ }
68
+ if (window.Verisoul) {
69
+ resolve(window.Verisoul);
70
+ return;
71
+ }
72
+ let script = document.querySelector(`script[src="${this.config.sdkUrl}"]`);
73
+ if (!script) {
74
+ script = document.createElement('script');
75
+ script.src = this.config.sdkUrl;
76
+ script.async = true;
77
+ script.setAttribute('verisoul-project-id', this.config.projectId);
78
+ (document.head || document.documentElement).appendChild(script);
79
+ }
80
+ script.addEventListener('load', () => {
81
+ if (window.Verisoul)
82
+ resolve(window.Verisoul);
83
+ else
84
+ reject(new Error('Verisoul SDK loaded but window.Verisoul is missing'));
85
+ });
86
+ script.addEventListener('error', () => reject(new Error('Failed to load the Verisoul SDK')));
87
+ });
88
+ }
89
+ withTimeout(promise) {
90
+ return new Promise((resolve, reject) => {
91
+ const timer = setTimeout(() => reject(new Error(`Verisoul timed out after ${this.timeoutMs}ms`)), this.timeoutMs);
92
+ promise.then(value => { clearTimeout(timer); resolve(value); }, error => { clearTimeout(timer); reject(error); });
93
+ });
94
+ }
95
+ }
96
+ exports.VerisoulService = VerisoulService;
package/lib/index.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Two responsibilities and nothing else: re-export what consumers are allowed to
5
5
  * reach, and run the self-initializing script tag. Everything with behaviour
6
- * lives behind a layer — `application` for the Device-Identifier and the
6
+ * lives behind a layer — `application` for the Purescore-Client and the
7
7
  * bootstrap that drives it, `domain` for the services they are built from,
8
8
  * `validation` for the rules.
9
9
  */
@@ -17,3 +17,4 @@ export { DeviceIdentifier };
17
17
  export type { DeviceIdentifierOptions, DeviceIdentifierConfig, DeviceIdentifierMetadata, TrackDetails, TrackDetailsContext, TransactionDetails, CreateTransaction, } from './domain/protocols/services/device-identifier';
18
18
  export type { DisplayChecks, DisplayChecksInput, Language, NavigatorUABrandVersion, UADataValues, NavigatorUAData, NetworkInformation, UAParserDeviceType, NavigationTiming, NetworkTimes, } from './domain/protocols/services/device-fingerprint';
19
19
  export type { AutoInitConfig, ScheduleRecord, ScheduleCheckpoint, } from './domain/protocols/services/single-script-injection';
20
+ export type { ClipboardActivityType, ClipboardActivity, TabSwitch, InteractionActivity, } from './domain/protocols/services/interaction-activity';
package/lib/index.js CHANGED
@@ -6,7 +6,7 @@ exports.DeviceIdentifier = void 0;
6
6
  *
7
7
  * Two responsibilities and nothing else: re-export what consumers are allowed to
8
8
  * reach, and run the self-initializing script tag. Everything with behaviour
9
- * lives behind a layer — `application` for the Device-Identifier and the
9
+ * lives behind a layer — `application` for the Purescore-Client and the
10
10
  * bootstrap that drives it, `domain` for the services they are built from,
11
11
  * `validation` for the rules.
12
12
  */
@@ -18,7 +18,7 @@ const auto_init_1 = require("./application/auto-init");
18
18
  *
19
19
  * When the tag that loaded this bundle carries `data-auto-init="true"`, the
20
20
  * bundle bootstraps itself from its own `data-*` attributes and drives the three
21
- * Device-Intelligence calls off an LOI-derived schedule — the buyer writes no
21
+ * PureScore-Intelligence calls off an LOI-derived schedule — the buyer writes no
22
22
  * bootstrap code and no `trackDetails` call at all.
23
23
  *
24
24
  * Without that attribute this is a no-op, which is what keeps the existing
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@purespectrum1/purescore-client",
3
3
  "description": "A library to identify purescore devices",
4
- "version": "0.7.0",
4
+ "version": "1.0.0",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
7
7
  "clean": "rm -rf lib && rm -rf dist",