@purespectrum1/purescore-client 0.7.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 (136) hide show
  1. package/README.md +55 -0
  2. package/lib/application/auto-init.d.ts +44 -0
  3. package/lib/application/auto-init.js +245 -0
  4. package/lib/application/device-identifier.d.ts +116 -0
  5. package/lib/application/device-identifier.js +169 -0
  6. package/lib/domain/models/crosscutting/device-intelligence.d.ts +11 -0
  7. package/lib/domain/models/crosscutting/device-intelligence.js +18 -0
  8. package/lib/domain/models/crosscutting/index.d.ts +1 -0
  9. package/lib/domain/models/crosscutting/index.js +17 -0
  10. package/lib/domain/protocols/cache/get-storage.d.ts +8 -0
  11. package/lib/domain/protocols/cache/get-storage.js +2 -0
  12. package/lib/domain/protocols/cache/index.d.ts +3 -0
  13. package/lib/domain/protocols/cache/index.js +19 -0
  14. package/lib/domain/protocols/cache/remove-storage.d.ts +6 -0
  15. package/lib/domain/protocols/cache/remove-storage.js +2 -0
  16. package/lib/domain/protocols/cache/set-storage.d.ts +7 -0
  17. package/lib/domain/protocols/cache/set-storage.js +2 -0
  18. package/lib/domain/protocols/http/http-client.d.ts +23 -0
  19. package/lib/domain/protocols/http/http-client.js +13 -0
  20. package/lib/domain/protocols/services/behavioural-biometrics/cursor-behavior.d.ts +63 -0
  21. package/lib/domain/protocols/services/behavioural-biometrics/cursor-behavior.js +2 -0
  22. package/lib/domain/protocols/services/behavioural-biometrics/index.d.ts +3 -0
  23. package/lib/domain/protocols/services/behavioural-biometrics/index.js +19 -0
  24. package/lib/domain/protocols/services/behavioural-biometrics/keystorke-metrics.d.ts +35 -0
  25. package/lib/domain/protocols/services/behavioural-biometrics/keystorke-metrics.js +2 -0
  26. package/lib/domain/protocols/services/behavioural-biometrics/touch-behavior.d.ts +88 -0
  27. package/lib/domain/protocols/services/behavioural-biometrics/touch-behavior.js +2 -0
  28. package/lib/domain/protocols/services/device-fingerprint/display.d.ts +22 -0
  29. package/lib/domain/protocols/services/device-fingerprint/display.js +2 -0
  30. package/lib/domain/protocols/services/device-fingerprint/index.d.ts +3 -0
  31. package/lib/domain/protocols/services/device-fingerprint/index.js +19 -0
  32. package/lib/domain/protocols/services/device-fingerprint/navigator.d.ts +63 -0
  33. package/lib/domain/protocols/services/device-fingerprint/navigator.js +2 -0
  34. package/lib/domain/protocols/services/device-fingerprint/utils.d.ts +40 -0
  35. package/lib/domain/protocols/services/device-fingerprint/utils.js +2 -0
  36. package/lib/domain/protocols/services/device-identifier.d.ts +96 -0
  37. package/lib/domain/protocols/services/device-identifier.js +2 -0
  38. package/lib/domain/protocols/services/device-intelligence.d.ts +5 -0
  39. package/lib/domain/protocols/services/device-intelligence.js +2 -0
  40. package/lib/domain/protocols/services/listeners.d.ts +9 -0
  41. package/lib/domain/protocols/services/listeners.js +2 -0
  42. package/lib/domain/protocols/services/single-script-injection/auto-init.d.ts +120 -0
  43. package/lib/domain/protocols/services/single-script-injection/auto-init.js +2 -0
  44. package/lib/domain/protocols/services/single-script-injection/index.d.ts +1 -0
  45. package/lib/domain/protocols/services/single-script-injection/index.js +17 -0
  46. package/lib/domain/services/behavioural-biometrics/collector.d.ts +94 -0
  47. package/lib/domain/services/behavioural-biometrics/collector.js +229 -0
  48. package/lib/domain/services/behavioural-biometrics/cursor-behavior.d.ts +87 -0
  49. package/lib/domain/services/behavioural-biometrics/cursor-behavior.js +369 -0
  50. package/lib/domain/services/behavioural-biometrics/index.d.ts +4 -0
  51. package/lib/domain/services/behavioural-biometrics/index.js +20 -0
  52. package/lib/domain/services/behavioural-biometrics/keystroke-metrics.d.ts +60 -0
  53. package/lib/domain/services/behavioural-biometrics/keystroke-metrics.js +213 -0
  54. package/lib/domain/services/behavioural-biometrics/touch-behavior.d.ts +125 -0
  55. package/lib/domain/services/behavioural-biometrics/touch-behavior.js +413 -0
  56. package/lib/domain/services/device-fingerprint/audio.d.ts +14 -0
  57. package/lib/domain/services/device-fingerprint/audio.js +109 -0
  58. package/lib/domain/services/device-fingerprint/canvas.d.ts +10 -0
  59. package/lib/domain/services/device-fingerprint/canvas.js +33 -0
  60. package/lib/domain/services/device-fingerprint/display.d.ts +49 -0
  61. package/lib/domain/services/device-fingerprint/display.js +88 -0
  62. package/lib/domain/services/device-fingerprint/hash.d.ts +14 -0
  63. package/lib/domain/services/device-fingerprint/hash.js +60 -0
  64. package/lib/domain/services/device-fingerprint/index.d.ts +8 -0
  65. package/lib/domain/services/device-fingerprint/index.js +24 -0
  66. package/lib/domain/services/device-fingerprint/navigator.d.ts +110 -0
  67. package/lib/domain/services/device-fingerprint/navigator.js +213 -0
  68. package/lib/domain/services/device-fingerprint/pointer-capability.d.ts +14 -0
  69. package/lib/domain/services/device-fingerprint/pointer-capability.js +42 -0
  70. package/lib/domain/services/device-fingerprint/storage.d.ts +44 -0
  71. package/lib/domain/services/device-fingerprint/storage.js +64 -0
  72. package/lib/domain/services/device-fingerprint/utils.d.ts +42 -0
  73. package/lib/domain/services/device-fingerprint/utils.js +95 -0
  74. package/lib/domain/services/device-intelligence.d.ts +10 -0
  75. package/lib/domain/services/device-intelligence.js +59 -0
  76. package/lib/domain/services/single-script-injection/checkpoint-scheduler.d.ts +37 -0
  77. package/lib/domain/services/single-script-injection/checkpoint-scheduler.js +94 -0
  78. package/lib/domain/services/single-script-injection/index.d.ts +3 -0
  79. package/lib/domain/services/single-script-injection/index.js +19 -0
  80. package/lib/domain/services/single-script-injection/response-delivery.d.ts +47 -0
  81. package/lib/domain/services/single-script-injection/response-delivery.js +115 -0
  82. package/lib/domain/services/single-script-injection/schedule-store.d.ts +69 -0
  83. package/lib/domain/services/single-script-injection/schedule-store.js +146 -0
  84. package/lib/domain/services/snapshot-store.d.ts +19 -0
  85. package/lib/domain/services/snapshot-store.js +36 -0
  86. package/lib/domain/services/spa-navigation.d.ts +15 -0
  87. package/lib/domain/services/spa-navigation.js +40 -0
  88. package/lib/index.d.ts +19 -0
  89. package/lib/index.js +27 -0
  90. package/lib/infra/cache/index.d.ts +3 -0
  91. package/lib/infra/cache/index.js +19 -0
  92. package/lib/infra/cache/local-storage-adapter.d.ts +8 -0
  93. package/lib/infra/cache/local-storage-adapter.js +14 -0
  94. package/lib/infra/cache/session-storage-adapter.d.ts +8 -0
  95. package/lib/infra/cache/session-storage-adapter.js +14 -0
  96. package/lib/infra/cache/web-storage-adapter.d.ts +13 -0
  97. package/lib/infra/cache/web-storage-adapter.js +33 -0
  98. package/lib/infra/http/axios-http-client.d.ts +4 -0
  99. package/lib/infra/http/axios-http-client.js +39 -0
  100. package/lib/validation/builder.d.ts +43 -0
  101. package/lib/validation/builder.js +85 -0
  102. package/lib/validation/composite.d.ts +13 -0
  103. package/lib/validation/composite.js +23 -0
  104. package/lib/validation/errors/index.d.ts +2 -0
  105. package/lib/validation/errors/index.js +18 -0
  106. package/lib/validation/errors/invalid-field-error.d.ts +7 -0
  107. package/lib/validation/errors/invalid-field-error.js +14 -0
  108. package/lib/validation/errors/required-field-error.d.ts +3 -0
  109. package/lib/validation/errors/required-field-error.js +10 -0
  110. package/lib/validation/index.d.ts +5 -0
  111. package/lib/validation/index.js +21 -0
  112. package/lib/validation/protocols/index.d.ts +1 -0
  113. package/lib/validation/protocols/index.js +17 -0
  114. package/lib/validation/protocols/validator.d.ts +11 -0
  115. package/lib/validation/protocols/validator.js +2 -0
  116. package/lib/validation/validators/behavioural-signal.d.ts +27 -0
  117. package/lib/validation/validators/behavioural-signal.js +56 -0
  118. package/lib/validation/validators/country-code.d.ts +14 -0
  119. package/lib/validation/validators/country-code.js +24 -0
  120. package/lib/validation/validators/global-function.d.ts +14 -0
  121. package/lib/validation/validators/global-function.js +29 -0
  122. package/lib/validation/validators/index.d.ts +9 -0
  123. package/lib/validation/validators/index.js +25 -0
  124. package/lib/validation/validators/interpolated-template.d.ts +14 -0
  125. package/lib/validation/validators/interpolated-template.js +28 -0
  126. package/lib/validation/validators/json-object.d.ts +14 -0
  127. package/lib/validation/validators/json-object.js +33 -0
  128. package/lib/validation/validators/language-code.d.ts +13 -0
  129. package/lib/validation/validators/language-code.js +23 -0
  130. package/lib/validation/validators/positive-number.d.ts +13 -0
  131. package/lib/validation/validators/positive-number.js +24 -0
  132. package/lib/validation/validators/required-string.d.ts +10 -0
  133. package/lib/validation/validators/required-string.js +19 -0
  134. package/lib/validation/validators/schedule-record.d.ts +26 -0
  135. package/lib/validation/validators/schedule-record.js +33 -0
  136. package/package.json +51 -0
