@deflectbot/deflect-sdk 1.4.1-beta.1 → 1.4.1

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