@deflectbot/deflect-sdk 1.3.6 → 1.3.8
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 +44 -0
- package/dist/index.esm.js +302 -0
- package/dist/index.js +6873 -194
- package/dist/index.min.js +15 -1
- package/package.json +14 -4
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
interface DeflectConfig {
|
|
2
|
+
actionId: string;
|
|
3
|
+
scriptUrl?: string;
|
|
4
|
+
}
|
|
5
|
+
declare global {
|
|
6
|
+
interface Window {
|
|
7
|
+
Deflect: any;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
declare class Deflect {
|
|
11
|
+
private config;
|
|
12
|
+
private scriptCache;
|
|
13
|
+
private isWarmupInProgress;
|
|
14
|
+
private hasWarmupError;
|
|
15
|
+
private static readonly SDK_ERROR_MARKER;
|
|
16
|
+
constructor();
|
|
17
|
+
private initializeSentry;
|
|
18
|
+
private initializeGlobalState;
|
|
19
|
+
private setupAutomaticWarmup;
|
|
20
|
+
private tryWarmup;
|
|
21
|
+
private buildScriptUrl;
|
|
22
|
+
private fetchScript;
|
|
23
|
+
private executeScript;
|
|
24
|
+
private prefetchNextScript;
|
|
25
|
+
private waitForGetToken;
|
|
26
|
+
private createScriptBlob;
|
|
27
|
+
private loadScriptElement;
|
|
28
|
+
private getTokenFromScript;
|
|
29
|
+
private cleanup;
|
|
30
|
+
configure(params: DeflectConfig): void;
|
|
31
|
+
private isTestMode;
|
|
32
|
+
solveChallenge(): Promise<string>;
|
|
33
|
+
getToken(): Promise<string>;
|
|
34
|
+
warmup(): Promise<boolean>;
|
|
35
|
+
clearCache(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Inject a fresh token as a hidden input into a form. Only accepts a submit event from onsubmit.
|
|
38
|
+
* Usage: <form ... onsubmit="return Deflect.injectToken(event)">
|
|
39
|
+
* Returns false to prevent double submit.
|
|
40
|
+
*/
|
|
41
|
+
injectToken(event: SubmitEvent): Promise<false>;
|
|
42
|
+
}
|
|
43
|
+
declare const DeflectInstance: Deflect;
|
|
44
|
+
export default DeflectInstance;
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import * as Sentry from "@sentry/browser";
|
|
2
|
+
class Deflect {
|
|
3
|
+
constructor() {
|
|
4
|
+
this.config = null;
|
|
5
|
+
this.scriptCache = null;
|
|
6
|
+
this.isWarmupInProgress = false;
|
|
7
|
+
this.hasWarmupError = false;
|
|
8
|
+
this.initializeSentry();
|
|
9
|
+
this.initializeGlobalState();
|
|
10
|
+
this.setupAutomaticWarmup();
|
|
11
|
+
}
|
|
12
|
+
static {
|
|
13
|
+
this.SDK_ERROR_MARKER = "__DEFLECT_SDK_ERROR__";
|
|
14
|
+
}
|
|
15
|
+
initializeSentry() {
|
|
16
|
+
try {
|
|
17
|
+
Sentry.init({
|
|
18
|
+
dsn: "https://4a62ed75ab362d4694ae4215c2b4f621@o4509968565665792.ingest.de.sentry.io/4510257986469968",
|
|
19
|
+
sendDefaultPii: true,
|
|
20
|
+
// Enable to capture IP address
|
|
21
|
+
environment: typeof window !== "undefined" ? window.location.hostname : "unknown",
|
|
22
|
+
beforeSend(event) {
|
|
23
|
+
const isSDKError = event.tags?.deflect_sdk_error === "true";
|
|
24
|
+
if (!isSDKError) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
if (typeof window !== "undefined" && typeof navigator !== "undefined") {
|
|
28
|
+
event.contexts = event.contexts || {};
|
|
29
|
+
event.contexts.browser = {
|
|
30
|
+
name: navigator.userAgent,
|
|
31
|
+
version: navigator.appVersion
|
|
32
|
+
};
|
|
33
|
+
event.request = event.request || {};
|
|
34
|
+
event.request.headers = event.request.headers || {};
|
|
35
|
+
event.request.headers["User-Agent"] = navigator.userAgent;
|
|
36
|
+
event.contexts.page = {
|
|
37
|
+
url: window.location.href,
|
|
38
|
+
referrer: document.referrer,
|
|
39
|
+
title: document.title
|
|
40
|
+
};
|
|
41
|
+
event.contexts.device = {
|
|
42
|
+
screen_width: window.screen.width,
|
|
43
|
+
screen_height: window.screen.height,
|
|
44
|
+
viewport_width: window.innerWidth,
|
|
45
|
+
viewport_height: window.innerHeight,
|
|
46
|
+
language: navigator.language,
|
|
47
|
+
platform: navigator.platform
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return event;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
} catch {
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
initializeGlobalState() {
|
|
57
|
+
if (typeof window === "undefined") return;
|
|
58
|
+
window.Deflect = window.Deflect || {};
|
|
59
|
+
}
|
|
60
|
+
setupAutomaticWarmup() {
|
|
61
|
+
if (typeof window === "undefined") return;
|
|
62
|
+
if (document.readyState === "loading") {
|
|
63
|
+
document.addEventListener("DOMContentLoaded", () => this.tryWarmup());
|
|
64
|
+
} else {
|
|
65
|
+
setTimeout(() => this.tryWarmup(), 100);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async tryWarmup() {
|
|
69
|
+
if (!this.config?.actionId || this.isWarmupInProgress || this.scriptCache || this.hasWarmupError) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
this.isWarmupInProgress = true;
|
|
73
|
+
try {
|
|
74
|
+
this.scriptCache = await this.fetchScript();
|
|
75
|
+
this.hasWarmupError = false;
|
|
76
|
+
} catch {
|
|
77
|
+
this.hasWarmupError = true;
|
|
78
|
+
} finally {
|
|
79
|
+
this.isWarmupInProgress = false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
buildScriptUrl(actionId) {
|
|
83
|
+
const baseUrl = this.config?.scriptUrl || "https://js.deflect.bot/main.js";
|
|
84
|
+
const nonce = Date.now().toString();
|
|
85
|
+
return `${baseUrl}?action_id=${actionId}&_=${nonce}`;
|
|
86
|
+
}
|
|
87
|
+
async fetchScript() {
|
|
88
|
+
if (!this.config?.actionId) {
|
|
89
|
+
throw new Error("actionId is required");
|
|
90
|
+
}
|
|
91
|
+
const url = this.buildScriptUrl(this.config.actionId);
|
|
92
|
+
const response = await fetch(url, { cache: "no-store" });
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
throw new Error(`Failed to fetch script: ${response.status}`);
|
|
95
|
+
}
|
|
96
|
+
const content = await response.text();
|
|
97
|
+
try {
|
|
98
|
+
const maybeError = JSON.parse(content);
|
|
99
|
+
if (maybeError.success === false || maybeError.error) {
|
|
100
|
+
const errorMessage = maybeError.error || "Script fetch failed";
|
|
101
|
+
const error = new Error(errorMessage);
|
|
102
|
+
if (errorMessage === "action_does_not_exist" || errorMessage.includes("invalid action")) {
|
|
103
|
+
error.isUserError = true;
|
|
104
|
+
}
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
} catch (e) {
|
|
108
|
+
if (e instanceof SyntaxError) {
|
|
109
|
+
} else {
|
|
110
|
+
throw e;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
content,
|
|
115
|
+
sessionId: response.headers.get("session_id") || void 0
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
async executeScript(script) {
|
|
119
|
+
if (script.sessionId && typeof window !== "undefined") {
|
|
120
|
+
window.Deflect.sessionId = script.sessionId;
|
|
121
|
+
}
|
|
122
|
+
const blobUrl = this.createScriptBlob(script.content);
|
|
123
|
+
const scriptElement = await this.loadScriptElement(blobUrl);
|
|
124
|
+
try {
|
|
125
|
+
await this.waitForGetToken();
|
|
126
|
+
const token = await this.getTokenFromScript();
|
|
127
|
+
this.prefetchNextScript();
|
|
128
|
+
return token;
|
|
129
|
+
} finally {
|
|
130
|
+
this.cleanup(blobUrl, scriptElement);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
prefetchNextScript() {
|
|
134
|
+
if (!this.hasWarmupError) {
|
|
135
|
+
this.tryWarmup().catch(() => {
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async waitForGetToken() {
|
|
140
|
+
return new Promise((resolve) => {
|
|
141
|
+
const checkInterval = setInterval(() => {
|
|
142
|
+
if (typeof window !== "undefined" && typeof window.Deflect?.getToken === "function") {
|
|
143
|
+
clearInterval(checkInterval);
|
|
144
|
+
resolve();
|
|
145
|
+
}
|
|
146
|
+
}, 10);
|
|
147
|
+
setTimeout(() => {
|
|
148
|
+
clearInterval(checkInterval);
|
|
149
|
+
resolve();
|
|
150
|
+
}, 1e4);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
createScriptBlob(content) {
|
|
154
|
+
const blob = new Blob([content], { type: "text/javascript" });
|
|
155
|
+
return URL.createObjectURL(blob);
|
|
156
|
+
}
|
|
157
|
+
async loadScriptElement(blobUrl) {
|
|
158
|
+
return new Promise((resolve, reject) => {
|
|
159
|
+
const script = document.createElement("script");
|
|
160
|
+
script.type = "module";
|
|
161
|
+
script.src = blobUrl;
|
|
162
|
+
script.onload = () => resolve(script);
|
|
163
|
+
script.onerror = () => reject(new Error("Script failed to load"));
|
|
164
|
+
document.head.appendChild(script);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
async getTokenFromScript() {
|
|
168
|
+
if (typeof window === "undefined" || typeof window.Deflect?.getToken !== "function") {
|
|
169
|
+
throw new Error("Script did not load properly - getToken not available");
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
return await window.Deflect.getToken();
|
|
173
|
+
} catch (error) {
|
|
174
|
+
throw new Error(`Script execution failed: ${error}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
cleanup(blobUrl, scriptElement) {
|
|
178
|
+
URL.revokeObjectURL(blobUrl);
|
|
179
|
+
scriptElement.remove();
|
|
180
|
+
if (typeof window !== "undefined" && window.Deflect?.getToken) {
|
|
181
|
+
delete window.Deflect.getToken;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
configure(params) {
|
|
185
|
+
try {
|
|
186
|
+
if (!params.actionId?.trim()) {
|
|
187
|
+
throw new Error("actionId is required and cannot be empty");
|
|
188
|
+
}
|
|
189
|
+
if (this.config?.actionId === params.actionId) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
this.hasWarmupError = false;
|
|
193
|
+
this.scriptCache = null;
|
|
194
|
+
this.config = { ...params };
|
|
195
|
+
if (typeof window !== "undefined") {
|
|
196
|
+
window.Deflect.actionId = params.actionId;
|
|
197
|
+
}
|
|
198
|
+
if (!this.isTestMode()) {
|
|
199
|
+
this.tryWarmup();
|
|
200
|
+
}
|
|
201
|
+
} catch (error) {
|
|
202
|
+
const isUserError = error && typeof error === "object" && "isUserError" in error && error.isUserError === true;
|
|
203
|
+
if (!isUserError) {
|
|
204
|
+
Sentry.captureException(error, {
|
|
205
|
+
tags: {
|
|
206
|
+
deflect_sdk_error: "true",
|
|
207
|
+
method: "configure",
|
|
208
|
+
actionId: params?.actionId || "unknown"
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
isTestMode() {
|
|
216
|
+
return this.config?.actionId === "t/FFFFFFFFFFFFF/111111111" || this.config?.actionId === "t/FFFFFFFFFFFFF/000000000";
|
|
217
|
+
}
|
|
218
|
+
// Deprecated name kept for backward compatibility
|
|
219
|
+
async solveChallenge() {
|
|
220
|
+
return this.getToken();
|
|
221
|
+
}
|
|
222
|
+
async getToken() {
|
|
223
|
+
try {
|
|
224
|
+
if (!this.config?.actionId) {
|
|
225
|
+
throw new Error("Must call configure() before solveChallenge()");
|
|
226
|
+
}
|
|
227
|
+
if (this.isTestMode()) {
|
|
228
|
+
return "TESTTOKEN";
|
|
229
|
+
}
|
|
230
|
+
if (this.config.actionId === "SENTRY_TEST") {
|
|
231
|
+
throw new Error("SENTRY_TEST: This is a test error to verify Sentry integration is working");
|
|
232
|
+
}
|
|
233
|
+
let script;
|
|
234
|
+
if (this.scriptCache && !this.isWarmupInProgress) {
|
|
235
|
+
script = this.scriptCache;
|
|
236
|
+
this.scriptCache = null;
|
|
237
|
+
} else {
|
|
238
|
+
script = await this.fetchScript();
|
|
239
|
+
}
|
|
240
|
+
return this.executeScript(script);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
const isUserError = error && typeof error === "object" && "isUserError" in error && error.isUserError === true;
|
|
243
|
+
if (!isUserError) {
|
|
244
|
+
Sentry.captureException(error, {
|
|
245
|
+
tags: {
|
|
246
|
+
deflect_sdk_error: "true",
|
|
247
|
+
method: "getToken",
|
|
248
|
+
actionId: this.config?.actionId || "unknown",
|
|
249
|
+
hasCache: this.scriptCache !== null,
|
|
250
|
+
hasWarmupError: this.hasWarmupError
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
async warmup() {
|
|
258
|
+
if (!this.config?.actionId) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
await this.tryWarmup();
|
|
263
|
+
return this.scriptCache !== null;
|
|
264
|
+
} catch {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
clearCache() {
|
|
269
|
+
this.scriptCache = null;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Inject a fresh token as a hidden input into a form. Only accepts a submit event from onsubmit.
|
|
273
|
+
* Usage: <form ... onsubmit="return Deflect.injectToken(event)">
|
|
274
|
+
* Returns false to prevent double submit.
|
|
275
|
+
*/
|
|
276
|
+
async injectToken(event) {
|
|
277
|
+
if (!event || !event.target || !(event.target instanceof HTMLFormElement)) {
|
|
278
|
+
throw new Error("injectToken: must be called from a form submit event");
|
|
279
|
+
}
|
|
280
|
+
event.preventDefault();
|
|
281
|
+
const form = event.target;
|
|
282
|
+
const token = await this.getToken();
|
|
283
|
+
Array.from(form.querySelectorAll('input[name="deflect_token"]')).forEach(
|
|
284
|
+
(el) => el.remove()
|
|
285
|
+
);
|
|
286
|
+
const hidden = document.createElement("input");
|
|
287
|
+
hidden.type = "hidden";
|
|
288
|
+
hidden.name = "deflect_token";
|
|
289
|
+
hidden.value = token;
|
|
290
|
+
form.appendChild(hidden);
|
|
291
|
+
form.submit();
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const DeflectInstance = new Deflect();
|
|
296
|
+
if (typeof window !== "undefined") {
|
|
297
|
+
window.Deflect = DeflectInstance;
|
|
298
|
+
}
|
|
299
|
+
var src_default = DeflectInstance;
|
|
300
|
+
export {
|
|
301
|
+
src_default as default
|
|
302
|
+
};
|