@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.
- package/README.md +135 -3
- package/lib/application/auto-init.d.ts +6 -3
- package/lib/application/auto-init.js +29 -20
- package/lib/application/device-identifier.d.ts +18 -1
- package/lib/application/device-identifier.js +43 -5
- package/lib/domain/errors/index.d.ts +1 -0
- package/lib/domain/errors/index.js +17 -0
- package/lib/domain/errors/transaction-rejected-error.d.ts +7 -0
- package/lib/domain/errors/transaction-rejected-error.js +14 -0
- package/lib/domain/models/crosscutting/device-intelligence.d.ts +3 -3
- package/lib/domain/models/crosscutting/device-intelligence.js +3 -3
- package/lib/domain/models/crosscutting/index.d.ts +1 -0
- package/lib/domain/models/crosscutting/index.js +1 -0
- package/lib/domain/models/crosscutting/verisoul.d.ts +19 -0
- package/lib/domain/models/crosscutting/verisoul.js +25 -0
- package/lib/domain/protocols/services/device-fingerprint/navigator.d.ts +1 -0
- package/lib/domain/protocols/services/device-identifier.d.ts +6 -6
- package/lib/domain/protocols/services/interaction-activity.d.ts +25 -0
- package/lib/domain/protocols/services/interaction-activity.js +2 -0
- package/lib/domain/protocols/services/single-script-injection/auto-init.d.ts +8 -8
- package/lib/domain/protocols/services/verisoul.d.ts +23 -0
- package/lib/domain/protocols/services/verisoul.js +2 -0
- package/lib/domain/services/device-fingerprint/index.d.ts +1 -0
- package/lib/domain/services/device-fingerprint/index.js +1 -0
- package/lib/domain/services/device-fingerprint/navigator.d.ts +2 -0
- package/lib/domain/services/device-fingerprint/navigator.js +7 -0
- package/lib/domain/services/device-fingerprint/webgl.d.ts +5 -0
- package/lib/domain/services/device-fingerprint/webgl.js +29 -0
- package/lib/domain/services/dom-mutation-tracker.d.ts +19 -0
- package/lib/domain/services/dom-mutation-tracker.js +47 -0
- package/lib/domain/services/interaction-activity.d.ts +43 -0
- package/lib/domain/services/interaction-activity.js +189 -0
- package/lib/domain/services/single-script-injection/checkpoint-scheduler.d.ts +1 -1
- package/lib/domain/services/single-script-injection/checkpoint-scheduler.js +3 -3
- package/lib/domain/services/single-script-injection/response-delivery.d.ts +3 -3
- package/lib/domain/services/single-script-injection/response-delivery.js +3 -3
- package/lib/domain/services/single-script-injection/schedule-store.d.ts +17 -7
- package/lib/domain/services/single-script-injection/schedule-store.js +11 -10
- package/lib/domain/services/snapshot-store.d.ts +1 -0
- package/lib/domain/services/snapshot-store.js +3 -0
- package/lib/domain/services/translation-detector.d.ts +20 -0
- package/lib/domain/services/translation-detector.js +119 -0
- package/lib/domain/services/verisoul.d.ts +27 -0
- package/lib/domain/services/verisoul.js +96 -0
- package/lib/index.d.ts +2 -1
- package/lib/index.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
# Purescore
|
|
1
|
+
# Purescore Client
|
|
2
2
|
|
|
3
3
|
`@purespectrum1/purescore-client` generates a stable, unique device ID from
|
|
4
4
|
device- and browser-level signals (fingerprinting). It ships as both a Node/TypeScript
|
|
5
5
|
library and a standalone UMD browser bundle, so the same identification logic can run
|
|
6
6
|
in a service or be dropped into a page with a `<script>` tag.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
For survey pages, the recommended integration is the self-initializing script tag
|
|
9
|
+
described in [Auto-init](#auto-init-single-script-tag).
|
|
10
10
|
|
|
11
11
|
## Requirements
|
|
12
12
|
|
|
@@ -41,6 +41,138 @@ Each release also uploads a version-pinned file (`bundle.<version>.js`) alongsid
|
|
|
41
41
|
floating `bundle.js`. Pin the versioned file in production so a new release cannot
|
|
42
42
|
change behaviour under you.
|
|
43
43
|
|
|
44
|
+
## Auto-init (single script tag)
|
|
45
|
+
|
|
46
|
+
The browser bundle can set itself up from its own `<script>` tag, so the survey page
|
|
47
|
+
needs no bootstrap code and no `trackDetails` calls. Add `data-auto-init="true"` and
|
|
48
|
+
the attributes below, and the bundle will:
|
|
49
|
+
|
|
50
|
+
1. create the transaction (`POST /v1/transaction`), retrying up to 3 times;
|
|
51
|
+
2. send the device and behavioural signals (`PATCH /v1/respondent/:transactionId`) on
|
|
52
|
+
a schedule derived from the survey's expected length of interview (LOI);
|
|
53
|
+
3. hand every response back to the page (see [Receiving the result](#receiving-the-result)).
|
|
54
|
+
|
|
55
|
+
Without `data-auto-init="true"` the bundle does nothing on load, so existing
|
|
56
|
+
integrations are unaffected.
|
|
57
|
+
|
|
58
|
+
### Example
|
|
59
|
+
|
|
60
|
+
```html
|
|
61
|
+
<script>
|
|
62
|
+
// Must be a global function, declared before the bundle tag (see data-callback).
|
|
63
|
+
function handlePureScore(result) {
|
|
64
|
+
if (result.continue === 0) {
|
|
65
|
+
// terminate the respondent
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
</script>
|
|
69
|
+
<script src="https://<cdn-host>/dist/bundle.<version>.js"
|
|
70
|
+
data-auto-init="true"
|
|
71
|
+
data-token="<device-intelligence-token>"
|
|
72
|
+
data-env="production"
|
|
73
|
+
data-callback="handlePureScore"
|
|
74
|
+
|
|
75
|
+
data-project-id="${project_id}"
|
|
76
|
+
data-expected-survey-loi="10"
|
|
77
|
+
data-survey-country="US"
|
|
78
|
+
data-survey-language="en"
|
|
79
|
+
|
|
80
|
+
data-buyer-id="456"
|
|
81
|
+
data-metadata='{"sup_resp_id":"24d373e4"}'
|
|
82
|
+
data-supplier-id="607"
|
|
83
|
+
data-supplier-member-id="m-24d373e4"
|
|
84
|
+
data-email-hash="<sha256-of-the-email>"
|
|
85
|
+
data-demographics-age="30"
|
|
86
|
+
data-demographics-gender="male"
|
|
87
|
+
data-demographics-zip="02134"></script>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`${project_id}` stands for whatever placeholder syntax your survey platform uses. It
|
|
91
|
+
has to be replaced with the real value before the page is served (see
|
|
92
|
+
[Validation](#validation)).
|
|
93
|
+
|
|
94
|
+
### Attributes
|
|
95
|
+
|
|
96
|
+
**Required.** If any of these is missing or invalid, auto-init logs an error to the
|
|
97
|
+
console and does not start.
|
|
98
|
+
|
|
99
|
+
| Attribute | Description |
|
|
100
|
+
| --- | --- |
|
|
101
|
+
| `data-auto-init` | Must be `"true"` to enable auto-init. |
|
|
102
|
+
| `data-token` | Purescore bearer token. |
|
|
103
|
+
| `data-project-id` | Project identifier. |
|
|
104
|
+
| `data-expected-survey-loi` | Expected length of interview, **in minutes**. A positive number (decimals allowed). |
|
|
105
|
+
| `data-survey-country` | ISO 3166-1 alpha-2 country code, e.g. `US`, `GB`. Converted to upper case. |
|
|
106
|
+
| `data-survey-language` | ISO 639-1 language code, e.g. `en`, `fr`. Converted to lower case. Use the language only: `en-US` is rejected. |
|
|
107
|
+
|
|
108
|
+
**Optional.** If one of these is set but invalid, it is logged and ignored, and
|
|
109
|
+
auto-init still runs.
|
|
110
|
+
|
|
111
|
+
| Attribute | Description |
|
|
112
|
+
| --- | --- |
|
|
113
|
+
| `data-env` | `production` or `staging`. Any other value, or none, means `staging`. **Set `production` on live surveys.** |
|
|
114
|
+
| `data-callback` | Name of a **global** function to call with each response. |
|
|
115
|
+
| `data-metadata` | A JSON **object** sent as `metadata` with every checkpoint. Use single quotes around the attribute so the JSON can keep its double quotes. |
|
|
116
|
+
| `data-buyer-id` | Added to the metadata as `buyer_id`. |
|
|
117
|
+
| `data-supplier-id` | Supplier identifier, sent when the transaction is created. |
|
|
118
|
+
| `data-supplier-member-id` | The supplier's respondent identifier, sent when the transaction is created. |
|
|
119
|
+
| `data-email-hash` | Hash of the respondent's email, sent when the transaction is created. Never send the plain email address. The value is not validated yet. |
|
|
120
|
+
| `data-demographics-age` | Respondent age. A positive number. |
|
|
121
|
+
| `data-demographics-gender` | Respondent gender. |
|
|
122
|
+
| `data-demographics-zip` | Respondent postal code. Keep it as a string so leading zeros are preserved. |
|
|
123
|
+
|
|
124
|
+
### Validation
|
|
125
|
+
|
|
126
|
+
- **Uninterpolated placeholders are rejected.** If a value still contains `${`, `{{`,
|
|
127
|
+
`{%` or `<%`, the survey platform didn't replace it. A literal like
|
|
128
|
+
`${transaction_id}` would otherwise be shared by every respondent. For a required
|
|
129
|
+
attribute this stops auto-init; for an optional one, that attribute is dropped.
|
|
130
|
+
- **Errors go to the browser console** with the prefix `Purescore-Client auto-init:`.
|
|
131
|
+
Check the console first when nothing seems to happen.
|
|
132
|
+
- **Only the first tag on a page runs.** If the bundle is loaded a second time, it
|
|
133
|
+
does nothing.
|
|
134
|
+
|
|
135
|
+
### Checkpoint schedule
|
|
136
|
+
|
|
137
|
+
The transaction makes at most 5 calls, timed from the respondent's first page load:
|
|
138
|
+
|
|
139
|
+
| Checkpoint | Due at | Example: LOI = 6 min |
|
|
140
|
+
| --- | --- | --- |
|
|
141
|
+
| 1 | 10 seconds | 0:10 |
|
|
142
|
+
| 2 | ⅓ × LOI | 2:00 |
|
|
143
|
+
| 3 | ⅔ × LOI | 4:00 |
|
|
144
|
+
| 4 | 1 × LOI | 6:00 |
|
|
145
|
+
| 5 | 2 × LOI | 12:00 |
|
|
146
|
+
|
|
147
|
+
### Receiving the result
|
|
148
|
+
|
|
149
|
+
Each response is delivered through three channels at once. Use whichever suits your
|
|
150
|
+
platform:
|
|
151
|
+
|
|
152
|
+
```js
|
|
153
|
+
// 1. The global function named in data-callback
|
|
154
|
+
function handlePureScore(result) { /* ... */ }
|
|
155
|
+
|
|
156
|
+
// 2. A DOM event on window (any number of listeners)
|
|
157
|
+
window.addEventListener('ps:purescore:result', function (event) {
|
|
158
|
+
var result = event.detail;
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// 3. A global you can read at any time
|
|
162
|
+
window.psPureScore && window.psPureScore.lastResult;
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
- **`data-callback` must name a function on `window`.** Declare it with a plain
|
|
166
|
+
`function` statement in a classic `<script>`, before the bundle tag. A function
|
|
167
|
+
declared inside an IIFE, an ES module or `jQuery(document).ready(...)` can't be
|
|
168
|
+
found, and the console will report `"<name>" is not a global function`.
|
|
169
|
+
- **Use the `continue` field to decide what to do.** `continue: 0` means the
|
|
170
|
+
respondent should be terminated.
|
|
171
|
+
- **On a later page, the latest response is delivered again.**
|
|
172
|
+
`window.psPureScore.lastResult` is set immediately. The event and the callback fire
|
|
173
|
+
once the document has finished parsing, so listeners declared further down the page
|
|
174
|
+
still receive it.
|
|
175
|
+
|
|
44
176
|
## Usage
|
|
45
177
|
|
|
46
178
|
```ts
|
|
@@ -16,11 +16,15 @@ export declare const TRANSACTION_RETRY_DELAY_MS = 1000;
|
|
|
16
16
|
export declare function resolveScriptTag(): HTMLScriptElement | null;
|
|
17
17
|
/**
|
|
18
18
|
* Reads the bootstrap configuration off the tag's `data-*` attributes, or
|
|
19
|
-
* returns null when
|
|
19
|
+
* returns null when the token is missing.
|
|
20
|
+
*
|
|
21
|
+
* A missing or unusable survey attribute is reported but not fatal: the
|
|
22
|
+
* transaction is still created with what the tag holds, so the service's own
|
|
23
|
+
* validation error shows up in the network response next to the console one.
|
|
20
24
|
*/
|
|
21
25
|
export declare function readAutoInitConfig(tag: HTMLScriptElement): AutoInitConfig | null;
|
|
22
26
|
/**
|
|
23
|
-
* Bootstraps the
|
|
27
|
+
* Bootstraps the Purescore-Client straight from the script tag: no constructor
|
|
24
28
|
* call and no `trackDetails` call on the buyer side.
|
|
25
29
|
*
|
|
26
30
|
* Strictly opt-in — it does nothing unless `data-auto-init="true"` is on the tag
|
|
@@ -38,7 +42,6 @@ export declare class AutoInitService {
|
|
|
38
42
|
private resolveTransactionId;
|
|
39
43
|
/**
|
|
40
44
|
* Registers the transaction, trying up to `TRANSACTION_CREATION_ATTEMPTS` times.
|
|
41
|
-
* Resolves with undefined when every attempt failed.
|
|
42
45
|
*/
|
|
43
46
|
private createTransaction;
|
|
44
47
|
}
|
|
@@ -15,6 +15,7 @@ exports.readAutoInitConfig = readAutoInitConfig;
|
|
|
15
15
|
const validation_1 = require("../validation");
|
|
16
16
|
const single_script_injection_1 = require("../domain/services/single-script-injection");
|
|
17
17
|
const cache_1 = require("../infra/cache");
|
|
18
|
+
const errors_1 = require("../domain/errors");
|
|
18
19
|
/**
|
|
19
20
|
* Guard flag on `window`: a second tag on the same page is a no-op.
|
|
20
21
|
*/
|
|
@@ -39,15 +40,18 @@ function resolveScriptTag() {
|
|
|
39
40
|
}
|
|
40
41
|
/**
|
|
41
42
|
* Reads the bootstrap configuration off the tag's `data-*` attributes, or
|
|
42
|
-
* returns null when
|
|
43
|
+
* returns null when the token is missing.
|
|
44
|
+
*
|
|
45
|
+
* A missing or unusable survey attribute is reported but not fatal: the
|
|
46
|
+
* transaction is still created with what the tag holds, so the service's own
|
|
47
|
+
* validation error shows up in the network response next to the console one.
|
|
43
48
|
*/
|
|
44
49
|
function readAutoInitConfig(tag) {
|
|
45
50
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
46
51
|
const data = tag.dataset;
|
|
52
|
+
const tokenError = validation_1.ValidationBuilder.of({ value: data.token, fieldName: 'data-token' }).required().validate();
|
|
47
53
|
const errors = [
|
|
48
|
-
validation_1.ValidationBuilder.of({ value: data.
|
|
49
|
-
.required(),
|
|
50
|
-
validation_1.ValidationBuilder.of({ value: data.surveyId, fieldName: 'data-survey-id' })
|
|
54
|
+
validation_1.ValidationBuilder.of({ value: data.projectId, fieldName: 'data-project-id' })
|
|
51
55
|
.required()
|
|
52
56
|
.interpolated(),
|
|
53
57
|
validation_1.ValidationBuilder.of({ value: data.expectedSurveyLoi, fieldName: 'data-expected-survey-loi' })
|
|
@@ -62,17 +66,19 @@ function readAutoInitConfig(tag) {
|
|
|
62
66
|
.required()
|
|
63
67
|
.interpolated()
|
|
64
68
|
.languageCode(),
|
|
65
|
-
].map(rules => rules.validate())
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
].map(rules => rules.validate());
|
|
70
|
+
const reported = [tokenError, ...errors].filter((error) => error !== undefined);
|
|
71
|
+
if (reported.length) {
|
|
72
|
+
console.error(`Purescore-Client auto-init: ${reported.map(e => e.message).join('; ')}`);
|
|
69
73
|
}
|
|
74
|
+
if (tokenError)
|
|
75
|
+
return null;
|
|
70
76
|
const config = {
|
|
71
77
|
token: ((_a = data.token) !== null && _a !== void 0 ? _a : '').trim(),
|
|
72
78
|
env: ((_b = data.env) === null || _b === void 0 ? void 0 : _b.trim().toLowerCase()) === 'production' ? 'production' : 'staging',
|
|
73
79
|
callbackName: ((_c = data.callback) === null || _c === void 0 ? void 0 : _c.trim()) || undefined,
|
|
74
80
|
metadata: parseMetadataAttribute(data.metadata),
|
|
75
|
-
|
|
81
|
+
projectId: ((_d = data.projectId) !== null && _d !== void 0 ? _d : '').trim(),
|
|
76
82
|
expectedSurveyLOI: Number(data.expectedSurveyLoi),
|
|
77
83
|
surveyCountry: ((_e = data.surveyCountry) !== null && _e !== void 0 ? _e : '').trim().toUpperCase(),
|
|
78
84
|
surveyLanguage: ((_f = data.surveyLanguage) !== null && _f !== void 0 ? _f : '').trim().toLowerCase(),
|
|
@@ -103,7 +109,7 @@ function readAutoInitConfig(tag) {
|
|
|
103
109
|
function optionalField(raw, fieldName, rules = builder => builder, convert = value => value) {
|
|
104
110
|
const error = rules(validation_1.ValidationBuilder.of({ value: raw, fieldName }).optional().interpolated()).validate();
|
|
105
111
|
if (error) {
|
|
106
|
-
console.error(`
|
|
112
|
+
console.error(`Purescore-Client auto-init: ${error.message}, ignoring it`);
|
|
107
113
|
return undefined;
|
|
108
114
|
}
|
|
109
115
|
const value = raw === null || raw === void 0 ? void 0 : raw.trim();
|
|
@@ -117,7 +123,7 @@ function optionalField(raw, fieldName, rules = builder => builder, convert = val
|
|
|
117
123
|
function parseMetadataAttribute(raw) {
|
|
118
124
|
const error = validation_1.ValidationBuilder.of({ value: raw, fieldName: 'data-metadata' }).jsonObject().validate();
|
|
119
125
|
if (error) {
|
|
120
|
-
console.error(`
|
|
126
|
+
console.error(`Purescore-Client auto-init: ${error.message}, ignoring it`);
|
|
121
127
|
return {};
|
|
122
128
|
}
|
|
123
129
|
return (raw === null || raw === void 0 ? void 0 : raw.trim()) ? JSON.parse(raw) : {};
|
|
@@ -130,7 +136,7 @@ function assignDefined(target, values) {
|
|
|
130
136
|
});
|
|
131
137
|
}
|
|
132
138
|
/**
|
|
133
|
-
* Bootstraps the
|
|
139
|
+
* Bootstraps the Purescore-Client straight from the script tag: no constructor
|
|
134
140
|
* call and no `trackDetails` call on the buyer side.
|
|
135
141
|
*
|
|
136
142
|
* Strictly opt-in — it does nothing unless `data-auto-init="true"` is on the tag
|
|
@@ -166,7 +172,7 @@ class AutoInitService {
|
|
|
166
172
|
var _a;
|
|
167
173
|
const deviceIdentifier = this.createDeviceIdentifier(config);
|
|
168
174
|
const delivery = new single_script_injection_1.ResponseDeliveryService(config.callbackName);
|
|
169
|
-
const store = new single_script_injection_1.ScheduleStore(config
|
|
175
|
+
const store = new single_script_injection_1.ScheduleStore(config, new cache_1.LocalStorageAdapter());
|
|
170
176
|
const credentials = { token: config.token, env: config.env };
|
|
171
177
|
// Re-emit the verdict from an earlier page before the schedule resumes, so a
|
|
172
178
|
// page that only now registers a listener still receives the current one.
|
|
@@ -176,13 +182,15 @@ class AutoInitService {
|
|
|
176
182
|
// The LOI comes off the tag, so every checkpoint is known up front. On a
|
|
177
183
|
// later page the record already carries it, and `applyLoi` leaves it alone.
|
|
178
184
|
store.resumeOrCreate();
|
|
179
|
-
|
|
185
|
+
if (Number.isFinite(config.expectedSurveyLOI) && config.expectedSurveyLOI > 0) {
|
|
186
|
+
store.applyLoi(config.expectedSurveyLOI);
|
|
187
|
+
}
|
|
180
188
|
const transactionReady = this.resolveTransactionId(store, deviceIdentifier, config, credentials);
|
|
181
189
|
const scheduler = new single_script_injection_1.CheckpointScheduler(store, () => __awaiter(this, void 0, void 0, function* () {
|
|
182
190
|
const transactionId = yield transactionReady;
|
|
183
191
|
if (!transactionId)
|
|
184
192
|
throw new Error('no transaction id to send the checkpoint for');
|
|
185
|
-
const response = yield deviceIdentifier.trackDetails(transactionId, config.metadata, credentials, {
|
|
193
|
+
const response = yield deviceIdentifier.trackDetails(transactionId, config.metadata, credentials, { projectId: config.projectId });
|
|
186
194
|
store.storeResponse(response);
|
|
187
195
|
delivery.deliver(response);
|
|
188
196
|
}));
|
|
@@ -206,7 +214,6 @@ class AutoInitService {
|
|
|
206
214
|
}
|
|
207
215
|
/**
|
|
208
216
|
* Registers the transaction, trying up to `TRANSACTION_CREATION_ATTEMPTS` times.
|
|
209
|
-
* Resolves with undefined when every attempt failed.
|
|
210
217
|
*/
|
|
211
218
|
createTransaction(deviceIdentifier, config, credentials) {
|
|
212
219
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -214,7 +221,7 @@ class AutoInitService {
|
|
|
214
221
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
215
222
|
};
|
|
216
223
|
const details = {
|
|
217
|
-
|
|
224
|
+
projectId: config.projectId,
|
|
218
225
|
expectedSurveyLOI: config.expectedSurveyLOI,
|
|
219
226
|
surveyCountry: config.surveyCountry,
|
|
220
227
|
surveyLanguage: config.surveyLanguage,
|
|
@@ -227,17 +234,19 @@ class AutoInitService {
|
|
|
227
234
|
});
|
|
228
235
|
for (let attempt = 1; attempt <= exports.TRANSACTION_CREATION_ATTEMPTS; attempt++) {
|
|
229
236
|
try {
|
|
230
|
-
const transactionId = yield deviceIdentifier.createTransaction(details, credentials);
|
|
237
|
+
const transactionId = yield deviceIdentifier.createTransaction(details, credentials, config.metadata);
|
|
231
238
|
if (transactionId)
|
|
232
239
|
return transactionId;
|
|
233
240
|
}
|
|
234
241
|
catch (error) {
|
|
235
|
-
|
|
242
|
+
if (error instanceof errors_1.TransactionRejectedError)
|
|
243
|
+
return undefined;
|
|
244
|
+
console.error(`Error creating PureScore-Intelligence transaction (attempt ${attempt}):`, error);
|
|
236
245
|
}
|
|
237
246
|
if (attempt < exports.TRANSACTION_CREATION_ATTEMPTS)
|
|
238
247
|
yield delay(exports.TRANSACTION_RETRY_DELAY_MS);
|
|
239
248
|
}
|
|
240
|
-
console.error(`
|
|
249
|
+
console.error(`Purescore-Client auto-init: could not create the transaction after ${exports.TRANSACTION_CREATION_ATTEMPTS} attempts`);
|
|
241
250
|
return undefined;
|
|
242
251
|
});
|
|
243
252
|
}
|
|
@@ -26,6 +26,11 @@ export declare class DeviceIdentifier implements TrackDetails, CreateTransaction
|
|
|
26
26
|
* Service responsible for performing API requests to the Purescore Intelligence service.
|
|
27
27
|
*/
|
|
28
28
|
private deviceIntelligenceService;
|
|
29
|
+
/**
|
|
30
|
+
* Source of the Verisoul session id sent with each respondent PATCH. Only
|
|
31
|
+
* set under the auto-init bootstrap; not configurable from the outside.
|
|
32
|
+
*/
|
|
33
|
+
private verisoul?;
|
|
29
34
|
private singleScriptInjection?;
|
|
30
35
|
private screen_resolution?;
|
|
31
36
|
private screen_orientation?;
|
|
@@ -61,6 +66,9 @@ export declare class DeviceIdentifier implements TrackDetails, CreateTransaction
|
|
|
61
66
|
};
|
|
62
67
|
webdriverEnabled: boolean;
|
|
63
68
|
estimatedRoundTripTime: number;
|
|
69
|
+
cpuCores: number | null;
|
|
70
|
+
deviceMemory: number | null;
|
|
71
|
+
gpu: string | null;
|
|
64
72
|
ua: string;
|
|
65
73
|
browser: {
|
|
66
74
|
name?: string;
|
|
@@ -110,7 +118,16 @@ export declare class DeviceIdentifier implements TrackDetails, CreateTransaction
|
|
|
110
118
|
* as inactive.
|
|
111
119
|
*/
|
|
112
120
|
deactivateBehavioralBiometricsDetection(): void;
|
|
113
|
-
|
|
121
|
+
/**
|
|
122
|
+
* The device details of a session
|
|
123
|
+
*/
|
|
124
|
+
private collectSessionDetails;
|
|
125
|
+
/**
|
|
126
|
+
* Registers the transaction together with session 0: the device details
|
|
127
|
+
* collected right now, so the transaction can be evaluated as soon as it
|
|
128
|
+
* exists.
|
|
129
|
+
*/
|
|
130
|
+
createTransaction(details: TransactionDetails, config?: DeviceIdentifierConfig, metadata?: DeviceIdentifierMetadata): Promise<string | undefined>;
|
|
114
131
|
trackDetails(transactionId: string, metadata?: DeviceIdentifierMetadata, config?: DeviceIdentifierConfig, context?: TrackDetailsContext): Promise<Record<string, any>>;
|
|
115
132
|
sendBehaviorBiometrics(transactionId: string, params: BehaviorBiometricsParams, config?: DeviceIdentifierConfig): Promise<any>;
|
|
116
133
|
}
|
|
@@ -12,9 +12,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.DeviceIdentifier = void 0;
|
|
13
13
|
const device_fingerprint_1 = require("../domain/services/device-fingerprint");
|
|
14
14
|
const behavioural_biometrics_1 = require("../domain/services/behavioural-biometrics");
|
|
15
|
+
const interaction_activity_1 = require("../domain/services/interaction-activity");
|
|
16
|
+
const errors_1 = require("../domain/errors");
|
|
15
17
|
const device_intelligence_1 = require("../domain/services/device-intelligence");
|
|
16
18
|
const crosscutting_1 = require("../domain/models/crosscutting");
|
|
19
|
+
const verisoul_1 = require("../domain/services/verisoul");
|
|
17
20
|
const spa_navigation_1 = require("../domain/services/spa-navigation");
|
|
21
|
+
const dom_mutation_tracker_1 = require("../domain/services/dom-mutation-tracker");
|
|
18
22
|
const axios_http_client_1 = require("../infra/http/axios-http-client");
|
|
19
23
|
const cache_1 = require("../infra/cache");
|
|
20
24
|
const hash = new device_fingerprint_1.Hash();
|
|
@@ -24,7 +28,10 @@ const navigatior = new device_fingerprint_1.Navigatior();
|
|
|
24
28
|
const canvas = new device_fingerprint_1.Canvas();
|
|
25
29
|
const utils = new device_fingerprint_1.Utils();
|
|
26
30
|
const audioFingerprint = new device_fingerprint_1.AudioFingerprint();
|
|
31
|
+
const webgl = new device_fingerprint_1.WebGL();
|
|
27
32
|
const pointerCapability = new device_fingerprint_1.PointerCapabilityDetector();
|
|
33
|
+
const domMutationTracker = new dom_mutation_tracker_1.DomMutationTracker(new cache_1.SessionStorageAdapter());
|
|
34
|
+
const interactionActivity = new interaction_activity_1.InteractionActivityTracker(new cache_1.SessionStorageAdapter());
|
|
28
35
|
// One collector shared for the lifetime of the page. It owns the trackers, which
|
|
29
36
|
// is what makes behavioural data survive a DeviceIdentifier being re-instantiated.
|
|
30
37
|
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());
|
|
@@ -52,7 +59,14 @@ class DeviceIdentifier {
|
|
|
52
59
|
this.token = token;
|
|
53
60
|
this.baseUrl = (0, crosscutting_1.resolveDeviceIntelligenceBaseUrl)(env);
|
|
54
61
|
this.deviceIntelligenceService = new device_intelligence_1.DeviceIntelligenceService(new axios_http_client_1.AxiosHttpClient(), this.baseUrl, this.token);
|
|
62
|
+
if (this.singleScriptInjection && typeof window !== 'undefined') {
|
|
63
|
+
// Load the SDK now so its session is ready by the first checkpoint.
|
|
64
|
+
this.verisoul = new verisoul_1.VerisoulService((0, crosscutting_1.resolveVerisoulConfig)(env));
|
|
65
|
+
this.verisoul.load();
|
|
66
|
+
}
|
|
55
67
|
this.activateBehavioralBiometricsDetection();
|
|
68
|
+
domMutationTracker.start();
|
|
69
|
+
interactionActivity.startTracking();
|
|
56
70
|
}
|
|
57
71
|
/**
|
|
58
72
|
* Generates the fingerprint data for the browser.
|
|
@@ -70,7 +84,7 @@ class DeviceIdentifier {
|
|
|
70
84
|
const hash = this.getIdentity(canvasFingerprint);
|
|
71
85
|
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
86
|
model: highEntropyClientHints === null || highEntropyClientHints === void 0 ? void 0 : highEntropyClientHints.model
|
|
73
|
-
}, webdriverEnabled: navigatior.isWebDriverEnabled(), estimatedRoundTripTime: navigatior.getEstimatedRoundTripTime() });
|
|
87
|
+
}, webdriverEnabled: navigatior.isWebDriverEnabled(), estimatedRoundTripTime: navigatior.getEstimatedRoundTripTime(), cpuCores: navigatior.getCpuCores(), deviceMemory: navigatior.getDeviceMemory(), gpu: webgl.getGpuRenderer() });
|
|
74
88
|
});
|
|
75
89
|
}
|
|
76
90
|
/**
|
|
@@ -127,10 +141,34 @@ class DeviceIdentifier {
|
|
|
127
141
|
deactivateBehavioralBiometricsDetection() {
|
|
128
142
|
behaviouralBiometrics.deactivate();
|
|
129
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* The device details of a session
|
|
146
|
+
*/
|
|
147
|
+
collectSessionDetails(metadata) {
|
|
148
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
149
|
+
var _a;
|
|
150
|
+
const [details, verisoulSessionId] = yield Promise.all([
|
|
151
|
+
this.getDetails(),
|
|
152
|
+
(_a = this.verisoul) === null || _a === void 0 ? void 0 : _a.getSessionId(),
|
|
153
|
+
]);
|
|
154
|
+
return Object.assign(Object.assign(Object.assign({ metadata }, details), (verisoulSessionId ? { verisoulSessionId } : {})), { domElementsMutationCount: domMutationTracker.domElementsMutationCount, interactionActivity: interactionActivity.getInteractionActivity() });
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Registers the transaction together with session 0: the device details
|
|
159
|
+
* collected right now, so the transaction can be evaluated as soon as it
|
|
160
|
+
* exists.
|
|
161
|
+
*/
|
|
130
162
|
createTransaction(details_1) {
|
|
131
|
-
return __awaiter(this, arguments, void 0, function* (details, config = {}) {
|
|
163
|
+
return __awaiter(this, arguments, void 0, function* (details, config = {}, metadata = {}) {
|
|
132
164
|
var _a;
|
|
133
|
-
const
|
|
165
|
+
const session = yield this.collectSessionDetails(metadata).catch(error => {
|
|
166
|
+
console.error('Error collecting the PureScore-Intelligence Session 0 details:', error);
|
|
167
|
+
return undefined;
|
|
168
|
+
});
|
|
169
|
+
const response = yield this.deviceIntelligenceService.performRequest('/v1/transaction', 'post', config, session ? Object.assign(Object.assign({}, details), { session }) : details);
|
|
170
|
+
if ((response === null || response === void 0 ? void 0 : response.statusCode) === 422)
|
|
171
|
+
throw new errors_1.TransactionRejectedError(response.body);
|
|
134
172
|
const transactionId = (_a = response === null || response === void 0 ? void 0 : response.body) === null || _a === void 0 ? void 0 : _a.transactionId;
|
|
135
173
|
const succeeded = typeof (response === null || response === void 0 ? void 0 : response.statusCode) === 'number'
|
|
136
174
|
&& response.statusCode < 400
|
|
@@ -143,7 +181,7 @@ class DeviceIdentifier {
|
|
|
143
181
|
return __awaiter(this, arguments, void 0, function* (transactionId, metadata = {}, config = {}, context = {}) {
|
|
144
182
|
var _a;
|
|
145
183
|
behaviouralBiometrics.pauseWindow();
|
|
146
|
-
const bbParams = context.
|
|
184
|
+
const bbParams = context.projectId ? Object.assign(Object.assign({}, metadata), { survey_id: context.projectId }) : metadata;
|
|
147
185
|
const bbResponse = yield this.sendBehaviorBiometrics(transactionId, bbParams, config);
|
|
148
186
|
if (bbResponse.statusCode >= 400) {
|
|
149
187
|
this.deactivateBehavioralBiometricsDetection();
|
|
@@ -151,7 +189,7 @@ class DeviceIdentifier {
|
|
|
151
189
|
else {
|
|
152
190
|
behaviouralBiometrics.startCycle(this.behaviouralBiometricsOptions);
|
|
153
191
|
}
|
|
154
|
-
const payload = Object.assign(Object.assign({
|
|
192
|
+
const payload = Object.assign(Object.assign({}, yield this.collectSessionDetails(metadata)), { transactionId });
|
|
155
193
|
const response = yield this.deviceIntelligenceService.performRequest(`/v1/respondent/${transactionId}`, 'patch', config, payload);
|
|
156
194
|
return (_a = response.body) !== null && _a !== void 0 ? _a : response;
|
|
157
195
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './transaction-rejected-error';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./transaction-rejected-error"), exports);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TransactionRejectedError = void 0;
|
|
4
|
+
class TransactionRejectedError extends Error {
|
|
5
|
+
/**
|
|
6
|
+
* @param body - the service's response body, kept so the caller can surface its validation message.
|
|
7
|
+
*/
|
|
8
|
+
constructor(body) {
|
|
9
|
+
super('PureScore-Intelligence rejected the transaction details (HTTP 422)');
|
|
10
|
+
this.body = body;
|
|
11
|
+
this.name = 'TransactionRejectedError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.TransactionRejectedError = TransactionRejectedError;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* NOTE: these endpoints are going to be changed to a new domain
|
|
3
3
|
*/
|
|
4
|
-
/**
|
|
4
|
+
/** PureScore-Intelligence API, per environment. */
|
|
5
5
|
export declare const PURESCORE_INTELLIGENCE_BASE_URL: {
|
|
6
|
-
readonly production: "https://purescore.spectrumsurveys.com";
|
|
7
|
-
readonly staging: "https://purescore-
|
|
6
|
+
readonly production: "https://purescore-intelligence.spectrumsurveys.com";
|
|
7
|
+
readonly staging: "https://purescore-intelligence-uat.spectrumsurveys.com";
|
|
8
8
|
};
|
|
9
9
|
export type DeviceIntelligenceEnv = keyof typeof PURESCORE_INTELLIGENCE_BASE_URL;
|
|
10
10
|
/** Anything other than an explicit `production` resolves to staging. */
|
|
@@ -5,10 +5,10 @@ exports.resolveDeviceIntelligenceBaseUrl = resolveDeviceIntelligenceBaseUrl;
|
|
|
5
5
|
/**
|
|
6
6
|
* NOTE: these endpoints are going to be changed to a new domain
|
|
7
7
|
*/
|
|
8
|
-
/**
|
|
8
|
+
/** PureScore-Intelligence API, per environment. */
|
|
9
9
|
exports.PURESCORE_INTELLIGENCE_BASE_URL = {
|
|
10
|
-
production: 'https://purescore.spectrumsurveys.com',
|
|
11
|
-
staging: 'https://purescore-
|
|
10
|
+
production: 'https://purescore-intelligence.spectrumsurveys.com',
|
|
11
|
+
staging: 'https://purescore-intelligence-uat.spectrumsurveys.com',
|
|
12
12
|
};
|
|
13
13
|
/** Anything other than an explicit `production` resolves to staging. */
|
|
14
14
|
function resolveDeviceIntelligenceBaseUrl(env) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { DeviceIntelligenceEnv } from './device-intelligence';
|
|
2
|
+
/**
|
|
3
|
+
* Verisoul SDK, per environment. The bundle is served through our own proxy
|
|
4
|
+
* (same hosts as ps-screener) so ad-blockers keyed on the vendor domain don't
|
|
5
|
+
* drop it; the project id is what ties the session to our Verisoul account.
|
|
6
|
+
*/
|
|
7
|
+
export declare const VERISOUL_CONFIG: {
|
|
8
|
+
readonly production: {
|
|
9
|
+
readonly sdkUrl: "https://vs.spectrumsurveys.com/bundle.js";
|
|
10
|
+
readonly projectId: "7a994dae-58a8-413f-a59a-655a7adbfb2d";
|
|
11
|
+
};
|
|
12
|
+
readonly staging: {
|
|
13
|
+
readonly sdkUrl: "https://sandbox.vs.spectrumsurveys.com/bundle.js";
|
|
14
|
+
readonly projectId: "6e7419d0-3be6-43d7-a4a4-9d44828d9f5e";
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
export type VerisoulEnvConfig = (typeof VERISOUL_CONFIG)[DeviceIntelligenceEnv];
|
|
18
|
+
/** Anything other than an explicit `production` resolves to the sandbox project. */
|
|
19
|
+
export declare function resolveVerisoulConfig(env?: string): VerisoulEnvConfig;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VERISOUL_CONFIG = void 0;
|
|
4
|
+
exports.resolveVerisoulConfig = resolveVerisoulConfig;
|
|
5
|
+
/**
|
|
6
|
+
* Verisoul SDK, per environment. The bundle is served through our own proxy
|
|
7
|
+
* (same hosts as ps-screener) so ad-blockers keyed on the vendor domain don't
|
|
8
|
+
* drop it; the project id is what ties the session to our Verisoul account.
|
|
9
|
+
*/
|
|
10
|
+
exports.VERISOUL_CONFIG = {
|
|
11
|
+
production: {
|
|
12
|
+
sdkUrl: 'https://vs.spectrumsurveys.com/bundle.js',
|
|
13
|
+
projectId: '7a994dae-58a8-413f-a59a-655a7adbfb2d',
|
|
14
|
+
},
|
|
15
|
+
staging: {
|
|
16
|
+
sdkUrl: 'https://sandbox.vs.spectrumsurveys.com/bundle.js',
|
|
17
|
+
projectId: '6e7419d0-3be6-43d7-a4a4-9d44828d9f5e',
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
/** Anything other than an explicit `production` resolves to the sandbox project. */
|
|
21
|
+
function resolveVerisoulConfig(env) {
|
|
22
|
+
return (env === null || env === void 0 ? void 0 : env.toLowerCase()) === 'production'
|
|
23
|
+
? exports.VERISOUL_CONFIG.production
|
|
24
|
+
: exports.VERISOUL_CONFIG.staging;
|
|
25
|
+
}
|