@deflectbot/deflect-sdk 1.3.7 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +75 -4
- package/dist/index.esm.js +228 -54
- package/dist/index.js +366 -54
- package/dist/index.min.js +2 -1
- package/dist/pulse.d.ts +99 -0
- package/package.json +6 -3
package/dist/index.d.ts
CHANGED
|
@@ -12,10 +12,19 @@ declare class Deflect {
|
|
|
12
12
|
private scriptCache;
|
|
13
13
|
private isWarmupInProgress;
|
|
14
14
|
private hasWarmupError;
|
|
15
|
+
private pulseReporter;
|
|
15
16
|
constructor();
|
|
16
17
|
private initializeGlobalState;
|
|
17
18
|
private setupAutomaticWarmup;
|
|
19
|
+
private resolvePulseConfig;
|
|
20
|
+
private reportError;
|
|
18
21
|
private tryWarmup;
|
|
22
|
+
/**
|
|
23
|
+
* @param actionId - The action ID to validate
|
|
24
|
+
* @returns The sanitized action ID
|
|
25
|
+
* @throws Error if actionId contains invalid characters
|
|
26
|
+
*/
|
|
27
|
+
private validateActionId;
|
|
19
28
|
private buildScriptUrl;
|
|
20
29
|
private fetchScript;
|
|
21
30
|
private executeScript;
|
|
@@ -25,18 +34,80 @@ declare class Deflect {
|
|
|
25
34
|
private loadScriptElement;
|
|
26
35
|
private getTokenFromScript;
|
|
27
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
|
+
*/
|
|
28
51
|
configure(params: DeflectConfig): void;
|
|
29
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
|
+
*/
|
|
30
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
|
+
*/
|
|
31
75
|
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
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
|
+
*/
|
|
33
94
|
clearCache(): void;
|
|
34
95
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
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
|
+
* ```
|
|
38
109
|
*/
|
|
39
|
-
injectToken(event: SubmitEvent): Promise<
|
|
110
|
+
injectToken(event: SubmitEvent): Promise<void>;
|
|
40
111
|
}
|
|
41
112
|
declare const DeflectInstance: Deflect;
|
|
42
113
|
export default DeflectInstance;
|
package/dist/index.esm.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import PulseReporter from "./pulse";
|
|
1
2
|
class Deflect {
|
|
2
3
|
constructor() {
|
|
3
4
|
this.config = null;
|
|
@@ -5,6 +6,7 @@ class Deflect {
|
|
|
5
6
|
this.isWarmupInProgress = false;
|
|
6
7
|
this.hasWarmupError = false;
|
|
7
8
|
this.initializeGlobalState();
|
|
9
|
+
this.pulseReporter = new PulseReporter(this.resolvePulseConfig());
|
|
8
10
|
this.setupAutomaticWarmup();
|
|
9
11
|
}
|
|
10
12
|
initializeGlobalState() {
|
|
@@ -19,6 +21,16 @@ class Deflect {
|
|
|
19
21
|
setTimeout(() => this.tryWarmup(), 100);
|
|
20
22
|
}
|
|
21
23
|
}
|
|
24
|
+
resolvePulseConfig() {
|
|
25
|
+
const runtimeConfig = typeof window !== "undefined" && typeof window.Deflect === "object" && window.Deflect?.pulseConfig && typeof window.Deflect.pulseConfig === "object" ? window.Deflect.pulseConfig : {};
|
|
26
|
+
return {
|
|
27
|
+
...runtimeConfig,
|
|
28
|
+
environment: runtimeConfig.environment || (typeof window !== "undefined" ? window.location.hostname : void 0)
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
reportError(error, tags, context) {
|
|
32
|
+
this.pulseReporter.captureException(error, { tags, context });
|
|
33
|
+
}
|
|
22
34
|
async tryWarmup() {
|
|
23
35
|
if (!this.config?.actionId || this.isWarmupInProgress || this.scriptCache || this.hasWarmupError) {
|
|
24
36
|
return;
|
|
@@ -33,50 +45,108 @@ class Deflect {
|
|
|
33
45
|
this.isWarmupInProgress = false;
|
|
34
46
|
}
|
|
35
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
|
+
*/
|
|
53
|
+
validateActionId(actionId) {
|
|
54
|
+
const sanitized = actionId.trim();
|
|
55
|
+
const validPattern = /^[a-zA-Z0-9/_-]+$/;
|
|
56
|
+
if (!validPattern.test(sanitized)) {
|
|
57
|
+
throw new Error("Invalid actionId format: contains disallowed characters");
|
|
58
|
+
}
|
|
59
|
+
return encodeURIComponent(sanitized);
|
|
60
|
+
}
|
|
36
61
|
buildScriptUrl(actionId) {
|
|
37
62
|
const baseUrl = this.config?.scriptUrl || "https://js.deflect.bot/main.js";
|
|
63
|
+
const sanitizedActionId = this.validateActionId(actionId);
|
|
38
64
|
const nonce = Date.now().toString();
|
|
39
|
-
return `${baseUrl}?action_id=${
|
|
65
|
+
return `${baseUrl}?action_id=${sanitizedActionId}&_=${nonce}`;
|
|
40
66
|
}
|
|
41
67
|
async fetchScript() {
|
|
42
68
|
if (!this.config?.actionId) {
|
|
43
69
|
throw new Error("actionId is required");
|
|
44
70
|
}
|
|
45
71
|
const url = this.buildScriptUrl(this.config.actionId);
|
|
46
|
-
const response = await fetch(url, { cache: "no-store" });
|
|
47
|
-
if (!response.ok) {
|
|
48
|
-
throw new Error(`Failed to fetch script: ${response.status}`);
|
|
49
|
-
}
|
|
50
|
-
const content = await response.text();
|
|
51
72
|
try {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
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}`);
|
|
55
80
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
+
}
|
|
60
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;
|
|
61
115
|
}
|
|
62
|
-
return {
|
|
63
|
-
content,
|
|
64
|
-
sessionId: response.headers.get("session_id") || void 0
|
|
65
|
-
};
|
|
66
116
|
}
|
|
67
117
|
async executeScript(script) {
|
|
68
118
|
if (script.sessionId && typeof window !== "undefined") {
|
|
69
119
|
window.Deflect.sessionId = script.sessionId;
|
|
70
120
|
}
|
|
71
121
|
const blobUrl = this.createScriptBlob(script.content);
|
|
72
|
-
|
|
122
|
+
let scriptElement = null;
|
|
73
123
|
try {
|
|
124
|
+
scriptElement = await this.loadScriptElement(blobUrl);
|
|
74
125
|
await this.waitForGetToken();
|
|
75
126
|
const token = await this.getTokenFromScript();
|
|
76
127
|
this.prefetchNextScript();
|
|
77
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;
|
|
78
144
|
} finally {
|
|
79
|
-
|
|
145
|
+
if (scriptElement) {
|
|
146
|
+
this.cleanup(blobUrl, scriptElement);
|
|
147
|
+
} else {
|
|
148
|
+
URL.revokeObjectURL(blobUrl);
|
|
149
|
+
}
|
|
80
150
|
}
|
|
81
151
|
}
|
|
82
152
|
prefetchNextScript() {
|
|
@@ -86,16 +156,16 @@ class Deflect {
|
|
|
86
156
|
}
|
|
87
157
|
}
|
|
88
158
|
async waitForGetToken() {
|
|
89
|
-
return new Promise((resolve) => {
|
|
159
|
+
return new Promise((resolve, reject) => {
|
|
90
160
|
const checkInterval = setInterval(() => {
|
|
91
161
|
if (typeof window !== "undefined" && typeof window.Deflect?.getToken === "function") {
|
|
92
162
|
clearInterval(checkInterval);
|
|
93
163
|
resolve();
|
|
94
164
|
}
|
|
95
|
-
},
|
|
165
|
+
}, 50);
|
|
96
166
|
setTimeout(() => {
|
|
97
167
|
clearInterval(checkInterval);
|
|
98
|
-
|
|
168
|
+
reject(new Error("Timeout: getToken function did not become available within 10 seconds"));
|
|
99
169
|
}, 1e4);
|
|
100
170
|
});
|
|
101
171
|
}
|
|
@@ -130,46 +200,137 @@ class Deflect {
|
|
|
130
200
|
delete window.Deflect.getToken;
|
|
131
201
|
}
|
|
132
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
|
+
*/
|
|
133
217
|
configure(params) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
window
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
this.
|
|
218
|
+
try {
|
|
219
|
+
if (!params.actionId?.trim()) {
|
|
220
|
+
throw new Error("actionId is required and cannot be empty");
|
|
221
|
+
}
|
|
222
|
+
if (this.config?.actionId === params.actionId) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
this.hasWarmupError = false;
|
|
226
|
+
this.scriptCache = null;
|
|
227
|
+
this.config = { ...params };
|
|
228
|
+
if (typeof window !== "undefined") {
|
|
229
|
+
window.Deflect.actionId = params.actionId;
|
|
230
|
+
}
|
|
231
|
+
if (!this.isTestMode()) {
|
|
232
|
+
this.tryWarmup();
|
|
233
|
+
}
|
|
234
|
+
} catch (error) {
|
|
235
|
+
const isUserError = error && typeof error === "object" && "isUserError" in error && error.isUserError === true;
|
|
236
|
+
this.reportError(
|
|
237
|
+
error,
|
|
238
|
+
{
|
|
239
|
+
deflect_sdk_error: "true",
|
|
240
|
+
deflect_user_error: isUserError ? "true" : "false",
|
|
241
|
+
method: "configure",
|
|
242
|
+
action_id: params?.actionId || "unknown"
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
actionId: params?.actionId,
|
|
246
|
+
hasCache: this.scriptCache !== null,
|
|
247
|
+
hasWarmupError: this.hasWarmupError
|
|
248
|
+
}
|
|
249
|
+
);
|
|
250
|
+
throw error;
|
|
148
251
|
}
|
|
149
252
|
}
|
|
150
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
|
+
}
|
|
151
257
|
return this.config?.actionId === "t/FFFFFFFFFFFFF/111111111" || this.config?.actionId === "t/FFFFFFFFFFFFF/000000000";
|
|
152
258
|
}
|
|
153
|
-
|
|
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
|
+
*/
|
|
154
263
|
async solveChallenge() {
|
|
155
264
|
return this.getToken();
|
|
156
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
|
+
*/
|
|
157
283
|
async getToken() {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
284
|
+
try {
|
|
285
|
+
if (!this.config?.actionId) {
|
|
286
|
+
throw new Error("Must call configure() before solveChallenge()");
|
|
287
|
+
}
|
|
288
|
+
if (this.isTestMode()) {
|
|
289
|
+
return "TESTTOKEN";
|
|
290
|
+
}
|
|
291
|
+
let script;
|
|
292
|
+
if (this.scriptCache && !this.isWarmupInProgress) {
|
|
293
|
+
script = this.scriptCache;
|
|
294
|
+
this.scriptCache = null;
|
|
295
|
+
} else {
|
|
296
|
+
script = await this.fetchScript();
|
|
297
|
+
}
|
|
298
|
+
return this.executeScript(script);
|
|
299
|
+
} catch (error) {
|
|
300
|
+
const isUserError = error && typeof error === "object" && "isUserError" in error && error.isUserError === true;
|
|
301
|
+
this.reportError(
|
|
302
|
+
error,
|
|
303
|
+
{
|
|
304
|
+
deflect_sdk_error: "true",
|
|
305
|
+
deflect_user_error: isUserError ? "true" : "false",
|
|
306
|
+
method: "getToken",
|
|
307
|
+
action_id: this.config?.actionId || "unknown",
|
|
308
|
+
has_cache: this.scriptCache !== null ? "true" : "false",
|
|
309
|
+
has_warmup_error: this.hasWarmupError ? "true" : "false",
|
|
310
|
+
stage: "call"
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
actionId: this.config?.actionId,
|
|
314
|
+
hasCache: this.scriptCache !== null,
|
|
315
|
+
hasWarmupError: this.hasWarmupError
|
|
316
|
+
}
|
|
317
|
+
);
|
|
318
|
+
throw error;
|
|
170
319
|
}
|
|
171
|
-
return this.executeScript(script);
|
|
172
320
|
}
|
|
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
|
+
*/
|
|
173
334
|
async warmup() {
|
|
174
335
|
if (!this.config?.actionId) {
|
|
175
336
|
return false;
|
|
@@ -181,13 +342,27 @@ class Deflect {
|
|
|
181
342
|
return false;
|
|
182
343
|
}
|
|
183
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
|
+
*/
|
|
184
349
|
clearCache() {
|
|
185
350
|
this.scriptCache = null;
|
|
186
351
|
}
|
|
187
352
|
/**
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
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
|
+
* ```
|
|
191
366
|
*/
|
|
192
367
|
async injectToken(event) {
|
|
193
368
|
if (!event || !event.target || !(event.target instanceof HTMLFormElement)) {
|
|
@@ -205,7 +380,6 @@ class Deflect {
|
|
|
205
380
|
hidden.value = token;
|
|
206
381
|
form.appendChild(hidden);
|
|
207
382
|
form.submit();
|
|
208
|
-
return false;
|
|
209
383
|
}
|
|
210
384
|
}
|
|
211
385
|
const DeflectInstance = new Deflect();
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,145 @@
|
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
5
5
|
|
|
6
|
+
// src/pulse.ts
|
|
7
|
+
var REPORT_CONFIG = {
|
|
8
|
+
endpoint: "https://service.yabadabado.top/pulse",
|
|
9
|
+
projectId: "deflect-sdk",
|
|
10
|
+
deploymentId: "688529b539803661332b3f70",
|
|
11
|
+
service: "deflect-sdk",
|
|
12
|
+
release: "unknown"
|
|
13
|
+
};
|
|
14
|
+
var PULSE_SDK_INFO = {
|
|
15
|
+
name: "deflect-js-sdk",
|
|
16
|
+
version: REPORT_CONFIG.release || "unknown",
|
|
17
|
+
language: "javascript"
|
|
18
|
+
};
|
|
19
|
+
var PulseReporter = class {
|
|
20
|
+
static {
|
|
21
|
+
__name(this, "PulseReporter");
|
|
22
|
+
}
|
|
23
|
+
constructor(config) {
|
|
24
|
+
this.config = {
|
|
25
|
+
...REPORT_CONFIG,
|
|
26
|
+
...config,
|
|
27
|
+
endpoint: (config?.endpoint || REPORT_CONFIG.endpoint).replace(/\/$/, "")
|
|
28
|
+
};
|
|
29
|
+
this.enabled = Boolean(
|
|
30
|
+
this.config.endpoint && this.config.projectId && this.config.deploymentId
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
captureException(error, options = {}) {
|
|
34
|
+
if (!this.enabled || typeof fetch !== "function") {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const event = this.buildErrorEvent(error, options);
|
|
38
|
+
fetch(`${this.config.endpoint}/capture`, {
|
|
39
|
+
method: "POST",
|
|
40
|
+
headers: {
|
|
41
|
+
"Content-Type": "application/json",
|
|
42
|
+
project_id: this.config.projectId
|
|
43
|
+
},
|
|
44
|
+
body: JSON.stringify(event),
|
|
45
|
+
keepalive: true
|
|
46
|
+
}).catch(() => {
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
buildErrorEvent(error, options) {
|
|
50
|
+
const normalizedError = this.normalizeError(error);
|
|
51
|
+
const environment = this.config.environment || (typeof window !== "undefined" ? window.location.hostname || "unknown" : "unknown");
|
|
52
|
+
const runtimeInfo = typeof navigator === "undefined" ? void 0 : {
|
|
53
|
+
name: navigator.userAgent || "browser",
|
|
54
|
+
version: navigator.appVersion
|
|
55
|
+
};
|
|
56
|
+
const osInfo = typeof navigator === "undefined" ? void 0 : {
|
|
57
|
+
name: navigator.platform
|
|
58
|
+
};
|
|
59
|
+
const deviceInfo = typeof window === "undefined" ? void 0 : {
|
|
60
|
+
model: `${window.screen.width}x${window.screen.height}`,
|
|
61
|
+
arch: typeof navigator !== "undefined" ? navigator.platform : void 0
|
|
62
|
+
};
|
|
63
|
+
const requestInfo = typeof window === "undefined" ? void 0 : {
|
|
64
|
+
method: "GET",
|
|
65
|
+
url: window.location.href,
|
|
66
|
+
headers: typeof navigator !== "undefined" ? {
|
|
67
|
+
"User-Agent": navigator.userAgent,
|
|
68
|
+
...typeof document !== "undefined" && document.referrer ? { Referer: document.referrer } : {}
|
|
69
|
+
} : void 0
|
|
70
|
+
};
|
|
71
|
+
const eventId = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : Math.random().toString(16).slice(2) + Date.now().toString(16);
|
|
72
|
+
return {
|
|
73
|
+
event_type: "error",
|
|
74
|
+
event_id: eventId,
|
|
75
|
+
deployment_id: this.config.deploymentId,
|
|
76
|
+
project_id: this.config.projectId,
|
|
77
|
+
environment,
|
|
78
|
+
service: this.config.service,
|
|
79
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
80
|
+
release: this.config.release,
|
|
81
|
+
sdk: {
|
|
82
|
+
...PULSE_SDK_INFO,
|
|
83
|
+
version: this.config.release || PULSE_SDK_INFO.version
|
|
84
|
+
},
|
|
85
|
+
tags: options.tags,
|
|
86
|
+
context: options.context,
|
|
87
|
+
error: {
|
|
88
|
+
level: options.level || "error",
|
|
89
|
+
message: normalizedError.message,
|
|
90
|
+
exception: this.buildExceptionPayload(normalizedError),
|
|
91
|
+
request: requestInfo,
|
|
92
|
+
runtime: runtimeInfo,
|
|
93
|
+
os: osInfo,
|
|
94
|
+
device: deviceInfo
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
normalizeError(error) {
|
|
99
|
+
if (error instanceof Error) {
|
|
100
|
+
return error;
|
|
101
|
+
}
|
|
102
|
+
const message = typeof error === "string" ? error : "Unknown error";
|
|
103
|
+
return new Error(message);
|
|
104
|
+
}
|
|
105
|
+
buildExceptionPayload(error) {
|
|
106
|
+
const frames = this.parseStack(error);
|
|
107
|
+
return {
|
|
108
|
+
type: error.name || "Error",
|
|
109
|
+
value: error.message,
|
|
110
|
+
stacktrace: frames.length ? { frames } : void 0
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
parseStack(error) {
|
|
114
|
+
if (!error.stack) {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
const frames = [];
|
|
118
|
+
const raw = error.stack.split("\n").slice(1);
|
|
119
|
+
for (const line of raw) {
|
|
120
|
+
const cleaned = line.trim().replace(/^at\s+/, "");
|
|
121
|
+
const hasLocation = cleaned.includes("(") && cleaned.endsWith(")");
|
|
122
|
+
const functionName = hasLocation ? cleaned.slice(0, cleaned.indexOf("(")).trim() : "";
|
|
123
|
+
const location = hasLocation ? cleaned.slice(cleaned.indexOf("(") + 1, cleaned.length - 1) : cleaned;
|
|
124
|
+
const parts = location.split(":");
|
|
125
|
+
const filename = parts[0] || void 0;
|
|
126
|
+
const lineno = parts.length > 1 ? Number(parts[1]) : void 0;
|
|
127
|
+
const colno = parts.length > 2 ? Number(parts[2]) : void 0;
|
|
128
|
+
if (!filename) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
frames.push({
|
|
132
|
+
filename,
|
|
133
|
+
abs_path: filename,
|
|
134
|
+
function: functionName || void 0,
|
|
135
|
+
lineno: Number.isFinite(lineno) ? lineno : void 0,
|
|
136
|
+
colno: Number.isFinite(colno) ? colno : void 0,
|
|
137
|
+
in_app: !filename.includes("node_modules")
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return frames;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
var pulse_default = PulseReporter;
|
|
144
|
+
|
|
6
145
|
// src/index.ts
|
|
7
146
|
var Deflect = class {
|
|
8
147
|
constructor() {
|
|
@@ -11,6 +150,7 @@
|
|
|
11
150
|
this.isWarmupInProgress = false;
|
|
12
151
|
this.hasWarmupError = false;
|
|
13
152
|
this.initializeGlobalState();
|
|
153
|
+
this.pulseReporter = new pulse_default(this.resolvePulseConfig());
|
|
14
154
|
this.setupAutomaticWarmup();
|
|
15
155
|
}
|
|
16
156
|
static {
|
|
@@ -28,6 +168,16 @@
|
|
|
28
168
|
setTimeout(() => this.tryWarmup(), 100);
|
|
29
169
|
}
|
|
30
170
|
}
|
|
171
|
+
resolvePulseConfig() {
|
|
172
|
+
const runtimeConfig = typeof window !== "undefined" && typeof window.Deflect === "object" && window.Deflect?.pulseConfig && typeof window.Deflect.pulseConfig === "object" ? window.Deflect.pulseConfig : {};
|
|
173
|
+
return {
|
|
174
|
+
...runtimeConfig,
|
|
175
|
+
environment: runtimeConfig.environment || (typeof window !== "undefined" ? window.location.hostname : void 0)
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
reportError(error, tags, context) {
|
|
179
|
+
this.pulseReporter.captureException(error, { tags, context });
|
|
180
|
+
}
|
|
31
181
|
async tryWarmup() {
|
|
32
182
|
if (!this.config?.actionId || this.isWarmupInProgress || this.scriptCache || this.hasWarmupError) {
|
|
33
183
|
return;
|
|
@@ -42,50 +192,108 @@
|
|
|
42
192
|
this.isWarmupInProgress = false;
|
|
43
193
|
}
|
|
44
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
|
+
*/
|
|
200
|
+
validateActionId(actionId) {
|
|
201
|
+
const sanitized = actionId.trim();
|
|
202
|
+
const validPattern = /^[a-zA-Z0-9/_-]+$/;
|
|
203
|
+
if (!validPattern.test(sanitized)) {
|
|
204
|
+
throw new Error("Invalid actionId format: contains disallowed characters");
|
|
205
|
+
}
|
|
206
|
+
return encodeURIComponent(sanitized);
|
|
207
|
+
}
|
|
45
208
|
buildScriptUrl(actionId) {
|
|
46
209
|
const baseUrl = this.config?.scriptUrl || "https://js.deflect.bot/main.js";
|
|
210
|
+
const sanitizedActionId = this.validateActionId(actionId);
|
|
47
211
|
const nonce = Date.now().toString();
|
|
48
|
-
return `${baseUrl}?action_id=${
|
|
212
|
+
return `${baseUrl}?action_id=${sanitizedActionId}&_=${nonce}`;
|
|
49
213
|
}
|
|
50
214
|
async fetchScript() {
|
|
51
215
|
if (!this.config?.actionId) {
|
|
52
216
|
throw new Error("actionId is required");
|
|
53
217
|
}
|
|
54
218
|
const url = this.buildScriptUrl(this.config.actionId);
|
|
55
|
-
const response = await fetch(url, { cache: "no-store" });
|
|
56
|
-
if (!response.ok) {
|
|
57
|
-
throw new Error(`Failed to fetch script: ${response.status}`);
|
|
58
|
-
}
|
|
59
|
-
const content = await response.text();
|
|
60
219
|
try {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
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}`);
|
|
64
227
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
+
}
|
|
69
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;
|
|
70
262
|
}
|
|
71
|
-
return {
|
|
72
|
-
content,
|
|
73
|
-
sessionId: response.headers.get("session_id") || void 0
|
|
74
|
-
};
|
|
75
263
|
}
|
|
76
264
|
async executeScript(script) {
|
|
77
265
|
if (script.sessionId && typeof window !== "undefined") {
|
|
78
266
|
window.Deflect.sessionId = script.sessionId;
|
|
79
267
|
}
|
|
80
268
|
const blobUrl = this.createScriptBlob(script.content);
|
|
81
|
-
|
|
269
|
+
let scriptElement = null;
|
|
82
270
|
try {
|
|
271
|
+
scriptElement = await this.loadScriptElement(blobUrl);
|
|
83
272
|
await this.waitForGetToken();
|
|
84
273
|
const token = await this.getTokenFromScript();
|
|
85
274
|
this.prefetchNextScript();
|
|
86
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;
|
|
87
291
|
} finally {
|
|
88
|
-
|
|
292
|
+
if (scriptElement) {
|
|
293
|
+
this.cleanup(blobUrl, scriptElement);
|
|
294
|
+
} else {
|
|
295
|
+
URL.revokeObjectURL(blobUrl);
|
|
296
|
+
}
|
|
89
297
|
}
|
|
90
298
|
}
|
|
91
299
|
prefetchNextScript() {
|
|
@@ -95,16 +303,16 @@
|
|
|
95
303
|
}
|
|
96
304
|
}
|
|
97
305
|
async waitForGetToken() {
|
|
98
|
-
return new Promise((resolve) => {
|
|
306
|
+
return new Promise((resolve, reject) => {
|
|
99
307
|
const checkInterval = setInterval(() => {
|
|
100
308
|
if (typeof window !== "undefined" && typeof window.Deflect?.getToken === "function") {
|
|
101
309
|
clearInterval(checkInterval);
|
|
102
310
|
resolve();
|
|
103
311
|
}
|
|
104
|
-
},
|
|
312
|
+
}, 50);
|
|
105
313
|
setTimeout(() => {
|
|
106
314
|
clearInterval(checkInterval);
|
|
107
|
-
|
|
315
|
+
reject(new Error("Timeout: getToken function did not become available within 10 seconds"));
|
|
108
316
|
}, 1e4);
|
|
109
317
|
});
|
|
110
318
|
}
|
|
@@ -139,46 +347,137 @@
|
|
|
139
347
|
delete window.Deflect.getToken;
|
|
140
348
|
}
|
|
141
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
|
+
*/
|
|
142
364
|
configure(params) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
window
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
this.
|
|
365
|
+
try {
|
|
366
|
+
if (!params.actionId?.trim()) {
|
|
367
|
+
throw new Error("actionId is required and cannot be empty");
|
|
368
|
+
}
|
|
369
|
+
if (this.config?.actionId === params.actionId) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
this.hasWarmupError = false;
|
|
373
|
+
this.scriptCache = null;
|
|
374
|
+
this.config = { ...params };
|
|
375
|
+
if (typeof window !== "undefined") {
|
|
376
|
+
window.Deflect.actionId = params.actionId;
|
|
377
|
+
}
|
|
378
|
+
if (!this.isTestMode()) {
|
|
379
|
+
this.tryWarmup();
|
|
380
|
+
}
|
|
381
|
+
} catch (error) {
|
|
382
|
+
const isUserError = error && typeof error === "object" && "isUserError" in error && error.isUserError === true;
|
|
383
|
+
this.reportError(
|
|
384
|
+
error,
|
|
385
|
+
{
|
|
386
|
+
deflect_sdk_error: "true",
|
|
387
|
+
deflect_user_error: isUserError ? "true" : "false",
|
|
388
|
+
method: "configure",
|
|
389
|
+
action_id: params?.actionId || "unknown"
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
actionId: params?.actionId,
|
|
393
|
+
hasCache: this.scriptCache !== null,
|
|
394
|
+
hasWarmupError: this.hasWarmupError
|
|
395
|
+
}
|
|
396
|
+
);
|
|
397
|
+
throw error;
|
|
157
398
|
}
|
|
158
399
|
}
|
|
159
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
|
+
}
|
|
160
404
|
return this.config?.actionId === "t/FFFFFFFFFFFFF/111111111" || this.config?.actionId === "t/FFFFFFFFFFFFF/000000000";
|
|
161
405
|
}
|
|
162
|
-
|
|
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
|
+
*/
|
|
163
410
|
async solveChallenge() {
|
|
164
411
|
return this.getToken();
|
|
165
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
|
+
*/
|
|
166
430
|
async getToken() {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
431
|
+
try {
|
|
432
|
+
if (!this.config?.actionId) {
|
|
433
|
+
throw new Error("Must call configure() before solveChallenge()");
|
|
434
|
+
}
|
|
435
|
+
if (this.isTestMode()) {
|
|
436
|
+
return "TESTTOKEN";
|
|
437
|
+
}
|
|
438
|
+
let script;
|
|
439
|
+
if (this.scriptCache && !this.isWarmupInProgress) {
|
|
440
|
+
script = this.scriptCache;
|
|
441
|
+
this.scriptCache = null;
|
|
442
|
+
} else {
|
|
443
|
+
script = await this.fetchScript();
|
|
444
|
+
}
|
|
445
|
+
return this.executeScript(script);
|
|
446
|
+
} catch (error) {
|
|
447
|
+
const isUserError = error && typeof error === "object" && "isUserError" in error && error.isUserError === true;
|
|
448
|
+
this.reportError(
|
|
449
|
+
error,
|
|
450
|
+
{
|
|
451
|
+
deflect_sdk_error: "true",
|
|
452
|
+
deflect_user_error: isUserError ? "true" : "false",
|
|
453
|
+
method: "getToken",
|
|
454
|
+
action_id: this.config?.actionId || "unknown",
|
|
455
|
+
has_cache: this.scriptCache !== null ? "true" : "false",
|
|
456
|
+
has_warmup_error: this.hasWarmupError ? "true" : "false",
|
|
457
|
+
stage: "call"
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
actionId: this.config?.actionId,
|
|
461
|
+
hasCache: this.scriptCache !== null,
|
|
462
|
+
hasWarmupError: this.hasWarmupError
|
|
463
|
+
}
|
|
464
|
+
);
|
|
465
|
+
throw error;
|
|
179
466
|
}
|
|
180
|
-
return this.executeScript(script);
|
|
181
467
|
}
|
|
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
|
+
*/
|
|
182
481
|
async warmup() {
|
|
183
482
|
if (!this.config?.actionId) {
|
|
184
483
|
return false;
|
|
@@ -190,13 +489,27 @@
|
|
|
190
489
|
return false;
|
|
191
490
|
}
|
|
192
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
|
+
*/
|
|
193
496
|
clearCache() {
|
|
194
497
|
this.scriptCache = null;
|
|
195
498
|
}
|
|
196
499
|
/**
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
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
|
+
* ```
|
|
200
513
|
*/
|
|
201
514
|
async injectToken(event) {
|
|
202
515
|
if (!event || !event.target || !(event.target instanceof HTMLFormElement)) {
|
|
@@ -214,7 +527,6 @@
|
|
|
214
527
|
hidden.value = token;
|
|
215
528
|
form.appendChild(hidden);
|
|
216
529
|
form.submit();
|
|
217
|
-
return false;
|
|
218
530
|
}
|
|
219
531
|
};
|
|
220
532
|
var DeflectInstance = new Deflect();
|
package/dist/index.min.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
"use strict";(()=>{var
|
|
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()}},w=new u;typeof window<"u"&&(window.Deflect=w);var P=w;})();
|
package/dist/pulse.d.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export type PulseEventType = "error" | "span" | "transaction" | "log" | "metric";
|
|
2
|
+
export type PulseLevel = "fatal" | "error" | "warning" | "info" | "debug";
|
|
3
|
+
export interface PulseReporterConfig {
|
|
4
|
+
endpoint: string;
|
|
5
|
+
projectId: string;
|
|
6
|
+
deploymentId: string;
|
|
7
|
+
environment?: string;
|
|
8
|
+
service?: string;
|
|
9
|
+
release?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface PulseSDKInfo {
|
|
12
|
+
name: string;
|
|
13
|
+
version: string;
|
|
14
|
+
language: string;
|
|
15
|
+
integrations?: string[];
|
|
16
|
+
}
|
|
17
|
+
export interface PulseStackFrame {
|
|
18
|
+
filename?: string;
|
|
19
|
+
abs_path?: string;
|
|
20
|
+
function?: string;
|
|
21
|
+
lineno?: number;
|
|
22
|
+
colno?: number;
|
|
23
|
+
in_app?: boolean;
|
|
24
|
+
context_line?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface PulseExceptionPayload {
|
|
27
|
+
type?: string;
|
|
28
|
+
value?: string;
|
|
29
|
+
stacktrace?: {
|
|
30
|
+
frames: PulseStackFrame[];
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export interface PulseRequestInfo {
|
|
34
|
+
method?: string;
|
|
35
|
+
url?: string;
|
|
36
|
+
headers?: Record<string, string>;
|
|
37
|
+
route?: string;
|
|
38
|
+
status_code?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface PulseRuntimeInfo {
|
|
41
|
+
name?: string;
|
|
42
|
+
version?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface PulseOSInfo {
|
|
45
|
+
name?: string;
|
|
46
|
+
version?: string;
|
|
47
|
+
}
|
|
48
|
+
export interface PulseDeviceInfo {
|
|
49
|
+
model?: string;
|
|
50
|
+
arch?: string;
|
|
51
|
+
}
|
|
52
|
+
export interface PulseErrorPayload {
|
|
53
|
+
level: PulseLevel;
|
|
54
|
+
message?: string;
|
|
55
|
+
exception?: PulseExceptionPayload;
|
|
56
|
+
culprit?: string;
|
|
57
|
+
request?: PulseRequestInfo;
|
|
58
|
+
runtime?: PulseRuntimeInfo;
|
|
59
|
+
os?: PulseOSInfo;
|
|
60
|
+
device?: PulseDeviceInfo;
|
|
61
|
+
}
|
|
62
|
+
export interface PulseEvent {
|
|
63
|
+
event_type: PulseEventType;
|
|
64
|
+
event_id: string;
|
|
65
|
+
deployment_id: string;
|
|
66
|
+
project_id: string;
|
|
67
|
+
environment?: string;
|
|
68
|
+
service?: string;
|
|
69
|
+
host?: string;
|
|
70
|
+
sdk?: PulseSDKInfo;
|
|
71
|
+
timestamp: string;
|
|
72
|
+
release?: string;
|
|
73
|
+
dist?: string;
|
|
74
|
+
deployment?: string;
|
|
75
|
+
tags?: Record<string, string>;
|
|
76
|
+
context?: Record<string, unknown>;
|
|
77
|
+
sample_rate?: number;
|
|
78
|
+
fingerprint?: string[];
|
|
79
|
+
ingest_key_id?: string;
|
|
80
|
+
payload_version?: number;
|
|
81
|
+
content_encoding?: string;
|
|
82
|
+
error?: PulseErrorPayload;
|
|
83
|
+
}
|
|
84
|
+
export interface PulseCaptureOptions {
|
|
85
|
+
tags?: Record<string, string>;
|
|
86
|
+
context?: Record<string, unknown>;
|
|
87
|
+
level?: PulseLevel;
|
|
88
|
+
}
|
|
89
|
+
export declare class PulseReporter {
|
|
90
|
+
private readonly config;
|
|
91
|
+
private readonly enabled;
|
|
92
|
+
constructor(config?: Partial<PulseReporterConfig>);
|
|
93
|
+
captureException(error: unknown, options?: PulseCaptureOptions): void;
|
|
94
|
+
private buildErrorEvent;
|
|
95
|
+
private normalizeError;
|
|
96
|
+
private buildExceptionPayload;
|
|
97
|
+
private parseStack;
|
|
98
|
+
}
|
|
99
|
+
export default PulseReporter;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deflectbot/deflect-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "SDK for deflect.bot - Use it for seamless captcha integration on any website.",
|
|
5
5
|
"main": "dist/index.min.js",
|
|
6
6
|
"module": "dist/index.esm.js",
|
|
@@ -24,13 +24,16 @@
|
|
|
24
24
|
"license": "MIT",
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@eslint/js": "^9.22.0",
|
|
27
|
+
"esbuild": "^0.23.0",
|
|
27
28
|
"eslint": "^9.22.0",
|
|
28
29
|
"globals": "^16.0.0",
|
|
29
30
|
"typescript": "^5.7.3",
|
|
30
|
-
"typescript-eslint": "^8.27.0"
|
|
31
|
-
"esbuild": "^0.23.0"
|
|
31
|
+
"typescript-eslint": "^8.27.0"
|
|
32
32
|
},
|
|
33
33
|
"publishConfig": {
|
|
34
34
|
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@sentry/browser": "^10.22.0"
|
|
35
38
|
}
|
|
36
39
|
}
|