@deflectbot/deflect-sdk 1.4.0 → 1.4.1-beta.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 CHANGED
@@ -12,6 +12,7 @@ declare class Deflect {
12
12
  private scriptCache;
13
13
  private isWarmupInProgress;
14
14
  private hasWarmupError;
15
+ private warmupPromise;
15
16
  private pulseReporter;
16
17
  constructor();
17
18
  private initializeGlobalState;
@@ -19,94 +20,14 @@ declare class Deflect {
19
20
  private resolvePulseConfig;
20
21
  private reportError;
21
22
  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
23
  private validateActionId;
28
- private buildScriptUrl;
29
- private fetchScript;
30
- private executeScript;
31
24
  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
25
  private isTestMode;
53
- /**
54
- * @deprecated Use {@link getToken} instead. This method is kept for backward compatibility.
55
- * @returns A promise that resolves to the Deflect token string
56
- */
57
- solveChallenge(): Promise<string>;
58
- /**
59
- * Retrieves a Deflect token for the configured action.
60
- * The token should be included in requests to your protected endpoints.
61
- *
62
- * @returns A promise that resolves to the Deflect token string
63
- * @throws Error if configure() has not been called first
64
- * @throws Error if the script fails to load or execute
65
- *
66
- * @example
67
- * ```typescript
68
- * const token = await Deflect.getToken();
69
- * // Include token in your API request
70
- * fetch('/api/protected', {
71
- * headers: { 'X-Deflect-Token': token }
72
- * });
73
- * ```
74
- */
26
+ configure(params: DeflectConfig): void;
75
27
  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
- */
28
+ private executeScript;
89
29
  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
30
  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
31
  injectToken(event: SubmitEvent): Promise<void>;
111
32
  }
112
33
  declare const DeflectInstance: Deflect;
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();
@@ -35,21 +38,21 @@ class Deflect {
35
38
  if (!this.config?.actionId || this.isWarmupInProgress || this.scriptCache || this.hasWarmupError) {
36
39
  return;
37
40
  }
41
+ this.validateActionId(this.config.actionId);
38
42
  this.isWarmupInProgress = true;
39
- try {
40
- this.scriptCache = await this.fetchScript();
41
- this.hasWarmupError = false;
42
- } catch {
43
- this.hasWarmupError = true;
44
- } finally {
45
- this.isWarmupInProgress = false;
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,167 +61,24 @@ 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
65
  if (!this.hasWarmupError) {
154
66
  this.tryWarmup().catch(() => {
155
67
  });
156
68
  }
157
69
  }
158
- async waitForGetToken() {
159
- return new Promise((resolve, reject) => {
160
- const checkInterval = setInterval(() => {
161
- if (typeof window !== "undefined" && typeof window.Deflect?.getToken === "function") {
162
- clearInterval(checkInterval);
163
- resolve();
164
- }
165
- }, 50);
166
- setTimeout(() => {
167
- clearInterval(checkInterval);
168
- reject(new Error("Timeout: getToken function did not become available within 10 seconds"));
169
- }, 1e4);
170
- });
171
- }
172
- createScriptBlob(content) {
173
- const blob = new Blob([content], { type: "text/javascript" });
174
- return URL.createObjectURL(blob);
175
- }
176
- async loadScriptElement(blobUrl) {
177
- return new Promise((resolve, reject) => {
178
- const script = document.createElement("script");
179
- script.type = "module";
180
- script.src = blobUrl;
181
- script.onload = () => resolve(script);
182
- script.onerror = () => reject(new Error("Script failed to load"));
183
- document.head.appendChild(script);
184
- });
185
- }
186
- async getTokenFromScript() {
187
- if (typeof window === "undefined" || typeof window.Deflect?.getToken !== "function") {
188
- throw new Error("Script did not load properly - getToken not available");
189
- }
190
- try {
191
- return await window.Deflect.getToken();
192
- } catch (error) {
193
- throw new Error(`Script execution failed: ${error}`);
194
- }
195
- }
196
- cleanup(blobUrl, scriptElement) {
197
- URL.revokeObjectURL(blobUrl);
198
- scriptElement.remove();
199
- if (typeof window !== "undefined" && window.Deflect?.getToken) {
200
- delete window.Deflect.getToken;
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
  }
81
+ this.validateActionId(params.actionId);
222
82
  if (this.config?.actionId === params.actionId) {
223
83
  return;
224
84
  }
@@ -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 solveChallenge()");
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.fetchScript();
131
+ script = await fetchScript(this.config.actionId, this.config.scriptUrl);
297
132
  }
298
133
  return this.executeScript(script);
299
134
  } catch (error) {
@@ -318,19 +153,41 @@ class Deflect {
318
153
  throw error;
319
154
  }
320
155
  }
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
- */
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;
@@ -342,28 +199,9 @@ class Deflect {
342
199
  return false;
343
200
  }
344
201
  }
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
202
  clearCache() {
350
203
  this.scriptCache = null;
351
204
  }
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
205
  async injectToken(event) {
368
206
  if (!event || !event.target || !(event.target instanceof HTMLFormElement)) {
369
207
  throw new Error("injectToken: must be called from a form submit event");
package/dist/index.js CHANGED
@@ -142,6 +142,97 @@
142
142
  };
143
143
  var pulse_default = PulseReporter;
144
144
 
145
+ // src/scriptClient.ts
146
+ function buildScriptUrl(actionId, scriptUrl) {
147
+ const baseUrl = scriptUrl || "https://js.deflect.bot/main.js";
148
+ const nonce = Date.now().toString();
149
+ return `${baseUrl}?action_id=${encodeURIComponent(actionId)}&_=${nonce}`;
150
+ }
151
+ __name(buildScriptUrl, "buildScriptUrl");
152
+ async function fetchScript(actionId, scriptUrl) {
153
+ const url = buildScriptUrl(actionId, scriptUrl);
154
+ const response = await fetch(url, {
155
+ cache: "no-store",
156
+ mode: "cors",
157
+ credentials: "omit"
158
+ });
159
+ const content = await response.text();
160
+ const error = parseErrorJson(content);
161
+ if (error) throw error;
162
+ return {
163
+ content,
164
+ sessionId: response.headers.get("session_id") || void 0
165
+ };
166
+ }
167
+ __name(fetchScript, "fetchScript");
168
+ function parseErrorJson(content) {
169
+ try {
170
+ const maybeError = JSON.parse(content);
171
+ if (maybeError.success === false || maybeError.error) {
172
+ const errorMessage = maybeError.error || "Script fetch failed";
173
+ const error = new Error(errorMessage);
174
+ if (errorMessage === "action_does_not_exist" || errorMessage.includes("invalid action")) {
175
+ error.isUserError = true;
176
+ }
177
+ return error;
178
+ }
179
+ } catch (err) {
180
+ if (!(err instanceof SyntaxError)) {
181
+ throw err;
182
+ }
183
+ }
184
+ return null;
185
+ }
186
+ __name(parseErrorJson, "parseErrorJson");
187
+
188
+ // src/scriptRunner.ts
189
+ function createBlobUrl(content) {
190
+ const blob = new Blob([content], { type: "text/javascript" });
191
+ return URL.createObjectURL(blob);
192
+ }
193
+ __name(createBlobUrl, "createBlobUrl");
194
+ function loadModuleScript(blobUrl) {
195
+ return new Promise((resolve, reject) => {
196
+ const script = document.createElement("script");
197
+ script.type = "module";
198
+ script.src = blobUrl;
199
+ script.onload = () => resolve(script);
200
+ script.onerror = () => reject(new Error("Script failed to load"));
201
+ document.head.appendChild(script);
202
+ });
203
+ }
204
+ __name(loadModuleScript, "loadModuleScript");
205
+ function waitForGlobalFunction(fnName, timeout = 1e4) {
206
+ return new Promise((resolve, reject) => {
207
+ const checkInterval = setInterval(() => {
208
+ if (typeof window !== "undefined" && typeof window.Deflect?.[fnName] === "function") {
209
+ clearInterval(checkInterval);
210
+ resolve();
211
+ }
212
+ }, 50);
213
+ setTimeout(() => {
214
+ clearInterval(checkInterval);
215
+ reject(new Error(`Timeout: ${fnName} did not become available within ${timeout / 1e3} seconds`));
216
+ }, timeout);
217
+ });
218
+ }
219
+ __name(waitForGlobalFunction, "waitForGlobalFunction");
220
+ async function getTokenFromGlobal(fnName) {
221
+ if (typeof window === "undefined" || typeof window.Deflect?.[fnName] !== "function") {
222
+ throw new Error(`${fnName} not available on window.Deflect`);
223
+ }
224
+ return window.Deflect[fnName]();
225
+ }
226
+ __name(getTokenFromGlobal, "getTokenFromGlobal");
227
+ function cleanup(blobUrl, scriptElement, fnName) {
228
+ URL.revokeObjectURL(blobUrl);
229
+ scriptElement.remove();
230
+ if (typeof window !== "undefined" && window.Deflect?.[fnName]) {
231
+ delete window.Deflect[fnName];
232
+ }
233
+ }
234
+ __name(cleanup, "cleanup");
235
+
145
236
  // src/index.ts
146
237
  var Deflect = class {
147
238
  constructor() {
@@ -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();
@@ -182,21 +274,21 @@
182
274
  if (!this.config?.actionId || this.isWarmupInProgress || this.scriptCache || this.hasWarmupError) {
183
275
  return;
184
276
  }
277
+ this.validateActionId(this.config.actionId);
185
278
  this.isWarmupInProgress = true;
186
- try {
187
- this.scriptCache = await this.fetchScript();
188
- this.hasWarmupError = false;
189
- } catch {
190
- this.hasWarmupError = true;
191
- } finally {
192
- this.isWarmupInProgress = false;
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,167 +297,24 @@
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
301
  if (!this.hasWarmupError) {
301
302
  this.tryWarmup().catch(() => {
302
303
  });
303
304
  }
304
305
  }
305
- async waitForGetToken() {
306
- return new Promise((resolve, reject) => {
307
- const checkInterval = setInterval(() => {
308
- if (typeof window !== "undefined" && typeof window.Deflect?.getToken === "function") {
309
- clearInterval(checkInterval);
310
- resolve();
311
- }
312
- }, 50);
313
- setTimeout(() => {
314
- clearInterval(checkInterval);
315
- reject(new Error("Timeout: getToken function did not become available within 10 seconds"));
316
- }, 1e4);
317
- });
318
- }
319
- createScriptBlob(content) {
320
- const blob = new Blob([content], { type: "text/javascript" });
321
- return URL.createObjectURL(blob);
322
- }
323
- async loadScriptElement(blobUrl) {
324
- return new Promise((resolve, reject) => {
325
- const script = document.createElement("script");
326
- script.type = "module";
327
- script.src = blobUrl;
328
- script.onload = () => resolve(script);
329
- script.onerror = () => reject(new Error("Script failed to load"));
330
- document.head.appendChild(script);
331
- });
332
- }
333
- async getTokenFromScript() {
334
- if (typeof window === "undefined" || typeof window.Deflect?.getToken !== "function") {
335
- throw new Error("Script did not load properly - getToken not available");
336
- }
337
- try {
338
- return await window.Deflect.getToken();
339
- } catch (error) {
340
- throw new Error(`Script execution failed: ${error}`);
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
  }
317
+ this.validateActionId(params.actionId);
369
318
  if (this.config?.actionId === params.actionId) {
370
319
  return;
371
320
  }
@@ -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 solveChallenge()");
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.fetchScript();
367
+ script = await fetchScript(this.config.actionId, this.config.scriptUrl);
444
368
  }
445
369
  return this.executeScript(script);
446
370
  } catch (error) {
@@ -465,19 +389,41 @@
465
389
  throw error;
466
390
  }
467
391
  }
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
- */
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;
@@ -489,28 +435,9 @@
489
435
  return false;
490
436
  }
