@deflectbot/deflect-sdk 1.4.1 → 1.4.2
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 +9 -84
- package/dist/index.esm.js +76 -231
- package/dist/index.js +165 -231
- 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
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
interface DeflectConfig {
|
|
2
|
+
/** The action ID to fetch the script for. */
|
|
2
3
|
actionId: string;
|
|
4
|
+
/** Override URL for the script. For staging/enterprise customers */
|
|
3
5
|
scriptUrl?: string;
|
|
6
|
+
/** Enable/disable automatic warmup/prefetch. Defaults to true. */
|
|
7
|
+
prefetch?: boolean;
|
|
4
8
|
}
|
|
5
9
|
declare global {
|
|
6
10
|
interface Window {
|
|
@@ -12,6 +16,7 @@ declare class Deflect {
|
|
|
12
16
|
private scriptCache;
|
|
13
17
|
private isWarmupInProgress;
|
|
14
18
|
private hasWarmupError;
|
|
19
|
+
private warmupPromise;
|
|
15
20
|
private pulseReporter;
|
|
16
21
|
constructor();
|
|
17
22
|
private initializeGlobalState;
|
|
@@ -19,95 +24,15 @@ declare class Deflect {
|
|
|
19
24
|
private resolvePulseConfig;
|
|
20
25
|
private reportError;
|
|
21
26
|
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
27
|
private validateActionId;
|
|
28
|
-
private buildScriptUrl;
|
|
29
|
-
private fetchScript;
|
|
30
|
-
private executeScript;
|
|
31
28
|
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
29
|
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
|
-
*/
|
|
30
|
+
configure(params: DeflectConfig): void;
|
|
75
31
|
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
|
-
*/
|
|
32
|
+
private executeScript;
|
|
89
33
|
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
34
|
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
35
|
injectToken(event: SubmitEvent): Promise<void>;
|
|
111
36
|
}
|
|
112
|
-
declare const
|
|
113
|
-
export default
|
|
37
|
+
declare const DeflectInstance: Deflect;
|
|
38
|
+
export default DeflectInstance;
|
package/dist/index.esm.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
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;
|
|
5
7
|
this.scriptCache = null;
|
|
6
8
|
this.isWarmupInProgress = false;
|
|
7
9
|
this.hasWarmupError = false;
|
|
10
|
+
this.warmupPromise = null;
|
|
8
11
|
this.initializeGlobalState();
|
|
9
12
|
this.pulseReporter = new PulseReporter(this.resolvePulseConfig());
|
|
10
13
|
this.setupAutomaticWarmup();
|
|
@@ -32,24 +35,24 @@ class Deflect {
|
|
|
32
35
|
this.pulseReporter.captureException(error, { tags, context });
|
|
33
36
|
}
|
|
34
37
|
async tryWarmup() {
|
|
35
|
-
if (!this.config?.actionId || this.isWarmupInProgress || this.scriptCache || this.hasWarmupError) {
|
|
38
|
+
if (!this.config?.actionId || this.config.prefetch === false || this.isWarmupInProgress || this.scriptCache || this.hasWarmupError) {
|
|
36
39
|
return;
|
|
37
40
|
}
|
|
41
|
+
this.validateActionId(this.config.actionId);
|
|
38
42
|
this.isWarmupInProgress = true;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
this.warmupPromise = (async () => {
|
|
44
|
+
try {
|
|
45
|
+
this.scriptCache = await fetchScript(this.config.actionId, this.config.scriptUrl);
|
|
46
|
+
this.hasWarmupError = false;
|
|
47
|
+
} catch {
|
|
48
|
+
this.hasWarmupError = true;
|
|
49
|
+
} finally {
|
|
50
|
+
this.isWarmupInProgress = false;
|
|
51
|
+
}
|
|
52
|
+
})();
|
|
53
|
+
await this.warmupPromise.catch(() => {
|
|
54
|
+
});
|
|
47
55
|
}
|
|
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
56
|
validateActionId(actionId) {
|
|
54
57
|
const sanitized = actionId.trim();
|
|
55
58
|
const validPattern = /^[a-zA-Z0-9/_-]+$/;
|
|
@@ -58,177 +61,34 @@ class Deflect {
|
|
|
58
61
|
}
|
|
59
62
|
return encodeURIComponent(sanitized);
|
|
60
63
|
}
|
|
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
64
|
prefetchNextScript() {
|
|
153
|
-
if (!this.hasWarmupError) {
|
|
65
|
+
if (!this.hasWarmupError && this.config?.prefetch !== false) {
|
|
154
66
|
this.tryWarmup().catch(() => {
|
|
155
67
|
});
|
|
156
68
|
}
|
|
157
69
|
}
|
|
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;
|
|
70
|
+
isTestMode() {
|
|
71
|
+
if (this.config?.actionId === "PULSE_TEST" || this.config?.actionId === "SENTRY_TEST") {
|
|
72
|
+
throw new Error("PULSE_TEST: This is a test error to verify Pulse integration is working");
|
|
201
73
|
}
|
|
74
|
+
return this.config?.actionId === "t/FFFFFFFFFFFFF/111111111" || this.config?.actionId === "t/FFFFFFFFFFFFF/000000000";
|
|
202
75
|
}
|
|
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
76
|
configure(params) {
|
|
218
77
|
try {
|
|
219
78
|
if (!params.actionId?.trim()) {
|
|
220
79
|
throw new Error("actionId is required and cannot be empty");
|
|
221
80
|
}
|
|
222
|
-
|
|
81
|
+
this.validateActionId(params.actionId);
|
|
82
|
+
if (this.config?.actionId === params.actionId && this.config.prefetch === params.prefetch) {
|
|
223
83
|
return;
|
|
224
84
|
}
|
|
225
85
|
this.hasWarmupError = false;
|
|
226
86
|
this.scriptCache = null;
|
|
227
|
-
this.config = { ...params };
|
|
87
|
+
this.config = { prefetch: true, ...params };
|
|
228
88
|
if (typeof window !== "undefined") {
|
|
229
89
|
window.Deflect.actionId = params.actionId;
|
|
230
90
|
}
|
|
231
|
-
if (!this.isTestMode()) {
|
|
91
|
+
if (!this.isTestMode() && this.config.prefetch !== false) {
|
|
232
92
|
this.tryWarmup();
|
|
233
93
|
}
|
|
234
94
|
} catch (error) {
|
|
@@ -250,50 +110,25 @@ class Deflect {
|
|
|
250
110
|
throw error;
|
|
251
111
|
}
|
|
252
112
|
}
|
|
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
113
|
async getToken() {
|
|
284
114
|
try {
|
|
285
115
|
if (!this.config?.actionId) {
|
|
286
|
-
throw new Error("Must call configure() before
|
|
116
|
+
throw new Error("Must call configure() before getToken()");
|
|
287
117
|
}
|
|
118
|
+
this.validateActionId(this.config.actionId);
|
|
288
119
|
if (this.isTestMode()) {
|
|
289
120
|
return "TESTTOKEN";
|
|
290
121
|
}
|
|
291
122
|
let script;
|
|
123
|
+
if (this.warmupPromise) {
|
|
124
|
+
await this.warmupPromise.catch(() => {
|
|
125
|
+
});
|
|
126
|
+
}
|
|
292
127
|
if (this.scriptCache && !this.isWarmupInProgress) {
|
|
293
128
|
script = this.scriptCache;
|
|
294
129
|
this.scriptCache = null;
|
|
295
130
|
} else {
|
|
296
|
-
script = await this.
|
|
131
|
+
script = await fetchScript(this.config.actionId, this.config.scriptUrl);
|
|
297
132
|
}
|
|
298
133
|
return this.executeScript(script);
|
|
299
134
|
} catch (error) {
|
|
@@ -318,52 +153,58 @@ class Deflect {
|
|
|
318
153
|
throw error;
|
|
319
154
|
}
|
|
320
155
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
156
|
+
async executeScript(script) {
|
|
157
|
+
if (script.sessionId && typeof window !== "undefined") {
|
|
158
|
+
window.Deflect.sessionId = script.sessionId;
|
|
159
|
+
}
|
|
160
|
+
const blobUrl = createBlobUrl(script.content);
|
|
161
|
+
let scriptElement = null;
|
|
162
|
+
try {
|
|
163
|
+
scriptElement = await loadModuleScript(blobUrl);
|
|
164
|
+
await waitForGlobalFunction("getChallengeToken");
|
|
165
|
+
const token = await getTokenFromGlobal("getChallengeToken");
|
|
166
|
+
this.prefetchNextScript();
|
|
167
|
+
return token;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
this.reportError(
|
|
170
|
+
error,
|
|
171
|
+
{
|
|
172
|
+
deflect_sdk_error: "true",
|
|
173
|
+
method: "executeScript",
|
|
174
|
+
stage: "load_or_execute",
|
|
175
|
+
action_id: this.config?.actionId || "unknown"
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
hasCache: this.scriptCache !== null,
|
|
179
|
+
hasWarmupError: this.hasWarmupError
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
throw error;
|
|
183
|
+
} finally {
|
|
184
|
+
if (scriptElement) {
|
|
185
|
+
cleanup(blobUrl, scriptElement, "getChallengeToken");
|
|
186
|
+
} else {
|
|
187
|
+
URL.revokeObjectURL(blobUrl);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
334
191
|
async warmup() {
|
|
335
192
|
if (!this.config?.actionId) {
|
|
336
193
|
return false;
|
|
337
194
|
}
|
|
338
195
|
try {
|
|
196
|
+
if (this.config.prefetch === false) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
339
199
|
await this.tryWarmup();
|
|
340
200
|
return this.scriptCache !== null;
|
|
341
201
|
} catch {
|
|
342
202
|
return false;
|
|
343
203
|
}
|
|
344
204
|
}
|
|
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
205
|
clearCache() {
|
|
350
206
|
this.scriptCache = null;
|
|
351
207
|
}
|
|
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
208
|
async injectToken(event) {
|
|
368
209
|
if (!event || !event.target || !(event.target instanceof HTMLFormElement)) {
|
|
369
210
|
throw new Error("injectToken: must be called from a form submit event");
|
|
@@ -382,7 +223,11 @@ class Deflect {
|
|
|
382
223
|
form.submit();
|
|
383
224
|
}
|
|
384
225
|
}
|
|
385
|
-
|
|
226
|
+
const DeflectInstance = new Deflect();
|
|
227
|
+
if (typeof window !== "undefined") {
|
|
228
|
+
window.Deflect = DeflectInstance;
|
|
229
|
+
}
|
|
230
|
+
var src_default = DeflectInstance;
|
|
386
231
|
export {
|
|
387
232
|
src_default as default
|
|
388
233
|
};
|
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() {
|
|
@@ -149,6 +240,7 @@
|
|
|
149
240
|
this.scriptCache = null;
|
|
150
241
|
this.isWarmupInProgress = false;
|
|
151
242
|
this.hasWarmupError = false;
|
|
243
|
+
this.warmupPromise = null;
|
|
152
244
|
this.initializeGlobalState();
|
|
153
245
|
this.pulseReporter = new pulse_default(this.resolvePulseConfig());
|
|
154
246
|
this.setupAutomaticWarmup();
|
|
@@ -179,24 +271,24 @@
|
|
|
179
271
|
this.pulseReporter.captureException(error, { tags, context });
|
|
180
272
|
}
|
|
181
273
|
async tryWarmup() {
|
|
182
|
-
if (!this.config?.actionId || this.isWarmupInProgress || this.scriptCache || this.hasWarmupError) {
|
|
274
|
+
if (!this.config?.actionId || this.config.prefetch === false || this.isWarmupInProgress || this.scriptCache || this.hasWarmupError) {
|
|
183
275
|
return;
|
|
184
276
|
}
|
|
277
|
+
this.validateActionId(this.config.actionId);
|
|
185
278
|
this.isWarmupInProgress = true;
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
279
|
+
this.warmupPromise = (async () => {
|
|
280
|
+
try {
|
|
281
|
+
this.scriptCache = await fetchScript(this.config.actionId, this.config.scriptUrl);
|
|
282
|
+
this.hasWarmupError = false;
|
|
283
|
+
} catch {
|
|
284
|
+
this.hasWarmupError = true;
|
|
285
|
+
} finally {
|
|
286
|
+
this.isWarmupInProgress = false;
|
|
287
|
+
}
|
|
288
|
+
})();
|
|
289
|
+
await this.warmupPromise.catch(() => {
|
|
290
|
+
});
|
|
194
291
|
}
|
|
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
292
|
validateActionId(actionId) {
|
|
201
293
|
const sanitized = actionId.trim();
|
|
202
294
|
const validPattern = /^[a-zA-Z0-9/_-]+$/;
|
|
@@ -205,177 +297,34 @@
|
|
|
205
297
|
}
|
|
206
298
|
return encodeURIComponent(sanitized);
|
|
207
299
|
}
|
|
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
300
|
prefetchNextScript() {
|
|
300
|
-
if (!this.hasWarmupError) {
|
|
301
|
+
if (!this.hasWarmupError && this.config?.prefetch !== false) {
|
|
301
302
|
this.tryWarmup().catch(() => {
|
|
302
303
|
});
|
|
303
304
|
}
|
|
304
305
|
}
|
|
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;
|
|
306
|
+
isTestMode() {
|
|
307
|
+
if (this.config?.actionId === "PULSE_TEST" || this.config?.actionId === "SENTRY_TEST") {
|
|
308
|
+
throw new Error("PULSE_TEST: This is a test error to verify Pulse integration is working");
|
|
348
309
|
}
|
|
310
|
+
return this.config?.actionId === "t/FFFFFFFFFFFFF/111111111" || this.config?.actionId === "t/FFFFFFFFFFFFF/000000000";
|
|
349
311
|
}
|
|
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
312
|
configure(params) {
|
|
365
313
|
try {
|
|
366
314
|
if (!params.actionId?.trim()) {
|
|
367
315
|
throw new Error("actionId is required and cannot be empty");
|
|
368
316
|
}
|
|
369
|
-
|
|
317
|
+
this.validateActionId(params.actionId);
|
|
318
|
+
if (this.config?.actionId === params.actionId && this.config.prefetch === params.prefetch) {
|
|
370
319
|
return;
|
|
371
320
|
}
|
|
372
321
|
this.hasWarmupError = false;
|
|
373
322
|
this.scriptCache = null;
|
|
374
|
-
this.config = { ...params };
|
|
323
|
+
this.config = { prefetch: true, ...params };
|
|
375
324
|
if (typeof window !== "undefined") {
|
|
376
325
|
window.Deflect.actionId = params.actionId;
|
|
377
326
|
}
|
|
378
|
-
if (!this.isTestMode()) {
|
|
327
|
+
if (!this.isTestMode() && this.config.prefetch !== false) {
|
|
379
328
|
this.tryWarmup();
|
|
380
329
|
}
|
|
381
330
|
} catch (error) {
|
|
@@ -397,50 +346,25 @@
|
|
|
397
346
|
throw error;
|
|
398
347
|
}
|
|
399
348
|
}
|
|
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
349
|
async getToken() {
|
|
431
350
|
try {
|
|
432
351
|
if (!this.config?.actionId) {
|
|
433
|
-
throw new Error("Must call configure() before
|
|
352
|
+
throw new Error("Must call configure() before getToken()");
|
|
434
353
|
}
|
|
354
|
+
this.validateActionId(this.config.actionId);
|
|
435
355
|
if (this.isTestMode()) {
|
|
436
356
|
return "TESTTOKEN";
|
|
437
357
|
}
|
|
438
358
|
let script;
|
|
359
|
+
if (this.warmupPromise) {
|
|
360
|
+
await this.warmupPromise.catch(() => {
|
|
361
|
+
});
|
|
362
|
+
}
|
|
439
363
|
if (this.scriptCache && !this.isWarmupInProgress) {
|
|
440
364
|
script = this.scriptCache;
|
|
441
365
|
this.scriptCache = null;
|
|
442
366
|
} else {
|
|
443
|
-
script = await this.
|
|
367
|
+
script = await fetchScript(this.config.actionId, this.config.scriptUrl);
|
|
444
368
|
}
|
|
445
369
|
return this.executeScript(script);
|
|
446
370
|
} catch (error) {
|
|
@@ -465,52 +389,58 @@
|
|
|
465
389
|
throw error;
|
|
466
390
|
}
|
|
467
391
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
392
|
+
async executeScript(script) {
|
|
393
|
+
if (script.sessionId && typeof window !== "undefined") {
|
|
394
|
+
window.Deflect.sessionId = script.sessionId;
|
|
395
|
+
}
|
|
396
|
+
const blobUrl = createBlobUrl(script.content);
|
|
397
|
+
let scriptElement = null;
|
|
398
|
+
try {
|
|
399
|
+
scriptElement = await loadModuleScript(blobUrl);
|
|
400
|
+
await waitForGlobalFunction("getChallengeToken");
|
|
401
|
+
const token = await getTokenFromGlobal("getChallengeToken");
|
|
402
|
+
this.prefetchNextScript();
|
|
403
|
+
return token;
|
|
404
|
+
} catch (error) {
|
|
405
|
+
this.reportError(
|
|
406
|
+
error,
|
|
407
|
+
{
|
|
408
|
+
deflect_sdk_error: "true",
|
|
409
|
+
method: "executeScript",
|
|
410
|
+
stage: "load_or_execute",
|
|
411
|
+
action_id: this.config?.actionId || "unknown"
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
hasCache: this.scriptCache !== null,
|
|
415
|
+
hasWarmupError: this.hasWarmupError
|
|
416
|
+
}
|
|
417
|
+
);
|
|
418
|
+
throw error;
|
|
419
|
+
} finally {
|
|
420
|
+
if (scriptElement) {
|
|
421
|
+
cleanup(blobUrl, scriptElement, "getChallengeToken");
|
|
422
|
+
} else {
|
|
423
|
+
URL.revokeObjectURL(blobUrl);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
481
427
|
async warmup() {
|
|
482
428
|
if (!this.config?.actionId) {
|
|
483
429
|
return false;
|
|
484
430
|
}
|
|
485
431
|
try {
|
|
432
|
+
if (this.config.prefetch === false) {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
486
435
|
await this.tryWarmup();
|
|
487
436
|
return this.scriptCache !== null;
|
|
488
437
|
} catch {
|
|
489
438
|
return false;
|
|
490
439
|
}
|
|
491
440
|
}
|
|
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
441
|
clearCache() {
|
|
497
442
|
this.scriptCache = null;
|
|
498
443
|
}
|
|
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
444
|
async injectToken(event) {
|
|
515
445
|
if (!event || !event.target || !(event.target instanceof HTMLFormElement)) {
|
|
516
446
|
throw new Error("injectToken: must be called from a form submit event");
|
|
@@ -529,5 +459,9 @@
|
|
|
529
459
|
form.submit();
|
|
530
460
|
}
|
|
531
461
|
};
|
|
532
|
-
var
|
|
462
|
+
var DeflectInstance = new Deflect();
|
|
463
|
+
if (typeof window !== "undefined") {
|
|
464
|
+
window.Deflect = DeflectInstance;
|
|
465
|
+
}
|
|
466
|
+
var src_default = DeflectInstance;
|
|
533
467
|
})();
|
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},h=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:h,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,g=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(g)?g: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.warmupPromise=null;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(){!this.config?.actionId||this.config.prefetch===!1||this.isWarmupInProgress||this.scriptCache||this.hasWarmupError||(this.validateActionId(this.config.actionId),this.isWarmupInProgress=!0,this.warmupPromise=(async()=>{try{this.scriptCache=await f(this.config.actionId,this.config.scriptUrl),this.hasWarmupError=!1}catch{this.hasWarmupError=!0}finally{this.isWarmupInProgress=!1}})(),await this.warmupPromise.catch(()=>{}))}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.config?.prefetch!==!1&&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&&this.config.prefetch===e.prefetch)return;this.hasWarmupError=!1,this.scriptCache=null,this.config={prefetch:!0,...e},typeof window<"u"&&(window.Deflect.actionId=e.actionId),!this.isTestMode()&&this.config.prefetch!==!1&&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.warmupPromise&&await this.warmupPromise.catch(()=>{}),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 this.config.prefetch===!1?!1:(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