@ave-id/embed 0.1.0 → 0.2.1

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.
Files changed (3) hide show
  1. package/README.md +68 -0
  2. package/dist/embed.js +519 -0
  3. package/package.json +10 -2
package/README.md CHANGED
@@ -10,6 +10,8 @@ npm install @ave-id/embed
10
10
 
11
11
  ## Usage
12
12
 
13
+ ### Inline iframe
14
+
13
15
  ```js
14
16
  import { mountAveEmbed } from "@ave-id/embed";
15
17
 
@@ -24,3 +26,69 @@ const { iframe } = mountAveEmbed({
24
26
  onError: (payload) => console.error("Embed error", payload),
25
27
  });
26
28
  ```
29
+
30
+ ### Modal sheet (mobile-friendly)
31
+
32
+ ```js
33
+ import { openAveSheet } from "@ave-id/embed";
34
+
35
+ const sheet = openAveSheet({
36
+ clientId: "YOUR_CLIENT_ID",
37
+ redirectUri: "https://yourapp.com/callback",
38
+ scope: "openid profile email",
39
+ onSuccess: ({ redirectUrl }) => {
40
+ window.location.href = redirectUrl;
41
+ },
42
+ onError: (payload) => console.error("Sheet error", payload),
43
+ onClose: () => console.log("Sheet closed"),
44
+ });
45
+
46
+ // sheet?.close();
47
+ ```
48
+
49
+ ### Popup window (desktop)
50
+
51
+ ```js
52
+ import { openAvePopup } from "@ave-id/embed";
53
+
54
+ const popup = openAvePopup({
55
+ clientId: "YOUR_CLIENT_ID",
56
+ redirectUri: "https://yourapp.com/callback",
57
+ scope: "openid profile email",
58
+ onSuccess: ({ redirectUrl }) => {
59
+ window.location.href = redirectUrl;
60
+ },
61
+ onError: (payload) => console.error("Popup error", payload),
62
+ onClose: () => console.log("Popup closed"),
63
+ });
64
+
65
+ // popup?.close();
66
+ ```
67
+
68
+ ## Options
69
+
70
+ Common:
71
+
72
+ - `clientId` (string)
73
+ - `redirectUri` (string)
74
+ - `scope` (string, default `openid profile email`)
75
+ - `issuer` (string, default `https://aveid.net`)
76
+ - `onSuccess(payload)`
77
+ - `onError(payload)`
78
+ - `onClose()`
79
+
80
+ `mountAveEmbed`:
81
+
82
+ - `container` (HTMLElement)
83
+ - `theme` (string)
84
+ - `width` (string | number)
85
+ - `height` (string | number)
86
+
87
+ `openAveSheet`:
88
+
89
+ - `theme` (string)
90
+
91
+ `openAvePopup`:
92
+
93
+ - `width` (number)
94
+ - `height` (number)
package/dist/embed.js ADDED
@@ -0,0 +1,519 @@
1
+ const DEFAULT_THEME = "dark";
2
+
3
+ /**
4
+ * Mount Ave auth embed as an inline iframe
5
+ */
6
+ export function mountAveEmbed({
7
+ container,
8
+ clientId,
9
+ redirectUri,
10
+ scope = "openid profile email",
11
+ issuer = "https://aveid.net",
12
+ theme = DEFAULT_THEME,
13
+ width = "100%",
14
+ height = 720,
15
+ onSuccess,
16
+ onError,
17
+ onClose,
18
+ }) {
19
+ if (!container) {
20
+ throw new Error("container is required");
21
+ }
22
+
23
+ const iframe = document.createElement("iframe");
24
+ const params = new URLSearchParams({
25
+ client_id: clientId,
26
+ redirect_uri: redirectUri,
27
+ scope,
28
+ embed: "1",
29
+ theme,
30
+ });
31
+
32
+ iframe.src = `${issuer}/signin?${params.toString()}`;
33
+ iframe.style.width = width;
34
+ iframe.style.height = typeof height === "number" ? `${height}px` : height;
35
+ iframe.style.border = "0";
36
+ iframe.style.borderRadius = "24px";
37
+ iframe.style.background = "#090909";
38
+ iframe.allow = "publickey-credentials-get";
39
+
40
+ container.appendChild(iframe);
41
+
42
+ const messageHandler = (event) => {
43
+ if (event.origin !== issuer) return;
44
+ const data = event.data || {};
45
+
46
+ if (data.type === "ave:success") {
47
+ onSuccess?.(data.payload);
48
+ }
49
+
50
+ if (data.type === "ave:error") {
51
+ onError?.(data.payload);
52
+ }
53
+
54
+ if (data.type === "ave:close") {
55
+ onClose?.();
56
+ }
57
+ };
58
+
59
+ window.addEventListener("message", messageHandler);
60
+
61
+ return {
62
+ iframe,
63
+ destroy() {
64
+ window.removeEventListener("message", messageHandler);
65
+ iframe.remove();
66
+ },
67
+ postMessage(payload) {
68
+ iframe.contentWindow?.postMessage(payload, issuer);
69
+ },
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Open Ave auth as a modal sheet overlay
75
+ */
76
+ export function openAveSheet({
77
+ clientId,
78
+ redirectUri,
79
+ scope = "openid profile email",
80
+ issuer = "https://aveid.net",
81
+ theme = DEFAULT_THEME,
82
+ onSuccess,
83
+ onError,
84
+ onClose,
85
+ }) {
86
+ // Create overlay backdrop
87
+ const overlay = document.createElement("div");
88
+ overlay.style.cssText = `
89
+ position: fixed;
90
+ inset: 0;
91
+ background: rgba(0, 0, 0, 0.7);
92
+ backdrop-filter: blur(4px);
93
+ z-index: 999999;
94
+ display: flex;
95
+ align-items: flex-end;
96
+ justify-content: center;
97
+ animation: aveSheetFadeIn 0.2s ease-out;
98
+ `;
99
+
100
+ // Create sheet container
101
+ const sheet = document.createElement("div");
102
+ sheet.style.cssText = `
103
+ width: 100%;
104
+ max-width: 500px;
105
+ max-height: 90vh;
106
+ background: #090909;
107
+ border-radius: 24px 24px 0 0;
108
+ overflow: hidden;
109
+ animation: aveSheetSlideUp 0.3s ease-out;
110
+ position: relative;
111
+ `;
112
+
113
+ // Add drag handle
114
+ const dragHandle = document.createElement("div");
115
+ dragHandle.style.cssText = `
116
+ width: 40px;
117
+ height: 4px;
118
+ background: #333;
119
+ border-radius: 2px;
120
+ margin: 12px auto;
121
+ `;
122
+ sheet.appendChild(dragHandle);
123
+
124
+ // Create close button
125
+ const closeBtn = document.createElement("button");
126
+ closeBtn.innerHTML = `
127
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888" stroke-width="2">
128
+ <path d="M18 6L6 18M6 6l12 12"/>
129
+ </svg>
130
+ `;
131
+ closeBtn.style.cssText = `
132
+ position: absolute;
133
+ top: 8px;
134
+ right: 12px;
135
+ background: transparent;
136
+ border: none;
137
+ cursor: pointer;
138
+ padding: 8px;
139
+ z-index: 10;
140
+ `;
141
+
142
+ // Create iframe
143
+ const iframe = document.createElement("iframe");
144
+ const params = new URLSearchParams({
145
+ client_id: clientId,
146
+ redirect_uri: redirectUri,
147
+ scope,
148
+ embed: "1",
149
+ theme,
150
+ });
151
+
152
+ iframe.src = `${issuer}/signin?${params.toString()}`;
153
+ iframe.style.cssText = `
154
+ width: 100%;
155
+ height: calc(90vh - 50px);
156
+ border: none;
157
+ background: #090909;
158
+ `;
159
+ iframe.allow = "publickey-credentials-get";
160
+
161
+ sheet.appendChild(closeBtn);
162
+ sheet.appendChild(iframe);
163
+ overlay.appendChild(sheet);
164
+
165
+ // Add animations
166
+ const style = document.createElement("style");
167
+ style.textContent = `
168
+ @keyframes aveSheetFadeIn {
169
+ from { opacity: 0; }
170
+ to { opacity: 1; }
171
+ }
172
+ @keyframes aveSheetSlideUp {
173
+ from { transform: translateY(100%); }
174
+ to { transform: translateY(0); }
175
+ }
176
+ @keyframes aveSheetSlideDown {
177
+ from { transform: translateY(0); }
178
+ to { transform: translateY(100%); }
179
+ }
180
+ @keyframes aveSheetFadeOut {
181
+ from { opacity: 1; }
182
+ to { opacity: 0; }
183
+ }
184
+ `;
185
+ document.head.appendChild(style);
186
+
187
+ // Close function
188
+ const close = () => {
189
+ sheet.style.animation = "aveSheetSlideDown 0.2s ease-in forwards";
190
+ overlay.style.animation = "aveSheetFadeOut 0.2s ease-in forwards";
191
+ setTimeout(() => {
192
+ overlay.remove();
193
+ style.remove();
194
+ window.removeEventListener("message", messageHandler);
195
+ onClose?.();
196
+ }, 200);
197
+ };
198
+
199
+ // Event handlers
200
+ closeBtn.onclick = close;
201
+ overlay.onclick = (e) => {
202
+ if (e.target === overlay) close();
203
+ };
204
+
205
+ const messageHandler = (event) => {
206
+ if (event.origin !== issuer) return;
207
+ const data = event.data || {};
208
+
209
+ if (data.type === "ave:success") {
210
+ close();
211
+ onSuccess?.(data.payload);
212
+ }
213
+
214
+ if (data.type === "ave:error") {
215
+ close();
216
+ onError?.(data.payload);
217
+ }
218
+
219
+ if (data.type === "ave:close") {
220
+ close();
221
+ }
222
+ };
223
+
224
+ window.addEventListener("message", messageHandler);
225
+
226
+ // Add to DOM
227
+ document.body.appendChild(overlay);
228
+
229
+ return {
230
+ close,
231
+ iframe,
232
+ };
233
+ }
234
+
235
+ /**
236
+ * Open Ave auth as a popup window (for desktop)
237
+ */
238
+ export function openAvePopup({
239
+ clientId,
240
+ redirectUri,
241
+ scope = "openid profile email",
242
+ issuer = "https://aveid.net",
243
+ width = 450,
244
+ height = 650,
245
+ onSuccess,
246
+ onError,
247
+ onClose,
248
+ }) {
249
+ const params = new URLSearchParams({
250
+ client_id: clientId,
251
+ redirect_uri: redirectUri,
252
+ scope,
253
+ embed: "1",
254
+ });
255
+
256
+ const left = (window.innerWidth - width) / 2 + window.screenX;
257
+ const top = (window.innerHeight - height) / 2 + window.screenY;
258
+
259
+ const popup = window.open(
260
+ `${issuer}/signin?${params.toString()}`,
261
+ "ave_auth",
262
+ `width=${width},height=${height},left=${left},top=${top},menubar=no,toolbar=no,location=no,status=no`
263
+ );
264
+
265
+ if (!popup) {
266
+ onError?.({ error: "popup_blocked", message: "Popup was blocked by the browser" });
267
+ return null;
268
+ }
269
+
270
+ const messageHandler = (event) => {
271
+ if (event.origin !== issuer) return;
272
+ const data = event.data || {};
273
+
274
+ if (data.type === "ave:success") {
275
+ popup.close();
276
+ window.removeEventListener("message", messageHandler);
277
+ onSuccess?.(data.payload);
278
+ }
279
+
280
+ if (data.type === "ave:error") {
281
+ popup.close();
282
+ window.removeEventListener("message", messageHandler);
283
+ onError?.(data.payload);
284
+ }
285
+
286
+ if (data.type === "ave:close") {
287
+ popup.close();
288
+ window.removeEventListener("message", messageHandler);
289
+ onClose?.();
290
+ }
291
+ };
292
+
293
+ window.addEventListener("message", messageHandler);
294
+
295
+ // Check if popup was closed
296
+ const pollTimer = setInterval(() => {
297
+ if (popup.closed) {
298
+ clearInterval(pollTimer);
299
+ window.removeEventListener("message", messageHandler);
300
+ onClose?.();
301
+ }
302
+ }, 500);
303
+
304
+ return {
305
+ popup,
306
+ close() {
307
+ clearInterval(pollTimer);
308
+ popup.close();
309
+ window.removeEventListener("message", messageHandler);
310
+ },
311
+ };
312
+ }
313
+
314
+ // ============================================
315
+ // Ave Signing Embeds
316
+ // ============================================
317
+
318
+ /**
319
+ * Open Ave signing as a modal sheet overlay
320
+ */
321
+ export function openAveSigningSheet({
322
+ requestId,
323
+ issuer = "https://aveid.net",
324
+ onSigned,
325
+ onDenied,
326
+ onClose,
327
+ }) {
328
+ // Create overlay backdrop
329
+ const overlay = document.createElement("div");
330
+ overlay.style.cssText = `
331
+ position: fixed;
332
+ inset: 0;
333
+ background: rgba(0, 0, 0, 0.8);
334
+ backdrop-filter: blur(4px);
335
+ z-index: 999999;
336
+ display: flex;
337
+ align-items: flex-end;
338
+ justify-content: center;
339
+ animation: aveSheetFadeIn 0.2s ease-out;
340
+ `;
341
+
342
+ // Create sheet container
343
+ const sheet = document.createElement("div");
344
+ sheet.style.cssText = `
345
+ width: 100%;
346
+ max-width: 600px;
347
+ max-height: 90vh;
348
+ background: #111111;
349
+ border-radius: 32px 32px 0 0;
350
+ overflow: hidden;
351
+ animation: aveSheetSlideUp 0.3s ease-out;
352
+ position: relative;
353
+ `;
354
+
355
+ // Add drag handle
356
+ const dragHandle = document.createElement("div");
357
+ dragHandle.style.cssText = `
358
+ width: 40px;
359
+ height: 4px;
360
+ background: #333;
361
+ border-radius: 2px;
362
+ margin: 12px auto;
363
+ `;
364
+ sheet.appendChild(dragHandle);
365
+
366
+ // Create iframe
367
+ const iframe = document.createElement("iframe");
368
+ const params = new URLSearchParams({
369
+ requestId,
370
+ embed: "1",
371
+ });
372
+
373
+ iframe.src = `${issuer}/sign?${params.toString()}`;
374
+ iframe.style.cssText = `
375
+ width: 100%;
376
+ height: calc(90vh - 30px);
377
+ border: none;
378
+ background: transparent;
379
+ `;
380
+ iframe.allow = "publickey-credentials-get";
381
+
382
+ sheet.appendChild(iframe);
383
+ overlay.appendChild(sheet);
384
+
385
+ // Add animations (reuse if already present)
386
+ if (!document.getElementById("ave-sheet-styles")) {
387
+ const style = document.createElement("style");
388
+ style.id = "ave-sheet-styles";
389
+ style.textContent = `
390
+ @keyframes aveSheetFadeIn {
391
+ from { opacity: 0; }
392
+ to { opacity: 1; }
393
+ }
394
+ @keyframes aveSheetSlideUp {
395
+ from { transform: translateY(100%); }
396
+ to { transform: translateY(0); }
397
+ }
398
+ @keyframes aveSheetSlideDown {
399
+ from { transform: translateY(0); }
400
+ to { transform: translateY(100%); }
401
+ }
402
+ @keyframes aveSheetFadeOut {
403
+ from { opacity: 1; }
404
+ to { opacity: 0; }
405
+ }
406
+ `;
407
+ document.head.appendChild(style);
408
+ }
409
+
410
+ // Close function
411
+ const close = () => {
412
+ sheet.style.animation = "aveSheetSlideDown 0.2s ease-in forwards";
413
+ overlay.style.animation = "aveSheetFadeOut 0.2s ease-in forwards";
414
+ setTimeout(() => {
415
+ overlay.remove();
416
+ window.removeEventListener("message", messageHandler);
417
+ onClose?.();
418
+ }, 200);
419
+ };
420
+
421
+ // Click outside to close
422
+ overlay.onclick = (e) => {
423
+ if (e.target === overlay) close();
424
+ };
425
+
426
+ const messageHandler = (event) => {
427
+ if (event.origin !== issuer) return;
428
+ const data = event.data || {};
429
+
430
+ if (data.type === "ave:signed") {
431
+ close();
432
+ onSigned?.(data.payload);
433
+ }
434
+
435
+ if (data.type === "ave:denied") {
436
+ close();
437
+ onDenied?.(data.payload);
438
+ }
439
+ };
440
+
441
+ window.addEventListener("message", messageHandler);
442
+
443
+ // Add to DOM
444
+ document.body.appendChild(overlay);
445
+
446
+ return {
447
+ close,
448
+ iframe,
449
+ };
450
+ }
451
+
452
+ /**
453
+ * Open Ave signing as a popup window
454
+ */
455
+ export function openAveSigningPopup({
456
+ requestId,
457
+ issuer = "https://aveid.net",
458
+ width = 500,
459
+ height = 600,
460
+ onSigned,
461
+ onDenied,
462
+ onClose,
463
+ }) {
464
+ const params = new URLSearchParams({
465
+ requestId,
466
+ embed: "1",
467
+ });
468
+
469
+ const left = (window.innerWidth - width) / 2 + window.screenX;
470
+ const top = (window.innerHeight - height) / 2 + window.screenY;
471
+
472
+ const popup = window.open(
473
+ `${issuer}/sign?${params.toString()}`,
474
+ "ave_signing",
475
+ `width=${width},height=${height},left=${left},top=${top},menubar=no,toolbar=no,location=no,status=no`
476
+ );
477
+
478
+ if (!popup) {
479
+ onDenied?.({ error: "popup_blocked", message: "Popup was blocked by the browser" });
480
+ return null;
481
+ }
482
+
483
+ const messageHandler = (event) => {
484
+ if (event.origin !== issuer) return;
485
+ const data = event.data || {};
486
+
487
+ if (data.type === "ave:signed") {
488
+ popup.close();
489
+ window.removeEventListener("message", messageHandler);
490
+ onSigned?.(data.payload);
491
+ }
492
+
493
+ if (data.type === "ave:denied") {
494
+ popup.close();
495
+ window.removeEventListener("message", messageHandler);
496
+ onDenied?.(data.payload);
497
+ }
498
+ };
499
+
500
+ window.addEventListener("message", messageHandler);
501
+
502
+ // Check if popup was closed
503
+ const pollTimer = setInterval(() => {
504
+ if (popup.closed) {
505
+ clearInterval(pollTimer);
506
+ window.removeEventListener("message", messageHandler);
507
+ onClose?.();
508
+ }
509
+ }, 500);
510
+
511
+ return {
512
+ popup,
513
+ close() {
514
+ clearInterval(pollTimer);
515
+ popup.close();
516
+ window.removeEventListener("message", messageHandler);
517
+ },
518
+ };
519
+ }
package/package.json CHANGED
@@ -1,13 +1,21 @@
1
1
  {
2
2
  "name": "@ave-id/embed",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "main": "dist/embed.js",
6
+ "module": "dist/embed.js",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/embed.js",
10
+ "default": "./dist/embed.js"
11
+ }
12
+ },
6
13
  "files": ["dist"],
7
14
  "publishConfig": {
8
15
  "access": "public"
9
16
  },
10
17
  "scripts": {
11
- "build": "mkdir -p dist && cp src/embed.js dist/embed.js"
18
+ "build": "mkdir -p dist && cp src/embed.js dist/embed.js",
19
+ "prepublishOnly": "npm run build"
12
20
  }
13
21
  }