package/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # Purescore Identifier
2
+
3
+ `@purespectrum1/purescore-client` generates a stable, unique device ID from
4
+ device- and browser-level signals (fingerprinting). It ships as both a Node/TypeScript
5
+ library and a standalone UMD browser bundle, so the same identification logic can run
6
+ in a service or be dropped into a page with a `<script>` tag.
7
+
8
+ > **Status:** project scaffolding. Build, test, lint and release pipelines are in place;
9
+ > the `src/` implementation is still being added, so the public API below is not final.
10
+
11
+ ## Requirements
12
+
13
+ | | |
14
+ | --- | --- |
15
+ | Node.js | 20.15.0 (the version CI builds against) |
16
+ | TypeScript | 5.9+ (bundled as a dev dependency) |
17
+ | Browsers | ES5-compatible output, so old browsers are supported |
18
+
19
+ ## Installation
20
+
21
+ ```sh
22
+ npm install @purespectrum1/purescore-client
23
+ ```
24
+
25
+ The package is published to the public npm registry. Because CI installs with
26
+ `--legacy-peer-deps`, use the same flag if you hit peer-dependency conflicts.
27
+
28
+ ### Browser bundle
29
+
30
+ Browser builds are published to S3 (`purescore-client-package/dist`) as a UMD bundle
31
+ that exposes the global `ps_purescore_identifier`:
32
+
33
+ ```html
34
+ <script src="https://<cdn-host>/dist/bundle.js"></script>
35
+ <script>
36
+ // window.ps_purescore_identifier
37
+ </script>
38
+ ```
39
+
40
+ Each release also uploads a version-pinned file (`bundle.<version>.js`) alongside the
41
+ floating `bundle.js`. Pin the versioned file in production so a new release cannot
42
+ change behaviour under you.
43
+
44
+ ## Usage
45
+
46
+ ```ts
47
+ import identifier from '@purespectrum1/purescore-client';
48
+ ```
49
+
50
+ The module entry point is `lib/index.js`, with type declarations emitted next to it.
51
+ Concrete API examples will be added here as the implementation lands.
52
+
53
+ ## License
54
+
55
+ ISC. Maintained by the Data Stars squad.
@@ -0,0 +1,44 @@
1
+ import { AutoInitConfig } from '../domain/protocols/services/single-script-injection/auto-init';
2
+ import { CreateTransaction, TrackDetails } from '../domain/protocols/services/device-identifier';
3
+ /**
4
+ * Guard flag on `window`: a second tag on the same page is a no-op.
5
+ */
6
+ export declare const INIT_FLAG = "__purescoreIdentifierInitialized";
7
+ export declare const TRANSACTION_CREATION_ATTEMPTS = 3;
8
+ export declare const TRANSACTION_RETRY_DELAY_MS = 1000;
9
+ /**
10
+ * Finds the script tag that loaded this bundle.
11
+ *
12
+ * `document.currentScript` is the direct answer while the tag is executing; the
13
+ * `data-auto-init` + `data-token` lookup covers dynamically-injected and module
14
+ * contexts where `currentScript` is null.
15
+ */
16
+ export declare function resolveScriptTag(): HTMLScriptElement | null;
17
+ /**
18
+ * Reads the bootstrap configuration off the tag's `data-*` attributes, or
19
+ * returns null when a required one is missing or unusable.
20
+ */
21
+ export declare function readAutoInitConfig(tag: HTMLScriptElement): AutoInitConfig | null;
22
+ /**
23
+ * Bootstraps the Device-Identifier straight from the script tag: no constructor
24
+ * call and no `trackDetails` call on the buyer side.
25
+ *
26
+ * Strictly opt-in — it does nothing unless `data-auto-init="true"` is on the tag
27
+ * — so every existing integration keeps working exactly as it does today.
28
+ */
29
+ export declare class AutoInitService {
30
+ private readonly createDeviceIdentifier;
31
+ constructor(createDeviceIdentifier: (config: AutoInitConfig) => TrackDetails & CreateTransaction);
32
+ /**
33
+ * Runs the bootstrap. Returns true when auto-init took over the calls for this
34
+ * page, false when the tag opted out or the configuration was unusable.
35
+ */
36
+ run(): boolean;
37
+ private bootstrap;
38
+ private resolveTransactionId;
39
+ /**
40
+ * Registers the transaction, trying up to `TRANSACTION_CREATION_ATTEMPTS` times.
41
+ * Resolves with undefined when every attempt failed.
42
+ */
43
+ private createTransaction;
44
+ }
@@ -0,0 +1,245 @@
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.AutoInitService = exports.TRANSACTION_RETRY_DELAY_MS = exports.TRANSACTION_CREATION_ATTEMPTS = exports.INIT_FLAG = void 0;
13
+ exports.resolveScriptTag = resolveScriptTag;
14
+ exports.readAutoInitConfig = readAutoInitConfig;
15
+ const validation_1 = require("../validation");
16
+ const single_script_injection_1 = require("../domain/services/single-script-injection");
17
+ const cache_1 = require("../infra/cache");
18
+ /**
19
+ * Guard flag on `window`: a second tag on the same page is a no-op.
20
+ */
21
+ exports.INIT_FLAG = '__purescoreIdentifierInitialized';
22
+ exports.TRANSACTION_CREATION_ATTEMPTS = 3;
23
+ exports.TRANSACTION_RETRY_DELAY_MS = 1000;
24
+ /**
25
+ * Finds the script tag that loaded this bundle.
26
+ *
27
+ * `document.currentScript` is the direct answer while the tag is executing; the
28
+ * `data-auto-init` + `data-token` lookup covers dynamically-injected and module
29
+ * contexts where `currentScript` is null.
30
+ */
31
+ function resolveScriptTag() {
32
+ var _a;
33
+ if (typeof document === 'undefined')
34
+ return null;
35
+ const current = document.currentScript;
36
+ if ((_a = current === null || current === void 0 ? void 0 : current.dataset) === null || _a === void 0 ? void 0 : _a.token)
37
+ return current;
38
+ return document.querySelector('script[data-auto-init][data-token]');
39
+ }
40
+ /**
41
+ * Reads the bootstrap configuration off the tag's `data-*` attributes, or
42
+ * returns null when a required one is missing or unusable.
43
+ */
44
+ function readAutoInitConfig(tag) {
45
+ var _a, _b, _c, _d, _e, _f, _g;
46
+ const data = tag.dataset;
47
+ const errors = [
48
+ validation_1.ValidationBuilder.of({ value: data.token, fieldName: 'data-token' })
49
+ .required(),
50
+ validation_1.ValidationBuilder.of({ value: data.surveyId, fieldName: 'data-survey-id' })
51
+ .required()
52
+ .interpolated(),
53
+ validation_1.ValidationBuilder.of({ value: data.expectedSurveyLoi, fieldName: 'data-expected-survey-loi' })
54
+ .required()
55
+ .interpolated()
56
+ .positiveNumber(),
57
+ validation_1.ValidationBuilder.of({ value: data.surveyCountry, fieldName: 'data-survey-country' })
58
+ .required()
59
+ .interpolated()
60
+ .countryCode(),
61
+ validation_1.ValidationBuilder.of({ value: data.surveyLanguage, fieldName: 'data-survey-language' })
62
+ .required()
63
+ .interpolated()
64
+ .languageCode(),
65
+ ].map(rules => rules.validate()).filter((error) => error !== undefined);
66
+ if (errors.length) {
67
+ console.error(`Device-Identifier auto-init: ${errors.map(e => e.message).join('; ')}`);
68
+ return null;
69
+ }
70
+ const config = {
71
+ token: ((_a = data.token) !== null && _a !== void 0 ? _a : '').trim(),
72
+ env: ((_b = data.env) === null || _b === void 0 ? void 0 : _b.trim().toLowerCase()) === 'production' ? 'production' : 'staging',
73
+ callbackName: ((_c = data.callback) === null || _c === void 0 ? void 0 : _c.trim()) || undefined,
74
+ metadata: parseMetadataAttribute(data.metadata),
75
+ surveyId: ((_d = data.surveyId) !== null && _d !== void 0 ? _d : '').trim(),
76
+ expectedSurveyLOI: Number(data.expectedSurveyLoi),
77
+ surveyCountry: ((_e = data.surveyCountry) !== null && _e !== void 0 ? _e : '').trim().toUpperCase(),
78
+ surveyLanguage: ((_f = data.surveyLanguage) !== null && _f !== void 0 ? _f : '').trim().toLowerCase(),
79
+ };
80
+ const buyerId = optionalField(data.buyerId, 'data-buyer-id');
81
+ if (buyerId)
82
+ config.metadata.buyer_id = buyerId;
83
+ assignDefined(config, {
84
+ supplierId: optionalField(data.supplierId, 'data-supplier-id'),
85
+ supplierMemberId: optionalField(data.supplierMemberId, 'data-supplier-member-id'),
86
+ emailHash: ((_g = data.emailHash) === null || _g === void 0 ? void 0 : _g.trim()) || undefined,
87
+ });
88
+ const demographics = {};
89
+ assignDefined(demographics, {
90
+ age: optionalField(data.demographicsAge, 'data-demographics-age', rules => rules.positiveNumber(), Number),
91
+ gender: optionalField(data.demographicsGender, 'data-demographics-gender'),
92
+ zip: optionalField(data.demographicsZip, 'data-demographics-zip'),
93
+ });
94
+ if (Object.keys(demographics).length)
95
+ config.demographics = demographics;
96
+ return config;
97
+ }
98
+ /**
99
+ * Reads an optional attribute. An absent one is simply left out; one that is
100
+ * present but unusable is reported and dropped rather than treated as fatal,
101
+ * since the transaction does not depend on it.
102
+ */
103
+ function optionalField(raw, fieldName, rules = builder => builder, convert = value => value) {
104
+ const error = rules(validation_1.ValidationBuilder.of({ value: raw, fieldName }).optional().interpolated()).validate();
105
+ if (error) {
106
+ console.error(`Device-Identifier auto-init: ${error.message}, ignoring it`);
107
+ return undefined;
108
+ }
109
+ const value = raw === null || raw === void 0 ? void 0 : raw.trim();
110
+ return value ? convert(value) : undefined;
111
+ }
112
+ /**
113
+ * Unusable metadata is reported and dropped rather than treated as fatal: the
114
+ * attribute is optional, so a malformed blob is no reason to abandon the
115
+ * transaction.
116
+ */
117
+ function parseMetadataAttribute(raw) {
118
+ const error = validation_1.ValidationBuilder.of({ value: raw, fieldName: 'data-metadata' }).jsonObject().validate();
119
+ if (error) {
120
+ console.error(`Device-Identifier auto-init: ${error.message}, ignoring it`);
121
+ return {};
122
+ }
123
+ return (raw === null || raw === void 0 ? void 0 : raw.trim()) ? JSON.parse(raw) : {};
124
+ }
125
+ /** Copies over only the values that are set, so absent fields stay absent. */
126
+ function assignDefined(target, values) {
127
+ Object.keys(values).forEach(key => {
128
+ if (values[key] !== undefined)
129
+ target[key] = values[key];
130
+ });
131
+ }
132
+ /**
133
+ * Bootstraps the Device-Identifier straight from the script tag: no constructor
134
+ * call and no `trackDetails` call on the buyer side.
135
+ *
136
+ * Strictly opt-in — it does nothing unless `data-auto-init="true"` is on the tag
137
+ * — so every existing integration keeps working exactly as it does today.
138
+ */
139
+ class AutoInitService {
140
+ constructor(createDeviceIdentifier) {
141
+ this.createDeviceIdentifier = createDeviceIdentifier;
142
+ }
143
+ /**
144
+ * Runs the bootstrap. Returns true when auto-init took over the calls for this
145
+ * page, false when the tag opted out or the configuration was unusable.
146
+ */
147
+ run() {
148
+ var _a;
149
+ if (typeof window === 'undefined')
150
+ return false;
151
+ if (window[exports.INIT_FLAG])
152
+ return false;
153
+ const tag = resolveScriptTag();
154
+ if (!tag)
155
+ return false;
156
+ if (((_a = tag.dataset.autoInit) !== null && _a !== void 0 ? _a : '').trim().toLowerCase() !== 'true')
157
+ return false;
158
+ const config = readAutoInitConfig(tag);
159
+ if (!config)
160
+ return false;
161
+ window[exports.INIT_FLAG] = true;
162
+ this.bootstrap(config);
163
+ return true;
164
+ }
165
+ bootstrap(config) {
166
+ var _a;
167
+ const deviceIdentifier = this.createDeviceIdentifier(config);
168
+ const delivery = new single_script_injection_1.ResponseDeliveryService(config.callbackName);
169
+ const store = new single_script_injection_1.ScheduleStore(config.surveyId, new cache_1.LocalStorageAdapter());
170
+ const credentials = { token: config.token, env: config.env };
171
+ // Re-emit the verdict from an earlier page before the schedule resumes, so a
172
+ // page that only now registers a listener still receives the current one.
173
+ const buffered = (_a = store.read()) === null || _a === void 0 ? void 0 : _a.lastResponse;
174
+ if (buffered)
175
+ delivery.replay(buffered);
176
+ // The LOI comes off the tag, so every checkpoint is known up front. On a
177
+ // later page the record already carries it, and `applyLoi` leaves it alone.
178
+ store.resumeOrCreate();
179
+ store.applyLoi(config.expectedSurveyLOI);
180
+ const transactionReady = this.resolveTransactionId(store, deviceIdentifier, config, credentials);
181
+ const scheduler = new single_script_injection_1.CheckpointScheduler(store, () => __awaiter(this, void 0, void 0, function* () {
182
+ const transactionId = yield transactionReady;
183
+ if (!transactionId)
184
+ throw new Error('no transaction id to send the checkpoint for');
185
+ const response = yield deviceIdentifier.trackDetails(transactionId, config.metadata, credentials, { surveyId: config.surveyId });
186
+ store.storeResponse(response);
187
+ delivery.deliver(response);
188
+ }));
189
+ scheduler.start();
190
+ transactionReady.then(transactionId => {
191
+ if (!transactionId)
192
+ scheduler.stop();
193
+ });
194
+ }
195
+ resolveTransactionId(store, deviceIdentifier, config, credentials) {
196
+ return __awaiter(this, void 0, void 0, function* () {
197
+ var _a;
198
+ const stored = (_a = store.read()) === null || _a === void 0 ? void 0 : _a.transactionId;
199
+ if (stored)
200
+ return stored;
201
+ const transactionId = yield this.createTransaction(deviceIdentifier, config, credentials);
202
+ if (transactionId)
203
+ store.storeTransactionId(transactionId);
204
+ return transactionId;
205
+ });
206
+ }
207
+ /**
208
+ * Registers the transaction, trying up to `TRANSACTION_CREATION_ATTEMPTS` times.
209
+ * Resolves with undefined when every attempt failed.
210
+ */
211
+ createTransaction(deviceIdentifier, config, credentials) {
212
+ return __awaiter(this, void 0, void 0, function* () {
213
+ const delay = (ms) => {
214
+ return new Promise(resolve => setTimeout(resolve, ms));
215
+ };
216
+ const details = {
217
+ surveyId: config.surveyId,
218
+ expectedSurveyLOI: config.expectedSurveyLOI,
219
+ surveyCountry: config.surveyCountry,
220
+ surveyLanguage: config.surveyLanguage,
221
+ };
222
+ assignDefined(details, {
223
+ supplierId: config.supplierId,
224
+ supplierMemberId: config.supplierMemberId,
225
+ emailHash: config.emailHash,
226
+ demographics: config.demographics,
227
+ });
228
+ for (let attempt = 1; attempt <= exports.TRANSACTION_CREATION_ATTEMPTS; attempt++) {
229
+ try {
230
+ const transactionId = yield deviceIdentifier.createTransaction(details, credentials);
231
+ if (transactionId)
232
+ return transactionId;
233
+ }
234
+ catch (error) {
235
+ console.error(`Error creating Device-Intelligence transaction (attempt ${attempt}):`, error);
236
+ }
237
+ if (attempt < exports.TRANSACTION_CREATION_ATTEMPTS)
238
+ yield delay(exports.TRANSACTION_RETRY_DELAY_MS);
239
+ }
240
+ console.error(`Device-Identifier auto-init: could not create the transaction after ${exports.TRANSACTION_CREATION_ATTEMPTS} attempts`);
241
+ return undefined;
242
+ });
243
+ }
244
+ }
245
+ exports.AutoInitService = AutoInitService;
@@ -0,0 +1,116 @@
1
+ import { DeviceIdentifierOptions, DeviceIdentifierConfig, DeviceIdentifierMetadata, BehaviouralBiometricsOptions, BehaviorBiometricsParams, CreateTransaction, TrackDetails, TrackDetailsContext, TransactionDetails } from '../domain/protocols/services/device-identifier';
2
+ /**
3
+ * DeviceIdentifier class for generating a unique identifier for a browser.
4
+ */
5
+ export declare class DeviceIdentifier implements TrackDetails, CreateTransaction {
6
+ /**
7
+ * Custom hash function.
8
+ *
9
+ * If provided, this function will be used to generate the hash instead of the default murmurhash3_32_gc.
10
+ */
11
+ private hasher?;
12
+ /**
13
+ * Token used to authenticate with the Purescore Intelligence service.
14
+ */
15
+ private token;
16
+ /**
17
+ * Base URL for the Purescore Identifier service.
18
+ */
19
+ private baseUrl;
20
+ /**
21
+ * Options stored from the last activateBehavioralBiometricsDetection call,
22
+ * reused when restarting the collection cycle after each trackDetails flush.
23
+ */
24
+ private behaviouralBiometricsOptions?;
25
+ /**
26
+ * Service responsible for performing API requests to the Purescore Intelligence service.
27
+ */
28
+ private deviceIntelligenceService;
29
+ private singleScriptInjection?;
30
+ private screen_resolution?;
31
+ private screen_orientation?;
32
+ private canvas?;
33
+ /**
34
+ * Constructor for the DeviceIdentifier class.
35
+ *
36
+ * @param options - Optional options for customizing the DeviceIdentifier generation.
37
+ */
38
+ constructor(options?: DeviceIdentifierOptions | ((value: string, seed: number) => number), config?: DeviceIdentifierConfig);
39
+ /**
40
+ * Generates the fingerprint data for the browser.
41
+ *
42
+ * @returns An object containing the fingerprint data.
43
+ */
44
+ getDetails(): Promise<{
45
+ hash: number;
46
+ canvas: string;
47
+ cookieEnabled: string | boolean;
48
+ display: {} | import("..").DisplayChecksInput;
49
+ javaEnabled: string | boolean;
50
+ tzOffset: number;
51
+ touchEnabled: boolean;
52
+ userAgent: string;
53
+ platform: string;
54
+ languages: import("..").Language;
55
+ displayChecks: import("..").DisplayChecks;
56
+ networkPerformance: import("..").NetworkTimes;
57
+ isWebCrawler: boolean;
58
+ audioFingerprint: number;
59
+ userAgentClientHints: {
60
+ model: string | undefined;
61
+ };
62
+ webdriverEnabled: boolean;
63
+ estimatedRoundTripTime: number;
64
+ ua: string;
65
+ browser: {
66
+ name?: string;
67
+ version?: string;
68
+ major?: string;
69
+ };
70
+ engine: {
71
+ name?: string;
72
+ version?: string;
73
+ };
74
+ os: {
75
+ name?: string;
76
+ version?: string;
77
+ };
78
+ device: {
79
+ vendor?: string;
80
+ model?: string;
81
+ type?: string;
82
+ };
83
+ cpu: {
84
+ architecture?: string;
85
+ };
86
+ isMobile: boolean;
87
+ isTablet: boolean;
88
+ isDesktop: boolean;
89
+ }>;
90
+ /**
91
+ * Generates a unique hash from browser and system properties (navigator, screen,
92
+ * storage, and more), using the provided hasher or murmurhash3_32_gc as a fallback.
93
+ *
94
+ * @param {string} [canvasFingerprint] - Precomputed canvas fingerprint to reuse
95
+ * instead of recomputing. Only used when the `canvas` option is enabled.
96
+ * @returns {number} A unique hash representing the user's browser and system.
97
+ */
98
+ getIdentity(canvasFingerprint?: string): number;
99
+ /**
100
+ * Activates behavioral biometrics detection by starting event listeners for
101
+ * touch, cursor, and keyboard interactions.
102
+ *
103
+ * @param options - Optional configuration for BB detection, such as max data
104
+ * points for cursor and keystroke tracking.
105
+ */
106
+ activateBehavioralBiometricsDetection(options?: BehaviouralBiometricsOptions): void;
107
+ /**
108
+ * Deactivates behavioral biometrics detection by stopping all event listeners
109
+ * for touch, cursor, and keyboard interactions, and marking the detection
110
+ * as inactive.
111
+ */
112
+ deactivateBehavioralBiometricsDetection(): void;
113
+ createTransaction(details: TransactionDetails, config?: DeviceIdentifierConfig): Promise<string | undefined>;
114
+ trackDetails(transactionId: string, metadata?: DeviceIdentifierMetadata, config?: DeviceIdentifierConfig, context?: TrackDetailsContext): Promise<Record<string, any>>;
115
+ sendBehaviorBiometrics(transactionId: string, params: BehaviorBiometricsParams, config?: DeviceIdentifierConfig): Promise<any>;
116
+ }
@@ -0,0 +1,169 @@
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.DeviceIdentifier = void 0;
13
+ const device_fingerprint_1 = require("../domain/services/device-fingerprint");
14
+ const behavioural_biometrics_1 = require("../domain/services/behavioural-biometrics");
15
+ const device_intelligence_1 = require("../domain/services/device-intelligence");
16
+ const crosscutting_1 = require("../domain/models/crosscutting");
17
+ const spa_navigation_1 = require("../domain/services/spa-navigation");
18
+ const axios_http_client_1 = require("../infra/http/axios-http-client");
19
+ const cache_1 = require("../infra/cache");
20
+ const hash = new device_fingerprint_1.Hash();
21
+ const storage = new device_fingerprint_1.Storage();
22
+ const display = new device_fingerprint_1.Display();
23
+ const navigatior = new device_fingerprint_1.Navigatior();
24
+ const canvas = new device_fingerprint_1.Canvas();
25
+ const utils = new device_fingerprint_1.Utils();
26
+ const audioFingerprint = new device_fingerprint_1.AudioFingerprint();
27
+ const pointerCapability = new device_fingerprint_1.PointerCapabilityDetector();
28
+ // One collector shared for the lifetime of the page. It owns the trackers, which
29
+ // is what makes behavioural data survive a DeviceIdentifier being re-instantiated.
30
+ const behaviouralBiometrics = new behavioural_biometrics_1.BehaviouralBiometricsCollector(new behavioural_biometrics_1.TouchBehavior(), new behavioural_biometrics_1.CursorBehavior(), new behavioural_biometrics_1.KeyboardMetrics(), new cache_1.SessionStorageAdapter(), new spa_navigation_1.SpaNavigationDetector());
31
+ /**
32
+ * DeviceIdentifier class for generating a unique identifier for a browser.
33
+ */
34
+ class DeviceIdentifier {
35
+ /**
36
+ * Constructor for the DeviceIdentifier class.
37
+ *
38
+ * @param options - Optional options for customizing the DeviceIdentifier generation.
39
+ */
40
+ constructor(options, config = {}) {
41
+ if (typeof options === 'object') {
42
+ this.hasher = options.hasher;
43
+ this.screen_resolution = options.screen_resolution;
44
+ this.screen_orientation = options.screen_orientation;
45
+ this.canvas = options.canvas;
46
+ this.singleScriptInjection = options.singleScriptInjection;
47
+ }
48
+ else if (typeof options === 'function') {
49
+ this.hasher = options;
50
+ }
51
+ const { token = '', env = 'staging' } = config;
52
+ this.token = token;
53
+ this.baseUrl = (0, crosscutting_1.resolveDeviceIntelligenceBaseUrl)(env);
54
+ this.deviceIntelligenceService = new device_intelligence_1.DeviceIntelligenceService(new axios_http_client_1.AxiosHttpClient(), this.baseUrl, this.token);
55
+ this.activateBehavioralBiometricsDetection();
56
+ }
57
+ /**
58
+ * Generates the fingerprint data for the browser.
59
+ *
60
+ * @returns An object containing the fingerprint data.
61
+ */
62
+ getDetails() {
63
+ return __awaiter(this, void 0, void 0, function* () {
64
+ const parsedData = navigatior.getDeviceType();
65
+ const [audioFingerprintResponse, highEntropyClientHints] = yield Promise.all([
66
+ audioFingerprint.getAudioFingerprint(),
67
+ navigatior.getHighEntropyClientHints(['model']),
68
+ ]);
69
+ const canvasFingerprint = canvas.getCanvasFingerprint();
70
+ const hash = this.getIdentity(canvasFingerprint);
71
+ return Object.assign(Object.assign({}, parsedData), { hash, canvas: canvasFingerprint, cookieEnabled: navigatior.isCookieEnabled(), display: display.getDisplayDetails(), javaEnabled: navigatior.isJavaEnabled(), tzOffset: utils.getTimezoneOffset(), touchEnabled: utils.getTouchSupport() || pointerCapability.hasTouchOrPen(), userAgent: navigatior.getUserAgent(), platform: navigatior.getPlatform(), languages: navigatior.getLanguages(), displayChecks: display.displayChecks(), networkPerformance: utils.getNetworkTime(), isWebCrawler: navigatior.isCrawler(), audioFingerprint: audioFingerprintResponse, userAgentClientHints: {
72
+ model: highEntropyClientHints === null || highEntropyClientHints === void 0 ? void 0 : highEntropyClientHints.model
73
+ }, webdriverEnabled: navigatior.isWebDriverEnabled(), estimatedRoundTripTime: navigatior.getEstimatedRoundTripTime() });
74
+ });
75
+ }
76
+ /**
77
+ * Generates a unique hash from browser and system properties (navigator, screen,
78
+ * storage, and more), using the provided hasher or murmurhash3_32_gc as a fallback.
79
+ *
80
+ * @param {string} [canvasFingerprint] - Precomputed canvas fingerprint to reuse
81
+ * instead of recomputing. Only used when the `canvas` option is enabled.
82
+ * @returns {number} A unique hash representing the user's browser and system.
83
+ */
84
+ getIdentity(canvasFingerprint) {
85
+ // Initialize an array to store the properties that will be used to generate the hash.
86
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
87
+ const keys = [];
88
+ const navigatorProperties = navigatior.getNavigatorProperties();
89
+ const screenProperties = display.getScreenProperties(this.screen_resolution, this.screen_orientation);
90
+ const storageProperties = storage.getStorageProperties();
91
+ keys.push(navigatorProperties.userAgent);
92
+ keys.push(navigatorProperties.language);
93
+ keys.push(screenProperties.colorDepth);
94
+ if (screenProperties.resolution) {
95
+ keys.push(screenProperties.resolution.join('x'));
96
+ }
97
+ keys.push(new Date().getTimezoneOffset());
98
+ keys.push(storageProperties.sessionStorage);
99
+ keys.push(storageProperties.localStorage);
100
+ keys.push(storageProperties.indexedDB);
101
+ keys.push(navigatorProperties.platform);
102
+ keys.push(navigatorProperties.doNotTrack);
103
+ if (this.canvas) {
104
+ keys.push(canvasFingerprint !== null && canvasFingerprint !== void 0 ? canvasFingerprint : canvas.getCanvasFingerprint());
105
+ }
106
+ const keyString = keys.join('###');
107
+ return this.hasher ? this.hasher(keyString, 31) : hash.murmurhash3_32_gc(keyString, 31);
108
+ }
109
+ /**
110
+ * Activates behavioral biometrics detection by starting event listeners for
111
+ * touch, cursor, and keyboard interactions.
112
+ *
113
+ * @param options - Optional configuration for BB detection, such as max data
114
+ * points for cursor and keystroke tracking.
115
+ */
116
+ activateBehavioralBiometricsDetection(options) {
117
+ if (typeof window === 'undefined')
118
+ return;
119
+ this.behaviouralBiometricsOptions = options;
120
+ behaviouralBiometrics.startCycle(options);
121
+ }
122
+ /**
123
+ * Deactivates behavioral biometrics detection by stopping all event listeners
124
+ * for touch, cursor, and keyboard interactions, and marking the detection
125
+ * as inactive.
126
+ */
127
+ deactivateBehavioralBiometricsDetection() {
128
+ behaviouralBiometrics.deactivate();
129
+ }
130
+ createTransaction(details_1) {
131
+ return __awaiter(this, arguments, void 0, function* (details, config = {}) {
132
+ var _a;
133
+ const response = yield this.deviceIntelligenceService.performRequest('/v1/transaction', 'post', config, details);
134
+ const transactionId = (_a = response === null || response === void 0 ? void 0 : response.body) === null || _a === void 0 ? void 0 : _a.transactionId;
135
+ const succeeded = typeof (response === null || response === void 0 ? void 0 : response.statusCode) === 'number'
136
+ && response.statusCode < 400
137
+ && typeof transactionId === 'string'
138
+ && transactionId.length > 0;
139
+ return succeeded ? transactionId : undefined;
140
+ });
141
+ }
142
+ trackDetails(transactionId_1) {
143
+ return __awaiter(this, arguments, void 0, function* (transactionId, metadata = {}, config = {}, context = {}) {
144
+ var _a;
145
+ behaviouralBiometrics.pauseWindow();
146
+ const bbParams = context.surveyId ? Object.assign(Object.assign({}, metadata), { survey_id: context.surveyId }) : metadata;
147
+ const bbResponse = yield this.sendBehaviorBiometrics(transactionId, bbParams, config);
148
+ if (bbResponse.statusCode >= 400) {
149
+ this.deactivateBehavioralBiometricsDetection();
150
+ }
151
+ else {
152
+ behaviouralBiometrics.startCycle(this.behaviouralBiometricsOptions);
153
+ }
154
+ const payload = Object.assign(Object.assign({ metadata }, yield this.getDetails()), { transactionId });
155
+ const response = yield this.deviceIntelligenceService.performRequest(`/v1/respondent/${transactionId}`, 'patch', config, payload);
156
+ return (_a = response.body) !== null && _a !== void 0 ? _a : response;
157
+ });
158
+ }
159
+ sendBehaviorBiometrics(transactionId_1, params_1) {
160
+ return __awaiter(this, arguments, void 0, function* (transactionId, params, config = {}) {
161
+ const { metrics, collectionDuration } = behaviouralBiometrics.snapshot();
162
+ return this.deviceIntelligenceService.performRequest(`/v1/respondent/${transactionId}/biometrics`, 'post', config, Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, params), metrics), { device: navigatior.getDeviceType().device, collectionDuration }), (this.singleScriptInjection ? { singleScriptInjection: true } : {})), { transactionId })).then(response => {
163
+ behaviouralBiometrics.reset();
164
+ return response;
165
+ });
166
+ });
167
+ }
168
+ }
169
+ exports.DeviceIdentifier = DeviceIdentifier;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * NOTE: these endpoints are going to be changed to a new domain
3
+ */
4
+ /** Device-Intelligence API, per environment. */
5
+ export declare const PURESCORE_INTELLIGENCE_BASE_URL: {
6
+ readonly production: "https://purescore.spectrumsurveys.com";
7
+ readonly staging: "https://purescore-staging.spectrumsurveys.com";
8
+ };
9
+ export type DeviceIntelligenceEnv = keyof typeof PURESCORE_INTELLIGENCE_BASE_URL;
10
+ /** Anything other than an explicit `production` resolves to staging. */
11
+ export declare function resolveDeviceIntelligenceBaseUrl(env?: string): string;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PURESCORE_INTELLIGENCE_BASE_URL = void 0;
4
+ exports.resolveDeviceIntelligenceBaseUrl = resolveDeviceIntelligenceBaseUrl;
5
+ /**
6
+ * NOTE: these endpoints are going to be changed to a new domain
7
+ */
8
+ /** Device-Intelligence API, per environment. */
9
+ exports.PURESCORE_INTELLIGENCE_BASE_URL = {
10
+ production: 'https://purescore.spectrumsurveys.com',
11
+ staging: 'https://purescore-staging.spectrumsurveys.com',
12
+ };
13
+ /** Anything other than an explicit `production` resolves to staging. */
14
+ function resolveDeviceIntelligenceBaseUrl(env) {
15
+ return (env === null || env === void 0 ? void 0 : env.toLowerCase()) === 'production'
16
+ ? exports.PURESCORE_INTELLIGENCE_BASE_URL.production
17
+ : exports.PURESCORE_INTELLIGENCE_BASE_URL.staging;
18
+ }
@@ -0,0 +1 @@
1
+ export * from './device-intelligence';