@deflectbot/deflect-sdk 1.4.0 → 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 +2 -82
- package/dist/index.esm.js +47 -218
- package/dist/index.js +136 -218
- package/dist/index.min.js +2 -2
- 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
|
@@ -19,94 +19,14 @@ declare class Deflect {
|
|
|
19
19
|
private resolvePulseConfig;
|
|
20
20
|
private reportError;
|
|
21
21
|
private tryWarmup;
|
|
22
|
-
/**
|
|
23
|
-
* @param actionId - The action ID to validate
|
|
24
|
-
* @returns The sanitized action ID
|
|
25
|
-
* @throws Error if actionId contains invalid characters
|
|
26
|
-
*/
|
|
27
22
|
private validateActionId;
|
|
28
|
-
private buildScriptUrl;
|
|
29
|
-
private fetchScript;
|
|
30
|
-
private executeScript;
|
|
31
23
|
private prefetchNextScript;
|
|
32
|
-
private waitForGetToken;
|
|
33
|
-
private createScriptBlob;
|
|
34
|
-
private loadScriptElement;
|
|
35
|
-
private getTokenFromScript;
|
|
36
|
-
private cleanup;
|
|
37
|
-
/**
|
|
38
|
-
* Configures the Deflect SDK with the provided parameters.
|
|
39
|
-
* Must be called before using getToken() or other SDK methods.
|
|
40
|
-
*
|
|
41
|
-
* @param params - Configuration options for the SDK
|
|
42
|
-
* @param params.actionId - The unique action identifier from your Deflect dashboard (required)
|
|
43
|
-
* @param params.scriptUrl - Optional custom script URL (defaults to Deflect CDN)
|
|
44
|
-
* @throws Error if actionId is empty or contains invalid characters
|
|
45
|
-
*
|
|
46
|
-
* @example
|
|
47
|
-
* ```typescript
|
|
48
|
-
* Deflect.configure({ actionId: "your-action-id" });
|
|
49
|
-
* ```
|
|
50
|
-
*/
|
|
51
|
-
configure(params: DeflectConfig): void;
|
|
52
24
|
private isTestMode;
|
|
53
|
-
|
|
54
|
-
* @deprecated Use {@link getToken} instead. This method is kept for backward compatibility.
|
|
55
|
-
* @returns A promise that resolves to the Deflect token string
|
|
56
|
-
*/
|
|
57
|
-
solveChallenge(): Promise<string>;
|
|
58
|
-
/**
|
|
59
|
-
* Retrieves a Deflect token for the configured action.
|
|
60
|
-
* The token should be included in requests to your protected endpoints.
|
|
61
|
-
*
|
|
62
|
-
* @returns A promise that resolves to the Deflect token string
|
|
63
|
-
* @throws Error if configure() has not been called first
|
|
64
|
-
* @throws Error if the script fails to load or execute
|
|
65
|
-
*
|
|
66
|
-
* @example
|
|
67
|
-
* ```typescript
|
|
68
|
-
* const token = await Deflect.getToken();
|
|
69
|
-
* // Include token in your API request
|
|
70
|
-
* fetch('/api/protected', {
|
|
71
|
-
* headers: { 'X-Deflect-Token': token }
|
|
72
|
-
* });
|
|
73
|
-
* ```
|
|
74
|
-
*/
|
|
25
|
+
configure(params: DeflectConfig): void;
|
|
75
26
|
getToken(): Promise<string>;
|
|
76
|
-
|
|
77
|
-
* Pre-fetches the challenge script to reduce latency on the first getToken() call.
|
|
78
|
-
* This is automatically called after configure(), but can be manually triggered.
|
|
79
|
-
*
|
|
80
|
-
* @returns A promise that resolves to true if warmup succeeded, false otherwise
|
|
81
|
-
*
|
|
82
|
-
* @example
|
|
83
|
-
* ```typescript
|
|
84
|
-
* Deflect.configure({ actionId: "your-action-id" });
|
|
85
|
-
* const warmedUp = await Deflect.warmup();
|
|
86
|
-
* console.log('Script pre-cached:', warmedUp);
|
|
87
|
-
* ```
|
|
88
|
-
*/
|
|
27
|
+
private executeScript;
|
|
89
28
|
warmup(): Promise<boolean>;
|
|
90
|
-
/**
|
|
91
|
-
* Clears the cached challenge script.
|
|
92
|
-
* Useful when you need to force a fresh script fetch on the next getToken() call.
|
|
93
|
-
*/
|
|
94
29
|
clearCache(): void;
|
|
95
|
-
/**
|
|
96
|
-
* Injects a Deflect token as a hidden input into a form and submits it.
|
|
97
|
-
* Designed for use with form onsubmit handlers.
|
|
98
|
-
*
|
|
99
|
-
* @param event - The form submit event
|
|
100
|
-
* @throws Error if not called from a form submit event
|
|
101
|
-
*
|
|
102
|
-
* @example
|
|
103
|
-
* ```html
|
|
104
|
-
* <form action="/login" method="POST" onsubmit="Deflect.injectToken(event)">
|
|
105
|
-
* <input name="username" />
|
|
106
|
-
* <button type="submit">Login</button>
|
|
107
|
-
* </form>
|
|
108
|
-
* ```
|
|
109
|
-
*/
|
|
110
30
|
injectToken(event: SubmitEvent): Promise<void>;
|
|
111
31
|
}
|
|
112
32
|
declare const DeflectInstance: Deflect;
|
package/dist/index.esm.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
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;
|
|
@@ -35,9 +37,10 @@ class Deflect {
|
|
|
35
37
|
if (!this.config?.actionId || this.isWarmupInProgress || this.scriptCache || this.hasWarmupError) {
|
|
36
38
|
return;
|
|
37
39
|
}
|
|
40
|
+
this.validateActionId(this.config.actionId);
|
|
38
41
|
this.isWarmupInProgress = true;
|
|
39
42
|
try {
|
|
40
|
-
this.scriptCache = await this.
|
|
43
|
+
this.scriptCache = await fetchScript(this.config.actionId, this.config.scriptUrl);
|
|
41
44
|
this.hasWarmupError = false;
|
|
42
45
|
} catch {
|
|
43
46
|
this.hasWarmupError = true;
|
|
@@ -45,11 +48,6 @@ class Deflect {
|
|
|
45
48
|
this.isWarmupInProgress = false;
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
|
-
/**
|
|
49
|
-
* @param actionId - The action ID to validate
|
|
50
|
-
* @returns The sanitized action ID
|
|
51
|
-
* @throws Error if actionId contains invalid characters
|
|
52
|
-
*/
|
|
53
51
|
validateActionId(actionId) {
|
|
54
52
|
const sanitized = actionId.trim();
|
|
55
53
|
const validPattern = /^[a-zA-Z0-9/_-]+$/;
|
|
@@ -58,167 +56,24 @@ class Deflect {
|
|
|
58
56
|
}
|
|
59
57
|
return encodeURIComponent(sanitized);
|
|
60
58
|
}
|
|
61
|
-
buildScriptUrl(actionId) {
|
|
62
|
-
const baseUrl = this.config?.scriptUrl || "https://js.deflect.bot/main.js";
|
|
63
|
-
const sanitizedActionId = this.validateActionId(actionId);
|
|
64
|
-
const nonce = Date.now().toString();
|
|
65
|
-
return `${baseUrl}?action_id=${sanitizedActionId}&_=${nonce}`;
|
|
66
|
-
}
|
|
67
|
-
async fetchScript() {
|
|
68
|
-
if (!this.config?.actionId) {
|
|
69
|
-
throw new Error("actionId is required");
|
|
70
|
-
}
|
|
71
|
-
const url = this.buildScriptUrl(this.config.actionId);
|
|
72
|
-
try {
|
|
73
|
-
const response = await fetch(url, {
|
|
74
|
-
cache: "no-store",
|
|
75
|
-
mode: "cors",
|
|
76
|
-
credentials: "omit"
|
|
77
|
-
});
|
|
78
|
-
if (!response.ok) {
|
|
79
|
-
throw new Error(`Failed to fetch script: ${response.status}`);
|
|
80
|
-
}
|
|
81
|
-
const content = await response.text();
|
|
82
|
-
try {
|
|
83
|
-
const maybeError = JSON.parse(content);
|
|
84
|
-
if (maybeError.success === false || maybeError.error) {
|
|
85
|
-
const errorMessage = maybeError.error || "Script fetch failed";
|
|
86
|
-
const error = new Error(errorMessage);
|
|
87
|
-
if (errorMessage === "action_does_not_exist" || errorMessage.includes("invalid action")) {
|
|
88
|
-
error.isUserError = true;
|
|
89
|
-
}
|
|
90
|
-
throw error;
|
|
91
|
-
}
|
|
92
|
-
} catch (e) {
|
|
93
|
-
if (!(e instanceof SyntaxError)) {
|
|
94
|
-
throw e;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return {
|
|
98
|
-
content,
|
|
99
|
-
sessionId: response.headers.get("session_id") || void 0
|
|
100
|
-
};
|
|
101
|
-
} catch (error) {
|
|
102
|
-
this.reportError(
|
|
103
|
-
error,
|
|
104
|
-
{
|
|
105
|
-
deflect_sdk_error: "true",
|
|
106
|
-
method: "fetchScript",
|
|
107
|
-
action_id: this.config.actionId
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
actionId: this.config.actionId,
|
|
111
|
-
url
|
|
112
|
-
}
|
|
113
|
-
);
|
|
114
|
-
throw error;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
async executeScript(script) {
|
|
118
|
-
if (script.sessionId && typeof window !== "undefined") {
|
|
119
|
-
window.Deflect.sessionId = script.sessionId;
|
|
120
|
-
}
|
|
121
|
-
const blobUrl = this.createScriptBlob(script.content);
|
|
122
|
-
let scriptElement = null;
|
|
123
|
-
try {
|
|
124
|
-
scriptElement = await this.loadScriptElement(blobUrl);
|
|
125
|
-
await this.waitForGetToken();
|
|
126
|
-
const token = await this.getTokenFromScript();
|
|
127
|
-
this.prefetchNextScript();
|
|
128
|
-
return token;
|
|
129
|
-
} catch (error) {
|
|
130
|
-
this.reportError(
|
|
131
|
-
error,
|
|
132
|
-
{
|
|
133
|
-
deflect_sdk_error: "true",
|
|
134
|
-
method: "executeScript",
|
|
135
|
-
stage: "load_or_execute",
|
|
136
|
-
action_id: this.config?.actionId || "unknown"
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
hasCache: this.scriptCache !== null,
|
|
140
|
-
hasWarmupError: this.hasWarmupError
|
|
141
|
-
}
|
|
142
|
-
);
|
|
143
|
-
throw error;
|
|
144
|
-
} finally {
|
|
145
|
-
if (scriptElement) {
|
|
146
|
-
this.cleanup(blobUrl, scriptElement);
|
|
147
|
-
} else {
|
|
148
|
-
URL.revokeObjectURL(blobUrl);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
59
|
prefetchNextScript() {
|
|
153
60
|
if (!this.hasWarmupError) {
|
|
154
61
|
this.tryWarmup().catch(() => {
|
|
155
62
|
});
|
|
156
63
|
}
|
|
157
64
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (typeof window !== "undefined" && typeof window.Deflect?.getToken === "function") {
|
|
162
|
-
clearInterval(checkInterval);
|
|
163
|
-
resolve();
|
|
164
|
-
}
|
|
165
|
-
}, 50);
|
|
166
|
-
setTimeout(() => {
|
|
167
|
-
clearInterval(checkInterval);
|
|
168
|
-
reject(new Error("Timeout: getToken function did not become available within 10 seconds"));
|
|
169
|
-
}, 1e4);
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
createScriptBlob(content) {
|
|
173
|
-
const blob = new Blob([content], { type: "text/javascript" });
|
|
174
|
-
return URL.createObjectURL(blob);
|
|
175
|
-
}
|
|
176
|
-
async loadScriptElement(blobUrl) {
|
|
177
|
-
return new Promise((resolve, reject) => {
|
|
178
|
-
const script = document.createElement("script");
|
|
179
|
-
script.type = "module";
|
|
180
|
-
script.src = blobUrl;
|
|
181
|
-
script.onload = () => resolve(script);
|
|
182
|
-
script.onerror = () => reject(new Error("Script failed to load"));
|
|
183
|
-
document.head.appendChild(script);
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
async getTokenFromScript() {
|
|
187
|
-
if (typeof window === "undefined" || typeof window.Deflect?.getToken !== "function") {
|
|
188
|
-
throw new Error("Script did not load properly - getToken not available");
|
|
189
|
-
}
|
|
190
|
-
try {
|
|
191
|
-
return await window.Deflect.getToken();
|
|
192
|
-
} catch (error) {
|
|
193
|
-
throw new Error(`Script execution failed: ${error}`);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
cleanup(blobUrl, scriptElement) {
|
|
197
|
-
URL.revokeObjectURL(blobUrl);
|
|
198
|
-
scriptElement.remove();
|
|
199
|
-
if (typeof window !== "undefined" && window.Deflect?.getToken) {
|
|
200
|
-
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");
|
|
201
68
|
}
|
|
69
|
+
return this.config?.actionId === "t/FFFFFFFFFFFFF/111111111" || this.config?.actionId === "t/FFFFFFFFFFFFF/000000000";
|
|
202
70
|
}
|
|
203
|
-
/**
|
|
204
|
-
* Configures the Deflect SDK with the provided parameters.
|
|
205
|
-
* Must be called before using getToken() or other SDK methods.
|
|
206
|
-
*
|
|
207
|
-
* @param params - Configuration options for the SDK
|
|
208
|
-
* @param params.actionId - The unique action identifier from your Deflect dashboard (required)
|
|
209
|
-
* @param params.scriptUrl - Optional custom script URL (defaults to Deflect CDN)
|
|
210
|
-
* @throws Error if actionId is empty or contains invalid characters
|
|
211
|
-
*
|
|
212
|
-
* @example
|
|
213
|
-
* ```typescript
|
|
214
|
-
* Deflect.configure({ actionId: "your-action-id" });
|
|
215
|
-
* ```
|
|
216
|
-
*/
|
|
217
71
|
configure(params) {
|
|
218
72
|
try {
|
|
219
73
|
if (!params.actionId?.trim()) {
|
|
220
74
|
throw new Error("actionId is required and cannot be empty");
|
|
221
75
|
}
|
|
76
|
+
this.validateActionId(params.actionId);
|
|
222
77
|
if (this.config?.actionId === params.actionId) {
|
|
223
78
|
return;
|
|
224
79
|
}
|
|
@@ -250,41 +105,12 @@ class Deflect {
|
|
|
250
105
|
throw error;
|
|
251
106
|
}
|
|
252
107
|
}
|
|
253
|
-
isTestMode() {
|
|
254
|
-
if (this.config?.actionId === "PULSE_TEST" || this.config?.actionId === "SENTRY_TEST") {
|
|
255
|
-
throw new Error("PULSE_TEST: This is a test error to verify Pulse integration is working");
|
|
256
|
-
}
|
|
257
|
-
return this.config?.actionId === "t/FFFFFFFFFFFFF/111111111" || this.config?.actionId === "t/FFFFFFFFFFFFF/000000000";
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* @deprecated Use {@link getToken} instead. This method is kept for backward compatibility.
|
|
261
|
-
* @returns A promise that resolves to the Deflect token string
|
|
262
|
-
*/
|
|
263
|
-
async solveChallenge() {
|
|
264
|
-
return this.getToken();
|
|
265
|
-
}
|
|
266
|
-
/**
|
|
267
|
-
* Retrieves a Deflect token for the configured action.
|
|
268
|
-
* The token should be included in requests to your protected endpoints.
|
|
269
|
-
*
|
|
270
|
-
* @returns A promise that resolves to the Deflect token string
|
|
271
|
-
* @throws Error if configure() has not been called first
|
|
272
|
-
* @throws Error if the script fails to load or execute
|
|
273
|
-
*
|
|
274
|
-
* @example
|
|
275
|
-
* ```typescript
|
|
276
|
-
* const token = await Deflect.getToken();
|
|
277
|
-
* // Include token in your API request
|
|
278
|
-
* fetch('/api/protected', {
|
|
279
|
-
* headers: { 'X-Deflect-Token': token }
|
|
280
|
-
* });
|
|
281
|
-
* ```
|
|
282
|
-
*/
|
|
283
108
|
async getToken() {
|
|
284
109
|
try {
|
|
285
110
|
if (!this.config?.actionId) {
|
|
286
|
-
throw new Error("Must call configure() before
|
|
111
|
+
throw new Error("Must call configure() before getToken()");
|
|
287
112
|
}
|
|
113
|
+
this.validateActionId(this.config.actionId);
|
|
288
114
|
if (this.isTestMode()) {
|
|
289
115
|
return "TESTTOKEN";
|
|
290
116
|
}
|
|
@@ -293,7 +119,7 @@ class Deflect {
|
|
|
293
119
|
script = this.scriptCache;
|
|
294
120
|
this.scriptCache = null;
|
|
295
121
|
} else {
|
|
296
|
-
script = await this.
|
|
122
|
+
script = await fetchScript(this.config.actionId, this.config.scriptUrl);
|
|
297
123
|
}
|
|
298
124
|
return this.executeScript(script);
|
|
299
125
|
} catch (error) {
|
|
@@ -318,19 +144,41 @@ class Deflect {
|
|
|
318
144
|
throw error;
|
|
319
145
|
}
|
|
320
146
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
+
);
|
|
173
|
+
throw error;
|
|
174
|
+
} finally {
|
|
175
|
+
if (scriptElement) {
|
|
176
|
+
cleanup(blobUrl, scriptElement, "getChallengeToken");
|
|
177
|
+
} else {
|
|
178
|
+
URL.revokeObjectURL(blobUrl);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
334
182
|
async warmup() {
|
|
335
183
|
if (!this.config?.actionId) {
|
|
336
184
|
return false;
|
|
@@ -342,28 +190,9 @@ class Deflect {
|
|
|
342
190
|
return false;
|
|
343
191
|
}
|
|
344
192
|
}
|
|
345
|
-
/**
|
|
346
|
-
* Clears the cached challenge script.
|
|
347
|
-
* Useful when you need to force a fresh script fetch on the next getToken() call.
|
|
348
|
-
*/
|
|
349
193
|
clearCache() {
|
|
350
194
|
this.scriptCache = null;
|
|
351
195
|
}
|
|
352
|
-
/**
|
|
353
|
-
* Injects a Deflect token as a hidden input into a form and submits it.
|
|
354
|
-
* Designed for use with form onsubmit handlers.
|
|
355
|
-
*
|
|
356
|
-
* @param event - The form submit event
|
|
357
|
-
* @throws Error if not called from a form submit event
|
|
358
|
-
*
|
|
359
|
-
* @example
|
|
360
|
-
* ```html
|
|
361
|
-
* <form action="/login" method="POST" onsubmit="Deflect.injectToken(event)">
|
|
362
|
-
* <input name="username" />
|
|
363
|
-
* <button type="submit">Login</button>
|
|
364
|
-
* </form>
|
|
365
|
-
* ```
|
|
366
|
-
*/
|
|
367
196
|
async injectToken(event) {
|
|
368
197
|
if (!event || !event.target || !(event.target instanceof HTMLFormElement)) {
|
|
369
198
|
throw new Error("injectToken: must be called from a form submit event");
|
package/dist/index.js
CHANGED
|
@@ -142,6 +142,97 @@
|
|
|
142
142
|
};
|
|
143
143
|
var pulse_default = PulseReporter;
|
|
144
144
|
|
|
145
|
+
// src/scriptClient.ts
|
|
146
|
+
function buildScriptUrl(actionId, scriptUrl) {
|
|
147
|
+
const baseUrl = scriptUrl || "https://js.deflect.bot/main.js";
|
|
148
|
+
const nonce = Date.now().toString();
|
|
149
|
+
return `${baseUrl}?action_id=${encodeURIComponent(actionId)}&_=${nonce}`;
|
|
150
|
+
}
|
|
151
|
+
__name(buildScriptUrl, "buildScriptUrl");
|
|
152
|
+
async function fetchScript(actionId, scriptUrl) {
|
|
153
|
+
const url = buildScriptUrl(actionId, scriptUrl);
|
|
154
|
+
const response = await fetch(url, {
|
|
155
|
+
cache: "no-store",
|
|
156
|
+
mode: "cors",
|
|
157
|
+
credentials: "omit"
|
|
158
|
+
});
|
|
159
|
+
const content = await response.text();
|
|
160
|
+
const error = parseErrorJson(content);
|
|
161
|
+
if (error) throw error;
|
|
162
|
+
return {
|
|
163
|
+
content,
|
|
164
|
+
sessionId: response.headers.get("session_id") || void 0
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
__name(fetchScript, "fetchScript");
|
|
168
|
+
function parseErrorJson(content) {
|
|
169
|
+
try {
|
|
170
|
+
const maybeError = JSON.parse(content);
|
|
171
|
+
if (maybeError.success === false || maybeError.error) {
|
|
172
|
+
const errorMessage = maybeError.error || "Script fetch failed";
|
|
173
|
+
const error = new Error(errorMessage);
|
|
174
|
+
if (errorMessage === "action_does_not_exist" || errorMessage.includes("invalid action")) {
|
|
175
|
+
error.isUserError = true;
|
|
176
|
+
}
|
|
177
|
+
return error;
|
|
178
|
+
}
|
|
179
|
+
} catch (err) {
|
|
180
|
+
if (!(err instanceof SyntaxError)) {
|
|
181
|
+
throw err;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
__name(parseErrorJson, "parseErrorJson");
|
|
187
|
+
|
|
188
|
+
// src/scriptRunner.ts
|
|
189
|
+
function createBlobUrl(content) {
|
|
190
|
+
const blob = new Blob([content], { type: "text/javascript" });
|
|
191
|
+
return URL.createObjectURL(blob);
|
|
192
|
+
}
|
|
193
|
+
__name(createBlobUrl, "createBlobUrl");
|
|
194
|
+
function loadModuleScript(blobUrl) {
|
|
195
|
+
return new Promise((resolve, reject) => {
|
|
196
|
+
const script = document.createElement("script");
|
|
197
|
+
script.type = "module";
|
|
198
|
+
script.src = blobUrl;
|
|
199
|
+
script.onload = () => resolve(script);
|
|
200
|
+
script.onerror = () => reject(new Error("Script failed to load"));
|
|
201
|
+
document.head.appendChild(script);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
__name(loadModuleScript, "loadModuleScript");
|
|
205
|
+
function waitForGlobalFunction(fnName, timeout = 1e4) {
|
|
206
|
+
return new Promise((resolve, reject) => {
|
|
207
|
+
const checkInterval = setInterval(() => {
|
|
208
|
+
if (typeof window !== "undefined" && typeof window.Deflect?.[fnName] === "function") {
|
|
209
|
+
clearInterval(checkInterval);
|
|
210
|
+
resolve();
|
|
211
|
+
}
|
|
212
|
+
}, 50);
|
|
213
|
+
setTimeout(() => {
|
|
214
|
+
clearInterval(checkInterval);
|
|
215
|
+
reject(new Error(`Timeout: ${fnName} did not become available within ${timeout / 1e3} seconds`));
|
|
216
|
+
}, timeout);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
__name(waitForGlobalFunction, "waitForGlobalFunction");
|
|
220
|
+
async function getTokenFromGlobal(fnName) {
|
|
221
|
+
if (typeof window === "undefined" || typeof window.Deflect?.[fnName] !== "function") {
|
|
222
|
+
throw new Error(`${fnName} not available on window.Deflect`);
|
|
223
|
+
}
|
|
224
|
+
return window.Deflect[fnName]();
|
|
225
|
+
}
|
|
226
|
+
__name(getTokenFromGlobal, "getTokenFromGlobal");
|
|
227
|
+
function cleanup(blobUrl, scriptElement, fnName) {
|
|
228
|
+
URL.revokeObjectURL(blobUrl);
|
|
229
|
+
scriptElement.remove();
|
|
230
|
+
if (typeof window !== "undefined" && window.Deflect?.[fnName]) {
|
|
231
|
+
delete window.Deflect[fnName];
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
__name(cleanup, "cleanup");
|
|
235
|
+
|
|
145
236
|
// src/index.ts
|
|
146
237
|
var Deflect = class {
|
|
147
238
|
constructor() {
|
|
@@ -182,9 +273,10 @@
|
|
|
182
273
|
if (!this.config?.actionId || this.isWarmupInProgress || this.scriptCache || this.hasWarmupError) {
|
|
183
274
|
return;
|
|
184
275
|
}
|
|
276
|
+
this.validateActionId(this.config.actionId);
|
|
185
277
|
this.isWarmupInProgress = true;
|
|
186
278
|
try {
|
|
187
|
-
this.scriptCache = await this.
|
|
279
|
+
this.scriptCache = await fetchScript(this.config.actionId, this.config.scriptUrl);
|
|
188
280
|
this.hasWarmupError = false;
|
|
189
281
|
} catch {
|
|
190
282
|
this.hasWarmupError = true;
|
|
@@ -192,11 +284,6 @@
|
|
|
192
284
|
this.isWarmupInProgress = false;
|
|
193
285
|
}
|
|
194
286
|
}
|
|
195
|
-
/**
|
|
196
|
-
* @param actionId - The action ID to validate
|
|
197
|
-
* @returns The sanitized action ID
|
|
198
|
-
* @throws Error if actionId contains invalid characters
|
|
199
|
-
*/
|
|
200
287
|
validateActionId(actionId) {
|
|
201
288
|
const sanitized = actionId.trim();
|
|
202
289
|
const validPattern = /^[a-zA-Z0-9/_-]+$/;
|
|
@@ -205,167 +292,24 @@
|
|
|
205
292
|
}
|
|
206
293
|
return encodeURIComponent(sanitized);
|
|
207
294
|
}
|
|
208
|
-
buildScriptUrl(actionId) {
|
|
209
|
-
const baseUrl = this.config?.scriptUrl || "https://js.deflect.bot/main.js";
|
|
210
|
-
const sanitizedActionId = this.validateActionId(actionId);
|
|
211
|
-
const nonce = Date.now().toString();
|
|
212
|
-
return `${baseUrl}?action_id=${sanitizedActionId}&_=${nonce}`;
|
|
213
|
-
}
|
|
214
|
-
async fetchScript() {
|
|
215
|
-
if (!this.config?.actionId) {
|
|
216
|
-
throw new Error("actionId is required");
|
|
217
|
-
}
|
|
218
|
-
const url = this.buildScriptUrl(this.config.actionId);
|
|
219
|
-
try {
|
|
220
|
-
const response = await fetch(url, {
|
|
221
|
-
cache: "no-store",
|
|
222
|
-
mode: "cors",
|
|
223
|
-
credentials: "omit"
|
|
224
|
-
});
|
|
225
|
-
if (!response.ok) {
|
|
226
|
-
throw new Error(`Failed to fetch script: ${response.status}`);
|
|
227
|
-
}
|
|
228
|
-
const content = await response.text();
|
|
229
|
-
try {
|
|
230
|
-
const maybeError = JSON.parse(content);
|
|
231
|
-
if (maybeError.success === false || maybeError.error) {
|
|
232
|
-
const errorMessage = maybeError.error || "Script fetch failed";
|
|
233
|
-
const error = new Error(errorMessage);
|
|
234
|
-
if (errorMessage === "action_does_not_exist" || errorMessage.includes("invalid action")) {
|
|
235
|
-
error.isUserError = true;
|
|
236
|
-
}
|
|
237
|
-
throw error;
|
|
238
|
-
}
|
|
239
|
-
} catch (e) {
|
|
240
|
-
if (!(e instanceof SyntaxError)) {
|
|
241
|
-
throw e;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
return {
|
|
245
|
-
content,
|
|
246
|
-
sessionId: response.headers.get("session_id") || void 0
|
|
247
|
-
};
|
|
248
|
-
} catch (error) {
|
|
249
|
-
this.reportError(
|
|
250
|
-
error,
|
|
251
|
-
{
|
|
252
|
-
deflect_sdk_error: "true",
|
|
253
|
-
method: "fetchScript",
|
|
254
|
-
action_id: this.config.actionId
|
|
255
|
-
},
|
|
256
|
-
{
|
|
257
|
-
actionId: this.config.actionId,
|
|
258
|
-
url
|
|
259
|
-
}
|
|
260
|
-
);
|
|
261
|
-
throw error;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
async executeScript(script) {
|
|
265
|
-
if (script.sessionId && typeof window !== "undefined") {
|
|
266
|
-
window.Deflect.sessionId = script.sessionId;
|
|
267
|
-
}
|
|
268
|
-
const blobUrl = this.createScriptBlob(script.content);
|
|
269
|
-
let scriptElement = null;
|
|
270
|
-
try {
|
|
271
|
-
scriptElement = await this.loadScriptElement(blobUrl);
|
|
272
|
-
await this.waitForGetToken();
|
|
273
|
-
const token = await this.getTokenFromScript();
|
|
274
|
-
this.prefetchNextScript();
|
|
275
|
-
return token;
|
|
276
|
-
} catch (error) {
|
|
277
|
-
this.reportError(
|
|
278
|
-
error,
|
|
279
|
-
{
|
|
280
|
-
deflect_sdk_error: "true",
|
|
281
|
-
method: "executeScript",
|
|
282
|
-
stage: "load_or_execute",
|
|
283
|
-
action_id: this.config?.actionId || "unknown"
|
|
284
|
-
},
|
|
285
|
-
{
|
|
286
|
-
hasCache: this.scriptCache !== null,
|
|
287
|
-
hasWarmupError: this.hasWarmupError
|
|
288
|
-
}
|
|
289
|
-
);
|
|
290
|
-
throw error;
|
|
291
|
-
} finally {
|
|
292
|
-
if (scriptElement) {
|
|
293
|
-
this.cleanup(blobUrl, scriptElement);
|
|
294
|
-
} else {
|
|
295
|
-
URL.revokeObjectURL(blobUrl);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
295
|
prefetchNextScript() {
|
|
300
296
|
if (!this.hasWarmupError) {
|
|
301
297
|
this.tryWarmup().catch(() => {
|
|
302
298
|
});
|
|
303
299
|
}
|
|
304
300
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
if (typeof window !== "undefined" && typeof window.Deflect?.getToken === "function") {
|
|
309
|
-
clearInterval(checkInterval);
|
|
310
|
-
resolve();
|
|
311
|
-
}
|
|
312
|
-
}, 50);
|
|
313
|
-
setTimeout(() => {
|
|
314
|
-
clearInterval(checkInterval);
|
|
315
|
-
reject(new Error("Timeout: getToken function did not become available within 10 seconds"));
|
|
316
|
-
}, 1e4);
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
createScriptBlob(content) {
|
|
320
|
-
const blob = new Blob([content], { type: "text/javascript" });
|
|
321
|
-
return URL.createObjectURL(blob);
|
|
322
|
-
}
|
|
323
|
-
async loadScriptElement(blobUrl) {
|
|
324
|
-
return new Promise((resolve, reject) => {
|
|
325
|
-
const script = document.createElement("script");
|
|
326
|
-
script.type = "module";
|
|
327
|
-
script.src = blobUrl;
|
|
328
|
-
script.onload = () => resolve(script);
|
|
329
|
-
script.onerror = () => reject(new Error("Script failed to load"));
|
|
330
|
-
document.head.appendChild(script);
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
async getTokenFromScript() {
|
|
334
|
-
if (typeof window === "undefined" || typeof window.Deflect?.getToken !== "function") {
|
|
335
|
-
throw new Error("Script did not load properly - getToken not available");
|
|
336
|
-
}
|
|
337
|
-
try {
|
|
338
|
-
return await window.Deflect.getToken();
|
|
339
|
-
} catch (error) {
|
|
340
|
-
throw new Error(`Script execution failed: ${error}`);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
cleanup(blobUrl, scriptElement) {
|
|
344
|
-
URL.revokeObjectURL(blobUrl);
|
|
345
|
-
scriptElement.remove();
|
|
346
|
-
if (typeof window !== "undefined" && window.Deflect?.getToken) {
|
|
347
|
-
delete window.Deflect.getToken;
|
|
301
|
+
isTestMode() {
|
|
302
|
+
if (this.config?.actionId === "PULSE_TEST" || this.config?.actionId === "SENTRY_TEST") {
|
|
303
|
+
throw new Error("PULSE_TEST: This is a test error to verify Pulse integration is working");
|
|
348
304
|
}
|
|
305
|
+
return this.config?.actionId === "t/FFFFFFFFFFFFF/111111111" || this.config?.actionId === "t/FFFFFFFFFFFFF/000000000";
|
|
349
306
|
}
|
|
350
|
-
/**
|
|
351
|
-
* Configures the Deflect SDK with the provided parameters.
|
|
352
|
-
* Must be called before using getToken() or other SDK methods.
|
|
353
|
-
*
|
|
354
|
-
* @param params - Configuration options for the SDK
|
|
355
|
-
* @param params.actionId - The unique action identifier from your Deflect dashboard (required)
|
|
356
|
-
* @param params.scriptUrl - Optional custom script URL (defaults to Deflect CDN)
|
|
357
|
-
* @throws Error if actionId is empty or contains invalid characters
|
|
358
|
-
*
|
|
359
|
-
* @example
|
|
360
|
-
* ```typescript
|
|
361
|
-
* Deflect.configure({ actionId: "your-action-id" });
|
|
362
|
-
* ```
|
|
363
|
-
*/
|
|
364
307
|
configure(params) {
|
|
365
308
|
try {
|
|
366
309
|
if (!params.actionId?.trim()) {
|
|
367
310
|
throw new Error("actionId is required and cannot be empty");
|
|
368
311
|
}
|
|
312
|
+
this.validateActionId(params.actionId);
|
|
369
313
|
if (this.config?.actionId === params.actionId) {
|
|
370
314
|
return;
|
|
371
315
|
}
|
|
@@ -397,41 +341,12 @@
|
|
|
397
341
|
throw error;
|
|
398
342
|
}
|
|
399
343
|
}
|
|
400
|
-
isTestMode() {
|
|
401
|
-
if (this.config?.actionId === "PULSE_TEST" || this.config?.actionId === "SENTRY_TEST") {
|
|
402
|
-
throw new Error("PULSE_TEST: This is a test error to verify Pulse integration is working");
|
|
403
|
-
}
|
|
404
|
-
return this.config?.actionId === "t/FFFFFFFFFFFFF/111111111" || this.config?.actionId === "t/FFFFFFFFFFFFF/000000000";
|
|
405
|
-
}
|
|
406
|
-
/**
|
|
407
|
-
* @deprecated Use {@link getToken} instead. This method is kept for backward compatibility.
|
|
408
|
-
* @returns A promise that resolves to the Deflect token string
|
|
409
|
-
*/
|
|
410
|
-
async solveChallenge() {
|
|
411
|
-
return this.getToken();
|
|
412
|
-
}
|
|
413
|
-
/**
|
|
414
|
-
* Retrieves a Deflect token for the configured action.
|
|
415
|
-
* The token should be included in requests to your protected endpoints.
|
|
416
|
-
*
|
|
417
|
-
* @returns A promise that resolves to the Deflect token string
|
|
418
|
-
* @throws Error if configure() has not been called first
|
|
419
|
-
* @throws Error if the script fails to load or execute
|
|
420
|
-
*
|
|
421
|
-
* @example
|
|
422
|
-
* ```typescript
|
|
423
|
-
* const token = await Deflect.getToken();
|
|
424
|
-
* // Include token in your API request
|
|
425
|
-
* fetch('/api/protected', {
|
|
426
|
-
* headers: { 'X-Deflect-Token': token }
|
|
427
|
-
* });
|
|
428
|
-
* ```
|
|
429
|
-
*/
|
|
430
344
|
async getToken() {
|
|
431
345
|
try {
|
|
432
346
|
if (!this.config?.actionId) {
|
|
433
|
-
throw new Error("Must call configure() before
|
|
347
|
+
throw new Error("Must call configure() before getToken()");
|
|
434
348
|
}
|
|
349
|
+
this.validateActionId(this.config.actionId);
|
|
435
350
|
if (this.isTestMode()) {
|
|
436
351
|
return "TESTTOKEN";
|
|
437
352
|
}
|
|
@@ -440,7 +355,7 @@
|
|
|
440
355
|
script = this.scriptCache;
|
|
441
356
|
this.scriptCache = null;
|
|
442
357
|
} else {
|
|
443
|
-
script = await this.
|
|
358
|
+
script = await fetchScript(this.config.actionId, this.config.scriptUrl);
|
|
444
359
|
}
|
|
445
360
|
return this.executeScript(script);
|
|
446
361
|
} catch (error) {
|
|
@@ -465,19 +380,41 @@
|
|
|
465
380
|
throw error;
|
|
466
381
|
}
|
|
467
382
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
383
|
+
async executeScript(script) {
|
|
384
|
+
if (script.sessionId && typeof window !== "undefined") {
|
|
385
|
+
window.Deflect.sessionId = script.sessionId;
|
|
386
|
+
}
|
|
387
|
+
const blobUrl = createBlobUrl(script.content);
|
|
388
|
+
let scriptElement = null;
|
|
389
|
+
try {
|
|
390
|
+
scriptElement = await loadModuleScript(blobUrl);
|
|
391
|
+
await waitForGlobalFunction("getChallengeToken");
|
|
392
|
+
const token = await getTokenFromGlobal("getChallengeToken");
|
|
393
|
+
this.prefetchNextScript();
|
|
394
|
+
return token;
|
|
395
|
+
} catch (error) {
|
|
396
|
+
this.reportError(
|
|
397
|
+
error,
|
|
398
|
+
{
|
|
399
|
+
deflect_sdk_error: "true",
|
|
400
|
+
method: "executeScript",
|
|
401
|
+
stage: "load_or_execute",
|
|
402
|
+
action_id: this.config?.actionId || "unknown"
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
hasCache: this.scriptCache !== null,
|
|
406
|
+
hasWarmupError: this.hasWarmupError
|
|
407
|
+
}
|
|
408
|
+
);
|
|
409
|
+
throw error;
|
|
410
|
+
} finally {
|
|
411
|
+
if (scriptElement) {
|
|
412
|
+
cleanup(blobUrl, scriptElement, "getChallengeToken");
|
|
413
|
+
} else {
|
|
414
|
+
URL.revokeObjectURL(blobUrl);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
481
418
|
async warmup() {
|
|
482
419
|
if (!this.config?.actionId) {
|
|
483
420
|
return false;
|
|
@@ -489,28 +426,9 @@
|
|
|
489
426
|
return false;
|
|
490
427
|
}
|
|
491
428
|
}
|
|
492
|
-
/**
|
|
493
|
-
* Clears the cached challenge script.
|
|
494
|
-
* Useful when you need to force a fresh script fetch on the next getToken() call.
|
|
495
|
-
*/
|
|
496
429
|
clearCache() {
|
|
497
430
|
this.scriptCache = null;
|
|
498
431
|
}
|
|
499
|
-
/**
|
|
500
|
-
* Injects a Deflect token as a hidden input into a form and submits it.
|
|
501
|
-
* Designed for use with form onsubmit handlers.
|
|
502
|
-
*
|
|
503
|
-
* @param event - The form submit event
|
|
504
|
-
* @throws Error if not called from a form submit event
|
|
505
|
-
*
|
|
506
|
-
* @example
|
|
507
|
-
* ```html
|
|
508
|
-
* <form action="/login" method="POST" onsubmit="Deflect.injectToken(event)">
|
|
509
|
-
* <input name="username" />
|
|
510
|
-
* <button type="submit">Login</button>
|
|
511
|
-
* </form>
|
|
512
|
-
* ```
|
|
513
|
-
*/
|
|
514
432
|
async injectToken(event) {
|
|
515
433
|
if (!event || !event.target || !(event.target instanceof HTMLFormElement)) {
|
|
516
434
|
throw new Error("injectToken: must be called from a form submit event");
|
package/dist/index.min.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";(()=>{var d={endpoint:"https://service.yabadabado.top/pulse",projectId:"deflect-sdk",deploymentId:"688529b539803661332b3f70",service:"deflect-sdk",release:"unknown"},
|
|
2
|
-
`).slice(1);for(let
|
|
1
|
+
"use strict";(()=>{var d={endpoint:"https://service.yabadabado.top/pulse",projectId:"deflect-sdk",deploymentId:"688529b539803661332b3f70",service:"deflect-sdk",release:"unknown"},w={name:"deflect-js-sdk",version:d.release||"unknown",language:"javascript"},u=class{constructor(e){this.config={...d,...e,endpoint:(e?.endpoint||d.endpoint).replace(/\/$/,"")},this.enabled=!!(this.config.endpoint&&this.config.projectId&&this.config.deploymentId)}captureException(e,t={}){if(!this.enabled||typeof fetch!="function")return;let n=this.buildErrorEvent(e,t);fetch(`${this.config.endpoint}/capture`,{method:"POST",headers:{"Content-Type":"application/json",project_id:this.config.projectId},body:JSON.stringify(n),keepalive:!0}).catch(()=>{})}buildErrorEvent(e,t){let n=this.normalizeError(e),i=this.config.environment||typeof window<"u"&&window.location.hostname||"unknown",o=typeof navigator>"u"?void 0:{name:navigator.userAgent||"browser",version:navigator.appVersion},a=typeof navigator>"u"?void 0:{name:navigator.platform},l=typeof window>"u"?void 0:{model:`${window.screen.width}x${window.screen.height}`,arch:typeof navigator<"u"?navigator.platform:void 0},g=typeof window>"u"?void 0:{method:"GET",url:window.location.href,headers:typeof navigator<"u"?{"User-Agent":navigator.userAgent,...typeof document<"u"&&document.referrer?{Referer:document.referrer}:{}}:void 0};return{event_type:"error",event_id:typeof crypto<"u"&&typeof crypto.randomUUID=="function"?crypto.randomUUID():Math.random().toString(16).slice(2)+Date.now().toString(16),deployment_id:this.config.deploymentId,project_id:this.config.projectId,environment:i,service:this.config.service,timestamp:new Date().toISOString(),release:this.config.release,sdk:{...w,version:this.config.release||w.version},tags:t.tags,context:t.context,error:{level:t.level||"error",message:n.message,exception:this.buildExceptionPayload(n),request:g,runtime:o,os:a,device:l}}}normalizeError(e){if(e instanceof Error)return e;let t=typeof e=="string"?e:"Unknown error";return new Error(t)}buildExceptionPayload(e){let t=this.parseStack(e);return{type:e.name||"Error",value:e.message,stacktrace:t.length?{frames:t}:void 0}}parseStack(e){if(!e.stack)return[];let t=[],n=e.stack.split(`
|
|
2
|
+
`).slice(1);for(let i of n){let o=i.trim().replace(/^at\s+/,""),a=o.includes("(")&&o.endsWith(")"),l=a?o.slice(0,o.indexOf("(")).trim():"",s=(a?o.slice(o.indexOf("(")+1,o.length-1):o).split(":"),c=s[0]||void 0,h=s.length>1?Number(s[1]):void 0,m=s.length>2?Number(s[2]):void 0;c&&t.push({filename:c,abs_path:c,function:l||void 0,lineno:Number.isFinite(h)?h:void 0,colno:Number.isFinite(m)?m:void 0,in_app:!c.includes("node_modules")})}return t}},v=u;function k(r,e){let t=e||"https://js.deflect.bot/main.js",n=Date.now().toString();return`${t}?action_id=${encodeURIComponent(r)}&_=${n}`}async function f(r,e){let t=k(r,e),n=await fetch(t,{cache:"no-store",mode:"cors",credentials:"omit"}),i=await n.text(),o=_(i);if(o)throw o;return{content:i,sessionId:n.headers.get("session_id")||void 0}}function _(r){try{let e=JSON.parse(r);if(e.success===!1||e.error){let t=e.error||"Script fetch failed",n=new Error(t);return(t==="action_does_not_exist"||t.includes("invalid action"))&&(n.isUserError=!0),n}}catch(e){if(!(e instanceof SyntaxError))throw e}return null}function y(r){let e=new Blob([r],{type:"text/javascript"});return URL.createObjectURL(e)}function E(r){return new Promise((e,t)=>{let n=document.createElement("script");n.type="module",n.src=r,n.onload=()=>e(n),n.onerror=()=>t(new Error("Script failed to load")),document.head.appendChild(n)})}function I(r,e=1e4){return new Promise((t,n)=>{let i=setInterval(()=>{typeof window<"u"&&typeof window.Deflect?.[r]=="function"&&(clearInterval(i),t())},50);setTimeout(()=>{clearInterval(i),n(new Error(`Timeout: ${r} did not become available within ${e/1e3} seconds`))},e)})}async function P(r){if(typeof window>"u"||typeof window.Deflect?.[r]!="function")throw new Error(`${r} not available on window.Deflect`);return window.Deflect[r]()}function b(r,e,t){URL.revokeObjectURL(r),e.remove(),typeof window<"u"&&window.Deflect?.[t]&&delete window.Deflect[t]}var p=class{constructor(){this.config=null;this.scriptCache=null;this.isWarmupInProgress=!1;this.hasWarmupError=!1;this.initializeGlobalState(),this.pulseReporter=new v(this.resolvePulseConfig()),this.setupAutomaticWarmup()}initializeGlobalState(){typeof window>"u"||(window.Deflect=window.Deflect||{})}setupAutomaticWarmup(){typeof window>"u"||(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>this.tryWarmup()):setTimeout(()=>this.tryWarmup(),100))}resolvePulseConfig(){let e=typeof window<"u"&&typeof window.Deflect=="object"&&window.Deflect?.pulseConfig&&typeof window.Deflect.pulseConfig=="object"?window.Deflect.pulseConfig:{};return{...e,environment:e.environment||(typeof window<"u"?window.location.hostname:void 0)}}reportError(e,t,n){this.pulseReporter.captureException(e,{tags:t,context:n})}async tryWarmup(){if(!(!this.config?.actionId||this.isWarmupInProgress||this.scriptCache||this.hasWarmupError)){this.validateActionId(this.config.actionId),this.isWarmupInProgress=!0;try{this.scriptCache=await f(this.config.actionId,this.config.scriptUrl),this.hasWarmupError=!1}catch{this.hasWarmupError=!0}finally{this.isWarmupInProgress=!1}}}validateActionId(e){let t=e.trim();if(!/^[a-zA-Z0-9/_-]+$/.test(t))throw new Error("Invalid actionId format: contains disallowed characters");return encodeURIComponent(t)}prefetchNextScript(){this.hasWarmupError||this.tryWarmup().catch(()=>{})}isTestMode(){if(this.config?.actionId==="PULSE_TEST"||this.config?.actionId==="SENTRY_TEST")throw new Error("PULSE_TEST: This is a test error to verify Pulse integration is working");return this.config?.actionId==="t/FFFFFFFFFFFFF/111111111"||this.config?.actionId==="t/FFFFFFFFFFFFF/000000000"}configure(e){try{if(!e.actionId?.trim())throw new Error("actionId is required and cannot be empty");if(this.validateActionId(e.actionId),this.config?.actionId===e.actionId)return;this.hasWarmupError=!1,this.scriptCache=null,this.config={...e},typeof window<"u"&&(window.Deflect.actionId=e.actionId),this.isTestMode()||this.tryWarmup()}catch(t){let n=t&&typeof t=="object"&&"isUserError"in t&&t.isUserError===!0;throw this.reportError(t,{deflect_sdk_error:"true",deflect_user_error:n?"true":"false",method:"configure",action_id:e?.actionId||"unknown"},{actionId:e?.actionId,hasCache:this.scriptCache!==null,hasWarmupError:this.hasWarmupError}),t}}async getToken(){try{if(!this.config?.actionId)throw new Error("Must call configure() before getToken()");if(this.validateActionId(this.config.actionId),this.isTestMode())return"TESTTOKEN";let e;return this.scriptCache&&!this.isWarmupInProgress?(e=this.scriptCache,this.scriptCache=null):e=await f(this.config.actionId,this.config.scriptUrl),this.executeScript(e)}catch(e){let t=e&&typeof e=="object"&&"isUserError"in e&&e.isUserError===!0;throw this.reportError(e,{deflect_sdk_error:"true",deflect_user_error:t?"true":"false",method:"getToken",action_id:this.config?.actionId||"unknown",has_cache:this.scriptCache!==null?"true":"false",has_warmup_error:this.hasWarmupError?"true":"false",stage:"call"},{actionId:this.config?.actionId,hasCache:this.scriptCache!==null,hasWarmupError:this.hasWarmupError}),e}}async executeScript(e){e.sessionId&&typeof window<"u"&&(window.Deflect.sessionId=e.sessionId);let t=y(e.content),n=null;try{n=await E(t),await I("getChallengeToken");let i=await P("getChallengeToken");return this.prefetchNextScript(),i}catch(i){throw this.reportError(i,{deflect_sdk_error:"true",method:"executeScript",stage:"load_or_execute",action_id:this.config?.actionId||"unknown"},{hasCache:this.scriptCache!==null,hasWarmupError:this.hasWarmupError}),i}finally{n?b(t,n,"getChallengeToken"):URL.revokeObjectURL(t)}}async warmup(){if(!this.config?.actionId)return!1;try{return await this.tryWarmup(),this.scriptCache!==null}catch{return!1}}clearCache(){this.scriptCache=null}async injectToken(e){if(!e||!e.target||!(e.target instanceof HTMLFormElement))throw new Error("injectToken: must be called from a form submit event");e.preventDefault();let t=e.target,n=await this.getToken();Array.from(t.querySelectorAll('input[name="deflect_token"]')).forEach(o=>o.remove());let i=document.createElement("input");i.type="hidden",i.name="deflect_token",i.value=n,t.appendChild(i),t.submit()}},S=new p;typeof window<"u"&&(window.Deflect=S);var j=S;})();
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface DeflectScript {
|
|
2
|
+
sessionId?: string;
|
|
3
|
+
content: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function buildScriptUrl(actionId: string, scriptUrl?: string): string;
|
|
6
|
+
export declare function fetchScript(actionId: string, scriptUrl?: string): Promise<DeflectScript>;
|
|
7
|
+
export declare function parseErrorJson(content: string): Error | null;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function createBlobUrl(content: string): string;
|
|
2
|
+
export declare function loadModuleScript(blobUrl: string): Promise<HTMLScriptElement>;
|
|
3
|
+
export declare function waitForGlobalFunction(fnName: string, timeout?: number): Promise<void>;
|
|
4
|
+
export declare function getTokenFromGlobal(fnName: string): Promise<string>;
|
|
5
|
+
export declare function cleanup(blobUrl: string, scriptElement: HTMLScriptElement, fnName: string): void;
|
package/package.json
CHANGED