@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/CHANGELOG.md +32 -0
- package/README.md +105 -0
- package/dist/base.d.ts +21 -0
- package/dist/ephemeral.d.ts +25 -8
- package/dist/login.d.ts +41 -8
- package/dist/qrauth-components.esm.js +170 -43
- package/dist/qrauth-components.js +170 -43
- package/dist/twofa.d.ts +41 -6
- package/package.json +12 -4
- package/dist/base.js +0 -148
- package/dist/ephemeral.js +0 -727
- package/dist/index.js +0 -4
- package/dist/login.js +0 -769
- package/dist/qrauth-components.esm.js.map +0 -7
- package/dist/qrauth-components.js.map +0 -7
- package/dist/twofa.js +0 -625
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
|
-
}
|