@deflectbot/deflect-sdk 1.4.1 → 1.4.4-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -25
- package/package.json +1 -1
- package/dist/index.d.ts +0 -113
- package/dist/index.esm.js +0 -388
- package/dist/index.js +0 -533
- package/dist/index.min.js +0 -2
- package/dist/pulse.d.ts +0 -99
package/README.md
CHANGED
|
@@ -31,48 +31,52 @@ Use `@deflect-sdk@latest` to always use the latest version, or specify a version
|
|
|
31
31
|
<script src="https://cdn.jsdelivr.net/npm/@deflectbot/deflect-sdk@latest/dist/index.min.js"></script>
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
##
|
|
34
|
+
## Usage (instance-based)
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
#### One-time global initialization.
|
|
36
|
+
The SDK uses lightweight clients so you can create multiple independent configs (one per actionId) without global state conflicts.
|
|
39
37
|
|
|
40
38
|
```ts
|
|
41
|
-
Deflect
|
|
39
|
+
import Deflect, { DeflectConfig } from "@deflectbot/deflect-sdk";
|
|
40
|
+
|
|
41
|
+
const client = Deflect.createClient({
|
|
42
42
|
actionId: "<ACTION_ID>",
|
|
43
|
-
|
|
43
|
+
// optional
|
|
44
|
+
scriptUrl: "https://custom-host/main.js", // overrides script origin
|
|
45
|
+
prefetch: true, // default
|
|
46
|
+
extraArgs: { debug: true }, // appended as query params => &debug=true
|
|
47
|
+
} satisfies DeflectConfig);
|
|
48
|
+
|
|
49
|
+
const token = await client.getToken();
|
|
44
50
|
```
|
|
45
51
|
|
|
46
|
-
|
|
52
|
+
### Multiple clients on the same page
|
|
47
53
|
|
|
48
54
|
```ts
|
|
49
|
-
const
|
|
50
|
-
|
|
55
|
+
const loginClient = Deflect.createClient({ actionId: "login" });
|
|
56
|
+
const signupClient = Deflect.createClient({ actionId: "signup" });
|
|
51
57
|
|
|
52
|
-
|
|
58
|
+
const loginToken = await loginClient.getToken();
|
|
59
|
+
const signupToken = await signupClient.getToken();
|
|
60
|
+
```
|
|
53
61
|
|
|
54
|
-
|
|
62
|
+
### Form helper with an instance
|
|
55
63
|
|
|
56
64
|
```html
|
|
57
|
-
<form action="/login" method="POST">
|
|
65
|
+
<form action="/login" method="POST" onsubmit="return loginClient.injectToken(event)">
|
|
58
66
|
<input name="username" />
|
|
59
67
|
<input name="password" type="password" />
|
|
60
68
|
<button type="submit">Login</button>
|
|
61
69
|
</form>
|
|
70
|
+
|
|
71
|
+
<script type="module">
|
|
72
|
+
import Deflect from "https://cdn.jsdelivr.net/npm/@deflectbot/deflect-sdk@latest/dist/index.esm.js";
|
|
73
|
+
const loginClient = Deflect.createClient({ actionId: "login" });
|
|
74
|
+
window.loginClient = loginClient;
|
|
75
|
+
</script>
|
|
62
76
|
```
|
|
63
77
|
|
|
64
|
-
|
|
78
|
+
`injectToken(event)` fetches a token, injects it as a hidden `deflect_token` input, and submits the form.
|
|
65
79
|
|
|
66
|
-
|
|
67
|
-
<form
|
|
68
|
-
action="/login"
|
|
69
|
-
method="POST"
|
|
70
|
-
onsubmit="return Deflect.injectToken(event)"
|
|
71
|
-
>
|
|
72
|
-
<input name="username" />
|
|
73
|
-
<input name="password" type="password" />
|
|
74
|
-
<button type="submit">Login</button>
|
|
75
|
-
</form>
|
|
76
|
-
```
|
|
80
|
+
### Legacy global (still available, but prefer clients)
|
|
77
81
|
|
|
78
|
-
`
|
|
82
|
+
The previous `Deflect.configure`/`Deflect.getToken` singleton still works for single-action setups, but the recommended approach is to create per-action clients as shown above.
|
package/package.json
CHANGED
package/dist/index.d.ts
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
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 pulseReporter;
|
|
16
|
-
constructor();
|
|
17
|
-
private initializeGlobalState;
|
|
18
|
-
private setupAutomaticWarmup;
|
|
19
|
-
private resolvePulseConfig;
|
|
20
|
-
private reportError;
|
|
21
|
-
private tryWarmup;
|
|
22
|
-
/**
|
|
23
|
-
* @param actionId - The action ID to validate
|
|
24
|
-
* @returns The sanitized action ID
|
|
25
|
-
* @throws Error if actionId contains invalid characters
|
|
26
|
-
*/
|
|
27
|
-
private validateActionId;
|
|
28
|
-
private buildScriptUrl;
|
|
29
|
-
private fetchScript;
|
|
30
|
-
private executeScript;
|
|
31
|
-
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
|
-
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
|
-
*/
|
|
75
|
-
getToken(): Promise<string>;
|
|
76
|
-
/**
|
|
77
|
-
* Pre-fetches the challenge script to reduce latency on the first getToken() call.
|
|
78
|
-
* This is automatically called after configure(), but can be manually triggered.
|
|
79
|
-
*
|
|
80
|
-
* @returns A promise that resolves to true if warmup succeeded, false otherwise
|
|
81
|
-
*
|
|
82
|
-
* @example
|
|
83
|
-
* ```typescript
|
|
84
|
-
* Deflect.configure({ actionId: "your-action-id" });
|
|
85
|
-
* const warmedUp = await Deflect.warmup();
|
|
86
|
-
* console.log('Script pre-cached:', warmedUp);
|
|
87
|
-
* ```
|
|
88
|
-
*/
|
|
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
|
-
*/
|
|
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
|
-
*/
|
|
110
|
-
injectToken(event: SubmitEvent): Promise<void>;
|
|
111
|
-
}
|
|
112
|
-
declare const _default: Deflect;
|
|
113
|
-
export default _default;
|
package/dist/index.esm.js
DELETED
|
@@ -1,388 +0,0 @@
|
|
|
1
|
-
import PulseReporter from "./pulse";
|
|
2
|
-
class Deflect {
|
|
3
|
-
constructor() {
|
|
4
|
-
this.config = null;
|
|
5
|
-
this.scriptCache = null;
|
|
6
|
-
this.isWarmupInProgress = false;
|
|
7
|
-
this.hasWarmupError = false;
|
|
8
|
-
this.initializeGlobalState();
|
|
9
|
-
this.pulseReporter = new PulseReporter(this.resolvePulseConfig());
|
|
10
|
-
this.setupAutomaticWarmup();
|
|
11
|
-
}
|
|
12
|
-
initializeGlobalState() {
|
|
13
|
-
if (typeof window === "undefined") return;
|
|
14
|
-
window.Deflect = window.Deflect || {};
|
|
15
|
-
}
|
|
16
|
-
setupAutomaticWarmup() {
|
|
17
|
-
if (typeof window === "undefined") return;
|
|
18
|
-
if (document.readyState === "loading") {
|
|
19
|
-
document.addEventListener("DOMContentLoaded", () => this.tryWarmup());
|
|
20
|
-
} else {
|
|
21
|
-
setTimeout(() => this.tryWarmup(), 100);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
resolvePulseConfig() {
|
|
25
|
-
const runtimeConfig = typeof window !== "undefined" && typeof window.Deflect === "object" && window.Deflect?.pulseConfig && typeof window.Deflect.pulseConfig === "object" ? window.Deflect.pulseConfig : {};
|
|
26
|
-
return {
|
|
27
|
-
...runtimeConfig,
|
|
28
|
-
environment: runtimeConfig.environment || (typeof window !== "undefined" ? window.location.hostname : void 0)
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
reportError(error, tags, context) {
|
|
32
|
-
this.pulseReporter.captureException(error, { tags, context });
|
|
33
|
-
}
|
|
34
|
-
async tryWarmup() {
|
|
35
|
-
if (!this.config?.actionId || this.isWarmupInProgress || this.scriptCache || this.hasWarmupError) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
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
|
-
}
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* @param actionId - The action ID to validate
|
|
50
|
-
* @returns The sanitized action ID
|
|
51
|
-
* @throws Error if actionId contains invalid characters
|
|
52
|
-
*/
|
|
53
|
-
validateActionId(actionId) {
|
|
54
|
-
const sanitized = actionId.trim();
|
|
55
|
-
const validPattern = /^[a-zA-Z0-9/_-]+$/;
|
|
56
|
-
if (!validPattern.test(sanitized)) {
|
|
57
|
-
throw new Error("Invalid actionId format: contains disallowed characters");
|
|
58
|
-
}
|
|
59
|
-
return encodeURIComponent(sanitized);
|
|
60
|
-
}
|
|
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
|
-
prefetchNextScript() {
|
|
153
|
-
if (!this.hasWarmupError) {
|
|
154
|
-
this.tryWarmup().catch(() => {
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
}
|
|
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;
|
|
201
|
-
}
|
|
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
|
-
*/
|
|
217
|
-
configure(params) {
|
|
218
|
-
try {
|
|
219
|
-
if (!params.actionId?.trim()) {
|
|
220
|
-
throw new Error("actionId is required and cannot be empty");
|
|
221
|
-
}
|
|
222
|
-
if (this.config?.actionId === params.actionId) {
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
this.hasWarmupError = false;
|
|
226
|
-
this.scriptCache = null;
|
|
227
|
-
this.config = { ...params };
|
|
228
|
-
if (typeof window !== "undefined") {
|
|
229
|
-
window.Deflect.actionId = params.actionId;
|
|
230
|
-
}
|
|
231
|
-
if (!this.isTestMode()) {
|
|
232
|
-
this.tryWarmup();
|
|
233
|
-
}
|
|
234
|
-
} catch (error) {
|
|
235
|
-
const isUserError = error && typeof error === "object" && "isUserError" in error && error.isUserError === true;
|
|
236
|
-
this.reportError(
|
|
237
|
-
error,
|
|
238
|
-
{
|
|
239
|
-
deflect_sdk_error: "true",
|
|
240
|
-
deflect_user_error: isUserError ? "true" : "false",
|
|
241
|
-
method: "configure",
|
|
242
|
-
action_id: params?.actionId || "unknown"
|
|
243
|
-
},
|
|
244
|
-
{
|
|
245
|
-
actionId: params?.actionId,
|
|
246
|
-
hasCache: this.scriptCache !== null,
|
|
247
|
-
hasWarmupError: this.hasWarmupError
|
|
248
|
-
}
|
|
249
|
-
);
|
|
250
|
-
throw error;
|
|
251
|
-
}
|
|
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
|
-
*/
|
|
283
|
-
async getToken() {
|
|
284
|
-
try {
|
|
285
|
-
if (!this.config?.actionId) {
|
|
286
|
-
throw new Error("Must call configure() before solveChallenge()");
|
|
287
|
-
}
|
|
288
|
-
if (this.isTestMode()) {
|
|
289
|
-
return "TESTTOKEN";
|
|
290
|
-
}
|
|
291
|
-
let script;
|
|
292
|
-
if (this.scriptCache && !this.isWarmupInProgress) {
|
|
293
|
-
script = this.scriptCache;
|
|
294
|
-
this.scriptCache = null;
|
|
295
|
-
} else {
|
|
296
|
-
script = await this.fetchScript();
|
|
297
|
-
}
|
|
298
|
-
return this.executeScript(script);
|
|
299
|
-
} catch (error) {
|
|
300
|
-
const isUserError = error && typeof error === "object" && "isUserError" in error && error.isUserError === true;
|
|
301
|
-
this.reportError(
|
|
302
|
-
error,
|
|
303
|
-
{
|
|
304
|
-
deflect_sdk_error: "true",
|
|
305
|
-
deflect_user_error: isUserError ? "true" : "false",
|
|
306
|
-
method: "getToken",
|
|
307
|
-
action_id: this.config?.actionId || "unknown",
|
|
308
|
-
has_cache: this.scriptCache !== null ? "true" : "false",
|
|
309
|
-
has_warmup_error: this.hasWarmupError ? "true" : "false",
|
|
310
|
-
stage: "call"
|
|
311
|
-
},
|
|
312
|
-
{
|
|
313
|
-
actionId: this.config?.actionId,
|
|
314
|
-
hasCache: this.scriptCache !== null,
|
|
315
|
-
hasWarmupError: this.hasWarmupError
|
|
316
|
-
}
|
|
317
|
-
);
|
|
318
|
-
throw error;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
/**
|
|
322
|
-
* Pre-fetches the challenge script to reduce latency on the first getToken() call.
|
|
323
|
-
* This is automatically called after configure(), but can be manually triggered.
|
|
324
|
-
*
|
|
325
|
-
* @returns A promise that resolves to true if warmup succeeded, false otherwise
|
|
326
|
-
*
|
|
327
|
-
* @example
|
|
328
|
-
* ```typescript
|
|
329
|
-
* Deflect.configure({ actionId: "your-action-id" });
|
|
330
|
-
* const warmedUp = await Deflect.warmup();
|
|
331
|
-
* console.log('Script pre-cached:', warmedUp);
|
|
332
|
-
* ```
|
|
333
|
-
*/
|
|
334
|
-
async warmup() {
|
|
335
|
-
if (!this.config?.actionId) {
|
|
336
|
-
return false;
|
|
337
|
-
}
|
|
338
|
-
try {
|
|
339
|
-
await this.tryWarmup();
|
|
340
|
-
return this.scriptCache !== null;
|
|
341
|
-
} catch {
|
|
342
|
-
return false;
|
|
343
|
-
}
|
|
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
|
-
*/
|
|
349
|
-
clearCache() {
|
|
350
|
-
this.scriptCache = null;
|
|
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
|
-
*/
|
|
367
|
-
async injectToken(event) {
|
|
368
|
-
if (!event || !event.target || !(event.target instanceof HTMLFormElement)) {
|
|
369
|
-
throw new Error("injectToken: must be called from a form submit event");
|
|
370
|
-
}
|
|
371
|
-
event.preventDefault();
|
|
372
|
-
const form = event.target;
|
|
373
|
-
const token = await this.getToken();
|
|
374
|
-
Array.from(form.querySelectorAll('input[name="deflect_token"]')).forEach(
|
|
375
|
-
(el) => el.remove()
|
|
376
|
-
);
|
|
377
|
-
const hidden = document.createElement("input");
|
|
378
|
-
hidden.type = "hidden";
|
|
379
|
-
hidden.name = "deflect_token";
|
|
380
|
-
hidden.value = token;
|
|
381
|
-
form.appendChild(hidden);
|
|
382
|
-
form.submit();
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
var src_default = new Deflect();
|
|
386
|
-
export {
|
|
387
|
-
src_default as default
|
|
388
|
-
};
|
package/dist/index.js
DELETED
|
@@ -1,533 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
(() => {
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
5
|
-
|
|
6
|
-
// src/pulse.ts
|
|
7
|
-
var REPORT_CONFIG = {
|
|
8
|
-
endpoint: "https://service.yabadabado.top/pulse",
|
|
9
|
-
projectId: "deflect-sdk",
|
|
10
|
-
deploymentId: "688529b539803661332b3f70",
|
|
11
|
-
service: "deflect-sdk",
|
|
12
|
-
release: "unknown"
|
|
13
|
-
};
|
|
14
|
-
var PULSE_SDK_INFO = {
|
|
15
|
-
name: "deflect-js-sdk",
|
|
16
|
-
version: REPORT_CONFIG.release || "unknown",
|
|
17
|
-
language: "javascript"
|
|
18
|
-
};
|
|
19
|
-
var PulseReporter = class {
|
|
20
|
-
static {
|
|
21
|
-
__name(this, "PulseReporter");
|
|
22
|
-
}
|
|
23
|
-
constructor(config) {
|
|
24
|
-
this.config = {
|
|
25
|
-
...REPORT_CONFIG,
|
|
26
|
-
...config,
|
|
27
|
-
endpoint: (config?.endpoint || REPORT_CONFIG.endpoint).replace(/\/$/, "")
|
|
28
|
-
};
|
|
29
|
-
this.enabled = Boolean(
|
|
30
|
-
this.config.endpoint && this.config.projectId && this.config.deploymentId
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
captureException(error, options = {}) {
|
|
34
|
-
if (!this.enabled || typeof fetch !== "function") {
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
const event = this.buildErrorEvent(error, options);
|
|
38
|
-
fetch(`${this.config.endpoint}/capture`, {
|
|
39
|
-
method: "POST",
|
|
40
|
-
headers: {
|
|
41
|
-
"Content-Type": "application/json",
|
|
42
|
-
project_id: this.config.projectId
|
|
43
|
-
},
|
|
44
|
-
body: JSON.stringify(event),
|
|
45
|
-
keepalive: true
|
|
46
|
-
}).catch(() => {
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
buildErrorEvent(error, options) {
|
|
50
|
-
const normalizedError = this.normalizeError(error);
|
|
51
|
-
const environment = this.config.environment || (typeof window !== "undefined" ? window.location.hostname || "unknown" : "unknown");
|
|
52
|
-
const runtimeInfo = typeof navigator === "undefined" ? void 0 : {
|
|
53
|
-
name: navigator.userAgent || "browser",
|
|
54
|
-
version: navigator.appVersion
|
|
55
|
-
};
|
|
56
|
-
const osInfo = typeof navigator === "undefined" ? void 0 : {
|
|
57
|
-
name: navigator.platform
|
|
58
|
-
};
|
|
59
|
-
const deviceInfo = typeof window === "undefined" ? void 0 : {
|
|
60
|
-
model: `${window.screen.width}x${window.screen.height}`,
|
|
61
|
-
arch: typeof navigator !== "undefined" ? navigator.platform : void 0
|
|
62
|
-
};
|
|
63
|
-
const requestInfo = typeof window === "undefined" ? void 0 : {
|
|
64
|
-
method: "GET",
|
|
65
|
-
url: window.location.href,
|
|
66
|
-
headers: typeof navigator !== "undefined" ? {
|
|
67
|
-
"User-Agent": navigator.userAgent,
|
|
68
|
-
...typeof document !== "undefined" && document.referrer ? { Referer: document.referrer } : {}
|
|
69
|
-
} : void 0
|
|
70
|
-
};
|
|
71
|
-
const eventId = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : Math.random().toString(16).slice(2) + Date.now().toString(16);
|
|
72
|
-
return {
|
|
73
|
-
event_type: "error",
|
|
74
|
-
event_id: eventId,
|
|
75
|
-
deployment_id: this.config.deploymentId,
|
|
76
|
-
project_id: this.config.projectId,
|
|
77
|
-
environment,
|
|
78
|
-
service: this.config.service,
|
|
79
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
80
|
-
release: this.config.release,
|
|
81
|
-
sdk: {
|
|
82
|
-
...PULSE_SDK_INFO,
|
|
83
|
-
version: this.config.release || PULSE_SDK_INFO.version
|
|
84
|
-
},
|
|
85
|
-
tags: options.tags,
|
|
86
|
-
context: options.context,
|
|
87
|
-
error: {
|
|
88
|
-
level: options.level || "error",
|
|
89
|
-
message: normalizedError.message,
|
|
90
|
-
exception: this.buildExceptionPayload(normalizedError),
|
|
91
|
-
request: requestInfo,
|
|
92
|
-
runtime: runtimeInfo,
|
|
93
|
-
os: osInfo,
|
|
94
|
-
device: deviceInfo
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
normalizeError(error) {
|
|
99
|
-
if (error instanceof Error) {
|
|
100
|
-
return error;
|
|
101
|
-
}
|
|
102
|
-
const message = typeof error === "string" ? error : "Unknown error";
|
|
103
|
-
return new Error(message);
|
|
104
|
-
}
|
|
105
|
-
buildExceptionPayload(error) {
|
|
106
|
-
const frames = this.parseStack(error);
|
|
107
|
-
return {
|
|
108
|
-
type: error.name || "Error",
|
|
109
|
-
value: error.message,
|
|
110
|
-
stacktrace: frames.length ? { frames } : void 0
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
parseStack(error) {
|
|
114
|
-
if (!error.stack) {
|
|
115
|
-
return [];
|
|
116
|
-
}
|
|
117
|
-
const frames = [];
|
|
118
|
-
const raw = error.stack.split("\n").slice(1);
|
|
119
|
-
for (const line of raw) {
|
|
120
|
-
const cleaned = line.trim().replace(/^at\s+/, "");
|
|
121
|
-
const hasLocation = cleaned.includes("(") && cleaned.endsWith(")");
|
|
122
|
-
const functionName = hasLocation ? cleaned.slice(0, cleaned.indexOf("(")).trim() : "";
|
|
123
|
-
const location = hasLocation ? cleaned.slice(cleaned.indexOf("(") + 1, cleaned.length - 1) : cleaned;
|
|
124
|
-
const parts = location.split(":");
|
|
125
|
-
const filename = parts[0] || void 0;
|
|
126
|
-
const lineno = parts.length > 1 ? Number(parts[1]) : void 0;
|
|
127
|
-
const colno = parts.length > 2 ? Number(parts[2]) : void 0;
|
|
128
|
-
if (!filename) {
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
frames.push({
|
|
132
|
-
filename,
|
|
133
|
-
abs_path: filename,
|
|
134
|
-
function: functionName || void 0,
|
|
135
|
-
lineno: Number.isFinite(lineno) ? lineno : void 0,
|
|
136
|
-
colno: Number.isFinite(colno) ? colno : void 0,
|
|
137
|
-
in_app: !filename.includes("node_modules")
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
return frames;
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
var pulse_default = PulseReporter;
|
|
144
|
-
|
|
145
|
-
// src/index.ts
|
|
146
|
-
var Deflect = class {
|
|
147
|
-
constructor() {
|
|
148
|
-
this.config = null;
|
|
149
|
-
this.scriptCache = null;
|
|
150
|
-
this.isWarmupInProgress = false;
|
|
151
|
-
this.hasWarmupError = false;
|
|
152
|
-
this.initializeGlobalState();
|
|
153
|
-
this.pulseReporter = new pulse_default(this.resolvePulseConfig());
|
|
154
|
-
this.setupAutomaticWarmup();
|
|
155
|
-
}
|
|
156
|
-
static {
|
|
157
|
-
__name(this, "Deflect");
|
|
158
|
-
}
|
|
159
|
-
initializeGlobalState() {
|
|
160
|
-
if (typeof window === "undefined") return;
|
|
161
|
-
window.Deflect = window.Deflect || {};
|
|
162
|
-
}
|
|
163
|
-
setupAutomaticWarmup() {
|
|
164
|
-
if (typeof window === "undefined") return;
|
|
165
|
-
if (document.readyState === "loading") {
|
|
166
|
-
document.addEventListener("DOMContentLoaded", () => this.tryWarmup());
|
|
167
|
-
} else {
|
|
168
|
-
setTimeout(() => this.tryWarmup(), 100);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
resolvePulseConfig() {
|
|
172
|
-
const runtimeConfig = typeof window !== "undefined" && typeof window.Deflect === "object" && window.Deflect?.pulseConfig && typeof window.Deflect.pulseConfig === "object" ? window.Deflect.pulseConfig : {};
|
|
173
|
-
return {
|
|
174
|
-
...runtimeConfig,
|
|
175
|
-
environment: runtimeConfig.environment || (typeof window !== "undefined" ? window.location.hostname : void 0)
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
reportError(error, tags, context) {
|
|
179
|
-
this.pulseReporter.captureException(error, { tags, context });
|
|
180
|
-
}
|
|
181
|
-
async tryWarmup() {
|
|
182
|
-
if (!this.config?.actionId || this.isWarmupInProgress || this.scriptCache || this.hasWarmupError) {
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
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
|
-
}
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* @param actionId - The action ID to validate
|
|
197
|
-
* @returns The sanitized action ID
|
|
198
|
-
* @throws Error if actionId contains invalid characters
|
|
199
|
-
*/
|
|
200
|
-
validateActionId(actionId) {
|
|
201
|
-
const sanitized = actionId.trim();
|
|
202
|
-
const validPattern = /^[a-zA-Z0-9/_-]+$/;
|
|
203
|
-
if (!validPattern.test(sanitized)) {
|
|
204
|
-
throw new Error("Invalid actionId format: contains disallowed characters");
|
|
205
|
-
}
|
|
206
|
-
return encodeURIComponent(sanitized);
|
|
207
|
-
}
|
|
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
|
-
prefetchNextScript() {
|
|
300
|
-
if (!this.hasWarmupError) {
|
|
301
|
-
this.tryWarmup().catch(() => {
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
}
|
|
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;
|
|
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
|
-
*/
|
|
364
|
-
configure(params) {
|
|
365
|
-
try {
|
|
366
|
-
if (!params.actionId?.trim()) {
|
|
367
|
-
throw new Error("actionId is required and cannot be empty");
|
|
368
|
-
}
|
|
369
|
-
if (this.config?.actionId === params.actionId) {
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
this.hasWarmupError = false;
|
|
373
|
-
this.scriptCache = null;
|
|
374
|
-
this.config = { ...params };
|
|
375
|
-
if (typeof window !== "undefined") {
|
|
376
|
-
window.Deflect.actionId = params.actionId;
|
|
377
|
-
}
|
|
378
|
-
if (!this.isTestMode()) {
|
|
379
|
-
this.tryWarmup();
|
|
380
|
-
}
|
|
381
|
-
} catch (error) {
|
|
382
|
-
const isUserError = error && typeof error === "object" && "isUserError" in error && error.isUserError === true;
|
|
383
|
-
this.reportError(
|
|
384
|
-
error,
|
|
385
|
-
{
|
|
386
|
-
deflect_sdk_error: "true",
|
|
387
|
-
deflect_user_error: isUserError ? "true" : "false",
|
|
388
|
-
method: "configure",
|
|
389
|
-
action_id: params?.actionId || "unknown"
|
|
390
|
-
},
|
|
391
|
-
{
|
|
392
|
-
actionId: params?.actionId,
|
|
393
|
-
hasCache: this.scriptCache !== null,
|
|
394
|
-
hasWarmupError: this.hasWarmupError
|
|
395
|
-
}
|
|
396
|
-
);
|
|
397
|
-
throw error;
|
|
398
|
-
}
|
|
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
|
-
*/
|
|
430
|
-
async getToken() {
|
|
431
|
-
try {
|
|
432
|
-
if (!this.config?.actionId) {
|
|
433
|
-
throw new Error("Must call configure() before solveChallenge()");
|
|
434
|
-
}
|
|
435
|
-
if (this.isTestMode()) {
|
|
436
|
-
return "TESTTOKEN";
|
|
437
|
-
}
|
|
438
|
-
let script;
|
|
439
|
-
if (this.scriptCache && !this.isWarmupInProgress) {
|
|
440
|
-
script = this.scriptCache;
|
|
441
|
-
this.scriptCache = null;
|
|
442
|
-
} else {
|
|
443
|
-
script = await this.fetchScript();
|
|
444
|
-
}
|
|
445
|
-
return this.executeScript(script);
|
|
446
|
-
} catch (error) {
|
|
447
|
-
const isUserError = error && typeof error === "object" && "isUserError" in error && error.isUserError === true;
|
|
448
|
-
this.reportError(
|
|
449
|
-
error,
|
|
450
|
-
{
|
|
451
|
-
deflect_sdk_error: "true",
|
|
452
|
-
deflect_user_error: isUserError ? "true" : "false",
|
|
453
|
-
method: "getToken",
|
|
454
|
-
action_id: this.config?.actionId || "unknown",
|
|
455
|
-
has_cache: this.scriptCache !== null ? "true" : "false",
|
|
456
|
-
has_warmup_error: this.hasWarmupError ? "true" : "false",
|
|
457
|
-
stage: "call"
|
|
458
|
-
},
|
|
459
|
-
{
|
|
460
|
-
actionId: this.config?.actionId,
|
|
461
|
-
hasCache: this.scriptCache !== null,
|
|
462
|
-
hasWarmupError: this.hasWarmupError
|
|
463
|
-
}
|
|
464
|
-
);
|
|
465
|
-
throw error;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
/**
|
|
469
|
-
* Pre-fetches the challenge script to reduce latency on the first getToken() call.
|
|
470
|
-
* This is automatically called after configure(), but can be manually triggered.
|
|
471
|
-
*
|
|
472
|
-
* @returns A promise that resolves to true if warmup succeeded, false otherwise
|
|
473
|
-
*
|
|
474
|
-
* @example
|
|
475
|
-
* ```typescript
|
|
476
|
-
* Deflect.configure({ actionId: "your-action-id" });
|
|
477
|
-
* const warmedUp = await Deflect.warmup();
|
|
478
|
-
* console.log('Script pre-cached:', warmedUp);
|
|
479
|
-
* ```
|
|
480
|
-
*/
|
|
481
|
-
async warmup() {
|
|
482
|
-
if (!this.config?.actionId) {
|
|
483
|
-
return false;
|
|
484
|
-
}
|
|
485
|
-
try {
|
|
486
|
-
await this.tryWarmup();
|
|
487
|
-
return this.scriptCache !== null;
|
|
488
|
-
} catch {
|
|
489
|
-
return false;
|
|
490
|
-
}
|
|
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
|
-
*/
|
|
496
|
-
clearCache() {
|
|
497
|
-
this.scriptCache = null;
|
|
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
|
-
*/
|
|
514
|
-
async injectToken(event) {
|
|
515
|
-
if (!event || !event.target || !(event.target instanceof HTMLFormElement)) {
|
|
516
|
-
throw new Error("injectToken: must be called from a form submit event");
|
|
517
|
-
}
|
|
518
|
-
event.preventDefault();
|
|
519
|
-
const form = event.target;
|
|
520
|
-
const token = await this.getToken();
|
|
521
|
-
Array.from(form.querySelectorAll('input[name="deflect_token"]')).forEach(
|
|
522
|
-
(el) => el.remove()
|
|
523
|
-
);
|
|
524
|
-
const hidden = document.createElement("input");
|
|
525
|
-
hidden.type = "hidden";
|
|
526
|
-
hidden.name = "deflect_token";
|
|
527
|
-
hidden.value = token;
|
|
528
|
-
form.appendChild(hidden);
|
|
529
|
-
form.submit();
|
|
530
|
-
}
|
|
531
|
-
};
|
|
532
|
-
var src_default = new Deflect();
|
|
533
|
-
})();
|
package/dist/index.min.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
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/dist/pulse.d.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
export type PulseEventType = "error" | "span" | "transaction" | "log" | "metric";
|
|
2
|
-
export type PulseLevel = "fatal" | "error" | "warning" | "info" | "debug";
|
|
3
|
-
export interface PulseReporterConfig {
|
|
4
|
-
endpoint: string;
|
|
5
|
-
projectId: string;
|
|
6
|
-
deploymentId: string;
|
|
7
|
-
environment?: string;
|
|
8
|
-
service?: string;
|
|
9
|
-
release?: string;
|
|
10
|
-
}
|
|
11
|
-
export interface PulseSDKInfo {
|
|
12
|
-
name: string;
|
|
13
|
-
version: string;
|
|
14
|
-
language: string;
|
|
15
|
-
integrations?: string[];
|
|
16
|
-
}
|
|
17
|
-
export interface PulseStackFrame {
|
|
18
|
-
filename?: string;
|
|
19
|
-
abs_path?: string;
|
|
20
|
-
function?: string;
|
|
21
|
-
lineno?: number;
|
|
22
|
-
colno?: number;
|
|
23
|
-
in_app?: boolean;
|
|
24
|
-
context_line?: string;
|
|
25
|
-
}
|
|
26
|
-
export interface PulseExceptionPayload {
|
|
27
|
-
type?: string;
|
|
28
|
-
value?: string;
|
|
29
|
-
stacktrace?: {
|
|
30
|
-
frames: PulseStackFrame[];
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
export interface PulseRequestInfo {
|
|
34
|
-
method?: string;
|
|
35
|
-
url?: string;
|
|
36
|
-
headers?: Record<string, string>;
|
|
37
|
-
route?: string;
|
|
38
|
-
status_code?: number;
|
|
39
|
-
}
|
|
40
|
-
export interface PulseRuntimeInfo {
|
|
41
|
-
name?: string;
|
|
42
|
-
version?: string;
|
|
43
|
-
}
|
|
44
|
-
export interface PulseOSInfo {
|
|
45
|
-
name?: string;
|
|
46
|
-
version?: string;
|
|
47
|
-
}
|
|
48
|
-
export interface PulseDeviceInfo {
|
|
49
|
-
model?: string;
|
|
50
|
-
arch?: string;
|
|
51
|
-
}
|
|
52
|
-
export interface PulseErrorPayload {
|
|
53
|
-
level: PulseLevel;
|
|
54
|
-
message?: string;
|
|
55
|
-
exception?: PulseExceptionPayload;
|
|
56
|
-
culprit?: string;
|
|
57
|
-
request?: PulseRequestInfo;
|
|
58
|
-
runtime?: PulseRuntimeInfo;
|
|
59
|
-
os?: PulseOSInfo;
|
|
60
|
-
device?: PulseDeviceInfo;
|
|
61
|
-
}
|
|
62
|
-
export interface PulseEvent {
|
|
63
|
-
event_type: PulseEventType;
|
|
64
|
-
event_id: string;
|
|
65
|
-
deployment_id: string;
|
|
66
|
-
project_id: string;
|
|
67
|
-
environment?: string;
|
|
68
|
-
service?: string;
|
|
69
|
-
host?: string;
|
|
70
|
-
sdk?: PulseSDKInfo;
|
|
71
|
-
timestamp: string;
|
|
72
|
-
release?: string;
|
|
73
|
-
dist?: string;
|
|
74
|
-
deployment?: string;
|
|
75
|
-
tags?: Record<string, string>;
|
|
76
|
-
context?: Record<string, unknown>;
|
|
77
|
-
sample_rate?: number;
|
|
78
|
-
fingerprint?: string[];
|
|
79
|
-
ingest_key_id?: string;
|
|
80
|
-
payload_version?: number;
|
|
81
|
-
content_encoding?: string;
|
|
82
|
-
error?: PulseErrorPayload;
|
|
83
|
-
}
|
|
84
|
-
export interface PulseCaptureOptions {
|
|
85
|
-
tags?: Record<string, string>;
|
|
86
|
-
context?: Record<string, unknown>;
|
|
87
|
-
level?: PulseLevel;
|
|
88
|
-
}
|
|
89
|
-
export declare class PulseReporter {
|
|
90
|
-
private readonly config;
|
|
91
|
-
private readonly enabled;
|
|
92
|
-
constructor(config?: Partial<PulseReporterConfig>);
|
|
93
|
-
captureException(error: unknown, options?: PulseCaptureOptions): void;
|
|
94
|
-
private buildErrorEvent;
|
|
95
|
-
private normalizeError;
|
|
96
|
-
private buildExceptionPayload;
|
|
97
|
-
private parseStack;
|
|
98
|
-
}
|
|
99
|
-
export default PulseReporter;
|