@deflectbot/deflect-sdk 1.4.1-beta.1 → 1.4.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 +84 -4
- package/dist/index.esm.js +219 -52
- package/dist/index.js +219 -141
- package/dist/index.min.js +2 -2
- package/package.json +1 -1
- package/dist/scriptClient.d.ts +0 -7
- package/dist/scriptRunner.d.ts +0 -5
package/dist/index.d.ts
CHANGED
|
@@ -19,15 +19,95 @@ 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
|
+
*/
|
|
22
27
|
private validateActionId;
|
|
28
|
+
private buildScriptUrl;
|
|
29
|
+
private fetchScript;
|
|
30
|
+
private executeScript;
|
|
23
31
|
private prefetchNextScript;
|
|
24
|
-
private
|
|
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
|
+
*/
|
|
25
51
|
configure(params: DeflectConfig): void;
|
|
52
|
+
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
|
+
*/
|
|
26
75
|
getToken(): Promise<string>;
|
|
27
|
-
|
|
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
|
+
*/
|
|
28
89
|
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
|
+
*/
|
|
29
94
|
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
|
+
*/
|
|
30
110
|
injectToken(event: SubmitEvent): Promise<void>;
|
|
31
111
|
}
|
|
32
|
-
declare const
|
|
33
|
-
export default
|
|
112
|
+
declare const _default: Deflect;
|
|
113
|
+
export default _default;
|
package/dist/index.esm.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import PulseReporter from "./pulse";
|
|
2
|
-
import { fetchScript } from "./scriptClient";
|
|
3
|
-
import { createBlobUrl, loadModuleScript, waitForGlobalFunction, getTokenFromGlobal, cleanup } from "./scriptRunner";
|
|
4
2
|
class Deflect {
|
|
5
3
|
constructor() {
|
|
6
4
|
this.config = null;
|
|
@@ -37,10 +35,9 @@ class Deflect {
|
|
|
37
35
|
if (!this.config?.actionId || this.isWarmupInProgress || this.scriptCache || this.hasWarmupError) {
|
|
38
36
|
return;
|
|
39
37
|
}
|
|
40
|
-
this.validateActionId(this.config.actionId);
|
|
41
38
|
this.isWarmupInProgress = true;
|
|
42
39
|
try {
|
|
43
|
-
this.scriptCache = await fetchScript(
|
|
40
|
+
this.scriptCache = await this.fetchScript();
|
|
44
41
|
this.hasWarmupError = false;
|
|
45
42
|
} catch {
|
|
46
43
|
this.hasWarmupError = true;
|
|
@@ -48,6 +45,11 @@ class Deflect {
|
|
|
48
45
|
this.isWarmupInProgress = false;
|
|
49
46
|
}
|
|
50
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* @param actionId - The action ID to validate
|
|
50
|
+
* @returns The sanitized action ID
|
|
51
|
+
* @throws Error if actionId contains invalid characters
|
|
52
|
+
*/
|
|
51
53
|
validateActionId(actionId) {
|
|
52
54
|
const sanitized = actionId.trim();
|
|
53
55
|
const validPattern = /^[a-zA-Z0-9/_-]+$/;
|
|
@@ -56,24 +58,167 @@ class Deflect {
|
|
|
56
58
|
}
|
|
57
59
|
return encodeURIComponent(sanitized);
|
|
58
60
|
}
|
|
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
|
+
}
|
|
59
152
|
prefetchNextScript() {
|
|
60
153
|
if (!this.hasWarmupError) {
|
|
61
154
|
this.tryWarmup().catch(() => {
|
|
62
155
|
});
|
|
63
156
|
}
|
|
64
157
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
158
|
+
async waitForGetToken() {
|
|
159
|
+
return new Promise((resolve, reject) => {
|
|
160
|
+
const checkInterval = setInterval(() => {
|
|
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;
|
|
68
201
|
}
|
|
69
|
-
return this.config?.actionId === "t/FFFFFFFFFFFFF/111111111" || this.config?.actionId === "t/FFFFFFFFFFFFF/000000000";
|
|
70
202
|
}
|
|
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
|
+
*/
|
|
71
217
|
configure(params) {
|
|
72
218
|
try {
|
|
73
219
|
if (!params.actionId?.trim()) {
|
|
74
220
|
throw new Error("actionId is required and cannot be empty");
|
|
75
221
|
}
|
|
76
|
-
this.validateActionId(params.actionId);
|
|
77
222
|
if (this.config?.actionId === params.actionId) {
|
|
78
223
|
return;
|
|
79
224
|
}
|
|
@@ -105,12 +250,41 @@ class Deflect {
|
|
|
105
250
|
throw error;
|
|
106
251
|
}
|
|
107
252
|
}
|
|
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
|
+
*/
|
|
108
283
|
async getToken() {
|
|
109
284
|
try {
|
|
110
285
|
if (!this.config?.actionId) {
|
|
111
|
-
throw new Error("Must call configure() before
|
|
286
|
+
throw new Error("Must call configure() before solveChallenge()");
|
|
112
287
|
}
|
|
113
|
-
this.validateActionId(this.config.actionId);
|
|
114
288
|
if (this.isTestMode()) {
|
|
115
289
|
return "TESTTOKEN";
|
|
116
290
|
}
|
|
@@ -119,7 +293,7 @@ class Deflect {
|
|
|
119
293
|
script = this.scriptCache;
|
|
120
294
|
this.scriptCache = null;
|
|
121
295
|
} else {
|
|
122
|
-
script = await fetchScript(
|
|
296
|
+
script = await this.fetchScript();
|
|
123
297
|
}
|
|
124
298
|
return this.executeScript(script);
|
|
125
299
|
} catch (error) {
|
|
@@ -144,41 +318,19 @@ class Deflect {
|
|
|
144
318
|
throw error;
|
|
145
319
|
}
|
|
146
320
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
}
|
|
321
|
+
/**
|
|
322
|
+
* Pre-fetches the challenge script to reduce latency on the first getToken() call.
|
|
323
|
+
* This is automatically called after configure(), but can be manually triggered.
|
|
324
|
+
*
|
|
325
|
+
* @returns A promise that resolves to true if warmup succeeded, false otherwise
|
|
326
|
+
*
|
|
327
|
+
* @example
|
|
328
|
+
* ```typescript
|
|
329
|
+
* Deflect.configure({ actionId: "your-action-id" });
|
|
330
|
+
* const warmedUp = await Deflect.warmup();
|
|
331
|
+
* console.log('Script pre-cached:', warmedUp);
|
|
332
|
+
* ```
|
|
333
|
+
*/
|
|
182
334
|
async warmup() {
|
|
183
335
|
if (!this.config?.actionId) {
|
|
184
336
|
return false;
|
|
@@ -190,9 +342,28 @@ class Deflect {
|
|
|
190
342
|
return false;
|
|
191
343
|
}
|
|
192
344
|
}
|
|
345
|
+
/**
|
|
346
|
+
* Clears the cached challenge script.
|
|
347
|
+
* Useful when you need to force a fresh script fetch on the next getToken() call.
|
|
348
|
+
*/
|
|
193
349
|
clearCache() {
|
|
194
350
|
this.scriptCache = null;
|
|
195
351
|
}
|
|
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
|
+
*/
|
|
196
367
|
async injectToken(event) {
|
|
197
368
|
if (!event || !event.target || !(event.target instanceof HTMLFormElement)) {
|
|
198
369
|
throw new Error("injectToken: must be called from a form submit event");
|
|
@@ -211,11 +382,7 @@ class Deflect {
|
|
|
211
382
|
form.submit();
|
|
212
383
|
}
|
|
213
384
|
}
|
|
214
|
-
|
|
215
|
-
if (typeof window !== "undefined") {
|
|
216
|
-
window.Deflect = DeflectInstance;
|
|
217
|
-
}
|
|
218
|
-
var src_default = DeflectInstance;
|
|
385
|
+
var src_default = new Deflect();
|
|
219
386
|
export {
|
|
220
387
|
src_default as default
|
|
221
388
|
};
|
package/dist/index.js
CHANGED
|
@@ -142,97 +142,6 @@
|
|
|
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
|
-
|
|
236
145
|
// src/index.ts
|
|
237
146
|
var Deflect = class {
|
|
238
147
|
constructor() {
|
|
@@ -273,10 +182,9 @@
|
|
|
273
182
|
if (!this.config?.actionId || this.isWarmupInProgress || this.scriptCache || this.hasWarmupError) {
|
|
274
183
|
return;
|
|
275
184
|
}
|
|
276
|
-
this.validateActionId(this.config.actionId);
|
|
277
185
|
this.isWarmupInProgress = true;
|
|
278
186
|
try {
|
|
279
|
-
this.scriptCache = await fetchScript(
|
|
187
|
+
this.scriptCache = await this.fetchScript();
|
|
280
188
|
this.hasWarmupError = false;
|
|
281
189
|
} catch {
|
|
282
190
|
this.hasWarmupError = true;
|
|
@@ -284,6 +192,11 @@
|
|
|
284
192
|
this.isWarmupInProgress = false;
|
|
285
193
|
}
|
|
286
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* @param actionId - The action ID to validate
|
|
197
|
+
* @returns The sanitized action ID
|
|
198
|
+
* @throws Error if actionId contains invalid characters
|
|
199
|
+
*/
|
|
287
200
|
validateActionId(actionId) {
|
|
288
201
|
const sanitized = actionId.trim();
|
|
289
202
|
const validPattern = /^[a-zA-Z0-9/_-]+$/;
|
|
@@ -292,24 +205,167 @@
|
|
|
292
205
|
}
|
|
293
206
|
return encodeURIComponent(sanitized);
|
|
294
207
|
}
|
|
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
|
+
}
|
|
295
299
|
prefetchNextScript() {
|
|
296
300
|
if (!this.hasWarmupError) {
|
|
297
301
|
this.tryWarmup().catch(() => {
|
|
298
302
|
});
|
|
299
303
|
}
|
|
300
304
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
305
|
+
async waitForGetToken() {
|
|
306
|
+
return new Promise((resolve, reject) => {
|
|
307
|
+
const checkInterval = setInterval(() => {
|
|
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}`);
|
|
304
341
|
}
|
|
305
|
-
return this.config?.actionId === "t/FFFFFFFFFFFFF/111111111" || this.config?.actionId === "t/FFFFFFFFFFFFF/000000000";
|
|
306
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;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
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
|
+
*/
|
|
307
364
|
configure(params) {
|
|
308
365
|
try {
|
|
309
366
|
if (!params.actionId?.trim()) {
|
|
310
367
|
throw new Error("actionId is required and cannot be empty");
|
|
311
368
|
}
|
|
312
|
-
this.validateActionId(params.actionId);
|
|
313
369
|
if (this.config?.actionId === params.actionId) {
|
|
314
370
|
return;
|
|
315
371
|
}
|
|
@@ -341,12 +397,41 @@
|
|
|
341
397
|
throw error;
|
|
342
398
|
}
|
|
343
399
|
}
|
|
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
|
+
*/
|
|
344
430
|
async getToken() {
|
|
345
431
|
try {
|
|
346
432
|
if (!this.config?.actionId) {
|
|
347
|
-
throw new Error("Must call configure() before
|
|
433
|
+
throw new Error("Must call configure() before solveChallenge()");
|
|
348
434
|
}
|
|
349
|
-
this.validateActionId(this.config.actionId);
|
|
350
435
|
if (this.isTestMode()) {
|
|
351
436
|
return "TESTTOKEN";
|
|
352
437
|
}
|
|
@@ -355,7 +440,7 @@
|
|
|
355
440
|
script = this.scriptCache;
|
|
356
441
|
this.scriptCache = null;
|
|
357
442
|
} else {
|
|
358
|
-
script = await fetchScript(
|
|
443
|
+
script = await this.fetchScript();
|
|
359
444
|
}
|
|
360
445
|
return this.executeScript(script);
|
|
361
446
|
} catch (error) {
|
|
@@ -380,41 +465,19 @@
|
|
|
380
465
|
throw error;
|
|
381
466
|
}
|
|
382
467
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
-
}
|
|
468
|
+
/**
|
|
469
|
+
* Pre-fetches the challenge script to reduce latency on the first getToken() call.
|
|
470
|
+
* This is automatically called after configure(), but can be manually triggered.
|
|
471
|
+
*
|
|
472
|
+
* @returns A promise that resolves to true if warmup succeeded, false otherwise
|
|
473
|
+
*
|
|
474
|
+
* @example
|
|
475
|
+
* ```typescript
|
|
476
|
+
* Deflect.configure({ actionId: "your-action-id" });
|
|
477
|
+
* const warmedUp = await Deflect.warmup();
|
|
478
|
+
* console.log('Script pre-cached:', warmedUp);
|
|
479
|
+
* ```
|
|
480
|
+
*/
|
|
418
481
|
async warmup() {
|
|
419
482
|
if (!this.config?.actionId) {
|
|
420
483
|
return false;
|
|
@@ -426,9 +489,28 @@
|
|
|
426
489
|
return false;
|
|
427
490
|
}
|
|
428
491
|
}
|
|
492
|
+
/**
|
|
493
|
+
* Clears the cached challenge script.
|
|
494
|
+
* Useful when you need to force a fresh script fetch on the next getToken() call.
|
|
495
|
+
*/
|
|
429
496
|
clearCache() {
|
|
430
497
|
this.scriptCache = null;
|
|
431
498
|
}
|
|
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
|
+
*/
|
|
432
514
|
async injectToken(event) {
|
|
433
515
|
if (!event || !event.target || !(event.target instanceof HTMLFormElement)) {
|
|
434
516
|
throw new Error("injectToken: must be called from a form submit event");
|
|
@@ -447,9 +529,5 @@
|
|
|
447
529
|
form.submit();
|
|
448
530
|
}
|
|
449
531
|
};
|
|
450
|
-
var
|
|
451
|
-
if (typeof window !== "undefined") {
|
|
452
|
-
window.Deflect = DeflectInstance;
|
|
453
|
-
}
|
|
454
|
-
var src_default = DeflectInstance;
|
|
532
|
+
var src_default = new Deflect();
|
|
455
533
|
})();
|
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"},g={name:"deflect-js-sdk",version:d.release||"unknown",language:"javascript"},l=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),r=this.config.environment||typeof window<"u"&&window.location.hostname||"unknown",i=typeof navigator>"u"?void 0:{name:navigator.userAgent||"browser",version:navigator.appVersion},o=typeof navigator>"u"?void 0:{name:navigator.platform},c=typeof window>"u"?void 0:{model:`${window.screen.width}x${window.screen.height}`,arch:typeof navigator<"u"?navigator.platform:void 0},f=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:r,service:this.config.service,timestamp:new Date().toISOString(),release:this.config.release,sdk:{...g,version:this.config.release||g.version},tags:t.tags,context:t.context,error:{level:t.level||"error",message:n.message,exception:this.buildExceptionPayload(n),request:f,runtime:i,os:o,device:c}}}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 r of n){let i=r.trim().replace(/^at\s+/,""),o=i.includes("(")&&i.endsWith(")"),c=o?i.slice(0,i.indexOf("(")).trim():"",s=(o?i.slice(i.indexOf("(")+1,i.length-1):i).split(":"),a=s[0]||void 0,p=s.length>1?Number(s[1]):void 0,h=s.length>2?Number(s[2]):void 0;a&&t.push({filename:a,abs_path:a,function:c||void 0,lineno:Number.isFinite(p)?p:void 0,colno:Number.isFinite(h)?h:void 0,in_app:!a.includes("node_modules")})}return t}},m=l;var u=class{constructor(){this.config=null;this.scriptCache=null;this.isWarmupInProgress=!1;this.hasWarmupError=!1;this.initializeGlobalState(),this.pulseReporter=new m(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.isWarmupInProgress=!0;try{this.scriptCache=await this.fetchScript(),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)}buildScriptUrl(e){let t=this.config?.scriptUrl||"https://js.deflect.bot/main.js",n=this.validateActionId(e),r=Date.now().toString();return`${t}?action_id=${n}&_=${r}`}async fetchScript(){if(!this.config?.actionId)throw new Error("actionId is required");let e=this.buildScriptUrl(this.config.actionId);try{let t=await fetch(e,{cache:"no-store",mode:"cors",credentials:"omit"});if(!t.ok)throw new Error(`Failed to fetch script: ${t.status}`);let n=await t.text();try{let r=JSON.parse(n);if(r.success===!1||r.error){let i=r.error||"Script fetch failed",o=new Error(i);throw(i==="action_does_not_exist"||i.includes("invalid action"))&&(o.isUserError=!0),o}}catch(r){if(!(r instanceof SyntaxError))throw r}return{content:n,sessionId:t.headers.get("session_id")||void 0}}catch(t){throw this.reportError(t,{deflect_sdk_error:"true",method:"fetchScript",action_id:this.config.actionId},{actionId:this.config.actionId,url:e}),t}}async executeScript(e){e.sessionId&&typeof window<"u"&&(window.Deflect.sessionId=e.sessionId);let t=this.createScriptBlob(e.content),n=null;try{n=await this.loadScriptElement(t),await this.waitForGetToken();let r=await this.getTokenFromScript();return this.prefetchNextScript(),r}catch(r){throw this.reportError(r,{deflect_sdk_error:"true",method:"executeScript",stage:"load_or_execute",action_id:this.config?.actionId||"unknown"},{hasCache:this.scriptCache!==null,hasWarmupError:this.hasWarmupError}),r}finally{n?this.cleanup(t,n):URL.revokeObjectURL(t)}}prefetchNextScript(){this.hasWarmupError||this.tryWarmup().catch(()=>{})}async waitForGetToken(){return new Promise((e,t)=>{let n=setInterval(()=>{typeof window<"u"&&typeof window.Deflect?.getToken=="function"&&(clearInterval(n),e())},50);setTimeout(()=>{clearInterval(n),t(new Error("Timeout: getToken function did not become available within 10 seconds"))},1e4)})}createScriptBlob(e){let t=new Blob([e],{type:"text/javascript"});return URL.createObjectURL(t)}async loadScriptElement(e){return new Promise((t,n)=>{let r=document.createElement("script");r.type="module",r.src=e,r.onload=()=>t(r),r.onerror=()=>n(new Error("Script failed to load")),document.head.appendChild(r)})}async getTokenFromScript(){if(typeof window>"u"||typeof window.Deflect?.getToken!="function")throw new Error("Script did not load properly - getToken not available");try{return await window.Deflect.getToken()}catch(e){throw new Error(`Script execution failed: ${e}`)}}cleanup(e,t){URL.revokeObjectURL(e),t.remove(),typeof window<"u"&&window.Deflect?.getToken&&delete window.Deflect.getToken}configure(e){try{if(!e.actionId?.trim())throw new Error("actionId is required and cannot be empty");if(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}}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"}async solveChallenge(){return this.getToken()}async getToken(){try{if(!this.config?.actionId)throw new Error("Must call configure() before solveChallenge()");if(this.isTestMode())return"TESTTOKEN";let e;return this.scriptCache&&!this.isWarmupInProgress?(e=this.scriptCache,this.scriptCache=null):e=await this.fetchScript(),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 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(i=>i.remove());let r=document.createElement("input");r.type="hidden",r.name="deflect_token",r.value=n,t.appendChild(r),t.submit()}},I=new u;})();
|
package/package.json
CHANGED
package/dist/scriptClient.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
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;
|
package/dist/scriptRunner.d.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
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;
|