@deflectbot/deflect-sdk 1.3.8 → 1.4.1-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +7 -18
- package/dist/index.esm.js +96 -177
- package/dist/index.js +298 -6716
- package/dist/index.min.js +2 -15
- package/dist/pulse.d.ts +99 -0
- package/dist/scriptClient.d.ts +7 -0
- package/dist/scriptRunner.d.ts +5 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -12,33 +12,22 @@ declare class Deflect {
|
|
|
12
12
|
private scriptCache;
|
|
13
13
|
private isWarmupInProgress;
|
|
14
14
|
private hasWarmupError;
|
|
15
|
-
private
|
|
15
|
+
private pulseReporter;
|
|
16
16
|
constructor();
|
|
17
|
-
private initializeSentry;
|
|
18
17
|
private initializeGlobalState;
|
|
19
18
|
private setupAutomaticWarmup;
|
|
19
|
+
private resolvePulseConfig;
|
|
20
|
+
private reportError;
|
|
20
21
|
private tryWarmup;
|
|
21
|
-
private
|
|
22
|
-
private fetchScript;
|
|
23
|
-
private executeScript;
|
|
22
|
+
private validateActionId;
|
|
24
23
|
private prefetchNextScript;
|
|
25
|
-
private waitForGetToken;
|
|
26
|
-
private createScriptBlob;
|
|
27
|
-
private loadScriptElement;
|
|
28
|
-
private getTokenFromScript;
|
|
29
|
-
private cleanup;
|
|
30
|
-
configure(params: DeflectConfig): void;
|
|
31
24
|
private isTestMode;
|
|
32
|
-
|
|
25
|
+
configure(params: DeflectConfig): void;
|
|
33
26
|
getToken(): Promise<string>;
|
|
27
|
+
private executeScript;
|
|
34
28
|
warmup(): Promise<boolean>;
|
|
35
29
|
clearCache(): void;
|
|
36
|
-
|
|
37
|
-
* Inject a fresh token as a hidden input into a form. Only accepts a submit event from onsubmit.
|
|
38
|
-
* Usage: <form ... onsubmit="return Deflect.injectToken(event)">
|
|
39
|
-
* Returns false to prevent double submit.
|
|
40
|
-
*/
|
|
41
|
-
injectToken(event: SubmitEvent): Promise<false>;
|
|
30
|
+
injectToken(event: SubmitEvent): Promise<void>;
|
|
42
31
|
}
|
|
43
32
|
declare const DeflectInstance: Deflect;
|
|
44
33
|
export default DeflectInstance;
|
package/dist/index.esm.js
CHANGED
|
@@ -1,58 +1,16 @@
|
|
|
1
|
-
import
|
|
1
|
+
import PulseReporter from "./pulse";
|
|
2
|
+
import { fetchScript } from "./scriptClient";
|
|
3
|
+
import { createBlobUrl, loadModuleScript, waitForGlobalFunction, getTokenFromGlobal, cleanup } from "./scriptRunner";
|
|
2
4
|
class Deflect {
|
|
3
5
|
constructor() {
|
|
4
6
|
this.config = null;
|
|
5
7
|
this.scriptCache = null;
|
|
6
8
|
this.isWarmupInProgress = false;
|
|
7
9
|
this.hasWarmupError = false;
|
|
8
|
-
this.initializeSentry();
|
|
9
10
|
this.initializeGlobalState();
|
|
11
|
+
this.pulseReporter = new PulseReporter(this.resolvePulseConfig());
|
|
10
12
|
this.setupAutomaticWarmup();
|
|
11
13
|
}
|
|
12
|
-
static {
|
|
13
|
-
this.SDK_ERROR_MARKER = "__DEFLECT_SDK_ERROR__";
|
|
14
|
-
}
|
|
15
|
-
initializeSentry() {
|
|
16
|
-
try {
|
|
17
|
-
Sentry.init({
|
|
18
|
-
dsn: "https://4a62ed75ab362d4694ae4215c2b4f621@o4509968565665792.ingest.de.sentry.io/4510257986469968",
|
|
19
|
-
sendDefaultPii: true,
|
|
20
|
-
// Enable to capture IP address
|
|
21
|
-
environment: typeof window !== "undefined" ? window.location.hostname : "unknown",
|
|
22
|
-
beforeSend(event) {
|
|
23
|
-
const isSDKError = event.tags?.deflect_sdk_error === "true";
|
|
24
|
-
if (!isSDKError) {
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
if (typeof window !== "undefined" && typeof navigator !== "undefined") {
|
|
28
|
-
event.contexts = event.contexts || {};
|
|
29
|
-
event.contexts.browser = {
|
|
30
|
-
name: navigator.userAgent,
|
|
31
|
-
version: navigator.appVersion
|
|
32
|
-
};
|
|
33
|
-
event.request = event.request || {};
|
|
34
|
-
event.request.headers = event.request.headers || {};
|
|
35
|
-
event.request.headers["User-Agent"] = navigator.userAgent;
|
|
36
|
-
event.contexts.page = {
|
|
37
|
-
url: window.location.href,
|
|
38
|
-
referrer: document.referrer,
|
|
39
|
-
title: document.title
|
|
40
|
-
};
|
|
41
|
-
event.contexts.device = {
|
|
42
|
-
screen_width: window.screen.width,
|
|
43
|
-
screen_height: window.screen.height,
|
|
44
|
-
viewport_width: window.innerWidth,
|
|
45
|
-
viewport_height: window.innerHeight,
|
|
46
|
-
language: navigator.language,
|
|
47
|
-
platform: navigator.platform
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
return event;
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
} catch {
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
14
|
initializeGlobalState() {
|
|
57
15
|
if (typeof window === "undefined") return;
|
|
58
16
|
window.Deflect = window.Deflect || {};
|
|
@@ -65,13 +23,24 @@ class Deflect {
|
|
|
65
23
|
setTimeout(() => this.tryWarmup(), 100);
|
|
66
24
|
}
|
|
67
25
|
}
|
|
26
|
+
resolvePulseConfig() {
|
|
27
|
+
const runtimeConfig = typeof window !== "undefined" && typeof window.Deflect === "object" && window.Deflect?.pulseConfig && typeof window.Deflect.pulseConfig === "object" ? window.Deflect.pulseConfig : {};
|
|
28
|
+
return {
|
|
29
|
+
...runtimeConfig,
|
|
30
|
+
environment: runtimeConfig.environment || (typeof window !== "undefined" ? window.location.hostname : void 0)
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
reportError(error, tags, context) {
|
|
34
|
+
this.pulseReporter.captureException(error, { tags, context });
|
|
35
|
+
}
|
|
68
36
|
async tryWarmup() {
|
|
69
37
|
if (!this.config?.actionId || this.isWarmupInProgress || this.scriptCache || this.hasWarmupError) {
|
|
70
38
|
return;
|
|
71
39
|
}
|
|
40
|
+
this.validateActionId(this.config.actionId);
|
|
72
41
|
this.isWarmupInProgress = true;
|
|
73
42
|
try {
|
|
74
|
-
this.scriptCache = await this.
|
|
43
|
+
this.scriptCache = await fetchScript(this.config.actionId, this.config.scriptUrl);
|
|
75
44
|
this.hasWarmupError = false;
|
|
76
45
|
} catch {
|
|
77
46
|
this.hasWarmupError = true;
|
|
@@ -79,56 +48,13 @@ class Deflect {
|
|
|
79
48
|
this.isWarmupInProgress = false;
|
|
80
49
|
}
|
|
81
50
|
}
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
async fetchScript() {
|
|
88
|
-
if (!this.config?.actionId) {
|
|
89
|
-
throw new Error("actionId is required");
|
|
90
|
-
}
|
|
91
|
-
const url = this.buildScriptUrl(this.config.actionId);
|
|
92
|
-
const response = await fetch(url, { cache: "no-store" });
|
|
93
|
-
if (!response.ok) {
|
|
94
|
-
throw new Error(`Failed to fetch script: ${response.status}`);
|
|
95
|
-
}
|
|
96
|
-
const content = await response.text();
|
|
97
|
-
try {
|
|
98
|
-
const maybeError = JSON.parse(content);
|
|
99
|
-
if (maybeError.success === false || maybeError.error) {
|
|
100
|
-
const errorMessage = maybeError.error || "Script fetch failed";
|
|
101
|
-
const error = new Error(errorMessage);
|
|
102
|
-
if (errorMessage === "action_does_not_exist" || errorMessage.includes("invalid action")) {
|
|
103
|
-
error.isUserError = true;
|
|
104
|
-
}
|
|
105
|
-
throw error;
|
|
106
|
-
}
|
|
107
|
-
} catch (e) {
|
|
108
|
-
if (e instanceof SyntaxError) {
|
|
109
|
-
} else {
|
|
110
|
-
throw e;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
return {
|
|
114
|
-
content,
|
|
115
|
-
sessionId: response.headers.get("session_id") || void 0
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
async executeScript(script) {
|
|
119
|
-
if (script.sessionId && typeof window !== "undefined") {
|
|
120
|
-
window.Deflect.sessionId = script.sessionId;
|
|
121
|
-
}
|
|
122
|
-
const blobUrl = this.createScriptBlob(script.content);
|
|
123
|
-
const scriptElement = await this.loadScriptElement(blobUrl);
|
|
124
|
-
try {
|
|
125
|
-
await this.waitForGetToken();
|
|
126
|
-
const token = await this.getTokenFromScript();
|
|
127
|
-
this.prefetchNextScript();
|
|
128
|
-
return token;
|
|
129
|
-
} finally {
|
|
130
|
-
this.cleanup(blobUrl, scriptElement);
|
|
51
|
+
validateActionId(actionId) {
|
|
52
|
+
const sanitized = actionId.trim();
|
|
53
|
+
const validPattern = /^[a-zA-Z0-9/_-]+$/;
|
|
54
|
+
if (!validPattern.test(sanitized)) {
|
|
55
|
+
throw new Error("Invalid actionId format: contains disallowed characters");
|
|
131
56
|
}
|
|
57
|
+
return encodeURIComponent(sanitized);
|
|
132
58
|
}
|
|
133
59
|
prefetchNextScript() {
|
|
134
60
|
if (!this.hasWarmupError) {
|
|
@@ -136,56 +62,18 @@ class Deflect {
|
|
|
136
62
|
});
|
|
137
63
|
}
|
|
138
64
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (typeof window !== "undefined" && typeof window.Deflect?.getToken === "function") {
|
|
143
|
-
clearInterval(checkInterval);
|
|
144
|
-
resolve();
|
|
145
|
-
}
|
|
146
|
-
}, 10);
|
|
147
|
-
setTimeout(() => {
|
|
148
|
-
clearInterval(checkInterval);
|
|
149
|
-
resolve();
|
|
150
|
-
}, 1e4);
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
createScriptBlob(content) {
|
|
154
|
-
const blob = new Blob([content], { type: "text/javascript" });
|
|
155
|
-
return URL.createObjectURL(blob);
|
|
156
|
-
}
|
|
157
|
-
async loadScriptElement(blobUrl) {
|
|
158
|
-
return new Promise((resolve, reject) => {
|
|
159
|
-
const script = document.createElement("script");
|
|
160
|
-
script.type = "module";
|
|
161
|
-
script.src = blobUrl;
|
|
162
|
-
script.onload = () => resolve(script);
|
|
163
|
-
script.onerror = () => reject(new Error("Script failed to load"));
|
|
164
|
-
document.head.appendChild(script);
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
async getTokenFromScript() {
|
|
168
|
-
if (typeof window === "undefined" || typeof window.Deflect?.getToken !== "function") {
|
|
169
|
-
throw new Error("Script did not load properly - getToken not available");
|
|
170
|
-
}
|
|
171
|
-
try {
|
|
172
|
-
return await window.Deflect.getToken();
|
|
173
|
-
} catch (error) {
|
|
174
|
-
throw new Error(`Script execution failed: ${error}`);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
cleanup(blobUrl, scriptElement) {
|
|
178
|
-
URL.revokeObjectURL(blobUrl);
|
|
179
|
-
scriptElement.remove();
|
|
180
|
-
if (typeof window !== "undefined" && window.Deflect?.getToken) {
|
|
181
|
-
delete window.Deflect.getToken;
|
|
65
|
+
isTestMode() {
|
|
66
|
+
if (this.config?.actionId === "PULSE_TEST" || this.config?.actionId === "SENTRY_TEST") {
|
|
67
|
+
throw new Error("PULSE_TEST: This is a test error to verify Pulse integration is working");
|
|
182
68
|
}
|
|
69
|
+
return this.config?.actionId === "t/FFFFFFFFFFFFF/111111111" || this.config?.actionId === "t/FFFFFFFFFFFFF/000000000";
|
|
183
70
|
}
|
|
184
71
|
configure(params) {
|
|
185
72
|
try {
|
|
186
73
|
if (!params.actionId?.trim()) {
|
|
187
74
|
throw new Error("actionId is required and cannot be empty");
|
|
188
75
|
}
|
|
76
|
+
this.validateActionId(params.actionId);
|
|
189
77
|
if (this.config?.actionId === params.actionId) {
|
|
190
78
|
return;
|
|
191
79
|
}
|
|
@@ -200,58 +88,95 @@ class Deflect {
|
|
|
200
88
|
}
|
|
201
89
|
} catch (error) {
|
|
202
90
|
const isUserError = error && typeof error === "object" && "isUserError" in error && error.isUserError === true;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
|
|
91
|
+
this.reportError(
|
|
92
|
+
error,
|
|
93
|
+
{
|
|
94
|
+
deflect_sdk_error: "true",
|
|
95
|
+
deflect_user_error: isUserError ? "true" : "false",
|
|
96
|
+
method: "configure",
|
|
97
|
+
action_id: params?.actionId || "unknown"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
actionId: params?.actionId,
|
|
101
|
+
hasCache: this.scriptCache !== null,
|
|
102
|
+
hasWarmupError: this.hasWarmupError
|
|
103
|
+
}
|
|
104
|
+
);
|
|
212
105
|
throw error;
|
|
213
106
|
}
|
|
214
107
|
}
|
|
215
|
-
isTestMode() {
|
|
216
|
-
return this.config?.actionId === "t/FFFFFFFFFFFFF/111111111" || this.config?.actionId === "t/FFFFFFFFFFFFF/000000000";
|
|
217
|
-
}
|
|
218
|
-
// Deprecated name kept for backward compatibility
|
|
219
|
-
async solveChallenge() {
|
|
220
|
-
return this.getToken();
|
|
221
|
-
}
|
|
222
108
|
async getToken() {
|
|
223
109
|
try {
|
|
224
110
|
if (!this.config?.actionId) {
|
|
225
|
-
throw new Error("Must call configure() before
|
|
111
|
+
throw new Error("Must call configure() before getToken()");
|
|
226
112
|
}
|
|
113
|
+
this.validateActionId(this.config.actionId);
|
|
227
114
|
if (this.isTestMode()) {
|
|
228
115
|
return "TESTTOKEN";
|
|
229
116
|
}
|
|
230
|
-
if (this.config.actionId === "SENTRY_TEST") {
|
|
231
|
-
throw new Error("SENTRY_TEST: This is a test error to verify Sentry integration is working");
|
|
232
|
-
}
|
|
233
117
|
let script;
|
|
234
118
|
if (this.scriptCache && !this.isWarmupInProgress) {
|
|
235
119
|
script = this.scriptCache;
|
|
236
120
|
this.scriptCache = null;
|
|
237
121
|
} else {
|
|
238
|
-
script = await this.
|
|
122
|
+
script = await fetchScript(this.config.actionId, this.config.scriptUrl);
|
|
239
123
|
}
|
|
240
124
|
return this.executeScript(script);
|
|
241
125
|
} catch (error) {
|
|
242
126
|
const isUserError = error && typeof error === "object" && "isUserError" in error && error.isUserError === true;
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
127
|
+
this.reportError(
|
|
128
|
+
error,
|
|
129
|
+
{
|
|
130
|
+
deflect_sdk_error: "true",
|
|
131
|
+
deflect_user_error: isUserError ? "true" : "false",
|
|
132
|
+
method: "getToken",
|
|
133
|
+
action_id: this.config?.actionId || "unknown",
|
|
134
|
+
has_cache: this.scriptCache !== null ? "true" : "false",
|
|
135
|
+
has_warmup_error: this.hasWarmupError ? "true" : "false",
|
|
136
|
+
stage: "call"
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
actionId: this.config?.actionId,
|
|
140
|
+
hasCache: this.scriptCache !== null,
|
|
141
|
+
hasWarmupError: this.hasWarmupError
|
|
142
|
+
}
|
|
143
|
+
);
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async executeScript(script) {
|
|
148
|
+
if (script.sessionId && typeof window !== "undefined") {
|
|
149
|
+
window.Deflect.sessionId = script.sessionId;
|
|
150
|
+
}
|
|
151
|
+
const blobUrl = createBlobUrl(script.content);
|
|
152
|
+
let scriptElement = null;
|
|
153
|
+
try {
|
|
154
|
+
scriptElement = await loadModuleScript(blobUrl);
|
|
155
|
+
await waitForGlobalFunction("getChallengeToken");
|
|
156
|
+
const token = await getTokenFromGlobal("getChallengeToken");
|
|
157
|
+
this.prefetchNextScript();
|
|
158
|
+
return token;
|
|
159
|
+
} catch (error) {
|
|
160
|
+
this.reportError(
|
|
161
|
+
error,
|
|
162
|
+
{
|
|
163
|
+
deflect_sdk_error: "true",
|
|
164
|
+
method: "executeScript",
|
|
165
|
+
stage: "load_or_execute",
|
|
166
|
+
action_id: this.config?.actionId || "unknown"
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
hasCache: this.scriptCache !== null,
|
|
170
|
+
hasWarmupError: this.hasWarmupError
|
|
171
|
+
}
|
|
172
|
+
);
|
|
254
173
|
throw error;
|
|
174
|
+
} finally {
|
|
175
|
+
if (scriptElement) {
|
|
176
|
+
cleanup(blobUrl, scriptElement, "getChallengeToken");
|
|
177
|
+
} else {
|
|
178
|
+
URL.revokeObjectURL(blobUrl);
|
|
179
|
+
}
|
|
255
180
|
}
|
|
256
181
|
}
|
|
257
182
|
async warmup() {
|
|
@@ -268,11 +193,6 @@ class Deflect {
|
|
|
268
193
|
clearCache() {
|
|
269
194
|
this.scriptCache = null;
|
|
270
195
|
}
|
|
271
|
-
/**
|
|
272
|
-
* Inject a fresh token as a hidden input into a form. Only accepts a submit event from onsubmit.
|
|
273
|
-
* Usage: <form ... onsubmit="return Deflect.injectToken(event)">
|
|
274
|
-
* Returns false to prevent double submit.
|
|
275
|
-
*/
|
|
276
196
|
async injectToken(event) {
|
|
277
197
|
if (!event || !event.target || !(event.target instanceof HTMLFormElement)) {
|
|
278
198
|
throw new Error("injectToken: must be called from a form submit event");
|
|
@@ -289,7 +209,6 @@ class Deflect {
|
|
|
289
209
|
hidden.value = token;
|
|
290
210
|
form.appendChild(hidden);
|
|
291
211
|
form.submit();
|
|
292
|
-
return false;
|
|
293
212
|
}
|
|
294
213
|
}
|
|
295
214
|
const DeflectInstance = new Deflect();
|