@deflectbot/deflect-sdk 1.2.3 → 1.3.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 +54 -26
- package/dist/deflect.global.min.js +1 -0
- package/dist/global.js +7 -0
- package/dist/index.js +190 -90
- package/dist/types/global.d.ts +7 -0
- package/dist/types/index.d.ts +24 -10
- package/package.json +6 -6
- package/src/global.ts +18 -0
- package/src/index.ts +182 -92
- package/src/types/window.d.ts +3 -0
package/README.md
CHANGED
|
@@ -1,50 +1,78 @@
|
|
|
1
|
-
# Deflect -
|
|
1
|
+
# Deflect SDK (Client-side)
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|

|
|
5
5
|
|
|
6
|
-
Deflect is an antibot
|
|
6
|
+
[Deflect](https://deflect.bot) is an advanced antibot engine that protects your site from bots on any scale.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
## Usage
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
See the [documentation](https://docs.deflect.bot/) for how to use this SDK with the Deflect APIs.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
#### yarn
|
|
11
15
|
|
|
12
16
|
```bash
|
|
13
|
-
|
|
14
|
-
<script src="https://cdn.jsdelivr.net/npm/@deflectbot/deflect-sdk/dist/index.min.js"></script>
|
|
17
|
+
yarn add @castleio/sdk
|
|
15
18
|
```
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
## NPM Install (Requires Manual Updates)
|
|
20
|
+
#### npm
|
|
20
21
|
|
|
21
22
|
```bash
|
|
22
|
-
npm install
|
|
23
|
+
npm install --save @castleio/sdk
|
|
23
24
|
```
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
#### CDN
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
Use `@deflect-sdk@latest` to always use the latest version, or specify a version with `@deflect-sdk@x.x.x`.
|
|
28
29
|
|
|
29
|
-
```
|
|
30
|
-
|
|
30
|
+
```html
|
|
31
|
+
<script src="https://cdn.jsdelivr.net/npm/@deflectbot/deflect-sdk@latest/dist/index.min.js" />
|
|
32
|
+
```
|
|
31
33
|
|
|
34
|
+
## Configuration
|
|
35
|
+
|
|
36
|
+
Deflect works through Defense Actions™, which allows you to either setup a global configuration one-time to work across your entire application, or individual and more customized Defense Actions™ for each protected endpoint.
|
|
37
|
+
|
|
38
|
+
#### One-time global initialization.
|
|
39
|
+
|
|
40
|
+
```ts
|
|
32
41
|
Deflect.configure({
|
|
33
|
-
actionId:
|
|
34
|
-
extraArgs: { mode: 'strict' }, // Optional
|
|
35
|
-
forceRefresh: true, // Optional
|
|
42
|
+
actionId: "<ACTION_ID>",
|
|
36
43
|
});
|
|
37
44
|
```
|
|
38
45
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
## Solve Challenge
|
|
46
|
+
#### To get a token to pass to a protected endpoint
|
|
42
47
|
|
|
43
48
|
```ts
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
const token = await Deflect.getToken(); // returns token as string
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
#### Helper for form submit
|
|
53
|
+
|
|
54
|
+
If you're using forms like this:
|
|
55
|
+
|
|
56
|
+
```html
|
|
57
|
+
<form action="/login" method="POST">
|
|
58
|
+
<input name="username" />
|
|
59
|
+
<input name="password" type="password" />
|
|
60
|
+
<button type="submit">Login</button>
|
|
61
|
+
</form>
|
|
50
62
|
```
|
|
63
|
+
|
|
64
|
+
You can use the helper as shown:
|
|
65
|
+
|
|
66
|
+
```html
|
|
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
|
+
```
|
|
77
|
+
|
|
78
|
+
`injectToken()` will fetch a token and insert it as a hidden input named `deflect_token` into your form. Use it directly in your form's onsubmit attribute:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var Deflect=(()=>{var c=Object.defineProperty;var l=Object.getOwnPropertyDescriptor;var d=Object.getOwnPropertyNames;var p=Object.prototype.hasOwnProperty;var f=(n,e)=>{for(var t in e)c(n,t,{get:e[t],enumerable:!0})},u=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of d(e))!p.call(n,i)&&i!==t&&c(n,i,{get:()=>e[i],enumerable:!(r=l(e,i))||r.enumerable});return n};var h=n=>u(c({},"__esModule",{value:!0}),n);var m={};f(m,{default:()=>w});var s=class{constructor(){this.config=null;this.scriptCache=null;this.isWarmupInProgress=!1;this.initializeGlobalState(),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))}async tryWarmup(){if(!(!this.config?.actionId||this.isWarmupInProgress||this.scriptCache)){this.isWarmupInProgress=!0;try{this.scriptCache=await this.fetchScript()}catch{}finally{this.isWarmupInProgress=!1}}}buildScriptUrl(e){let t=this.config?.scriptUrl||"https://js.deflect.bot/main.js";if(this.config?.scriptUrl)return t;let r=Date.now().toString();return`${t}?action_id=${e}&_=${r}`}async fetchScript(){if(!this.config?.actionId)throw new Error("actionId is required");let e=this.buildScriptUrl(this.config.actionId),t=await fetch(e,{cache:"no-store"});if(!t.ok)throw new Error(`Failed to fetch script: ${t.status}`);return{content:await t.text(),sessionId:t.headers.get("session_id")||void 0}}async executeScript(e){e.sessionId&&typeof window<"u"&&(window.Deflect.sessionId=e.sessionId);let t=this.createReadyPromise(),r=this.createScriptBlob(e.content),i=await this.loadScriptElement(r);try{await t;let o=await this.getTokenFromScript();return this.prefetchNextScript(),o}finally{this.cleanup(r,i)}}prefetchNextScript(){this.tryWarmup().catch(()=>{})}createReadyPromise(){return new Promise(e=>{typeof window<"u"&&(window.Deflect.ready={promise:new Promise(t=>{window.Deflect.ready.resolve=t}),resolve:()=>e()})})}createScriptBlob(e){let t=new Blob([e],{type:"text/javascript"});return URL.createObjectURL(t)}async loadScriptElement(e){return new Promise((t,r)=>{let i=document.createElement("script");i.type="module",i.src=e,i.onload=()=>t(i),i.onerror=()=>r(new Error("Script failed to load")),document.head.appendChild(i)})}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){if(!e.actionId?.trim())throw new Error("actionId is required and cannot be empty");this.config={...e},typeof window<"u"&&(window.Deflect.actionId=e.actionId),this.tryWarmup()}async solveChallenge(){return this.getToken()}async getToken(){if(!this.config?.actionId)throw new Error("Must call configure() before solveChallenge()");let e;return this.scriptCache&&!this.isWarmupInProgress?(e=this.scriptCache,this.scriptCache=null):e=await this.fetchScript(),this.executeScript(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,r=await this.getToken();Array.from(t.querySelectorAll('input[name="deflect_token"]')).forEach(o=>o.remove());let i=document.createElement("input");return i.type="hidden",i.name="deflect_token",i.value=r,t.appendChild(i),t.submit(),!1}},a=new s;typeof window<"u"&&(window.Deflect=a);var w=a;return h(m);})();
|
package/dist/global.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Re-export the default instance while also assigning it to window.Deflect for non-module usage.
|
|
2
|
+
import DeflectInstance from "./index";
|
|
3
|
+
if (typeof window !== "undefined") {
|
|
4
|
+
// Avoid overwriting if a user already attached something custom.
|
|
5
|
+
window.Deflect = DeflectInstance;
|
|
6
|
+
}
|
|
7
|
+
export default DeflectInstance;
|
package/dist/index.js
CHANGED
|
@@ -9,117 +9,217 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
};
|
|
10
10
|
class Deflect {
|
|
11
11
|
constructor() {
|
|
12
|
-
this.
|
|
13
|
-
this.
|
|
14
|
-
this.
|
|
12
|
+
this.config = null;
|
|
13
|
+
this.scriptCache = null;
|
|
14
|
+
this.isWarmupInProgress = false;
|
|
15
|
+
this.initializeGlobalState();
|
|
16
|
+
this.setupAutomaticWarmup();
|
|
17
|
+
}
|
|
18
|
+
initializeGlobalState() {
|
|
19
|
+
if (typeof window === "undefined")
|
|
20
|
+
return;
|
|
15
21
|
window.Deflect = window.Deflect || {};
|
|
16
|
-
window.Deflect.extraArgs = window.Deflect.extraArgs || {};
|
|
17
|
-
if (typeof window !== "undefined") {
|
|
18
|
-
window.addEventListener("load", () => {
|
|
19
|
-
setTimeout(() => {
|
|
20
|
-
this._warmupScript();
|
|
21
|
-
}, 500);
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
22
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
setupAutomaticWarmup() {
|
|
24
|
+
if (typeof window === "undefined")
|
|
25
|
+
return;
|
|
26
|
+
if (document.readyState === "loading") {
|
|
27
|
+
document.addEventListener("DOMContentLoaded", () => this.tryWarmup());
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
setTimeout(() => this.tryWarmup(), 100);
|
|
30
31
|
}
|
|
31
|
-
return `https://js.deflect.bot/main.js?action_id=${window.Deflect.actionId}&_=${nonce}`;
|
|
32
32
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
fetch(scriptUrl, { cache: "no-store" })
|
|
38
|
-
.then((res) => __awaiter(this, void 0, void 0, function* () {
|
|
39
|
-
if (!res.ok)
|
|
33
|
+
tryWarmup() {
|
|
34
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
35
|
+
var _a;
|
|
36
|
+
if (!((_a = this.config) === null || _a === void 0 ? void 0 : _a.actionId) || this.isWarmupInProgress || this.scriptCache) {
|
|
40
37
|
return;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
38
|
+
}
|
|
39
|
+
this.isWarmupInProgress = true;
|
|
40
|
+
try {
|
|
41
|
+
this.scriptCache = yield this.fetchScript();
|
|
42
|
+
}
|
|
43
|
+
catch (_b) {
|
|
44
|
+
/* empty */
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
this.isWarmupInProgress = false;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
buildScriptUrl(actionId) {
|
|
52
|
+
var _a, _b;
|
|
53
|
+
const baseUrl = ((_a = this.config) === null || _a === void 0 ? void 0 : _a.scriptUrl) || "https://js.deflect.bot/main.js";
|
|
54
|
+
if ((_b = this.config) === null || _b === void 0 ? void 0 : _b.scriptUrl) {
|
|
55
|
+
return baseUrl;
|
|
56
|
+
}
|
|
57
|
+
const nonce = Date.now().toString();
|
|
58
|
+
return `${baseUrl}?action_id=${actionId}&_=${nonce}`;
|
|
59
|
+
}
|
|
60
|
+
fetchScript() {
|
|
61
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
62
|
+
var _a;
|
|
63
|
+
if (!((_a = this.config) === null || _a === void 0 ? void 0 : _a.actionId)) {
|
|
64
|
+
throw new Error("actionId is required");
|
|
65
|
+
}
|
|
66
|
+
const url = this.buildScriptUrl(this.config.actionId);
|
|
67
|
+
const response = yield fetch(url, { cache: "no-store" });
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
throw new Error(`Failed to fetch script: ${response.status}`);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
content: yield response.text(),
|
|
73
|
+
sessionId: response.headers.get("session_id") || undefined,
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
executeScript(script) {
|
|
78
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
79
|
+
if (script.sessionId && typeof window !== "undefined") {
|
|
80
|
+
window.Deflect.sessionId = script.sessionId;
|
|
81
|
+
}
|
|
82
|
+
const readyPromise = this.createReadyPromise();
|
|
83
|
+
const blobUrl = this.createScriptBlob(script.content);
|
|
84
|
+
const scriptElement = yield this.loadScriptElement(blobUrl);
|
|
85
|
+
try {
|
|
86
|
+
yield readyPromise;
|
|
87
|
+
const token = yield this.getTokenFromScript();
|
|
88
|
+
this.prefetchNextScript();
|
|
89
|
+
return token;
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
this.cleanup(blobUrl, scriptElement);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
prefetchNextScript() {
|
|
97
|
+
this.tryWarmup().catch(() => { });
|
|
98
|
+
}
|
|
99
|
+
createReadyPromise() {
|
|
100
|
+
return new Promise((resolve) => {
|
|
101
|
+
if (typeof window !== "undefined") {
|
|
102
|
+
window.Deflect.ready = {
|
|
103
|
+
promise: new Promise((innerResolve) => {
|
|
104
|
+
window.Deflect.ready.resolve = innerResolve;
|
|
105
|
+
}),
|
|
106
|
+
resolve: () => resolve(),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
createScriptBlob(content) {
|
|
112
|
+
const blob = new Blob([content], { type: "text/javascript" });
|
|
113
|
+
return URL.createObjectURL(blob);
|
|
114
|
+
}
|
|
115
|
+
loadScriptElement(blobUrl) {
|
|
116
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
117
|
+
return new Promise((resolve, reject) => {
|
|
118
|
+
const script = document.createElement("script");
|
|
119
|
+
script.type = "module";
|
|
120
|
+
script.src = blobUrl;
|
|
121
|
+
script.onload = () => resolve(script);
|
|
122
|
+
script.onerror = () => reject(new Error("Script failed to load"));
|
|
123
|
+
document.head.appendChild(script);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
getTokenFromScript() {
|
|
128
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
129
|
+
var _a;
|
|
130
|
+
if (typeof window === "undefined" ||
|
|
131
|
+
typeof ((_a = window.Deflect) === null || _a === void 0 ? void 0 : _a.getToken) !== "function") {
|
|
132
|
+
throw new Error("Script did not load properly - getToken not available");
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
return yield window.Deflect.getToken();
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
throw new Error(`Script execution failed: ${error}`);
|
|
139
|
+
}
|
|
56
140
|
});
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
141
|
+
}
|
|
142
|
+
cleanup(blobUrl, scriptElement) {
|
|
143
|
+
var _a;
|
|
144
|
+
URL.revokeObjectURL(blobUrl);
|
|
145
|
+
scriptElement.remove();
|
|
146
|
+
if (typeof window !== "undefined" && ((_a = window.Deflect) === null || _a === void 0 ? void 0 : _a.getToken)) {
|
|
147
|
+
delete window.Deflect.getToken;
|
|
148
|
+
}
|
|
61
149
|
}
|
|
62
150
|
configure(params) {
|
|
63
|
-
|
|
64
|
-
|
|
151
|
+
var _a;
|
|
152
|
+
if (!((_a = params.actionId) === null || _a === void 0 ? void 0 : _a.trim())) {
|
|
153
|
+
throw new Error("actionId is required and cannot be empty");
|
|
65
154
|
}
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
|
|
155
|
+
this.config = Object.assign({}, params);
|
|
156
|
+
if (typeof window !== "undefined") {
|
|
157
|
+
window.Deflect.actionId = params.actionId;
|
|
69
158
|
}
|
|
159
|
+
this.tryWarmup();
|
|
70
160
|
}
|
|
161
|
+
// Deprecated name kept for backward compatibility
|
|
71
162
|
solveChallenge() {
|
|
163
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
164
|
+
return this.getToken();
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
getToken() {
|
|
72
168
|
return __awaiter(this, void 0, void 0, function* () {
|
|
73
169
|
var _a;
|
|
74
|
-
if (!
|
|
75
|
-
throw new Error("
|
|
170
|
+
if (!((_a = this.config) === null || _a === void 0 ? void 0 : _a.actionId)) {
|
|
171
|
+
throw new Error("Must call configure() before solveChallenge()");
|
|
76
172
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
scriptText = this._prefetchedScriptText;
|
|
82
|
-
this._hasUsedPrefetch = true;
|
|
173
|
+
let script;
|
|
174
|
+
if (this.scriptCache && !this.isWarmupInProgress) {
|
|
175
|
+
script = this.scriptCache;
|
|
176
|
+
this.scriptCache = null;
|
|
83
177
|
}
|
|
84
178
|
else {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
const blob = new Blob([scriptText], { type: "text/javascript" });
|
|
97
|
-
const blobUrl = URL.createObjectURL(blob);
|
|
98
|
-
const scriptEl = document.createElement("script");
|
|
99
|
-
scriptEl.type = "module";
|
|
100
|
-
scriptEl.src = blobUrl;
|
|
101
|
-
document.head.appendChild(scriptEl);
|
|
102
|
-
yield new Promise((resolve, reject) => {
|
|
103
|
-
scriptEl.onload = () => resolve();
|
|
104
|
-
scriptEl.onerror = () => reject("Failed to load the Deflect script");
|
|
105
|
-
});
|
|
106
|
-
yield ((_a = window.Deflect.ready) === null || _a === void 0 ? void 0 : _a.promise);
|
|
107
|
-
if (typeof window.Deflect === "undefined" ||
|
|
108
|
-
typeof window.Deflect.getToken !== "function") {
|
|
109
|
-
throw new Error("Deflect script did not load properly");
|
|
179
|
+
script = yield this.fetchScript();
|
|
180
|
+
}
|
|
181
|
+
return this.executeScript(script);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
warmup() {
|
|
185
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
186
|
+
var _a;
|
|
187
|
+
if (!((_a = this.config) === null || _a === void 0 ? void 0 : _a.actionId)) {
|
|
188
|
+
return false;
|
|
110
189
|
}
|
|
111
|
-
let token;
|
|
112
190
|
try {
|
|
113
|
-
|
|
191
|
+
yield this.tryWarmup();
|
|
192
|
+
return this.scriptCache !== null;
|
|
114
193
|
}
|
|
115
|
-
catch (
|
|
116
|
-
|
|
194
|
+
catch (_b) {
|
|
195
|
+
return false;
|
|
117
196
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
clearCache() {
|
|
200
|
+
this.scriptCache = null;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Inject a fresh token as a hidden input into a form. Only accepts a submit event from onsubmit.
|
|
204
|
+
* Usage: <form ... onsubmit="return Deflect.injectToken(event)">
|
|
205
|
+
* Returns false to prevent double submit.
|
|
206
|
+
*/
|
|
207
|
+
injectToken(event) {
|
|
208
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
209
|
+
if (!event || !event.target || !(event.target instanceof HTMLFormElement)) {
|
|
210
|
+
throw new Error("injectToken: must be called from a form submit event");
|
|
211
|
+
}
|
|
212
|
+
event.preventDefault();
|
|
213
|
+
const form = event.target;
|
|
214
|
+
const token = yield this.getToken();
|
|
215
|
+
Array.from(form.querySelectorAll('input[name="deflect_token"]')).forEach((el) => el.remove());
|
|
216
|
+
const hidden = document.createElement("input");
|
|
217
|
+
hidden.type = "hidden";
|
|
218
|
+
hidden.name = "deflect_token";
|
|
219
|
+
hidden.value = token;
|
|
220
|
+
form.appendChild(hidden);
|
|
221
|
+
form.submit();
|
|
222
|
+
return false;
|
|
123
223
|
});
|
|
124
224
|
}
|
|
125
225
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,21 +1,35 @@
|
|
|
1
1
|
interface DeflectConfig {
|
|
2
2
|
actionId: string;
|
|
3
3
|
scriptUrl?: string;
|
|
4
|
-
extraArgs?: {
|
|
5
|
-
[key: string]: string;
|
|
6
|
-
};
|
|
7
4
|
}
|
|
8
5
|
declare class Deflect {
|
|
9
|
-
private
|
|
10
|
-
private
|
|
11
|
-
private
|
|
6
|
+
private config;
|
|
7
|
+
private scriptCache;
|
|
8
|
+
private isWarmupInProgress;
|
|
12
9
|
constructor();
|
|
13
|
-
private
|
|
14
|
-
private
|
|
15
|
-
private
|
|
16
|
-
private
|
|
10
|
+
private initializeGlobalState;
|
|
11
|
+
private setupAutomaticWarmup;
|
|
12
|
+
private tryWarmup;
|
|
13
|
+
private buildScriptUrl;
|
|
14
|
+
private fetchScript;
|
|
15
|
+
private executeScript;
|
|
16
|
+
private prefetchNextScript;
|
|
17
|
+
private createReadyPromise;
|
|
18
|
+
private createScriptBlob;
|
|
19
|
+
private loadScriptElement;
|
|
20
|
+
private getTokenFromScript;
|
|
21
|
+
private cleanup;
|
|
17
22
|
configure(params: DeflectConfig): void;
|
|
18
23
|
solveChallenge(): Promise<string>;
|
|
24
|
+
getToken(): Promise<string>;
|
|
25
|
+
warmup(): Promise<boolean>;
|
|
26
|
+
clearCache(): void;
|
|
27
|
+
/**
|
|
28
|
+
* Inject a fresh token as a hidden input into a form. Only accepts a submit event from onsubmit.
|
|
29
|
+
* Usage: <form ... onsubmit="return Deflect.injectToken(event)">
|
|
30
|
+
* Returns false to prevent double submit.
|
|
31
|
+
*/
|
|
32
|
+
injectToken(event: SubmitEvent): Promise<false>;
|
|
19
33
|
}
|
|
20
34
|
declare const _default: Deflect;
|
|
21
35
|
export default _default;
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deflectbot/deflect-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "SDK for deflect.bot - Use it for seamless captcha integration on any website.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/types/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"build": "tsc"
|
|
8
|
+
"build": "tsc && npm run build:global",
|
|
9
|
+
"build:global": "esbuild src/global.ts --bundle --format=iife --global-name=Deflect --minify --outfile=dist/deflect.global.min.js"
|
|
9
10
|
},
|
|
10
11
|
"author": "Deflect",
|
|
11
12
|
"license": "MIT",
|
|
@@ -14,12 +15,11 @@
|
|
|
14
15
|
"eslint": "^9.22.0",
|
|
15
16
|
"globals": "^16.0.0",
|
|
16
17
|
"typescript": "^5.7.3",
|
|
17
|
-
"typescript-eslint": "^8.27.0"
|
|
18
|
+
"typescript-eslint": "^8.27.0",
|
|
19
|
+
"esbuild": "^0.23.0"
|
|
18
20
|
},
|
|
19
21
|
"publishConfig": {
|
|
20
22
|
"access": "public"
|
|
21
23
|
},
|
|
22
|
-
"dependencies": {
|
|
23
|
-
"@deflectbot/deflect-sdk": "file:"
|
|
24
|
-
}
|
|
24
|
+
"dependencies": {}
|
|
25
25
|
}
|
package/src/global.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Re-export the default instance while also assigning it to window.Deflect for non-module usage.
|
|
2
|
+
import DeflectInstance from "./index";
|
|
3
|
+
|
|
4
|
+
// Ensure side effects (constructor) already ran on import.
|
|
5
|
+
// Attach to window for classic script tag usage.
|
|
6
|
+
declare global {
|
|
7
|
+
interface Window {
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK attaches dynamic shape at runtime
|
|
9
|
+
Deflect: any;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (typeof window !== "undefined") {
|
|
14
|
+
// Avoid overwriting if a user already attached something custom.
|
|
15
|
+
window.Deflect = DeflectInstance;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default DeflectInstance;
|
package/src/index.ts
CHANGED
|
@@ -1,147 +1,237 @@
|
|
|
1
1
|
interface DeflectConfig {
|
|
2
2
|
actionId: string;
|
|
3
3
|
scriptUrl?: string;
|
|
4
|
-
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
interface DeflectScript {
|
|
7
|
+
sessionId?: string;
|
|
8
|
+
content: string;
|
|
5
9
|
}
|
|
6
10
|
|
|
7
11
|
class Deflect {
|
|
8
|
-
private
|
|
9
|
-
private
|
|
10
|
-
private
|
|
12
|
+
private config: DeflectConfig | null = null;
|
|
13
|
+
private scriptCache: DeflectScript | null = null;
|
|
14
|
+
private isWarmupInProgress = false;
|
|
11
15
|
|
|
12
16
|
constructor() {
|
|
17
|
+
this.initializeGlobalState();
|
|
18
|
+
this.setupAutomaticWarmup();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private initializeGlobalState(): void {
|
|
22
|
+
if (typeof window === "undefined") return;
|
|
13
23
|
window.Deflect = window.Deflect || {};
|
|
14
|
-
|
|
24
|
+
}
|
|
15
25
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
26
|
+
private setupAutomaticWarmup(): void {
|
|
27
|
+
if (typeof window === "undefined") return;
|
|
28
|
+
|
|
29
|
+
if (document.readyState === "loading") {
|
|
30
|
+
document.addEventListener("DOMContentLoaded", () => this.tryWarmup());
|
|
31
|
+
} else {
|
|
32
|
+
setTimeout(() => this.tryWarmup(), 100);
|
|
22
33
|
}
|
|
23
34
|
}
|
|
24
35
|
|
|
25
|
-
private
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const separator = this._customScriptUrl.includes("?") ? "&" : "?";
|
|
29
|
-
return `${this._customScriptUrl}${separator}action_id=${window.Deflect.actionId}&_=${nonce}`;
|
|
36
|
+
private async tryWarmup(): Promise<void> {
|
|
37
|
+
if (!this.config?.actionId || this.isWarmupInProgress || this.scriptCache) {
|
|
38
|
+
return;
|
|
30
39
|
}
|
|
31
|
-
return `https://js.deflect.bot/main.js?action_id=${window.Deflect.actionId}&_=${nonce}`;
|
|
32
|
-
}
|
|
33
40
|
|
|
34
|
-
|
|
35
|
-
if (!window.Deflect.actionId) return;
|
|
41
|
+
this.isWarmupInProgress = true;
|
|
36
42
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
try {
|
|
44
|
+
this.scriptCache = await this.fetchScript();
|
|
45
|
+
} catch {
|
|
46
|
+
/* empty */
|
|
47
|
+
} finally {
|
|
48
|
+
this.isWarmupInProgress = false;
|
|
49
|
+
}
|
|
44
50
|
}
|
|
45
51
|
|
|
46
|
-
private
|
|
47
|
-
this.
|
|
48
|
-
this._prefetchedScriptText = null;
|
|
52
|
+
private buildScriptUrl(actionId: string): string {
|
|
53
|
+
const baseUrl = this.config?.scriptUrl || "https://js.deflect.bot/main.js";
|
|
49
54
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
}
|
|
55
|
+
if (this.config?.scriptUrl) {
|
|
56
|
+
return baseUrl;
|
|
57
|
+
}
|
|
54
58
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const promise = new Promise<void>((resolve) => {
|
|
58
|
-
resolveFn = resolve;
|
|
59
|
-
});
|
|
60
|
-
window.Deflect.ready = {
|
|
61
|
-
promise,
|
|
62
|
-
resolve: () => resolveFn(),
|
|
63
|
-
};
|
|
59
|
+
const nonce = Date.now().toString();
|
|
60
|
+
return `${baseUrl}?action_id=${actionId}&_=${nonce}`;
|
|
64
61
|
}
|
|
65
62
|
|
|
66
|
-
|
|
67
|
-
if (!
|
|
68
|
-
throw new Error("actionId is required
|
|
63
|
+
private async fetchScript(): Promise<DeflectScript> {
|
|
64
|
+
if (!this.config?.actionId) {
|
|
65
|
+
throw new Error("actionId is required");
|
|
69
66
|
}
|
|
70
|
-
window.Deflect.actionId = params.actionId;
|
|
71
67
|
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
const url = this.buildScriptUrl(this.config.actionId);
|
|
69
|
+
const response = await fetch(url, { cache: "no-store" });
|
|
70
|
+
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
throw new Error(`Failed to fetch script: ${response.status}`);
|
|
74
73
|
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
content: await response.text(),
|
|
77
|
+
sessionId: response.headers.get("session_id") || undefined,
|
|
78
|
+
};
|
|
75
79
|
}
|
|
76
80
|
|
|
77
|
-
async
|
|
78
|
-
if (
|
|
79
|
-
|
|
81
|
+
private async executeScript(script: DeflectScript): Promise<string> {
|
|
82
|
+
if (script.sessionId && typeof window !== "undefined") {
|
|
83
|
+
window.Deflect.sessionId = script.sessionId;
|
|
80
84
|
}
|
|
81
85
|
|
|
82
|
-
this.
|
|
86
|
+
const readyPromise = this.createReadyPromise();
|
|
83
87
|
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
const blobUrl = this.createScriptBlob(script.content);
|
|
89
|
+
const scriptElement = await this.loadScriptElement(blobUrl);
|
|
86
90
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
this._hasUsedPrefetch = true;
|
|
90
|
-
} else {
|
|
91
|
-
const scriptUrl = this._getScriptUrl();
|
|
92
|
-
const response = await fetch(scriptUrl, { cache: "no-store" });
|
|
91
|
+
try {
|
|
92
|
+
await readyPromise;
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
throw new Error("Failed to fetch the Deflect script");
|
|
96
|
-
}
|
|
94
|
+
const token = await this.getTokenFromScript();
|
|
97
95
|
|
|
98
|
-
|
|
99
|
-
scriptText = await response.text();
|
|
100
|
-
}
|
|
96
|
+
this.prefetchNextScript();
|
|
101
97
|
|
|
102
|
-
|
|
103
|
-
|
|
98
|
+
return token;
|
|
99
|
+
} finally {
|
|
100
|
+
this.cleanup(blobUrl, scriptElement);
|
|
104
101
|
}
|
|
102
|
+
}
|
|
105
103
|
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
private prefetchNextScript(): void {
|
|
105
|
+
this.tryWarmup().catch(() => {});
|
|
106
|
+
}
|
|
108
107
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
private createReadyPromise(): Promise<void> {
|
|
109
|
+
return new Promise<void>((resolve) => {
|
|
110
|
+
if (typeof window !== "undefined") {
|
|
111
|
+
window.Deflect.ready = {
|
|
112
|
+
promise: new Promise<void>((innerResolve) => {
|
|
113
|
+
window.Deflect.ready.resolve = innerResolve;
|
|
114
|
+
}),
|
|
115
|
+
resolve: () => resolve(),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
112
120
|
|
|
113
|
-
|
|
121
|
+
private createScriptBlob(content: string): string {
|
|
122
|
+
const blob = new Blob([content], { type: "text/javascript" });
|
|
123
|
+
return URL.createObjectURL(blob);
|
|
124
|
+
}
|
|
114
125
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
126
|
+
private async loadScriptElement(blobUrl: string): Promise<HTMLScriptElement> {
|
|
127
|
+
return new Promise((resolve, reject) => {
|
|
128
|
+
const script = document.createElement("script");
|
|
129
|
+
script.type = "module";
|
|
130
|
+
script.src = blobUrl;
|
|
119
131
|
|
|
120
|
-
|
|
132
|
+
script.onload = () => resolve(script);
|
|
133
|
+
script.onerror = () => reject(new Error("Script failed to load"));
|
|
121
134
|
|
|
135
|
+
document.head.appendChild(script);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private async getTokenFromScript(): Promise<string> {
|
|
122
140
|
if (
|
|
123
|
-
typeof window
|
|
124
|
-
typeof window.Deflect
|
|
141
|
+
typeof window === "undefined" ||
|
|
142
|
+
typeof window.Deflect?.getToken !== "function"
|
|
125
143
|
) {
|
|
126
|
-
throw new Error("
|
|
144
|
+
throw new Error("Script did not load properly - getToken not available");
|
|
127
145
|
}
|
|
128
146
|
|
|
129
|
-
let token: string;
|
|
130
|
-
|
|
131
147
|
try {
|
|
132
|
-
|
|
148
|
+
return await window.Deflect.getToken();
|
|
133
149
|
} catch (error) {
|
|
134
|
-
throw new Error(`
|
|
150
|
+
throw new Error(`Script execution failed: ${error}`);
|
|
135
151
|
}
|
|
152
|
+
}
|
|
136
153
|
|
|
137
|
-
|
|
138
|
-
|
|
154
|
+
private cleanup(blobUrl: string, scriptElement: HTMLScriptElement): void {
|
|
139
155
|
URL.revokeObjectURL(blobUrl);
|
|
140
|
-
|
|
156
|
+
scriptElement.remove();
|
|
157
|
+
|
|
158
|
+
if (typeof window !== "undefined" && window.Deflect?.getToken) {
|
|
159
|
+
delete window.Deflect.getToken;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
141
162
|
|
|
142
|
-
|
|
163
|
+
public configure(params: DeflectConfig): void {
|
|
164
|
+
if (!params.actionId?.trim()) {
|
|
165
|
+
throw new Error("actionId is required and cannot be empty");
|
|
166
|
+
}
|
|
167
|
+
this.config = { ...params };
|
|
168
|
+
if (typeof window !== "undefined") {
|
|
169
|
+
window.Deflect.actionId = params.actionId;
|
|
170
|
+
}
|
|
171
|
+
this.tryWarmup();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Deprecated name kept for backward compatibility
|
|
175
|
+
public async solveChallenge(): Promise<string> {
|
|
176
|
+
return this.getToken();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
public async getToken(): Promise<string> {
|
|
180
|
+
if (!this.config?.actionId) {
|
|
181
|
+
throw new Error("Must call configure() before solveChallenge()");
|
|
182
|
+
}
|
|
143
183
|
|
|
144
|
-
|
|
184
|
+
let script: DeflectScript;
|
|
185
|
+
|
|
186
|
+
if (this.scriptCache && !this.isWarmupInProgress) {
|
|
187
|
+
script = this.scriptCache;
|
|
188
|
+
this.scriptCache = null;
|
|
189
|
+
} else {
|
|
190
|
+
script = await this.fetchScript();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return this.executeScript(script);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
public async warmup(): Promise<boolean> {
|
|
197
|
+
if (!this.config?.actionId) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
await this.tryWarmup();
|
|
203
|
+
return this.scriptCache !== null;
|
|
204
|
+
} catch {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
public clearCache(): void {
|
|
210
|
+
this.scriptCache = null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Inject a fresh token as a hidden input into a form. Only accepts a submit event from onsubmit.
|
|
215
|
+
* Usage: <form ... onsubmit="return Deflect.injectToken(event)">
|
|
216
|
+
* Returns false to prevent double submit.
|
|
217
|
+
*/
|
|
218
|
+
public async injectToken(event: SubmitEvent): Promise<false> {
|
|
219
|
+
if (!event || !event.target || !(event.target instanceof HTMLFormElement)) {
|
|
220
|
+
throw new Error("injectToken: must be called from a form submit event");
|
|
221
|
+
}
|
|
222
|
+
event.preventDefault();
|
|
223
|
+
const form = event.target;
|
|
224
|
+
const token = await this.getToken();
|
|
225
|
+
Array.from(form.querySelectorAll('input[name="deflect_token"]')).forEach(
|
|
226
|
+
(el) => el.remove()
|
|
227
|
+
);
|
|
228
|
+
const hidden = document.createElement("input");
|
|
229
|
+
hidden.type = "hidden";
|
|
230
|
+
hidden.name = "deflect_token";
|
|
231
|
+
hidden.value = token;
|
|
232
|
+
form.appendChild(hidden);
|
|
233
|
+
form.submit();
|
|
234
|
+
return false;
|
|
145
235
|
}
|
|
146
236
|
}
|
|
147
237
|
|
package/src/types/window.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// Global window augmentation for Deflect SDK.
|
|
2
|
+
// Using 'any' here keeps backward compatibility and avoids circular type issues.
|
|
1
3
|
interface Window {
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK attaches dynamic shape at runtime
|
|
2
5
|
Deflect: any;
|
|
3
6
|
}
|