491
437
  }
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
438
  clearCache() {
497
439
  this.scriptCache = null;
498
440
  }
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
441
  async injectToken(event) {
515
442
  if (!event || !event.target || !(event.target instanceof HTMLFormElement)) {
516
443
  throw new Error("injectToken: must be called from a form submit event");
package/dist/index.min.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";(()=>{var d={endpoint:"https://service.yabadabado.top/pulse",projectId:"deflect-sdk",deploymentId:"688529b539803661332b3f70",service:"deflect-sdk",release:"unknown"},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;})();
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.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.tryWarmup().catch(()=>{})}isTestMode(){if(this.config?.actionId==="PULSE_TEST"||this.config?.actionId==="SENTRY_TEST")throw new Error("PULSE_TEST: This is a test error to verify Pulse integration is working");return this.config?.actionId==="t/FFFFFFFFFFFFF/111111111"||this.config?.actionId==="t/FFFFFFFFFFFFF/000000000"}configure(e){try{if(!e.actionId?.trim())throw new Error("actionId is required and cannot be empty");if(this.validateActionId(e.actionId),this.config?.actionId===e.actionId)return;this.hasWarmupError=!1,this.scriptCache=null,this.config={...e},typeof window<"u"&&(window.Deflect.actionId=e.actionId),this.isTestMode()||this.tryWarmup()}catch(t){let n=t&&typeof t=="object"&&"isUserError"in t&&t.isUserError===!0;throw this.reportError(t,{deflect_sdk_error:"true",deflect_user_error:n?"true":"false",method:"configure",action_id:e?.actionId||"unknown"},{actionId:e?.actionId,hasCache:this.scriptCache!==null,hasWarmupError:this.hasWarmupError}),t}}async getToken(){try{if(!this.config?.actionId)throw new Error("Must call configure() before getToken()");if(this.validateActionId(this.config.actionId),this.isTestMode())return"TESTTOKEN";let e;return this.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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deflectbot/deflect-sdk",
3
- "version": "1.4.0",
3
+ "version": "1.4.1-beta.2",
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",