@eudi-verify/embed 0.1.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/LICENSE +184 -0
- package/README.md +267 -0
- package/dist/eudi-verify.cjs +962 -0
- package/dist/eudi-verify.cjs.map +1 -0
- package/dist/eudi-verify.d.cts +205 -0
- package/dist/eudi-verify.d.ts +205 -0
- package/dist/eudi-verify.iife.js +1677 -0
- package/dist/eudi-verify.iife.js.map +1 -0
- package/dist/eudi-verify.js +926 -0
- package/dist/eudi-verify.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,1677 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var EudiVerify = (() => {
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var src_exports = {};
|
|
23
|
+
__export(src_exports, {
|
|
24
|
+
CSS_VARIABLES: () => CSS_VARIABLES,
|
|
25
|
+
EudiVerifyElement: () => EudiVerifyElement,
|
|
26
|
+
STATE_MESSAGES: () => STATE_MESSAGES,
|
|
27
|
+
VERSION: () => VERSION,
|
|
28
|
+
announce: () => announce,
|
|
29
|
+
clearAnnouncement: () => clearAnnouncement,
|
|
30
|
+
createFocusTrap: () => createFocusTrap,
|
|
31
|
+
createStyles: () => createStyles,
|
|
32
|
+
getFocusableElements: () => getFocusableElements,
|
|
33
|
+
getStateId: () => getStateId,
|
|
34
|
+
renderWidget: () => renderWidget,
|
|
35
|
+
updateWidgetState: () => updateWidgetState
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// ../client/dist/index.js
|
|
39
|
+
var TERMINAL_STATUSES = Object.freeze([
|
|
40
|
+
"verified",
|
|
41
|
+
"rejected",
|
|
42
|
+
"expired",
|
|
43
|
+
"cancelled",
|
|
44
|
+
"error"
|
|
45
|
+
]);
|
|
46
|
+
function isTerminalStatus(status) {
|
|
47
|
+
return TERMINAL_STATUSES.includes(status);
|
|
48
|
+
}
|
|
49
|
+
var EudiClientError = class extends Error {
|
|
50
|
+
constructor(message) {
|
|
51
|
+
super(message);
|
|
52
|
+
this.name = "EudiClientError";
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var NetworkError = class extends EudiClientError {
|
|
56
|
+
cause;
|
|
57
|
+
constructor(message, cause) {
|
|
58
|
+
super(message);
|
|
59
|
+
this.name = "NetworkError";
|
|
60
|
+
this.cause = cause;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
var ApiResponseError = class extends EudiClientError {
|
|
64
|
+
statusCode;
|
|
65
|
+
errorCode;
|
|
66
|
+
details;
|
|
67
|
+
constructor(statusCode, response) {
|
|
68
|
+
super(response.message);
|
|
69
|
+
this.name = "ApiResponseError";
|
|
70
|
+
this.statusCode = statusCode;
|
|
71
|
+
this.errorCode = response.error;
|
|
72
|
+
this.details = response.details;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
var SessionNotFoundError = class extends ApiResponseError {
|
|
76
|
+
sessionId;
|
|
77
|
+
constructor(sessionId) {
|
|
78
|
+
super(404, { error: "not_found", message: `Session ${sessionId} not found` });
|
|
79
|
+
this.name = "SessionNotFoundError";
|
|
80
|
+
this.sessionId = sessionId;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
var RateLimitError = class extends ApiResponseError {
|
|
84
|
+
retryAfterMs;
|
|
85
|
+
constructor(retryAfterMs) {
|
|
86
|
+
super(429, { error: "rate_limit", message: "Rate limit exceeded" });
|
|
87
|
+
this.name = "RateLimitError";
|
|
88
|
+
this.retryAfterMs = retryAfterMs;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
function createApiClient(config) {
|
|
92
|
+
const { baseUrl, timeoutMs = 3e4 } = config;
|
|
93
|
+
const fetchFn = config.fetch ?? fetch;
|
|
94
|
+
const normalizedBaseUrl = baseUrl.replace(/\/$/, "");
|
|
95
|
+
async function request(method, path, body) {
|
|
96
|
+
const url = `${normalizedBaseUrl}${path}`;
|
|
97
|
+
const controller = new AbortController();
|
|
98
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
99
|
+
try {
|
|
100
|
+
const response = await fetchFn(url, {
|
|
101
|
+
method,
|
|
102
|
+
headers: {
|
|
103
|
+
"Content-Type": "application/json",
|
|
104
|
+
Accept: "application/json"
|
|
105
|
+
},
|
|
106
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
107
|
+
signal: controller.signal
|
|
108
|
+
});
|
|
109
|
+
clearTimeout(timeoutId);
|
|
110
|
+
if (!response.ok) {
|
|
111
|
+
await handleErrorResponse(response, path);
|
|
112
|
+
}
|
|
113
|
+
const data = await response.json();
|
|
114
|
+
return data;
|
|
115
|
+
} catch (error) {
|
|
116
|
+
clearTimeout(timeoutId);
|
|
117
|
+
if (error instanceof ApiResponseError) {
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
if (error instanceof Error) {
|
|
121
|
+
if (error.name === "AbortError") {
|
|
122
|
+
throw new NetworkError(`Request timeout after ${timeoutMs}ms`);
|
|
123
|
+
}
|
|
124
|
+
throw new NetworkError(`Network request failed: ${error.message}`, error);
|
|
125
|
+
}
|
|
126
|
+
throw new NetworkError("Unknown network error");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async function handleErrorResponse(response, path) {
|
|
130
|
+
const { status } = response;
|
|
131
|
+
if (status === 404) {
|
|
132
|
+
const sessionIdMatch = path.match(/\/sessions\/([^/]+)/);
|
|
133
|
+
if (sessionIdMatch) {
|
|
134
|
+
throw new SessionNotFoundError(sessionIdMatch[1]);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (status === 429) {
|
|
138
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
139
|
+
const retryAfterMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : void 0;
|
|
140
|
+
throw new RateLimitError(retryAfterMs);
|
|
141
|
+
}
|
|
142
|
+
let errorBody;
|
|
143
|
+
try {
|
|
144
|
+
errorBody = await response.json();
|
|
145
|
+
} catch {
|
|
146
|
+
errorBody = {
|
|
147
|
+
error: "unknown_error",
|
|
148
|
+
message: `HTTP ${status}`
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
throw new ApiResponseError(status, errorBody);
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
async createSession(verificationRequest) {
|
|
155
|
+
return request("POST", "/sessions", { request: verificationRequest });
|
|
156
|
+
},
|
|
157
|
+
async getSession(sessionId) {
|
|
158
|
+
return request("GET", `/sessions/${encodeURIComponent(sessionId)}`);
|
|
159
|
+
},
|
|
160
|
+
async cancelSession(sessionId) {
|
|
161
|
+
return request(
|
|
162
|
+
"POST",
|
|
163
|
+
`/sessions/${encodeURIComponent(sessionId)}/cancel`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
var DEFAULT_CONFIG = {
|
|
169
|
+
initialIntervalMs: 1e3,
|
|
170
|
+
maxIntervalMs: 1e4,
|
|
171
|
+
backoffMultiplier: 2
|
|
172
|
+
};
|
|
173
|
+
function createPoller(fn, config = {}) {
|
|
174
|
+
const { initialIntervalMs, maxIntervalMs, backoffMultiplier } = {
|
|
175
|
+
...DEFAULT_CONFIG,
|
|
176
|
+
...config
|
|
177
|
+
};
|
|
178
|
+
let currentInterval = initialIntervalMs;
|
|
179
|
+
let timeoutId = null;
|
|
180
|
+
let running = false;
|
|
181
|
+
async function poll() {
|
|
182
|
+
if (!running) return;
|
|
183
|
+
try {
|
|
184
|
+
const shouldStop = await fn();
|
|
185
|
+
if (shouldStop || !running) {
|
|
186
|
+
running = false;
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
} catch {
|
|
190
|
+
}
|
|
191
|
+
if (!running) return;
|
|
192
|
+
currentInterval = Math.min(
|
|
193
|
+
currentInterval * backoffMultiplier,
|
|
194
|
+
maxIntervalMs
|
|
195
|
+
);
|
|
196
|
+
timeoutId = setTimeout(() => {
|
|
197
|
+
void poll();
|
|
198
|
+
}, currentInterval);
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
start() {
|
|
202
|
+
if (running) return;
|
|
203
|
+
running = true;
|
|
204
|
+
currentInterval = initialIntervalMs;
|
|
205
|
+
void poll();
|
|
206
|
+
},
|
|
207
|
+
stop() {
|
|
208
|
+
running = false;
|
|
209
|
+
if (timeoutId !== null) {
|
|
210
|
+
clearTimeout(timeoutId);
|
|
211
|
+
timeoutId = null;
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
reset() {
|
|
215
|
+
currentInterval = initialIntervalMs;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
var EC_LEVELS = { L: 0, M: 1, Q: 2, H: 3 };
|
|
220
|
+
var VERSION_TABLE = {
|
|
221
|
+
L: [
|
|
222
|
+
{ version: 1, totalCodewords: 19, ecCodewordsPerBlock: 7, numBlocks: 1 },
|
|
223
|
+
{ version: 2, totalCodewords: 34, ecCodewordsPerBlock: 10, numBlocks: 1 },
|
|
224
|
+
{ version: 3, totalCodewords: 55, ecCodewordsPerBlock: 15, numBlocks: 1 },
|
|
225
|
+
{ version: 4, totalCodewords: 80, ecCodewordsPerBlock: 20, numBlocks: 1 },
|
|
226
|
+
{ version: 5, totalCodewords: 108, ecCodewordsPerBlock: 26, numBlocks: 1 },
|
|
227
|
+
{ version: 6, totalCodewords: 136, ecCodewordsPerBlock: 18, numBlocks: 2 },
|
|
228
|
+
{ version: 7, totalCodewords: 156, ecCodewordsPerBlock: 20, numBlocks: 2 },
|
|
229
|
+
{ version: 8, totalCodewords: 194, ecCodewordsPerBlock: 24, numBlocks: 2 },
|
|
230
|
+
{ version: 9, totalCodewords: 232, ecCodewordsPerBlock: 30, numBlocks: 2 },
|
|
231
|
+
{ version: 10, totalCodewords: 274, ecCodewordsPerBlock: 18, numBlocks: 4 },
|
|
232
|
+
{ version: 11, totalCodewords: 401, ecCodewordsPerBlock: 20, numBlocks: 4 },
|
|
233
|
+
{ version: 12, totalCodewords: 466, ecCodewordsPerBlock: 24, numBlocks: 4 },
|
|
234
|
+
{ version: 13, totalCodewords: 532, ecCodewordsPerBlock: 26, numBlocks: 4 },
|
|
235
|
+
{ version: 14, totalCodewords: 588, ecCodewordsPerBlock: 30, numBlocks: 4 }
|
|
236
|
+
],
|
|
237
|
+
M: [
|
|
238
|
+
{ version: 1, totalCodewords: 16, ecCodewordsPerBlock: 10, numBlocks: 1 },
|
|
239
|
+
{ version: 2, totalCodewords: 28, ecCodewordsPerBlock: 16, numBlocks: 1 },
|
|
240
|
+
{ version: 3, totalCodewords: 44, ecCodewordsPerBlock: 26, numBlocks: 1 },
|
|
241
|
+
{ version: 4, totalCodewords: 64, ecCodewordsPerBlock: 18, numBlocks: 2 },
|
|
242
|
+
{ version: 5, totalCodewords: 86, ecCodewordsPerBlock: 24, numBlocks: 2 },
|
|
243
|
+
{ version: 6, totalCodewords: 108, ecCodewordsPerBlock: 16, numBlocks: 4 },
|
|
244
|
+
{ version: 7, totalCodewords: 124, ecCodewordsPerBlock: 18, numBlocks: 4 },
|
|
245
|
+
{ version: 8, totalCodewords: 154, ecCodewordsPerBlock: 22, numBlocks: 4 },
|
|
246
|
+
{ version: 9, totalCodewords: 182, ecCodewordsPerBlock: 22, numBlocks: 5 },
|
|
247
|
+
{ version: 10, totalCodewords: 216, ecCodewordsPerBlock: 26, numBlocks: 5 }
|
|
248
|
+
],
|
|
249
|
+
Q: [
|
|
250
|
+
{ version: 1, totalCodewords: 13, ecCodewordsPerBlock: 13, numBlocks: 1 },
|
|
251
|
+
{ version: 2, totalCodewords: 22, ecCodewordsPerBlock: 22, numBlocks: 1 },
|
|
252
|
+
{ version: 3, totalCodewords: 34, ecCodewordsPerBlock: 18, numBlocks: 2 },
|
|
253
|
+
{ version: 4, totalCodewords: 48, ecCodewordsPerBlock: 26, numBlocks: 2 },
|
|
254
|
+
{ version: 5, totalCodewords: 62, ecCodewordsPerBlock: 18, numBlocks: 4 },
|
|
255
|
+
{ version: 6, totalCodewords: 76, ecCodewordsPerBlock: 24, numBlocks: 4 },
|
|
256
|
+
{ version: 7, totalCodewords: 88, ecCodewordsPerBlock: 18, numBlocks: 6 },
|
|
257
|
+
{ version: 8, totalCodewords: 110, ecCodewordsPerBlock: 22, numBlocks: 6 },
|
|
258
|
+
{ version: 9, totalCodewords: 132, ecCodewordsPerBlock: 20, numBlocks: 8 },
|
|
259
|
+
{ version: 10, totalCodewords: 154, ecCodewordsPerBlock: 24, numBlocks: 8 }
|
|
260
|
+
],
|
|
261
|
+
H: [
|
|
262
|
+
{ version: 1, totalCodewords: 9, ecCodewordsPerBlock: 17, numBlocks: 1 },
|
|
263
|
+
{ version: 2, totalCodewords: 16, ecCodewordsPerBlock: 28, numBlocks: 1 },
|
|
264
|
+
{ version: 3, totalCodewords: 26, ecCodewordsPerBlock: 22, numBlocks: 2 },
|
|
265
|
+
{ version: 4, totalCodewords: 36, ecCodewordsPerBlock: 16, numBlocks: 4 },
|
|
266
|
+
{ version: 5, totalCodewords: 46, ecCodewordsPerBlock: 22, numBlocks: 4 },
|
|
267
|
+
{ version: 6, totalCodewords: 60, ecCodewordsPerBlock: 28, numBlocks: 4 },
|
|
268
|
+
{ version: 7, totalCodewords: 66, ecCodewordsPerBlock: 26, numBlocks: 5 },
|
|
269
|
+
{ version: 8, totalCodewords: 86, ecCodewordsPerBlock: 26, numBlocks: 6 },
|
|
270
|
+
{ version: 9, totalCodewords: 100, ecCodewordsPerBlock: 24, numBlocks: 8 },
|
|
271
|
+
{ version: 10, totalCodewords: 122, ecCodewordsPerBlock: 28, numBlocks: 8 }
|
|
272
|
+
]
|
|
273
|
+
};
|
|
274
|
+
var FORMAT_INFO_STRINGS = {
|
|
275
|
+
0: [1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0],
|
|
276
|
+
1: [1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1],
|
|
277
|
+
2: [1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0],
|
|
278
|
+
3: [1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1],
|
|
279
|
+
4: [1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1],
|
|
280
|
+
5: [1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0],
|
|
281
|
+
6: [1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1],
|
|
282
|
+
7: [1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0],
|
|
283
|
+
8: [1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0],
|
|
284
|
+
9: [1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1],
|
|
285
|
+
10: [1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0],
|
|
286
|
+
11: [1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1],
|
|
287
|
+
12: [1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1],
|
|
288
|
+
13: [1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0],
|
|
289
|
+
14: [1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1],
|
|
290
|
+
15: [1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0],
|
|
291
|
+
16: [0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1],
|
|
292
|
+
17: [0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0],
|
|
293
|
+
18: [0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1],
|
|
294
|
+
19: [0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0],
|
|
295
|
+
20: [0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0],
|
|
296
|
+
21: [0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1],
|
|
297
|
+
22: [0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0],
|
|
298
|
+
23: [0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1],
|
|
299
|
+
24: [0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1],
|
|
300
|
+
25: [0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0],
|
|
301
|
+
26: [0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1],
|
|
302
|
+
27: [0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0],
|
|
303
|
+
28: [0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0],
|
|
304
|
+
29: [0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1],
|
|
305
|
+
30: [0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0],
|
|
306
|
+
31: [0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1]
|
|
307
|
+
};
|
|
308
|
+
var ALIGNMENT_PATTERNS = [
|
|
309
|
+
[],
|
|
310
|
+
[],
|
|
311
|
+
[6, 18],
|
|
312
|
+
[6, 22],
|
|
313
|
+
[6, 26],
|
|
314
|
+
[6, 30],
|
|
315
|
+
[6, 34],
|
|
316
|
+
[6, 22, 38],
|
|
317
|
+
[6, 24, 42],
|
|
318
|
+
[6, 26, 46],
|
|
319
|
+
[6, 28, 50],
|
|
320
|
+
[6, 30, 54],
|
|
321
|
+
[6, 32, 58],
|
|
322
|
+
[6, 34, 62],
|
|
323
|
+
[6, 26, 46, 66]
|
|
324
|
+
];
|
|
325
|
+
function getVersionInfo(dataLength, ecLevel) {
|
|
326
|
+
const table = VERSION_TABLE[ecLevel];
|
|
327
|
+
for (const info of table) {
|
|
328
|
+
const dataCodewords = info.totalCodewords - info.ecCodewordsPerBlock * info.numBlocks;
|
|
329
|
+
const maxBytes = dataCodewords - (info.version < 10 ? 2 : 3);
|
|
330
|
+
if (maxBytes >= dataLength) {
|
|
331
|
+
return info;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
throw new Error(`Data too long for QR code (max ~270 bytes with ${ecLevel} EC)`);
|
|
335
|
+
}
|
|
336
|
+
function createByteData(data, version) {
|
|
337
|
+
const bytes = new TextEncoder().encode(data);
|
|
338
|
+
const bits = [];
|
|
339
|
+
bits.push(0, 1, 0, 0);
|
|
340
|
+
const countBits = version < 10 ? 8 : 16;
|
|
341
|
+
for (let i = countBits - 1; i >= 0; i--) {
|
|
342
|
+
bits.push(bytes.length >> i & 1);
|
|
343
|
+
}
|
|
344
|
+
for (const byte of bytes) {
|
|
345
|
+
for (let i = 7; i >= 0; i--) {
|
|
346
|
+
bits.push(byte >> i & 1);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
bits.push(0, 0, 0, 0);
|
|
350
|
+
while (bits.length % 8 !== 0) {
|
|
351
|
+
bits.push(0);
|
|
352
|
+
}
|
|
353
|
+
const codewords = [];
|
|
354
|
+
for (let i = 0; i < bits.length; i += 8) {
|
|
355
|
+
let byte = 0;
|
|
356
|
+
for (let j = 0; j < 8; j++) {
|
|
357
|
+
byte = byte << 1 | (bits[i + j] ?? 0);
|
|
358
|
+
}
|
|
359
|
+
codewords.push(byte);
|
|
360
|
+
}
|
|
361
|
+
return codewords;
|
|
362
|
+
}
|
|
363
|
+
var GF_EXP = new Uint8Array(512);
|
|
364
|
+
var GF_LOG = new Uint8Array(256);
|
|
365
|
+
(function initGaloisField() {
|
|
366
|
+
let x = 1;
|
|
367
|
+
for (let i = 0; i < 255; i++) {
|
|
368
|
+
GF_EXP[i] = x;
|
|
369
|
+
GF_LOG[x] = i;
|
|
370
|
+
x <<= 1;
|
|
371
|
+
if (x & 256) x ^= 285;
|
|
372
|
+
}
|
|
373
|
+
for (let i = 255; i < 512; i++) {
|
|
374
|
+
GF_EXP[i] = GF_EXP[i - 255];
|
|
375
|
+
}
|
|
376
|
+
})();
|
|
377
|
+
function gfMul(a, b) {
|
|
378
|
+
if (a === 0 || b === 0) return 0;
|
|
379
|
+
return GF_EXP[GF_LOG[a] + GF_LOG[b]];
|
|
380
|
+
}
|
|
381
|
+
function generateECCodewords(data, numECCodewords) {
|
|
382
|
+
const gen = [1];
|
|
383
|
+
for (let i = 0; i < numECCodewords; i++) {
|
|
384
|
+
const newGen = new Array(gen.length + 1).fill(0);
|
|
385
|
+
for (let j = 0; j < gen.length; j++) {
|
|
386
|
+
newGen[j] ^= gen[j];
|
|
387
|
+
newGen[j + 1] ^= gfMul(gen[j], GF_EXP[i]);
|
|
388
|
+
}
|
|
389
|
+
gen.length = newGen.length;
|
|
390
|
+
for (let j = 0; j < newGen.length; j++) gen[j] = newGen[j];
|
|
391
|
+
}
|
|
392
|
+
const msg = [...data, ...new Array(numECCodewords).fill(0)];
|
|
393
|
+
for (let i = 0; i < data.length; i++) {
|
|
394
|
+
const coef = msg[i];
|
|
395
|
+
if (coef !== 0) {
|
|
396
|
+
for (let j = 0; j < gen.length; j++) {
|
|
397
|
+
msg[i + j] ^= gfMul(gen[j], coef);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return msg.slice(data.length);
|
|
402
|
+
}
|
|
403
|
+
function interleaveBlocks(dataCodewords, info) {
|
|
404
|
+
const { numBlocks, ecCodewordsPerBlock, totalCodewords } = info;
|
|
405
|
+
const totalDataCodewords = totalCodewords - numBlocks * ecCodewordsPerBlock;
|
|
406
|
+
const smallBlockSize = Math.floor(totalDataCodewords / numBlocks);
|
|
407
|
+
const largeBlockCount = totalDataCodewords % numBlocks;
|
|
408
|
+
const dataBlocks = [];
|
|
409
|
+
const ecBlocks = [];
|
|
410
|
+
let offset = 0;
|
|
411
|
+
for (let i = 0; i < numBlocks; i++) {
|
|
412
|
+
const blockSize = smallBlockSize + (i >= numBlocks - largeBlockCount ? 1 : 0);
|
|
413
|
+
const block = dataCodewords.slice(offset, offset + blockSize);
|
|
414
|
+
offset += blockSize;
|
|
415
|
+
dataBlocks.push(block);
|
|
416
|
+
ecBlocks.push(generateECCodewords(block, ecCodewordsPerBlock));
|
|
417
|
+
}
|
|
418
|
+
const result = [];
|
|
419
|
+
const maxDataLen = Math.max(...dataBlocks.map((b) => b.length));
|
|
420
|
+
for (let i = 0; i < maxDataLen; i++) {
|
|
421
|
+
for (const block of dataBlocks) {
|
|
422
|
+
if (i < block.length) result.push(block[i]);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
for (let i = 0; i < ecCodewordsPerBlock; i++) {
|
|
426
|
+
for (const block of ecBlocks) {
|
|
427
|
+
result.push(block[i]);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return result;
|
|
431
|
+
}
|
|
432
|
+
function createMatrix(version) {
|
|
433
|
+
const size = version * 4 + 17;
|
|
434
|
+
const matrix = Array.from(
|
|
435
|
+
{ length: size },
|
|
436
|
+
() => new Array(size).fill(null)
|
|
437
|
+
);
|
|
438
|
+
function setFinderPattern(row, col) {
|
|
439
|
+
for (let r = -1; r <= 7; r++) {
|
|
440
|
+
for (let c = -1; c <= 7; c++) {
|
|
441
|
+
const nr = row + r;
|
|
442
|
+
const nc = col + c;
|
|
443
|
+
if (nr < 0 || nr >= size || nc < 0 || nc >= size) continue;
|
|
444
|
+
const isOuter = r === -1 || r === 7 || c === -1 || c === 7;
|
|
445
|
+
const isInner = r >= 1 && r <= 5 && c >= 1 && c <= 5;
|
|
446
|
+
const isCore = r >= 2 && r <= 4 && c >= 2 && c <= 4;
|
|
447
|
+
matrix[nr][nc] = isOuter ? 0 : isCore ? 1 : isInner ? 0 : 1;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
setFinderPattern(0, 0);
|
|
452
|
+
setFinderPattern(0, size - 7);
|
|
453
|
+
setFinderPattern(size - 7, 0);
|
|
454
|
+
for (let i = 8; i < size - 8; i++) {
|
|
455
|
+
matrix[6][i] = i % 2 === 0 ? 1 : 0;
|
|
456
|
+
matrix[i][6] = i % 2 === 0 ? 1 : 0;
|
|
457
|
+
}
|
|
458
|
+
matrix[size - 8][8] = 1;
|
|
459
|
+
if (version >= 2) {
|
|
460
|
+
const positions = ALIGNMENT_PATTERNS[version];
|
|
461
|
+
for (const row of positions) {
|
|
462
|
+
for (const col of positions) {
|
|
463
|
+
if (matrix[row][col] !== null) continue;
|
|
464
|
+
for (let r = -2; r <= 2; r++) {
|
|
465
|
+
for (let c = -2; c <= 2; c++) {
|
|
466
|
+
const isOuter = Math.abs(r) === 2 || Math.abs(c) === 2;
|
|
467
|
+
const isCore = r === 0 && c === 0;
|
|
468
|
+
matrix[row + r][col + c] = isOuter || isCore ? 1 : 0;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return matrix;
|
|
475
|
+
}
|
|
476
|
+
function placeData(matrix, codewords) {
|
|
477
|
+
const size = matrix.length;
|
|
478
|
+
let bitIndex = 0;
|
|
479
|
+
const bits = [];
|
|
480
|
+
for (const cw of codewords) {
|
|
481
|
+
for (let i = 7; i >= 0; i--) {
|
|
482
|
+
bits.push(cw >> i & 1);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
let col = size - 1;
|
|
486
|
+
let goingUp = true;
|
|
487
|
+
while (col > 0) {
|
|
488
|
+
if (col === 6) col--;
|
|
489
|
+
for (let row = goingUp ? size - 1 : 0; goingUp ? row >= 0 : row < size; row += goingUp ? -1 : 1) {
|
|
490
|
+
for (const offset of [0, -1]) {
|
|
491
|
+
const c = col + offset;
|
|
492
|
+
if (c < 0 || matrix[row][c] !== null) continue;
|
|
493
|
+
matrix[row][c] = bitIndex < bits.length ? bits[bitIndex++] : 0;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
col -= 2;
|
|
497
|
+
goingUp = !goingUp;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
function applyMask(matrix, maskPattern) {
|
|
501
|
+
const size = matrix.length;
|
|
502
|
+
const maskFns = [
|
|
503
|
+
(r, c) => (r + c) % 2 === 0,
|
|
504
|
+
(r) => r % 2 === 0,
|
|
505
|
+
(_, c) => c % 3 === 0,
|
|
506
|
+
(r, c) => (r + c) % 3 === 0,
|
|
507
|
+
(r, c) => (Math.floor(r / 2) + Math.floor(c / 3)) % 2 === 0,
|
|
508
|
+
(r, c) => r * c % 2 + r * c % 3 === 0,
|
|
509
|
+
(r, c) => (r * c % 2 + r * c % 3) % 2 === 0,
|
|
510
|
+
(r, c) => ((r + c) % 2 + r * c % 3) % 2 === 0
|
|
511
|
+
];
|
|
512
|
+
const maskFn = maskFns[maskPattern];
|
|
513
|
+
const reserved = createReservedMask(size);
|
|
514
|
+
for (let r = 0; r < size; r++) {
|
|
515
|
+
for (let c = 0; c < size; c++) {
|
|
516
|
+
if (!reserved[r][c] && maskFn(r, c)) {
|
|
517
|
+
matrix[r][c] ^= 1;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
function createReservedMask(size) {
|
|
523
|
+
const reserved = Array.from(
|
|
524
|
+
{ length: size },
|
|
525
|
+
() => new Array(size).fill(false)
|
|
526
|
+
);
|
|
527
|
+
for (let i = 0; i < 8; i++) {
|
|
528
|
+
for (let j = 0; j < 8; j++) {
|
|
529
|
+
reserved[i][j] = true;
|
|
530
|
+
reserved[i][size - 8 + j] = true;
|
|
531
|
+
reserved[size - 8 + i][j] = true;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
for (let i = 0; i < 9; i++) {
|
|
535
|
+
reserved[i][8] = true;
|
|
536
|
+
reserved[8][i] = true;
|
|
537
|
+
reserved[size - 8 + i - 1][8] = true;
|
|
538
|
+
reserved[8][size - 8 + i - 1] = true;
|
|
539
|
+
}
|
|
540
|
+
for (let i = 0; i < size; i++) {
|
|
541
|
+
reserved[6][i] = true;
|
|
542
|
+
reserved[i][6] = true;
|
|
543
|
+
}
|
|
544
|
+
return reserved;
|
|
545
|
+
}
|
|
546
|
+
function addFormatInfo(matrix, ecLevel, maskPattern) {
|
|
547
|
+
const formatIndex = EC_LEVELS[ecLevel] * 8 + maskPattern;
|
|
548
|
+
const bits = FORMAT_INFO_STRINGS[formatIndex];
|
|
549
|
+
const size = matrix.length;
|
|
550
|
+
for (let i = 0; i < 6; i++) {
|
|
551
|
+
matrix[8][i] = bits[i];
|
|
552
|
+
matrix[i][8] = bits[14 - i];
|
|
553
|
+
}
|
|
554
|
+
matrix[8][7] = bits[6];
|
|
555
|
+
matrix[8][8] = bits[7];
|
|
556
|
+
matrix[7][8] = bits[8];
|
|
557
|
+
for (let i = 0; i < 7; i++) {
|
|
558
|
+
matrix[8][size - 1 - i] = bits[14 - i];
|
|
559
|
+
matrix[size - 1 - i][8] = bits[i];
|
|
560
|
+
}
|
|
561
|
+
matrix[size - 8][8] = bits[7];
|
|
562
|
+
}
|
|
563
|
+
function evaluatePenalty(matrix) {
|
|
564
|
+
const size = matrix.length;
|
|
565
|
+
let penalty = 0;
|
|
566
|
+
for (let r = 0; r < size; r++) {
|
|
567
|
+
let count = 1;
|
|
568
|
+
for (let c = 1; c < size; c++) {
|
|
569
|
+
if (matrix[r][c] === matrix[r][c - 1]) {
|
|
570
|
+
count++;
|
|
571
|
+
if (count === 5) penalty += 3;
|
|
572
|
+
else if (count > 5) penalty++;
|
|
573
|
+
} else {
|
|
574
|
+
count = 1;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
for (let c = 0; c < size; c++) {
|
|
579
|
+
let count = 1;
|
|
580
|
+
for (let r = 1; r < size; r++) {
|
|
581
|
+
if (matrix[r][c] === matrix[r - 1][c]) {
|
|
582
|
+
count++;
|
|
583
|
+
if (count === 5) penalty += 3;
|
|
584
|
+
else if (count > 5) penalty++;
|
|
585
|
+
} else {
|
|
586
|
+
count = 1;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return penalty;
|
|
591
|
+
}
|
|
592
|
+
function generateQRMatrix(data, ecLevel) {
|
|
593
|
+
const info = getVersionInfo(data.length, ecLevel);
|
|
594
|
+
const dataCodewords = createByteData(data, info.version);
|
|
595
|
+
const totalDataCodewords = info.totalCodewords - info.numBlocks * info.ecCodewordsPerBlock;
|
|
596
|
+
while (dataCodewords.length < totalDataCodewords) {
|
|
597
|
+
dataCodewords.push(dataCodewords.length % 2 === 0 ? 236 : 17);
|
|
598
|
+
}
|
|
599
|
+
const codewords = interleaveBlocks(dataCodewords, info);
|
|
600
|
+
const baseMatrix = createMatrix(info.version);
|
|
601
|
+
placeData(baseMatrix, codewords);
|
|
602
|
+
let bestMatrix = null;
|
|
603
|
+
let bestPenalty = Infinity;
|
|
604
|
+
for (let mask = 0; mask < 8; mask++) {
|
|
605
|
+
const matrix = baseMatrix.map(
|
|
606
|
+
(row) => row.map((cell) => cell === null ? 0 : cell)
|
|
607
|
+
);
|
|
608
|
+
applyMask(matrix, mask);
|
|
609
|
+
addFormatInfo(matrix, ecLevel, mask);
|
|
610
|
+
const penalty = evaluatePenalty(matrix);
|
|
611
|
+
if (penalty < bestPenalty) {
|
|
612
|
+
bestPenalty = penalty;
|
|
613
|
+
bestMatrix = matrix;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return bestMatrix;
|
|
617
|
+
}
|
|
618
|
+
function generateQRSvg(data, options = {}) {
|
|
619
|
+
const { size = 200, errorCorrection = "L", quietZone = 4 } = options;
|
|
620
|
+
const matrix = generateQRMatrix(data, errorCorrection);
|
|
621
|
+
const moduleCount = matrix.length;
|
|
622
|
+
const totalModules = moduleCount + quietZone * 2;
|
|
623
|
+
const moduleSize = size / totalModules;
|
|
624
|
+
let paths = "";
|
|
625
|
+
for (let r = 0; r < moduleCount; r++) {
|
|
626
|
+
for (let c = 0; c < moduleCount; c++) {
|
|
627
|
+
if (matrix[r][c] === 1) {
|
|
628
|
+
const x = (c + quietZone) * moduleSize;
|
|
629
|
+
const y = (r + quietZone) * moduleSize;
|
|
630
|
+
paths += `M${x},${y}h${moduleSize}v${moduleSize}h-${moduleSize}z`;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${size} ${size}" width="${size}" height="${size}"><rect width="100%" height="100%" fill="white"/><path d="${paths}" fill="black"/></svg>`;
|
|
635
|
+
}
|
|
636
|
+
function generateQRDataUrl(data, options = {}) {
|
|
637
|
+
const svg = generateQRSvg(data, options);
|
|
638
|
+
const encoded = encodeURIComponent(svg).replace(/'/g, "%27").replace(/"/g, "%22");
|
|
639
|
+
return `data:image/svg+xml,${encoded}`;
|
|
640
|
+
}
|
|
641
|
+
function createVerification(config) {
|
|
642
|
+
const { apiUrl, polling, qr, fetch: fetchFn } = config;
|
|
643
|
+
const client = createApiClient({ baseUrl: apiUrl, fetch: fetchFn });
|
|
644
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
645
|
+
let currentState = { status: "idle" };
|
|
646
|
+
let currentSessionId = null;
|
|
647
|
+
let poller = null;
|
|
648
|
+
function setState(newState) {
|
|
649
|
+
currentState = newState;
|
|
650
|
+
for (const callback of subscribers) {
|
|
651
|
+
try {
|
|
652
|
+
callback(newState);
|
|
653
|
+
} catch {
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
function mapSessionToState(session) {
|
|
658
|
+
switch (session.status) {
|
|
659
|
+
case "pending":
|
|
660
|
+
if (session.qrUrl) {
|
|
661
|
+
return {
|
|
662
|
+
status: "showQR",
|
|
663
|
+
qrDataUrl: generateQRDataUrl(session.qrUrl, qr),
|
|
664
|
+
qrUrl: session.qrUrl,
|
|
665
|
+
sessionId: session.id
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
return { status: "loading" };
|
|
669
|
+
case "waiting_for_wallet":
|
|
670
|
+
return { status: "waitingForWallet", sessionId: session.id };
|
|
671
|
+
case "verified":
|
|
672
|
+
return {
|
|
673
|
+
status: "verified",
|
|
674
|
+
token: session.token,
|
|
675
|
+
claims: session.claims
|
|
676
|
+
};
|
|
677
|
+
case "rejected":
|
|
678
|
+
return { status: "rejected", error: session.error };
|
|
679
|
+
case "expired":
|
|
680
|
+
return { status: "expired" };
|
|
681
|
+
case "cancelled":
|
|
682
|
+
return { status: "idle" };
|
|
683
|
+
case "error":
|
|
684
|
+
return { status: "error", error: session.error ?? "Verification failed" };
|
|
685
|
+
default:
|
|
686
|
+
return { status: "error", error: "Unknown session status" };
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
async function pollSession() {
|
|
690
|
+
if (!currentSessionId) return true;
|
|
691
|
+
try {
|
|
692
|
+
const session = await client.getSession(currentSessionId);
|
|
693
|
+
const newState = mapSessionToState(session);
|
|
694
|
+
if (newState.status !== currentState.status || newState.status === "showQR" && currentState.status === "showQR" && newState.sessionId !== currentState.sessionId) {
|
|
695
|
+
setState(newState);
|
|
696
|
+
}
|
|
697
|
+
if (isTerminalStatus(session.status)) {
|
|
698
|
+
return true;
|
|
699
|
+
}
|
|
700
|
+
return false;
|
|
701
|
+
} catch (error) {
|
|
702
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
703
|
+
setState({ status: "error", error: errorMessage });
|
|
704
|
+
return true;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
function stopPolling() {
|
|
708
|
+
if (poller) {
|
|
709
|
+
poller.stop();
|
|
710
|
+
poller = null;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
function startPolling() {
|
|
714
|
+
stopPolling();
|
|
715
|
+
poller = createPoller(pollSession, polling);
|
|
716
|
+
poller.start();
|
|
717
|
+
}
|
|
718
|
+
return {
|
|
719
|
+
get state() {
|
|
720
|
+
return currentState;
|
|
721
|
+
},
|
|
722
|
+
async start(request) {
|
|
723
|
+
stopPolling();
|
|
724
|
+
currentSessionId = null;
|
|
725
|
+
setState({ status: "loading" });
|
|
726
|
+
try {
|
|
727
|
+
const session = await client.createSession(request);
|
|
728
|
+
currentSessionId = session.id;
|
|
729
|
+
const newState = mapSessionToState(session);
|
|
730
|
+
setState(newState);
|
|
731
|
+
if (!isTerminalStatus(session.status)) {
|
|
732
|
+
startPolling();
|
|
733
|
+
}
|
|
734
|
+
} catch (error) {
|
|
735
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
736
|
+
setState({ status: "error", error: errorMessage });
|
|
737
|
+
}
|
|
738
|
+
},
|
|
739
|
+
async cancel() {
|
|
740
|
+
if (!currentSessionId) return;
|
|
741
|
+
stopPolling();
|
|
742
|
+
try {
|
|
743
|
+
await client.cancelSession(currentSessionId);
|
|
744
|
+
currentSessionId = null;
|
|
745
|
+
setState({ status: "idle" });
|
|
746
|
+
} catch (error) {
|
|
747
|
+
const errorMessage = error instanceof Error ? error.message : "Failed to cancel";
|
|
748
|
+
setState({ status: "error", error: errorMessage });
|
|
749
|
+
}
|
|
750
|
+
},
|
|
751
|
+
destroy() {
|
|
752
|
+
stopPolling();
|
|
753
|
+
currentSessionId = null;
|
|
754
|
+
subscribers.clear();
|
|
755
|
+
},
|
|
756
|
+
subscribe(callback) {
|
|
757
|
+
subscribers.add(callback);
|
|
758
|
+
try {
|
|
759
|
+
callback(currentState);
|
|
760
|
+
} catch {
|
|
761
|
+
}
|
|
762
|
+
return () => {
|
|
763
|
+
subscribers.delete(callback);
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// src/styles.ts
|
|
770
|
+
var CSS_VARIABLES = {
|
|
771
|
+
"--eudi-primary": "#003399",
|
|
772
|
+
"--eudi-text": "#1a1a1a",
|
|
773
|
+
"--eudi-background": "#ffffff",
|
|
774
|
+
"--eudi-border-radius": "8px",
|
|
775
|
+
"--eudi-font-family": "system-ui, sans-serif",
|
|
776
|
+
"--eudi-error": "#d32f2f"
|
|
777
|
+
};
|
|
778
|
+
function createStyles() {
|
|
779
|
+
return (
|
|
780
|
+
/* css */
|
|
781
|
+
`
|
|
782
|
+
:host {
|
|
783
|
+
display: block;
|
|
784
|
+
font-family: var(--eudi-font-family, ${CSS_VARIABLES["--eudi-font-family"]});
|
|
785
|
+
color: var(--eudi-text, ${CSS_VARIABLES["--eudi-text"]});
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
:host([hidden]) {
|
|
789
|
+
display: none;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
*,
|
|
793
|
+
*::before,
|
|
794
|
+
*::after {
|
|
795
|
+
box-sizing: border-box;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
.eudi-widget {
|
|
799
|
+
background: var(--eudi-background, ${CSS_VARIABLES["--eudi-background"]});
|
|
800
|
+
border: 1px solid color-mix(in srgb, var(--eudi-text, ${CSS_VARIABLES["--eudi-text"]}) 20%, transparent);
|
|
801
|
+
border-radius: var(--eudi-border-radius, ${CSS_VARIABLES["--eudi-border-radius"]});
|
|
802
|
+
padding: 24px;
|
|
803
|
+
text-align: center;
|
|
804
|
+
min-width: 280px;
|
|
805
|
+
max-width: 400px;
|
|
806
|
+
margin-inline: auto;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/* State containers - only one visible at a time */
|
|
810
|
+
.eudi-state {
|
|
811
|
+
display: none;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
.eudi-state[data-active] {
|
|
815
|
+
display: block;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/* Start button */
|
|
819
|
+
.eudi-start-btn {
|
|
820
|
+
display: inline-flex;
|
|
821
|
+
align-items: center;
|
|
822
|
+
justify-content: center;
|
|
823
|
+
gap: 8px;
|
|
824
|
+
padding: 12px 24px;
|
|
825
|
+
font-size: 16px;
|
|
826
|
+
font-weight: 500;
|
|
827
|
+
font-family: inherit;
|
|
828
|
+
color: #ffffff;
|
|
829
|
+
background: var(--eudi-primary, ${CSS_VARIABLES["--eudi-primary"]});
|
|
830
|
+
border: none;
|
|
831
|
+
border-radius: var(--eudi-border-radius, ${CSS_VARIABLES["--eudi-border-radius"]});
|
|
832
|
+
cursor: pointer;
|
|
833
|
+
transition: background-color 0.2s ease, transform 0.1s ease;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
.eudi-start-btn:hover {
|
|
837
|
+
background: color-mix(in srgb, var(--eudi-primary, ${CSS_VARIABLES["--eudi-primary"]}) 85%, black);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
.eudi-start-btn:focus-visible {
|
|
841
|
+
outline: 2px solid var(--eudi-primary, ${CSS_VARIABLES["--eudi-primary"]});
|
|
842
|
+
outline-offset: 2px;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
.eudi-start-btn:active {
|
|
846
|
+
transform: scale(0.98);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/* EU stars icon */
|
|
850
|
+
.eudi-icon {
|
|
851
|
+
width: 20px;
|
|
852
|
+
height: 20px;
|
|
853
|
+
flex-shrink: 0;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/* Loading state */
|
|
857
|
+
.eudi-loading {
|
|
858
|
+
display: flex;
|
|
859
|
+
flex-direction: column;
|
|
860
|
+
align-items: center;
|
|
861
|
+
gap: 12px;
|
|
862
|
+
padding: 16px 0;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
.eudi-spinner {
|
|
866
|
+
width: 40px;
|
|
867
|
+
height: 40px;
|
|
868
|
+
border: 3px solid color-mix(in srgb, var(--eudi-primary, ${CSS_VARIABLES["--eudi-primary"]}) 20%, transparent);
|
|
869
|
+
border-top-color: var(--eudi-primary, ${CSS_VARIABLES["--eudi-primary"]});
|
|
870
|
+
border-radius: 50%;
|
|
871
|
+
animation: eudi-spin 0.8s linear infinite;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
@keyframes eudi-spin {
|
|
875
|
+
to {
|
|
876
|
+
transform: rotate(360deg);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/* Reduced motion */
|
|
881
|
+
@media (prefers-reduced-motion: reduce) {
|
|
882
|
+
.eudi-spinner {
|
|
883
|
+
animation: none;
|
|
884
|
+
border-top-color: var(--eudi-primary, ${CSS_VARIABLES["--eudi-primary"]});
|
|
885
|
+
border-right-color: var(--eudi-primary, ${CSS_VARIABLES["--eudi-primary"]});
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
.eudi-start-btn {
|
|
889
|
+
transition: none;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/* QR code state */
|
|
894
|
+
.eudi-qr {
|
|
895
|
+
display: flex;
|
|
896
|
+
flex-direction: column;
|
|
897
|
+
align-items: center;
|
|
898
|
+
gap: 16px;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
.eudi-qr-img {
|
|
902
|
+
width: 200px;
|
|
903
|
+
height: 200px;
|
|
904
|
+
border: 1px solid color-mix(in srgb, var(--eudi-text, ${CSS_VARIABLES["--eudi-text"]}) 15%, transparent);
|
|
905
|
+
border-radius: 4px;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
.eudi-qr-text {
|
|
909
|
+
margin: 0;
|
|
910
|
+
font-size: 14px;
|
|
911
|
+
color: color-mix(in srgb, var(--eudi-text, ${CSS_VARIABLES["--eudi-text"]}) 80%, transparent);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
.eudi-cancel-btn {
|
|
915
|
+
margin-top: 8px;
|
|
916
|
+
padding: 8px 16px;
|
|
917
|
+
font-size: 14px;
|
|
918
|
+
font-family: inherit;
|
|
919
|
+
color: var(--eudi-text, ${CSS_VARIABLES["--eudi-text"]});
|
|
920
|
+
background: transparent;
|
|
921
|
+
border: 1px solid color-mix(in srgb, var(--eudi-text, ${CSS_VARIABLES["--eudi-text"]}) 30%, transparent);
|
|
922
|
+
border-radius: var(--eudi-border-radius, ${CSS_VARIABLES["--eudi-border-radius"]});
|
|
923
|
+
cursor: pointer;
|
|
924
|
+
transition: background-color 0.2s ease;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
.eudi-cancel-btn:hover {
|
|
928
|
+
background: color-mix(in srgb, var(--eudi-text, ${CSS_VARIABLES["--eudi-text"]}) 5%, transparent);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
.eudi-cancel-btn:focus-visible {
|
|
932
|
+
outline: 2px solid var(--eudi-primary, ${CSS_VARIABLES["--eudi-primary"]});
|
|
933
|
+
outline-offset: 2px;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/* Waiting for wallet */
|
|
937
|
+
.eudi-waiting {
|
|
938
|
+
display: flex;
|
|
939
|
+
flex-direction: column;
|
|
940
|
+
align-items: center;
|
|
941
|
+
gap: 12px;
|
|
942
|
+
padding: 16px 0;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
.eudi-waiting-icon {
|
|
946
|
+
width: 48px;
|
|
947
|
+
height: 48px;
|
|
948
|
+
color: var(--eudi-primary, ${CSS_VARIABLES["--eudi-primary"]});
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
.eudi-waiting-text {
|
|
952
|
+
margin: 0;
|
|
953
|
+
font-size: 16px;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/* Success state */
|
|
957
|
+
.eudi-success {
|
|
958
|
+
display: flex;
|
|
959
|
+
flex-direction: column;
|
|
960
|
+
align-items: center;
|
|
961
|
+
gap: 12px;
|
|
962
|
+
padding: 16px 0;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
.eudi-success-icon {
|
|
966
|
+
width: 48px;
|
|
967
|
+
height: 48px;
|
|
968
|
+
color: var(--eudi-primary, ${CSS_VARIABLES["--eudi-primary"]});
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
.eudi-success-text {
|
|
972
|
+
margin: 0;
|
|
973
|
+
font-size: 18px;
|
|
974
|
+
font-weight: 500;
|
|
975
|
+
color: var(--eudi-primary, ${CSS_VARIABLES["--eudi-primary"]});
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/* Error/rejected/expired states */
|
|
979
|
+
.eudi-error {
|
|
980
|
+
display: flex;
|
|
981
|
+
flex-direction: column;
|
|
982
|
+
align-items: center;
|
|
983
|
+
gap: 12px;
|
|
984
|
+
padding: 16px 0;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
.eudi-error-icon {
|
|
988
|
+
width: 48px;
|
|
989
|
+
height: 48px;
|
|
990
|
+
color: var(--eudi-error, ${CSS_VARIABLES["--eudi-error"]});
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
.eudi-error-text {
|
|
994
|
+
margin: 0;
|
|
995
|
+
font-size: 16px;
|
|
996
|
+
color: var(--eudi-error, ${CSS_VARIABLES["--eudi-error"]});
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
.eudi-error-detail {
|
|
1000
|
+
margin: 0;
|
|
1001
|
+
font-size: 14px;
|
|
1002
|
+
color: color-mix(in srgb, var(--eudi-text, ${CSS_VARIABLES["--eudi-text"]}) 70%, transparent);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
.eudi-retry-btn {
|
|
1006
|
+
margin-top: 8px;
|
|
1007
|
+
padding: 10px 20px;
|
|
1008
|
+
font-size: 14px;
|
|
1009
|
+
font-weight: 500;
|
|
1010
|
+
font-family: inherit;
|
|
1011
|
+
color: #ffffff;
|
|
1012
|
+
background: var(--eudi-primary, ${CSS_VARIABLES["--eudi-primary"]});
|
|
1013
|
+
border: none;
|
|
1014
|
+
border-radius: var(--eudi-border-radius, ${CSS_VARIABLES["--eudi-border-radius"]});
|
|
1015
|
+
cursor: pointer;
|
|
1016
|
+
transition: background-color 0.2s ease;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
.eudi-retry-btn:hover {
|
|
1020
|
+
background: color-mix(in srgb, var(--eudi-primary, ${CSS_VARIABLES["--eudi-primary"]}) 85%, black);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
.eudi-retry-btn:focus-visible {
|
|
1024
|
+
outline: 2px solid var(--eudi-primary, ${CSS_VARIABLES["--eudi-primary"]});
|
|
1025
|
+
outline-offset: 2px;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/* Screen reader only */
|
|
1029
|
+
.eudi-sr-only {
|
|
1030
|
+
position: absolute;
|
|
1031
|
+
width: 1px;
|
|
1032
|
+
height: 1px;
|
|
1033
|
+
padding: 0;
|
|
1034
|
+
margin: -1px;
|
|
1035
|
+
overflow: hidden;
|
|
1036
|
+
clip: rect(0, 0, 0, 0);
|
|
1037
|
+
white-space: nowrap;
|
|
1038
|
+
border: 0;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
/* Demo mode banner */
|
|
1042
|
+
.eudi-demo-banner {
|
|
1043
|
+
display: flex;
|
|
1044
|
+
align-items: center;
|
|
1045
|
+
justify-content: center;
|
|
1046
|
+
gap: 8px;
|
|
1047
|
+
padding: 8px 12px;
|
|
1048
|
+
margin-bottom: 16px;
|
|
1049
|
+
font-size: 13px;
|
|
1050
|
+
color: #7c5c00;
|
|
1051
|
+
background: #fef3cd;
|
|
1052
|
+
border: 1px solid #ffe69c;
|
|
1053
|
+
border-radius: 4px;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
.eudi-demo-banner[hidden] {
|
|
1057
|
+
display: none;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
.eudi-warning-icon {
|
|
1061
|
+
width: 16px;
|
|
1062
|
+
height: 16px;
|
|
1063
|
+
flex-shrink: 0;
|
|
1064
|
+
color: #7c5c00;
|
|
1065
|
+
}
|
|
1066
|
+
`
|
|
1067
|
+
);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// src/render.ts
|
|
1071
|
+
var EU_STARS_ICON = (
|
|
1072
|
+
/* html */
|
|
1073
|
+
`
|
|
1074
|
+
<svg class="eudi-icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
1075
|
+
<circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
|
1076
|
+
<g fill="currentColor">
|
|
1077
|
+
<polygon points="12,4 12.5,5.5 14,5.5 12.75,6.5 13.25,8 12,7 10.75,8 11.25,6.5 10,5.5 11.5,5.5"/>
|
|
1078
|
+
<polygon points="17,6.5 17.35,7.6 18.5,7.6 17.6,8.3 17.9,9.4 17,8.7 16.1,9.4 16.4,8.3 15.5,7.6 16.65,7.6"/>
|
|
1079
|
+
<polygon points="19,11 19.35,12.1 20.5,12.1 19.6,12.8 19.9,13.9 19,13.2 18.1,13.9 18.4,12.8 17.5,12.1 18.65,12.1"/>
|
|
1080
|
+
<polygon points="17,15.5 17.35,16.6 18.5,16.6 17.6,17.3 17.9,18.4 17,17.7 16.1,18.4 16.4,17.3 15.5,16.6 16.65,16.6"/>
|
|
1081
|
+
<polygon points="12,18 12.35,19.1 13.5,19.1 12.6,19.8 12.9,20.9 12,20.2 11.1,20.9 11.4,19.8 10.5,19.1 11.65,19.1"/>
|
|
1082
|
+
<polygon points="7,15.5 7.35,16.6 8.5,16.6 7.6,17.3 7.9,18.4 7,17.7 6.1,18.4 6.4,17.3 5.5,16.6 6.65,16.6"/>
|
|
1083
|
+
<polygon points="5,11 5.35,12.1 6.5,12.1 5.6,12.8 5.9,13.9 5,13.2 4.1,13.9 4.4,12.8 3.5,12.1 4.65,12.1"/>
|
|
1084
|
+
<polygon points="7,6.5 7.35,7.6 8.5,7.6 7.6,8.3 7.9,9.4 7,8.7 6.1,9.4 6.4,8.3 5.5,7.6 6.65,7.6"/>
|
|
1085
|
+
</g>
|
|
1086
|
+
</svg>
|
|
1087
|
+
`
|
|
1088
|
+
);
|
|
1089
|
+
var SUCCESS_ICON = (
|
|
1090
|
+
/* html */
|
|
1091
|
+
`
|
|
1092
|
+
<svg class="eudi-success-icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
1093
|
+
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
|
|
1094
|
+
<path d="M8 12l3 3 5-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
1095
|
+
</svg>
|
|
1096
|
+
`
|
|
1097
|
+
);
|
|
1098
|
+
var ERROR_ICON = (
|
|
1099
|
+
/* html */
|
|
1100
|
+
`
|
|
1101
|
+
<svg class="eudi-error-icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
1102
|
+
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
|
|
1103
|
+
<path d="M15 9l-6 6M9 9l6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
1104
|
+
</svg>
|
|
1105
|
+
`
|
|
1106
|
+
);
|
|
1107
|
+
var EXPIRED_ICON = (
|
|
1108
|
+
/* html */
|
|
1109
|
+
`
|
|
1110
|
+
<svg class="eudi-error-icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
1111
|
+
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
|
|
1112
|
+
<path d="M12 6v6l4 2" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
1113
|
+
</svg>
|
|
1114
|
+
`
|
|
1115
|
+
);
|
|
1116
|
+
var WALLET_ICON = (
|
|
1117
|
+
/* html */
|
|
1118
|
+
`
|
|
1119
|
+
<svg class="eudi-waiting-icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
1120
|
+
<rect x="2" y="6" width="20" height="14" rx="2" stroke="currentColor" stroke-width="2"/>
|
|
1121
|
+
<path d="M2 10h20" stroke="currentColor" stroke-width="2"/>
|
|
1122
|
+
<circle cx="17" cy="14" r="1.5" fill="currentColor"/>
|
|
1123
|
+
</svg>
|
|
1124
|
+
`
|
|
1125
|
+
);
|
|
1126
|
+
var WARNING_ICON = (
|
|
1127
|
+
/* html */
|
|
1128
|
+
`
|
|
1129
|
+
<svg class="eudi-warning-icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
1130
|
+
<path d="M12 9v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
1131
|
+
</svg>
|
|
1132
|
+
`
|
|
1133
|
+
);
|
|
1134
|
+
function getStateId(status) {
|
|
1135
|
+
return `eudi-state-${status}`;
|
|
1136
|
+
}
|
|
1137
|
+
function renderIdle() {
|
|
1138
|
+
return (
|
|
1139
|
+
/* html */
|
|
1140
|
+
`
|
|
1141
|
+
<div id="${getStateId("idle")}" class="eudi-state" data-active>
|
|
1142
|
+
<button class="eudi-start-btn" type="button" data-action="start">
|
|
1143
|
+
${EU_STARS_ICON}
|
|
1144
|
+
<span>Verify with EU Wallet</span>
|
|
1145
|
+
</button>
|
|
1146
|
+
</div>
|
|
1147
|
+
`
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
function renderLoading() {
|
|
1151
|
+
return (
|
|
1152
|
+
/* html */
|
|
1153
|
+
`
|
|
1154
|
+
<div id="${getStateId("loading")}" class="eudi-state">
|
|
1155
|
+
<div class="eudi-loading">
|
|
1156
|
+
<div class="eudi-spinner" aria-hidden="true"></div>
|
|
1157
|
+
<p>Loading...</p>
|
|
1158
|
+
</div>
|
|
1159
|
+
</div>
|
|
1160
|
+
`
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
function renderShowQR() {
|
|
1164
|
+
return (
|
|
1165
|
+
/* html */
|
|
1166
|
+
`
|
|
1167
|
+
<div id="${getStateId("showQR")}" class="eudi-state">
|
|
1168
|
+
<div class="eudi-qr">
|
|
1169
|
+
<img class="eudi-qr-img" src="" alt="Scan with EUDI Wallet" />
|
|
1170
|
+
<p class="eudi-qr-text">Scan with your EU Digital Identity Wallet</p>
|
|
1171
|
+
<button class="eudi-cancel-btn" type="button" data-action="cancel">Cancel</button>
|
|
1172
|
+
</div>
|
|
1173
|
+
</div>
|
|
1174
|
+
`
|
|
1175
|
+
);
|
|
1176
|
+
}
|
|
1177
|
+
function renderWaitingForWallet() {
|
|
1178
|
+
return (
|
|
1179
|
+
/* html */
|
|
1180
|
+
`
|
|
1181
|
+
<div id="${getStateId("waitingForWallet")}" class="eudi-state">
|
|
1182
|
+
<div class="eudi-waiting">
|
|
1183
|
+
${WALLET_ICON}
|
|
1184
|
+
<p class="eudi-waiting-text">Waiting for wallet approval...</p>
|
|
1185
|
+
<button class="eudi-cancel-btn" type="button" data-action="cancel">Cancel</button>
|
|
1186
|
+
</div>
|
|
1187
|
+
</div>
|
|
1188
|
+
`
|
|
1189
|
+
);
|
|
1190
|
+
}
|
|
1191
|
+
function renderVerified() {
|
|
1192
|
+
return (
|
|
1193
|
+
/* html */
|
|
1194
|
+
`
|
|
1195
|
+
<div id="${getStateId("verified")}" class="eudi-state">
|
|
1196
|
+
<div class="eudi-success">
|
|
1197
|
+
${SUCCESS_ICON}
|
|
1198
|
+
<p class="eudi-success-text">Verified</p>
|
|
1199
|
+
</div>
|
|
1200
|
+
</div>
|
|
1201
|
+
`
|
|
1202
|
+
);
|
|
1203
|
+
}
|
|
1204
|
+
function renderRejected() {
|
|
1205
|
+
return (
|
|
1206
|
+
/* html */
|
|
1207
|
+
`
|
|
1208
|
+
<div id="${getStateId("rejected")}" class="eudi-state">
|
|
1209
|
+
<div class="eudi-error">
|
|
1210
|
+
${ERROR_ICON}
|
|
1211
|
+
<p class="eudi-error-text">Verification declined</p>
|
|
1212
|
+
<p class="eudi-error-detail"></p>
|
|
1213
|
+
<button class="eudi-retry-btn" type="button" data-action="reset">Try again</button>
|
|
1214
|
+
</div>
|
|
1215
|
+
</div>
|
|
1216
|
+
`
|
|
1217
|
+
);
|
|
1218
|
+
}
|
|
1219
|
+
function renderExpired() {
|
|
1220
|
+
return (
|
|
1221
|
+
/* html */
|
|
1222
|
+
`
|
|
1223
|
+
<div id="${getStateId("expired")}" class="eudi-state">
|
|
1224
|
+
<div class="eudi-error">
|
|
1225
|
+
${EXPIRED_ICON}
|
|
1226
|
+
<p class="eudi-error-text">Session expired</p>
|
|
1227
|
+
<button class="eudi-retry-btn" type="button" data-action="reset">Try again</button>
|
|
1228
|
+
</div>
|
|
1229
|
+
</div>
|
|
1230
|
+
`
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
function renderError() {
|
|
1234
|
+
return (
|
|
1235
|
+
/* html */
|
|
1236
|
+
`
|
|
1237
|
+
<div id="${getStateId("error")}" class="eudi-state">
|
|
1238
|
+
<div class="eudi-error">
|
|
1239
|
+
${ERROR_ICON}
|
|
1240
|
+
<p class="eudi-error-text">Verification failed</p>
|
|
1241
|
+
<p class="eudi-error-detail"></p>
|
|
1242
|
+
<button class="eudi-retry-btn" type="button" data-action="reset">Try again</button>
|
|
1243
|
+
</div>
|
|
1244
|
+
</div>
|
|
1245
|
+
`
|
|
1246
|
+
);
|
|
1247
|
+
}
|
|
1248
|
+
function renderDemoBanner() {
|
|
1249
|
+
return (
|
|
1250
|
+
/* html */
|
|
1251
|
+
`
|
|
1252
|
+
<div class="eudi-demo-banner" role="status" aria-live="polite" hidden>
|
|
1253
|
+
${WARNING_ICON}
|
|
1254
|
+
<span>Demo mode \u2014 credentials are simulated</span>
|
|
1255
|
+
</div>
|
|
1256
|
+
`
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
function renderWidget() {
|
|
1260
|
+
return (
|
|
1261
|
+
/* html */
|
|
1262
|
+
`
|
|
1263
|
+
<div class="eudi-widget" role="region" aria-label="Identity verification">
|
|
1264
|
+
<div class="eudi-sr-only" aria-live="polite" aria-atomic="true"></div>
|
|
1265
|
+
${renderDemoBanner()}
|
|
1266
|
+
${renderIdle()}
|
|
1267
|
+
${renderLoading()}
|
|
1268
|
+
${renderShowQR()}
|
|
1269
|
+
${renderWaitingForWallet()}
|
|
1270
|
+
${renderVerified()}
|
|
1271
|
+
${renderRejected()}
|
|
1272
|
+
${renderExpired()}
|
|
1273
|
+
${renderError()}
|
|
1274
|
+
</div>
|
|
1275
|
+
`
|
|
1276
|
+
);
|
|
1277
|
+
}
|
|
1278
|
+
function updateWidgetState(container, state) {
|
|
1279
|
+
const stateContainers = container.querySelectorAll(".eudi-state");
|
|
1280
|
+
for (const stateEl of stateContainers) {
|
|
1281
|
+
if (stateEl.id === getStateId(state.status)) {
|
|
1282
|
+
stateEl.setAttribute("data-active", "");
|
|
1283
|
+
} else {
|
|
1284
|
+
stateEl.removeAttribute("data-active");
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
if (state.status === "showQR") {
|
|
1288
|
+
const img = container.querySelector(".eudi-qr-img");
|
|
1289
|
+
if (img && "qrDataUrl" in state) {
|
|
1290
|
+
img.src = state.qrDataUrl;
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
if (state.status === "rejected" && "error" in state && state.error) {
|
|
1294
|
+
const detail = container.querySelector(`#${getStateId("rejected")} .eudi-error-detail`);
|
|
1295
|
+
if (detail) {
|
|
1296
|
+
detail.textContent = state.error;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
if (state.status === "error" && "error" in state) {
|
|
1300
|
+
const detail = container.querySelector(`#${getStateId("error")} .eudi-error-detail`);
|
|
1301
|
+
if (detail) {
|
|
1302
|
+
detail.textContent = state.error;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// src/a11y.ts
|
|
1308
|
+
var FOCUSABLE_SELECTORS = [
|
|
1309
|
+
"button:not([disabled])",
|
|
1310
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
1311
|
+
"a[href]",
|
|
1312
|
+
"input:not([disabled])"
|
|
1313
|
+
].join(", ");
|
|
1314
|
+
function getFocusableElements(container) {
|
|
1315
|
+
return Array.from(container.querySelectorAll(FOCUSABLE_SELECTORS));
|
|
1316
|
+
}
|
|
1317
|
+
function createFocusTrap(container) {
|
|
1318
|
+
let previouslyFocused = null;
|
|
1319
|
+
function handleKeyDown(event) {
|
|
1320
|
+
if (event.key !== "Tab") return;
|
|
1321
|
+
const focusable = getFocusableElements(container);
|
|
1322
|
+
if (focusable.length === 0) return;
|
|
1323
|
+
const firstElement = focusable[0];
|
|
1324
|
+
const lastElement = focusable[focusable.length - 1];
|
|
1325
|
+
if (event.shiftKey && document.activeElement === firstElement) {
|
|
1326
|
+
event.preventDefault();
|
|
1327
|
+
lastElement.focus();
|
|
1328
|
+
} else if (!event.shiftKey && document.activeElement === lastElement) {
|
|
1329
|
+
event.preventDefault();
|
|
1330
|
+
firstElement.focus();
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
function activate() {
|
|
1334
|
+
previouslyFocused = document.activeElement;
|
|
1335
|
+
container.addEventListener("keydown", handleKeyDown);
|
|
1336
|
+
const focusable = getFocusableElements(container);
|
|
1337
|
+
if (focusable.length > 0) {
|
|
1338
|
+
focusable[0].focus();
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
function deactivate() {
|
|
1342
|
+
container.removeEventListener("keydown", handleKeyDown);
|
|
1343
|
+
if (previouslyFocused && typeof previouslyFocused.focus === "function") {
|
|
1344
|
+
previouslyFocused.focus();
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
activate();
|
|
1348
|
+
return deactivate;
|
|
1349
|
+
}
|
|
1350
|
+
function announce(liveRegion, message, priority = "polite") {
|
|
1351
|
+
liveRegion.setAttribute("aria-live", priority);
|
|
1352
|
+
liveRegion.textContent = "";
|
|
1353
|
+
requestAnimationFrame(() => {
|
|
1354
|
+
liveRegion.textContent = message;
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1357
|
+
function clearAnnouncement(liveRegion) {
|
|
1358
|
+
liveRegion.textContent = "";
|
|
1359
|
+
}
|
|
1360
|
+
var STATE_MESSAGES = {
|
|
1361
|
+
idle: "",
|
|
1362
|
+
loading: "Loading verification session...",
|
|
1363
|
+
showQR: "QR code ready. Scan with your EU Digital Identity Wallet.",
|
|
1364
|
+
waitingForWallet: "Waiting for wallet approval...",
|
|
1365
|
+
verified: "Identity verified successfully.",
|
|
1366
|
+
rejected: "Verification was declined.",
|
|
1367
|
+
expired: "Verification session expired.",
|
|
1368
|
+
error: "Verification error occurred."
|
|
1369
|
+
};
|
|
1370
|
+
function getAnnouncementPriority(status) {
|
|
1371
|
+
switch (status) {
|
|
1372
|
+
case "verified":
|
|
1373
|
+
case "rejected":
|
|
1374
|
+
case "expired":
|
|
1375
|
+
case "error":
|
|
1376
|
+
return "assertive";
|
|
1377
|
+
default:
|
|
1378
|
+
return "polite";
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
// src/element.ts
|
|
1383
|
+
var OBSERVED_ATTRIBUTES = ["api-url", "request", "auto-start"];
|
|
1384
|
+
var EudiVerifyElement = class extends HTMLElement {
|
|
1385
|
+
static get observedAttributes() {
|
|
1386
|
+
return OBSERVED_ATTRIBUTES;
|
|
1387
|
+
}
|
|
1388
|
+
#shadow;
|
|
1389
|
+
#verification = null;
|
|
1390
|
+
#unsubscribe = null;
|
|
1391
|
+
#container = null;
|
|
1392
|
+
#liveRegion = null;
|
|
1393
|
+
#lastStatus = null;
|
|
1394
|
+
#isDemo = null;
|
|
1395
|
+
#demoBanner = null;
|
|
1396
|
+
constructor() {
|
|
1397
|
+
super();
|
|
1398
|
+
this.#shadow = this.attachShadow({ mode: "open" });
|
|
1399
|
+
}
|
|
1400
|
+
/**
|
|
1401
|
+
* Get the API URL attribute.
|
|
1402
|
+
*/
|
|
1403
|
+
get apiUrl() {
|
|
1404
|
+
return this.getAttribute("api-url") ?? "";
|
|
1405
|
+
}
|
|
1406
|
+
/**
|
|
1407
|
+
* Set the API URL attribute.
|
|
1408
|
+
*/
|
|
1409
|
+
set apiUrl(value) {
|
|
1410
|
+
this.setAttribute("api-url", value);
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* Get the request attribute (JSON string).
|
|
1414
|
+
*/
|
|
1415
|
+
get request() {
|
|
1416
|
+
return this.getAttribute("request") ?? "";
|
|
1417
|
+
}
|
|
1418
|
+
/**
|
|
1419
|
+
* Set the request attribute (JSON string).
|
|
1420
|
+
*/
|
|
1421
|
+
set request(value) {
|
|
1422
|
+
this.setAttribute("request", value);
|
|
1423
|
+
}
|
|
1424
|
+
/**
|
|
1425
|
+
* Check if auto-start is enabled.
|
|
1426
|
+
*/
|
|
1427
|
+
get autoStart() {
|
|
1428
|
+
return this.hasAttribute("auto-start");
|
|
1429
|
+
}
|
|
1430
|
+
/**
|
|
1431
|
+
* Set auto-start attribute.
|
|
1432
|
+
*/
|
|
1433
|
+
set autoStart(value) {
|
|
1434
|
+
if (value) {
|
|
1435
|
+
this.setAttribute("auto-start", "");
|
|
1436
|
+
} else {
|
|
1437
|
+
this.removeAttribute("auto-start");
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Current verification state (read-only).
|
|
1442
|
+
*/
|
|
1443
|
+
get state() {
|
|
1444
|
+
return this.#verification?.state ?? null;
|
|
1445
|
+
}
|
|
1446
|
+
connectedCallback() {
|
|
1447
|
+
this.#render();
|
|
1448
|
+
this.#setupEventListeners();
|
|
1449
|
+
if (this.autoStart && this.apiUrl && this.request) {
|
|
1450
|
+
this.start();
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
disconnectedCallback() {
|
|
1454
|
+
this.#cleanup();
|
|
1455
|
+
}
|
|
1456
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
1457
|
+
if (oldValue === newValue) return;
|
|
1458
|
+
if (name === "api-url" && this.#verification) {
|
|
1459
|
+
this.#cleanup();
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
/**
|
|
1463
|
+
* Start the verification flow.
|
|
1464
|
+
*/
|
|
1465
|
+
start() {
|
|
1466
|
+
if (!this.apiUrl) {
|
|
1467
|
+
console.error("[eudi-verify] api-url attribute is required");
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
let requestObj;
|
|
1471
|
+
try {
|
|
1472
|
+
requestObj = this.request ? JSON.parse(this.request) : {};
|
|
1473
|
+
} catch {
|
|
1474
|
+
console.error("[eudi-verify] Invalid JSON in request attribute");
|
|
1475
|
+
this.#dispatchError("Invalid verification request");
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
this.#ensureVerification();
|
|
1479
|
+
this.#verification.start(requestObj);
|
|
1480
|
+
}
|
|
1481
|
+
/**
|
|
1482
|
+
* Cancel the current verification.
|
|
1483
|
+
*/
|
|
1484
|
+
cancel() {
|
|
1485
|
+
this.#verification?.cancel();
|
|
1486
|
+
}
|
|
1487
|
+
/**
|
|
1488
|
+
* Reset to idle state.
|
|
1489
|
+
*/
|
|
1490
|
+
reset() {
|
|
1491
|
+
this.#cleanup();
|
|
1492
|
+
this.#updateState({ status: "idle" });
|
|
1493
|
+
}
|
|
1494
|
+
#render() {
|
|
1495
|
+
this.#shadow.innerHTML = `
|
|
1496
|
+
<style>${createStyles()}</style>
|
|
1497
|
+
${renderWidget()}
|
|
1498
|
+
`;
|
|
1499
|
+
this.#container = this.#shadow.querySelector(".eudi-widget");
|
|
1500
|
+
this.#liveRegion = this.#shadow.querySelector("[aria-live]");
|
|
1501
|
+
this.#demoBanner = this.#shadow.querySelector(".eudi-demo-banner");
|
|
1502
|
+
}
|
|
1503
|
+
#setupEventListeners() {
|
|
1504
|
+
this.#shadow.addEventListener("click", (event) => {
|
|
1505
|
+
const target = event.target;
|
|
1506
|
+
const button = target.closest("[data-action]");
|
|
1507
|
+
if (!button) return;
|
|
1508
|
+
const action = button.dataset.action;
|
|
1509
|
+
switch (action) {
|
|
1510
|
+
case "start":
|
|
1511
|
+
this.start();
|
|
1512
|
+
break;
|
|
1513
|
+
case "cancel":
|
|
1514
|
+
this.cancel();
|
|
1515
|
+
break;
|
|
1516
|
+
case "reset":
|
|
1517
|
+
this.reset();
|
|
1518
|
+
this.start();
|
|
1519
|
+
break;
|
|
1520
|
+
}
|
|
1521
|
+
});
|
|
1522
|
+
}
|
|
1523
|
+
#ensureVerification() {
|
|
1524
|
+
if (this.#verification) return;
|
|
1525
|
+
this.#verification = createVerification({
|
|
1526
|
+
apiUrl: this.apiUrl
|
|
1527
|
+
});
|
|
1528
|
+
this.#unsubscribe = this.#verification.subscribe((state) => {
|
|
1529
|
+
this.#handleStateChange(state);
|
|
1530
|
+
});
|
|
1531
|
+
this.#detectDemoMode();
|
|
1532
|
+
}
|
|
1533
|
+
async #detectDemoMode() {
|
|
1534
|
+
if (this.#isDemo !== null || !this.apiUrl) return;
|
|
1535
|
+
try {
|
|
1536
|
+
const response = await fetch(`${this.apiUrl}/sessions`, {
|
|
1537
|
+
method: "HEAD"
|
|
1538
|
+
});
|
|
1539
|
+
const mode = response.headers.get("X-Eudi-Mode");
|
|
1540
|
+
this.#isDemo = mode === "demo";
|
|
1541
|
+
this.#updateDemoBanner();
|
|
1542
|
+
} catch {
|
|
1543
|
+
this.#isDemo = false;
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
#updateDemoBanner() {
|
|
1547
|
+
if (!this.#demoBanner) return;
|
|
1548
|
+
if (this.#isDemo === true) {
|
|
1549
|
+
this.#demoBanner.removeAttribute("hidden");
|
|
1550
|
+
} else {
|
|
1551
|
+
this.#demoBanner.setAttribute("hidden", "");
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
#handleStateChange(state) {
|
|
1555
|
+
this.#updateState(state);
|
|
1556
|
+
this.#dispatchStateChange(state);
|
|
1557
|
+
switch (state.status) {
|
|
1558
|
+
case "verified":
|
|
1559
|
+
if ("token" in state && "claims" in state) {
|
|
1560
|
+
this.#dispatchVerified(state.token, state.claims);
|
|
1561
|
+
}
|
|
1562
|
+
break;
|
|
1563
|
+
case "rejected":
|
|
1564
|
+
this.#dispatchRejected("error" in state ? state.error : void 0);
|
|
1565
|
+
break;
|
|
1566
|
+
case "expired":
|
|
1567
|
+
this.#dispatchExpired();
|
|
1568
|
+
break;
|
|
1569
|
+
case "error":
|
|
1570
|
+
if ("error" in state) {
|
|
1571
|
+
this.#dispatchError(state.error);
|
|
1572
|
+
}
|
|
1573
|
+
break;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
#updateState(state) {
|
|
1577
|
+
if (!this.#container) return;
|
|
1578
|
+
updateWidgetState(this.#container, state);
|
|
1579
|
+
if (this.#liveRegion && state.status !== this.#lastStatus) {
|
|
1580
|
+
const message = STATE_MESSAGES[state.status];
|
|
1581
|
+
if (message) {
|
|
1582
|
+
const priority = getAnnouncementPriority(state.status);
|
|
1583
|
+
announce(this.#liveRegion, message, priority);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
this.#lastStatus = state.status;
|
|
1587
|
+
this.#manageFocus(state);
|
|
1588
|
+
}
|
|
1589
|
+
#manageFocus(state) {
|
|
1590
|
+
if (!this.#container) return;
|
|
1591
|
+
switch (state.status) {
|
|
1592
|
+
case "showQR": {
|
|
1593
|
+
const cancelBtn = this.#container.querySelector("#eudi-state-showQR .eudi-cancel-btn");
|
|
1594
|
+
cancelBtn?.focus();
|
|
1595
|
+
break;
|
|
1596
|
+
}
|
|
1597
|
+
case "verified":
|
|
1598
|
+
case "rejected":
|
|
1599
|
+
case "expired":
|
|
1600
|
+
case "error": {
|
|
1601
|
+
const retryBtn = this.#container.querySelector(
|
|
1602
|
+
`#eudi-state-${state.status} .eudi-retry-btn`
|
|
1603
|
+
);
|
|
1604
|
+
retryBtn?.focus();
|
|
1605
|
+
break;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
#dispatchVerified(token, claims) {
|
|
1610
|
+
this.dispatchEvent(
|
|
1611
|
+
new CustomEvent("verified", {
|
|
1612
|
+
bubbles: true,
|
|
1613
|
+
composed: true,
|
|
1614
|
+
detail: { token, claims }
|
|
1615
|
+
})
|
|
1616
|
+
);
|
|
1617
|
+
}
|
|
1618
|
+
#dispatchRejected(error) {
|
|
1619
|
+
this.dispatchEvent(
|
|
1620
|
+
new CustomEvent("rejected", {
|
|
1621
|
+
bubbles: true,
|
|
1622
|
+
composed: true,
|
|
1623
|
+
detail: { error }
|
|
1624
|
+
})
|
|
1625
|
+
);
|
|
1626
|
+
}
|
|
1627
|
+
#dispatchExpired() {
|
|
1628
|
+
this.dispatchEvent(
|
|
1629
|
+
new CustomEvent("expired", {
|
|
1630
|
+
bubbles: true,
|
|
1631
|
+
composed: true,
|
|
1632
|
+
detail: {}
|
|
1633
|
+
})
|
|
1634
|
+
);
|
|
1635
|
+
}
|
|
1636
|
+
#dispatchError(error) {
|
|
1637
|
+
this.dispatchEvent(
|
|
1638
|
+
new CustomEvent("error", {
|
|
1639
|
+
bubbles: true,
|
|
1640
|
+
composed: true,
|
|
1641
|
+
detail: { error }
|
|
1642
|
+
})
|
|
1643
|
+
);
|
|
1644
|
+
}
|
|
1645
|
+
#dispatchStateChange(state) {
|
|
1646
|
+
this.dispatchEvent(
|
|
1647
|
+
new CustomEvent("state-change", {
|
|
1648
|
+
bubbles: true,
|
|
1649
|
+
composed: true,
|
|
1650
|
+
detail: { state }
|
|
1651
|
+
})
|
|
1652
|
+
);
|
|
1653
|
+
}
|
|
1654
|
+
#cleanup() {
|
|
1655
|
+
if (this.#unsubscribe) {
|
|
1656
|
+
this.#unsubscribe();
|
|
1657
|
+
this.#unsubscribe = null;
|
|
1658
|
+
}
|
|
1659
|
+
if (this.#verification) {
|
|
1660
|
+
this.#verification.destroy();
|
|
1661
|
+
this.#verification = null;
|
|
1662
|
+
}
|
|
1663
|
+
if (this.#liveRegion) {
|
|
1664
|
+
clearAnnouncement(this.#liveRegion);
|
|
1665
|
+
}
|
|
1666
|
+
this.#lastStatus = null;
|
|
1667
|
+
}
|
|
1668
|
+
};
|
|
1669
|
+
|
|
1670
|
+
// src/index.ts
|
|
1671
|
+
var VERSION = "0.1.0";
|
|
1672
|
+
if (typeof customElements !== "undefined" && !customElements.get("eudi-verify")) {
|
|
1673
|
+
customElements.define("eudi-verify", EudiVerifyElement);
|
|
1674
|
+
}
|
|
1675
|
+
return __toCommonJS(src_exports);
|
|
1676
|
+
})();
|
|
1677
|
+
//# sourceMappingURL=eudi-verify.iife.js.map
|