ribaunt 0.0.5
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/LICENSE +287 -0
- package/dist/cjs/index.cjs +175 -0
- package/dist/cjs/index.d.cts +31 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/solver.d.ts +16 -0
- package/dist/cjs/solver.d.ts.map +1 -0
- package/dist/cjs/solver.js +83 -0
- package/dist/cjs/solver.js.map +1 -0
- package/dist/cjs/widget-browser.d.ts +7 -0
- package/dist/cjs/widget-browser.d.ts.map +1 -0
- package/dist/cjs/widget-browser.js +12 -0
- package/dist/cjs/widget-browser.js.map +1 -0
- package/dist/cjs/widget.d.ts +64 -0
- package/dist/cjs/widget.d.ts.map +1 -0
- package/dist/cjs/widget.js +402 -0
- package/dist/cjs/widget.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +167 -0
- package/dist/index.js.map +1 -0
- package/dist/solver.d.ts +16 -0
- package/dist/solver.d.ts.map +1 -0
- package/dist/solver.js +79 -0
- package/dist/solver.js.map +1 -0
- package/dist/widget-browser.d.ts +7 -0
- package/dist/widget-browser.d.ts.map +1 -0
- package/dist/widget-browser.js +8 -0
- package/dist/widget-browser.js.map +1 -0
- package/dist/widget.d.ts +64 -0
- package/dist/widget.d.ts.map +1 -0
- package/dist/widget.js +398 -0
- package/dist/widget.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ribaunt CAPTCHA Widget - Web Component
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* ```html
|
|
6
|
+
* <ribaunt-widget
|
|
7
|
+
* challenge-endpoint="/api/challenge"
|
|
8
|
+
* verify-endpoint="/api/verify"
|
|
9
|
+
* show-warning="true"
|
|
10
|
+
* warning-message="Custom warning message"
|
|
11
|
+
* ></ribaunt-widget>
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* Or in React/Vue/Svelte:
|
|
15
|
+
* ```jsx
|
|
16
|
+
* <ribaunt-widget
|
|
17
|
+
* challengeEndpoint="/api/challenge"
|
|
18
|
+
* verifyEndpoint="/api/verify"
|
|
19
|
+
* />
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Events:
|
|
23
|
+
* - verify: Fired when verification is complete
|
|
24
|
+
* - error: Fired when an error occurs
|
|
25
|
+
* - state-change: Fired when widget state changes
|
|
26
|
+
*/
|
|
27
|
+
type WidgetState = 'initial' | 'verifying' | 'done' | 'error';
|
|
28
|
+
export declare class RibauntWidget extends HTMLElement {
|
|
29
|
+
private shadow;
|
|
30
|
+
private state;
|
|
31
|
+
private progress;
|
|
32
|
+
private captchaElement;
|
|
33
|
+
private checkboxElement;
|
|
34
|
+
private messageElement;
|
|
35
|
+
private warningElement;
|
|
36
|
+
static get observedAttributes(): string[];
|
|
37
|
+
constructor();
|
|
38
|
+
connectedCallback(): void;
|
|
39
|
+
disconnectedCallback(): void;
|
|
40
|
+
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
41
|
+
private render;
|
|
42
|
+
private attachEventListeners;
|
|
43
|
+
private removeEventListeners;
|
|
44
|
+
private handleClick;
|
|
45
|
+
private handleKeyPress;
|
|
46
|
+
private getMessage;
|
|
47
|
+
private setState;
|
|
48
|
+
private setProgress;
|
|
49
|
+
private verify;
|
|
50
|
+
/**
|
|
51
|
+
* Public API: Reset the widget to initial state
|
|
52
|
+
*/
|
|
53
|
+
reset(): void;
|
|
54
|
+
/**
|
|
55
|
+
* Public API: Get current state
|
|
56
|
+
*/
|
|
57
|
+
getState(): WidgetState;
|
|
58
|
+
/**
|
|
59
|
+
* Public API: Programmatically trigger verification
|
|
60
|
+
*/
|
|
61
|
+
startVerification(): void;
|
|
62
|
+
}
|
|
63
|
+
export default RibauntWidget;
|
|
64
|
+
//# sourceMappingURL=widget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widget.d.ts","sourceRoot":"","sources":["../../src/widget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AA+KH,KAAK,WAAW,GAAG,SAAS,GAAG,WAAW,GAAG,MAAM,GAAG,OAAO,CAAC;AAE9D,qBAAa,aAAc,SAAQ,WAAW;IAC5C,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,eAAe,CAA+B;IACtD,OAAO,CAAC,cAAc,CAAqC;IAC3D,OAAO,CAAC,cAAc,CAA+B;IAErD,MAAM,KAAK,kBAAkB,aAQ5B;;IASD,iBAAiB;IAKjB,oBAAoB;IAIpB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAMvF,OAAO,CAAC,MAAM;IA+Bd,OAAO,CAAC,oBAAoB;IAO5B,OAAO,CAAC,oBAAoB;IAO5B,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,UAAU;IAalB,OAAO,CAAC,QAAQ;IAoBhB,OAAO,CAAC,WAAW;YAYL,MAAM;IAgEpB;;OAEG;IACH,KAAK;IAKL;;OAEG;IACH,QAAQ,IAAI,WAAW;IAIvB;;OAEG;IACH,iBAAiB;CAKlB;AAQD,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Ribaunt CAPTCHA Widget - Web Component
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* ```html
|
|
7
|
+
* <ribaunt-widget
|
|
8
|
+
* challenge-endpoint="/api/challenge"
|
|
9
|
+
* verify-endpoint="/api/verify"
|
|
10
|
+
* show-warning="true"
|
|
11
|
+
* warning-message="Custom warning message"
|
|
12
|
+
* ></ribaunt-widget>
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* Or in React/Vue/Svelte:
|
|
16
|
+
* ```jsx
|
|
17
|
+
* <ribaunt-widget
|
|
18
|
+
* challengeEndpoint="/api/challenge"
|
|
19
|
+
* verifyEndpoint="/api/verify"
|
|
20
|
+
* />
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* Events:
|
|
24
|
+
* - verify: Fired when verification is complete
|
|
25
|
+
* - error: Fired when an error occurs
|
|
26
|
+
* - state-change: Fired when widget state changes
|
|
27
|
+
*/
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.RibauntWidget = void 0;
|
|
30
|
+
const solver_js_1 = require("./solver.js");
|
|
31
|
+
const WIDGET_STYLES = `
|
|
32
|
+
/* Widget Container Styles */
|
|
33
|
+
:host,
|
|
34
|
+
:host * {
|
|
35
|
+
box-sizing: border-box;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.captcha {
|
|
39
|
+
background-color: var(--ribaunt-background, #fdfdfd);
|
|
40
|
+
border: 1px solid var(--ribaunt-border-color, #dddddd8f);
|
|
41
|
+
border-radius: var(--ribaunt-border-radius, 14px);
|
|
42
|
+
user-select: none;
|
|
43
|
+
height: var(--ribaunt-widget-height, 58px);
|
|
44
|
+
width: var(--ribaunt-widget-width, 230px);
|
|
45
|
+
display: flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
padding: var(--ribaunt-widget-padding, 14px);
|
|
48
|
+
gap: var(--ribaunt-gap, 15px);
|
|
49
|
+
cursor: pointer;
|
|
50
|
+
transition: filter .2s, transform .2s;
|
|
51
|
+
position: relative;
|
|
52
|
+
-webkit-tap-highlight-color: rgba(255, 255, 255, 0);
|
|
53
|
+
overflow: hidden;
|
|
54
|
+
color: var(--ribaunt-color, #212121);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.captcha:hover {
|
|
58
|
+
filter: brightness(98%);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Checkbox Styles */
|
|
62
|
+
.checkbox {
|
|
63
|
+
width: var(--ribaunt-checkbox-size, 25px);
|
|
64
|
+
height: var(--ribaunt-checkbox-size, 25px);
|
|
65
|
+
border: var(--ribaunt-checkbox-border, 1px solid #aaaaaad1);
|
|
66
|
+
border-radius: var(--ribaunt-checkbox-border-radius, 6px);
|
|
67
|
+
background-color: var(--ribaunt-checkbox-background, #fafafa91);
|
|
68
|
+
transition: opacity .2s;
|
|
69
|
+
margin-top: var(--ribaunt-checkbox-margin, 2px);
|
|
70
|
+
margin-bottom: var(--ribaunt-checkbox-margin, 2px);
|
|
71
|
+
flex-shrink: 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Font Family */
|
|
75
|
+
.captcha * {
|
|
76
|
+
font-family: var(--ribaunt-font, system, -apple-system, "BlinkMacSystemFont", ".SFNSText-Regular", "San Francisco", "Roboto", "Segoe UI", "Helvetica Neue", "Lucida Grande", "Ubuntu", "arial", sans-serif);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Label Text */
|
|
80
|
+
.captcha p {
|
|
81
|
+
margin: 0;
|
|
82
|
+
font-weight: 500;
|
|
83
|
+
font-size: 15px;
|
|
84
|
+
user-select: none;
|
|
85
|
+
transition: opacity .2s;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Verifying State */
|
|
89
|
+
.captcha[data-state=verifying] .checkbox {
|
|
90
|
+
background: none;
|
|
91
|
+
display: flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
justify-content: center;
|
|
94
|
+
transform: scale(1.1);
|
|
95
|
+
border: none;
|
|
96
|
+
border-radius: 50%;
|
|
97
|
+
background: conic-gradient(
|
|
98
|
+
var(--ribaunt-spinner-color, #000) 0%,
|
|
99
|
+
var(--ribaunt-spinner-color, #000) var(--progress, 0%),
|
|
100
|
+
var(--ribaunt-spinner-background-color, #eee) var(--progress, 0%),
|
|
101
|
+
var(--ribaunt-spinner-background-color, #eee) 100%
|
|
102
|
+
);
|
|
103
|
+
position: relative;
|
|
104
|
+
transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.captcha[data-state=verifying] .checkbox::after {
|
|
108
|
+
content: "";
|
|
109
|
+
background-color: var(--ribaunt-background, #fdfdfd);
|
|
110
|
+
width: calc(100% - var(--ribaunt-spinner-thickness, 5px));
|
|
111
|
+
height: calc(100% - var(--ribaunt-spinner-thickness, 5px));
|
|
112
|
+
border-radius: 50%;
|
|
113
|
+
margin: calc(var(--ribaunt-spinner-thickness, 5px) / 2);
|
|
114
|
+
transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* Done/Success State */
|
|
118
|
+
.captcha[data-state=done] .checkbox {
|
|
119
|
+
border: 1px solid transparent;
|
|
120
|
+
background-image: var(--ribaunt-checkmark, url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cstyle%3E%40keyframes%20anim%7B0%25%7Bstroke-dashoffset%3A23.21320343017578px%7Dto%7Bstroke-dashoffset%3A0%7D%7D%3C%2Fstyle%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%2300a67d%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%222%22%20d%3D%22m5%2012%205%205L20%207%22%20style%3D%22stroke-dashoffset%3A0%3Bstroke-dasharray%3A23.21320343017578px%3Banimation%3Aanim%20.5s%20ease%22%2F%3E%3C%2Fsvg%3E"));
|
|
121
|
+
background-size: cover;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* Error State */
|
|
125
|
+
.captcha[data-state=error] .checkbox {
|
|
126
|
+
border: 1px solid transparent;
|
|
127
|
+
background-image: var(--ribaunt-error-cross, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='96' height='96' viewBox='0 0 24 24'%3E%3Cpath fill='%23f55b50' d='M11 15h2v2h-2zm0-8h2v6h-2zm1-5C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2m0 18a8 8 0 0 1-8-8a8 8 0 0 1 8-8a8 8 0 0 1 8 8a8 8 0 0 1-8 8'/%3E%3C/svg%3E"));
|
|
128
|
+
background-size: cover;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* Disabled State */
|
|
132
|
+
.captcha[disabled] {
|
|
133
|
+
cursor: not-allowed;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.captcha[disabled][data-state=verifying] {
|
|
137
|
+
cursor: progress;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.captcha[disabled][data-state=done] {
|
|
141
|
+
cursor: default;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* Logo */
|
|
145
|
+
.logo {
|
|
146
|
+
position: absolute;
|
|
147
|
+
bottom: 8px;
|
|
148
|
+
right: 8px;
|
|
149
|
+
width: 20px;
|
|
150
|
+
height: auto;
|
|
151
|
+
opacity: 0.6;
|
|
152
|
+
transition: opacity 0.2s, color 0.2s;
|
|
153
|
+
color: var(--ribaunt-logo-color, #666);
|
|
154
|
+
pointer-events: auto;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.logo svg {
|
|
158
|
+
width: 100%;
|
|
159
|
+
height: auto;
|
|
160
|
+
display: block;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* Logo color adapts to theme */
|
|
164
|
+
@media (prefers-color-scheme: dark) {
|
|
165
|
+
.logo {
|
|
166
|
+
color: var(--ribaunt-logo-color, #999);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* Warning Message */
|
|
171
|
+
.warning {
|
|
172
|
+
width: var(--ribaunt-widget-width, 230px);
|
|
173
|
+
background: rgb(237, 56, 46);
|
|
174
|
+
color: white;
|
|
175
|
+
padding: 4px 6px;
|
|
176
|
+
padding-bottom: calc(var(--ribaunt-border-radius, 14px) + 5px);
|
|
177
|
+
font-size: 10px;
|
|
178
|
+
box-sizing: border-box;
|
|
179
|
+
font-family: system-ui;
|
|
180
|
+
border-top-left-radius: 8px;
|
|
181
|
+
border-top-right-radius: 8px;
|
|
182
|
+
text-align: center;
|
|
183
|
+
user-select: none;
|
|
184
|
+
margin-bottom: -35.5px;
|
|
185
|
+
opacity: 0;
|
|
186
|
+
transition: margin-bottom .3s, opacity .3s;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.warning.visible {
|
|
190
|
+
margin-bottom: calc(-1 * var(--ribaunt-border-radius, 14px));
|
|
191
|
+
opacity: 1;
|
|
192
|
+
}
|
|
193
|
+
`;
|
|
194
|
+
const RIBAUNT_LOGO = `
|
|
195
|
+
<svg width="500" height="384" viewBox="0 0 500 384" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
196
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M368.357 122.188H236.618L187.59 0H450.972L500 122.188L434.14 286.295H302.478L368.357 122.188Z" fill="currentColor"/>
|
|
197
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M236.619 122.188L341.575 383.702H209.913L170.796 286.199L131.663 383.702H0L104.957 122.188H236.619Z" fill="currentColor"/>
|
|
198
|
+
</svg>
|
|
199
|
+
`;
|
|
200
|
+
class RibauntWidget extends HTMLElement {
|
|
201
|
+
static get observedAttributes() {
|
|
202
|
+
return [
|
|
203
|
+
'challenge-endpoint',
|
|
204
|
+
'verify-endpoint',
|
|
205
|
+
'show-warning',
|
|
206
|
+
'warning-message',
|
|
207
|
+
'disabled',
|
|
208
|
+
];
|
|
209
|
+
}
|
|
210
|
+
constructor() {
|
|
211
|
+
super();
|
|
212
|
+
this.state = 'initial';
|
|
213
|
+
this.progress = 0;
|
|
214
|
+
this.captchaElement = null;
|
|
215
|
+
this.checkboxElement = null;
|
|
216
|
+
this.messageElement = null;
|
|
217
|
+
this.warningElement = null;
|
|
218
|
+
this.shadow = this.attachShadow({ mode: 'open' });
|
|
219
|
+
this.handleClick = this.handleClick.bind(this);
|
|
220
|
+
this.handleKeyPress = this.handleKeyPress.bind(this);
|
|
221
|
+
}
|
|
222
|
+
connectedCallback() {
|
|
223
|
+
this.render();
|
|
224
|
+
this.attachEventListeners();
|
|
225
|
+
}
|
|
226
|
+
disconnectedCallback() {
|
|
227
|
+
this.removeEventListeners();
|
|
228
|
+
}
|
|
229
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
230
|
+
if (oldValue !== newValue) {
|
|
231
|
+
this.render();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
render() {
|
|
235
|
+
const showWarning = this.hasAttribute('show-warning') && this.getAttribute('show-warning') !== 'false';
|
|
236
|
+
const warningMessage = this.getAttribute('warning-message') || 'Enable WASM for significantly faster solving';
|
|
237
|
+
this.shadow.innerHTML = `
|
|
238
|
+
<style>${WIDGET_STYLES}</style>
|
|
239
|
+
<div>
|
|
240
|
+
${showWarning ? `<div class="warning ${showWarning ? 'visible' : ''}">${warningMessage}</div>` : ''}
|
|
241
|
+
<div class="captcha" data-state="${this.state}" role="button" tabindex="0" aria-label="${this.getMessage()}">
|
|
242
|
+
<div class="checkbox"></div>
|
|
243
|
+
<p>${this.getMessage()}</p>
|
|
244
|
+
<a class="logo" href="https://ribaunt.com" target="_blank" rel="noopener noreferrer" aria-label="Powered by Ribaunt">
|
|
245
|
+
${RIBAUNT_LOGO}
|
|
246
|
+
</a>
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
`;
|
|
250
|
+
this.captchaElement = this.shadow.querySelector('.captcha');
|
|
251
|
+
this.checkboxElement = this.shadow.querySelector('.checkbox');
|
|
252
|
+
this.messageElement = this.shadow.querySelector('p');
|
|
253
|
+
this.warningElement = this.shadow.querySelector('.warning');
|
|
254
|
+
// Update progress CSS variable if verifying
|
|
255
|
+
if (this.state === 'verifying' && this.captchaElement) {
|
|
256
|
+
this.captchaElement.style.setProperty('--progress', `${this.progress}%`);
|
|
257
|
+
}
|
|
258
|
+
this.attachEventListeners();
|
|
259
|
+
}
|
|
260
|
+
attachEventListeners() {
|
|
261
|
+
if (this.captchaElement) {
|
|
262
|
+
this.captchaElement.addEventListener('click', this.handleClick);
|
|
263
|
+
this.captchaElement.addEventListener('keypress', this.handleKeyPress);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
removeEventListeners() {
|
|
267
|
+
if (this.captchaElement) {
|
|
268
|
+
this.captchaElement.removeEventListener('click', this.handleClick);
|
|
269
|
+
this.captchaElement.removeEventListener('keypress', this.handleKeyPress);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
handleClick() {
|
|
273
|
+
if (this.state !== 'initial' && this.state !== 'error')
|
|
274
|
+
return;
|
|
275
|
+
this.verify();
|
|
276
|
+
}
|
|
277
|
+
handleKeyPress(e) {
|
|
278
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
279
|
+
e.preventDefault();
|
|
280
|
+
this.handleClick();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
getMessage() {
|
|
284
|
+
switch (this.state) {
|
|
285
|
+
case 'initial':
|
|
286
|
+
return "I'm a human";
|
|
287
|
+
case 'verifying':
|
|
288
|
+
return `Verifying... ${this.progress}%`;
|
|
289
|
+
case 'done':
|
|
290
|
+
return "You're a human";
|
|
291
|
+
case 'error':
|
|
292
|
+
return 'Error. Try again.';
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
setState(newState) {
|
|
296
|
+
this.state = newState;
|
|
297
|
+
if (this.captchaElement) {
|
|
298
|
+
this.captchaElement.setAttribute('data-state', this.state);
|
|
299
|
+
this.captchaElement.setAttribute('aria-label', this.getMessage());
|
|
300
|
+
}
|
|
301
|
+
if (this.messageElement) {
|
|
302
|
+
this.messageElement.textContent = this.getMessage();
|
|
303
|
+
}
|
|
304
|
+
// Dispatch state change event
|
|
305
|
+
this.dispatchEvent(new CustomEvent('state-change', {
|
|
306
|
+
detail: { state: this.state },
|
|
307
|
+
bubbles: true,
|
|
308
|
+
composed: true,
|
|
309
|
+
}));
|
|
310
|
+
}
|
|
311
|
+
setProgress(value) {
|
|
312
|
+
// Smooth the progress value for better animation
|
|
313
|
+
const smoothedProgress = Math.round(value * 10) / 10;
|
|
314
|
+
this.progress = smoothedProgress;
|
|
315
|
+
if (this.messageElement) {
|
|
316
|
+
this.messageElement.textContent = this.getMessage();
|
|
317
|
+
}
|
|
318
|
+
if (this.captchaElement) {
|
|
319
|
+
this.captchaElement.style.setProperty('--progress', `${smoothedProgress}%`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
async verify() {
|
|
323
|
+
this.setState('verifying');
|
|
324
|
+
this.setProgress(0);
|
|
325
|
+
try {
|
|
326
|
+
const challengeEndpoint = this.getAttribute('challenge-endpoint');
|
|
327
|
+
const verifyEndpoint = this.getAttribute('verify-endpoint');
|
|
328
|
+
// Get challenge tokens
|
|
329
|
+
let tokens = [];
|
|
330
|
+
if (challengeEndpoint) {
|
|
331
|
+
const response = await fetch(challengeEndpoint);
|
|
332
|
+
if (!response.ok)
|
|
333
|
+
throw new Error('Failed to fetch challenge');
|
|
334
|
+
const data = await response.json();
|
|
335
|
+
tokens = data.challenges || data.tokens || data;
|
|
336
|
+
}
|
|
337
|
+
if (!tokens || !Array.isArray(tokens) || tokens.length === 0) {
|
|
338
|
+
throw new Error('No challenge tokens available');
|
|
339
|
+
}
|
|
340
|
+
// Solve challenges using the browser-compatible solver
|
|
341
|
+
const solutions = await (0, solver_js_1.solveChallenge)(tokens, (progress) => {
|
|
342
|
+
this.setProgress(progress);
|
|
343
|
+
});
|
|
344
|
+
// Verify solution
|
|
345
|
+
if (verifyEndpoint) {
|
|
346
|
+
const response = await fetch(verifyEndpoint, {
|
|
347
|
+
method: 'POST',
|
|
348
|
+
headers: { 'Content-Type': 'application/json' },
|
|
349
|
+
body: JSON.stringify({ tokens, solutions }),
|
|
350
|
+
});
|
|
351
|
+
if (!response.ok) {
|
|
352
|
+
throw new Error('Verification failed');
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
this.setState('done');
|
|
356
|
+
// Dispatch verify event
|
|
357
|
+
this.dispatchEvent(new CustomEvent('verify', {
|
|
358
|
+
detail: { solutions },
|
|
359
|
+
bubbles: true,
|
|
360
|
+
composed: true,
|
|
361
|
+
}));
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
this.setState('error');
|
|
365
|
+
// Dispatch error event
|
|
366
|
+
this.dispatchEvent(new CustomEvent('error', {
|
|
367
|
+
detail: { error: error instanceof Error ? error.message : String(error) },
|
|
368
|
+
bubbles: true,
|
|
369
|
+
composed: true,
|
|
370
|
+
}));
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Public API: Reset the widget to initial state
|
|
375
|
+
*/
|
|
376
|
+
reset() {
|
|
377
|
+
this.setState('initial');
|
|
378
|
+
this.setProgress(0);
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Public API: Get current state
|
|
382
|
+
*/
|
|
383
|
+
getState() {
|
|
384
|
+
return this.state;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Public API: Programmatically trigger verification
|
|
388
|
+
*/
|
|
389
|
+
startVerification() {
|
|
390
|
+
if (this.state === 'initial' || this.state === 'error') {
|
|
391
|
+
this.verify();
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
exports.RibauntWidget = RibauntWidget;
|
|
396
|
+
// Register the custom element
|
|
397
|
+
if (typeof window !== 'undefined' && !customElements.get('ribaunt-widget')) {
|
|
398
|
+
customElements.define('ribaunt-widget', RibauntWidget);
|
|
399
|
+
}
|
|
400
|
+
// Export for use in TypeScript
|
|
401
|
+
exports.default = RibauntWidget;
|
|
402
|
+
//# sourceMappingURL=widget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widget.js","sourceRoot":"","sources":["../../src/widget.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type ChallengeToken = string;
|
|
2
|
+
export interface ChallengeSolution {
|
|
3
|
+
nonce: string;
|
|
4
|
+
hash: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Creates one or more PoW challenges and returns them as signed JWT tokens.
|
|
8
|
+
*
|
|
9
|
+
* @param difficulty - Number of leading zeros required in the hash (default 6)
|
|
10
|
+
* @param amount - Number of challenges to create (default 4)
|
|
11
|
+
* @param ttlSeconds - Time to live for each challenge in seconds (default 30)
|
|
12
|
+
* @returns An array of JWT challenge tokens
|
|
13
|
+
*/
|
|
14
|
+
export declare function createChallenge(difficulty?: number, amount?: number, ttlSeconds?: number): ChallengeToken[];
|
|
15
|
+
/**
|
|
16
|
+
* Solves one or more PoW challenges encoded in JWT tokens.
|
|
17
|
+
*
|
|
18
|
+
* @param token - The JWT challenge token or an array of tokens
|
|
19
|
+
* @returns The nonce/hash pair for single input or an array of them for multiple tokens
|
|
20
|
+
*/
|
|
21
|
+
export declare function solveChallenge(token: ChallengeToken): ChallengeSolution | undefined;
|
|
22
|
+
export declare function solveChallenge(token: ChallengeToken[]): ChallengeSolution[] | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Verifies a PoW solution returned by the client.
|
|
25
|
+
*
|
|
26
|
+
* @param token - The original JWT issued as the challenge (single token or array of tokens)
|
|
27
|
+
* @param nonce - The nonce/answer submitted by the client (single nonce, array of nonces, or array of solution objects)
|
|
28
|
+
* @returns true only if every provided solution is valid; otherwise false
|
|
29
|
+
*/
|
|
30
|
+
export declare function verifySolution(token: ChallengeToken | ChallengeToken[], nonce: number | string | Array<number | string> | ChallengeSolution | ChallengeSolution[]): boolean;
|
|
31
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAeA,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC;AAEpC,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAqCD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,UAAU,GAAE,MAAU,EACtB,MAAM,GAAE,MAAU,EAClB,UAAU,GAAE,MAAW,GACtB,cAAc,EAAE,CAKlB;AAoDD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,GAAG,iBAAiB,GAAG,SAAS,CAAC;AACrF,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,iBAAiB,EAAE,GAAG,SAAS,CAAC;AAuBzF;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,cAAc,GAAG,cAAc,EAAE,EACxC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,iBAAiB,GAAG,iBAAiB,EAAE,GACxF,OAAO,CAqDT"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import jwt from 'jsonwebtoken';
|
|
3
|
+
const secret = process.env.RIBAUNT_SECRET;
|
|
4
|
+
if (!secret) {
|
|
5
|
+
throw new Error('RIBAUNT_SECRET environment variable is not set!');
|
|
6
|
+
}
|
|
7
|
+
function generateChallenge(length = 8) {
|
|
8
|
+
const buffer = crypto.randomBytes(length);
|
|
9
|
+
return buffer.toString('base64').slice(0, length);
|
|
10
|
+
}
|
|
11
|
+
function createChallengePayload(difficulty, ttlSeconds) {
|
|
12
|
+
return {
|
|
13
|
+
challenge: generateChallenge(),
|
|
14
|
+
difficulty,
|
|
15
|
+
expires: Math.floor(Date.now() / 1000) + ttlSeconds,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function signChallenge(payload) {
|
|
19
|
+
return jwt.sign(payload, secret);
|
|
20
|
+
}
|
|
21
|
+
function assertValidAmount(amount) {
|
|
22
|
+
if (!Number.isFinite(amount)) {
|
|
23
|
+
throw new Error('Challenge amount must be a finite number');
|
|
24
|
+
}
|
|
25
|
+
const normalized = Math.floor(amount);
|
|
26
|
+
if (normalized < 1) {
|
|
27
|
+
throw new Error('Challenge amount must be at least 1');
|
|
28
|
+
}
|
|
29
|
+
return normalized;
|
|
30
|
+
}
|
|
31
|
+
function createSingleChallenge(difficulty, ttlSeconds) {
|
|
32
|
+
const payload = createChallengePayload(difficulty, ttlSeconds);
|
|
33
|
+
return signChallenge(payload);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Creates one or more PoW challenges and returns them as signed JWT tokens.
|
|
37
|
+
*
|
|
38
|
+
* @param difficulty - Number of leading zeros required in the hash (default 6)
|
|
39
|
+
* @param amount - Number of challenges to create (default 4)
|
|
40
|
+
* @param ttlSeconds - Time to live for each challenge in seconds (default 30)
|
|
41
|
+
* @returns An array of JWT challenge tokens
|
|
42
|
+
*/
|
|
43
|
+
export function createChallenge(difficulty = 5, amount = 4, ttlSeconds = 30) {
|
|
44
|
+
const normalizedAmount = assertValidAmount(amount);
|
|
45
|
+
const challenges = Array.from({ length: normalizedAmount }, () => createSingleChallenge(difficulty, ttlSeconds));
|
|
46
|
+
return challenges;
|
|
47
|
+
}
|
|
48
|
+
function solveSingleChallenge(token) {
|
|
49
|
+
try {
|
|
50
|
+
const payload = jwt.decode(token);
|
|
51
|
+
if (!payload)
|
|
52
|
+
return undefined;
|
|
53
|
+
const { challenge, difficulty } = payload;
|
|
54
|
+
const prefix = '0'.repeat(difficulty);
|
|
55
|
+
let nonce = 0;
|
|
56
|
+
while (true) {
|
|
57
|
+
const hash = crypto
|
|
58
|
+
.createHash('sha256')
|
|
59
|
+
.update(`${challenge}${nonce}`)
|
|
60
|
+
.digest('hex');
|
|
61
|
+
if (hash.startsWith(prefix)) {
|
|
62
|
+
return { nonce: String(nonce), hash };
|
|
63
|
+
}
|
|
64
|
+
nonce++;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function verifySingleSolution(token, nonce) {
|
|
72
|
+
if (nonce === undefined || nonce === null) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const payload = jwt.verify(token, secret);
|
|
77
|
+
const now = Math.floor(Date.now() / 1000);
|
|
78
|
+
if (payload.expires < now)
|
|
79
|
+
return false;
|
|
80
|
+
const nonceValue = typeof nonce === 'number' ? String(nonce) : nonce;
|
|
81
|
+
const hash = crypto
|
|
82
|
+
.createHash('sha256')
|
|
83
|
+
.update(`${payload.challenge}${nonceValue}`)
|
|
84
|
+
.digest('hex');
|
|
85
|
+
const prefix = '0'.repeat(payload.difficulty);
|
|
86
|
+
return hash.startsWith(prefix);
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export function solveChallenge(token) {
|
|
93
|
+
if (Array.isArray(token)) {
|
|
94
|
+
const solutions = [];
|
|
95
|
+
for (const singleToken of token) {
|
|
96
|
+
const solution = solveSingleChallenge(singleToken);
|
|
97
|
+
if (!solution) {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
solutions.push(solution);
|
|
101
|
+
}
|
|
102
|
+
return solutions;
|
|
103
|
+
}
|
|
104
|
+
const solution = solveSingleChallenge(token);
|
|
105
|
+
return solution;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Verifies a PoW solution returned by the client.
|
|
109
|
+
*
|
|
110
|
+
* @param token - The original JWT issued as the challenge (single token or array of tokens)
|
|
111
|
+
* @param nonce - The nonce/answer submitted by the client (single nonce, array of nonces, or array of solution objects)
|
|
112
|
+
* @returns true only if every provided solution is valid; otherwise false
|
|
113
|
+
*/
|
|
114
|
+
export function verifySolution(token, nonce) {
|
|
115
|
+
if (Array.isArray(token)) {
|
|
116
|
+
let nonces;
|
|
117
|
+
if (Array.isArray(nonce)) {
|
|
118
|
+
if (nonce.length !== token.length) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
if (nonce.length > 0 && typeof nonce[0] === 'object' && 'nonce' in nonce[0]) {
|
|
122
|
+
nonces = nonce.map(s => s.nonce);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
nonces = nonce;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
for (let index = 0; index < token.length; index++) {
|
|
132
|
+
const challengeToken = token[index];
|
|
133
|
+
const nonceEntry = nonces[index];
|
|
134
|
+
if (challengeToken === undefined || nonceEntry === undefined) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
if (!verifySingleSolution(challengeToken, nonceEntry)) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
let effectiveNonce;
|
|
144
|
+
if (Array.isArray(nonce)) {
|
|
145
|
+
if (nonce.length === 0) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
if (typeof nonce[0] === 'object' && 'nonce' in nonce[0]) {
|
|
149
|
+
effectiveNonce = nonce[0].nonce;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
effectiveNonce = nonce[0];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else if (typeof nonce === 'object' && 'nonce' in nonce) {
|
|
156
|
+
effectiveNonce = nonce.nonce;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
effectiveNonce = nonce;
|
|
160
|
+
}
|
|
161
|
+
if (effectiveNonce === undefined) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
const result = verifySingleSolution(token, effectiveNonce);
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=index.js.map
|