@qrauth/web-components 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/base.js DELETED
@@ -1,148 +0,0 @@
1
- /**
2
- * QRAuthElement — base class for all QRAuth Web Components.
3
- * Provides: Shadow DOM setup, CSS custom property theming, SSE connection management,
4
- * lifecycle hooks, and event dispatching.
5
- */
6
- export class QRAuthElement extends HTMLElement {
7
- shadow;
8
- _sseConnection = null;
9
- _pollInterval = null;
10
- static get observedAttributes() {
11
- return ['tenant', 'theme', 'base-url'];
12
- }
13
- constructor() {
14
- super();
15
- this.shadow = this.attachShadow({ mode: 'open' });
16
- }
17
- // Getters for common attributes
18
- get tenant() { return this.getAttribute('tenant') || ''; }
19
- get theme() { return this.getAttribute('theme') || 'light'; }
20
- get baseUrl() {
21
- const val = this.getAttribute('base-url');
22
- // Allow empty string for local dev (relative URLs), fallback to production
23
- return val !== null ? val : 'https://qrauth.io';
24
- }
25
- connectedCallback() {
26
- this.render();
27
- }
28
- disconnectedCallback() {
29
- this.cleanup();
30
- }
31
- attributeChangedCallback(_name, oldValue, newValue) {
32
- if (oldValue !== newValue) {
33
- this.render();
34
- }
35
- }
36
- // --- SSE Management ---
37
- connectSSE(url) {
38
- this.disconnectSSE();
39
- // EventSource doesn't support custom headers natively.
40
- // For public (unauthenticated) SSE endpoints this works directly.
41
- this._sseConnection = new EventSource(url);
42
- this._sseConnection.onerror = () => {
43
- // Fall back to polling on SSE failure — caller should initiate polling
44
- this.disconnectSSE();
45
- };
46
- }
47
- onSSEEvent(event, callback) {
48
- if (this._sseConnection) {
49
- this._sseConnection.addEventListener(event, (e) => {
50
- const msg = e;
51
- try {
52
- callback(JSON.parse(msg.data));
53
- }
54
- catch { /* invalid JSON */ }
55
- });
56
- }
57
- }
58
- disconnectSSE() {
59
- if (this._sseConnection) {
60
- this._sseConnection.close();
61
- this._sseConnection = null;
62
- }
63
- }
64
- // --- Polling (fallback / primary for authenticated endpoints) ---
65
- startPolling(url, intervalMs, callback, headers) {
66
- this.stopPolling();
67
- const poll = async () => {
68
- try {
69
- const res = await fetch(url, { headers });
70
- if (res.ok)
71
- callback(await res.json());
72
- }
73
- catch { /* network error — retry next interval */ }
74
- };
75
- poll(); // immediate first poll
76
- this._pollInterval = setInterval(poll, intervalMs);
77
- }
78
- stopPolling() {
79
- if (this._pollInterval) {
80
- clearInterval(this._pollInterval);
81
- this._pollInterval = null;
82
- }
83
- }
84
- // --- Cleanup ---
85
- cleanup() {
86
- this.disconnectSSE();
87
- this.stopPolling();
88
- }
89
- // --- Event dispatch ---
90
- emit(name, detail) {
91
- this.dispatchEvent(new CustomEvent(name, {
92
- bubbles: true,
93
- composed: true, // crosses shadow DOM boundary
94
- detail,
95
- }));
96
- }
97
- // --- PKCE helpers ---
98
- async generateCodeVerifier() {
99
- const bytes = new Uint8Array(32);
100
- crypto.getRandomValues(bytes);
101
- return this._base64url(bytes);
102
- }
103
- async computeCodeChallenge(verifier) {
104
- const encoder = new TextEncoder();
105
- const data = encoder.encode(verifier);
106
- const hash = await crypto.subtle.digest('SHA-256', data);
107
- return this._base64url(new Uint8Array(hash));
108
- }
109
- _base64url(bytes) {
110
- const binString = Array.from(bytes, (byte) => String.fromCodePoint(byte)).join('');
111
- return btoa(binString).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
112
- }
113
- // --- Shared base styles (host-level theming via CSS custom properties) ---
114
- getBaseStyles() {
115
- return `
116
- :host {
117
- display: inline-block;
118
- font-family: var(--qrauth-font, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);
119
- --_primary: var(--qrauth-primary, #00a76f);
120
- --_primary-dark: var(--qrauth-primary-dark, #007a52);
121
- --_text: var(--qrauth-text, #1a1a2e);
122
- --_text-muted: var(--qrauth-text-muted, #637381);
123
- --_bg: var(--qrauth-bg, #ffffff);
124
- --_surface: var(--qrauth-surface, #f9fafb);
125
- --_border: var(--qrauth-border, #e0e0e0);
126
- --_radius: var(--qrauth-radius, 12px);
127
- --_shadow: var(--qrauth-shadow, 0 24px 48px rgba(0,0,0,0.15));
128
- --_btn-bg: var(--qrauth-btn-bg, #1b2a4a);
129
- --_btn-hover: var(--qrauth-btn-hover, #263b66);
130
- --_success: #00a76f;
131
- --_error: #ff5630;
132
- --_warning: #ffab00;
133
- --_disabled: #919eab;
134
- }
135
- :host([theme="dark"]) {
136
- --_text: var(--qrauth-text, #f0f0f0);
137
- --_text-muted: var(--qrauth-text-muted, #919eab);
138
- --_bg: var(--qrauth-bg, #1a1a2e);
139
- --_surface: var(--qrauth-surface, #242436);
140
- --_border: var(--qrauth-border, rgba(255,255,255,0.12));
141
- --_shadow: var(--qrauth-shadow, 0 24px 48px rgba(0,0,0,0.5));
142
- --_btn-bg: var(--qrauth-btn-bg, #263b66);
143
- --_btn-hover: var(--qrauth-btn-hover, #2e4578);
144
- }
145
- * { box-sizing: border-box; margin: 0; padding: 0; }
146
- `;
147
- }
148
- }