@qidcloud/sdk 1.2.3 → 1.2.4
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/README.md +1561 -158
- package/dist/index.js +730 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +730 -13
- package/dist/index.mjs.map +1 -1
- package/dist/{index.d.ts → src/index.d.ts} +8 -0
- package/dist/src/modules/auth.d.ts +155 -0
- package/dist/src/modules/billing.d.ts +57 -0
- package/dist/src/modules/db.d.ts +49 -0
- package/dist/src/modules/project.d.ts +112 -0
- package/dist/src/modules/resource.d.ts +25 -0
- package/dist/src/modules/sdk.d.ts +29 -0
- package/dist/{modules → src/modules}/vault.d.ts +26 -14
- package/dist/src/types.d.ts +126 -0
- package/package.json +17 -3
- package/dist/modules/auth.d.ts +0 -27
- package/dist/modules/db.d.ts +0 -21
- package/dist/types.d.ts +0 -46
- package/rollup.config.mjs +0 -26
- package/src/components/QidCloudLogin.tsx +0 -64
- package/src/components/QidSignInButton.tsx +0 -260
- package/src/hooks/useQidAuth.ts +0 -179
- package/src/index.ts +0 -58
- package/src/modules/auth.ts +0 -106
- package/src/modules/db.ts +0 -40
- package/src/modules/edge.ts +0 -98
- package/src/modules/logs.ts +0 -30
- package/src/modules/vault.ts +0 -124
- package/src/types.ts +0 -52
- package/tsconfig.json +0 -31
- /package/dist/{components → src/components}/QidCloudLogin.d.ts +0 -0
- /package/dist/{components → src/components}/QidSignInButton.d.ts +0 -0
- /package/dist/{hooks → src/hooks}/useQidAuth.d.ts +0 -0
- /package/dist/{modules → src/modules}/edge.d.ts +0 -0
- /package/dist/{modules → src/modules}/logs.d.ts +0 -0
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { QidCloud } from '../index';
|
|
3
|
-
import { QidSignInButton } from './QidSignInButton';
|
|
4
|
-
import { QidUser } from '../types';
|
|
5
|
-
|
|
6
|
-
interface QidCloudLoginProps {
|
|
7
|
-
sdk: QidCloud;
|
|
8
|
-
onSuccess: (user: QidUser, token: string) => void;
|
|
9
|
-
onError?: (error: string) => void;
|
|
10
|
-
className?: string; // Wrapper class
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* A comprehensive login component for QidCloud.
|
|
15
|
-
* STRICT SECURITY: ONLY supports QR Scanning (Mobile App) for Post-Quantum security.
|
|
16
|
-
* Conventional login is not available in the SDK to prevent password transmission in third-party apps.
|
|
17
|
-
*/
|
|
18
|
-
export const QidCloudLogin: React.FC<QidCloudLoginProps> = ({
|
|
19
|
-
sdk,
|
|
20
|
-
onSuccess,
|
|
21
|
-
onError,
|
|
22
|
-
className
|
|
23
|
-
}) => {
|
|
24
|
-
return (
|
|
25
|
-
<div className={`qid-login-container ${className || ''}`} style={{
|
|
26
|
-
fontFamily: "'Inter', sans-serif",
|
|
27
|
-
maxWidth: '380px',
|
|
28
|
-
margin: '0 auto',
|
|
29
|
-
padding: '30px',
|
|
30
|
-
backgroundColor: '#050505',
|
|
31
|
-
borderRadius: '24px',
|
|
32
|
-
border: '1px solid #333',
|
|
33
|
-
boxShadow: '0 20px 80px rgba(0,0,0,0.8)',
|
|
34
|
-
color: '#fff',
|
|
35
|
-
textAlign: 'center'
|
|
36
|
-
}}>
|
|
37
|
-
<div style={{ marginBottom: '30px' }}>
|
|
38
|
-
<h2 style={{ fontSize: '1.5rem', fontWeight: 700, margin: '0 0 10px 0', letterSpacing: '-0.02em' }}>
|
|
39
|
-
QidCloud Login
|
|
40
|
-
</h2>
|
|
41
|
-
<p style={{ color: '#888', fontSize: '0.9rem', margin: 0 }}>
|
|
42
|
-
Secure Post-Quantum Access
|
|
43
|
-
</p>
|
|
44
|
-
</div>
|
|
45
|
-
|
|
46
|
-
<div style={{ marginBottom: '20px', padding: '10px', backgroundColor: '#111', borderRadius: '16px', border: '1px dashed #333' }}>
|
|
47
|
-
<p style={{ fontSize: '0.85rem', color: '#888', marginBottom: '20px', lineHeight: '1.5' }}>
|
|
48
|
-
Login instantly by scanning the secure QR code with your <strong>QidCloud App</strong>.
|
|
49
|
-
</p>
|
|
50
|
-
<QidSignInButton
|
|
51
|
-
sdk={sdk}
|
|
52
|
-
onSuccess={onSuccess}
|
|
53
|
-
onError={onError}
|
|
54
|
-
buttonText="Login with QIDCLOUD"
|
|
55
|
-
className="qid-qr-btn-full"
|
|
56
|
-
/>
|
|
57
|
-
</div>
|
|
58
|
-
|
|
59
|
-
<p style={{ fontSize: '0.75rem', color: '#444', marginTop: '20px' }}>
|
|
60
|
-
Credentials are never entered on this device.
|
|
61
|
-
</p>
|
|
62
|
-
</div>
|
|
63
|
-
);
|
|
64
|
-
};
|
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { QRCodeSVG } from 'qrcode.react';
|
|
3
|
-
import { QidCloud } from '../index';
|
|
4
|
-
import { QidUser } from '../types';
|
|
5
|
-
import { useQidAuth } from '../hooks/useQidAuth';
|
|
6
|
-
|
|
7
|
-
interface QidSignInButtonProps {
|
|
8
|
-
sdk: QidCloud;
|
|
9
|
-
onSuccess: (user: QidUser, token: string) => void;
|
|
10
|
-
onError?: (error: string) => void;
|
|
11
|
-
className?: string;
|
|
12
|
-
buttonText?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* A ready-to-use React component for QidCloud QR identity authentication.
|
|
17
|
-
*/
|
|
18
|
-
export const QidSignInButton: React.FC<QidSignInButtonProps> = ({
|
|
19
|
-
sdk,
|
|
20
|
-
onSuccess,
|
|
21
|
-
onError,
|
|
22
|
-
className,
|
|
23
|
-
buttonText = 'LOGIN WITH QIDCLOUD'
|
|
24
|
-
}: QidSignInButtonProps) => {
|
|
25
|
-
const {
|
|
26
|
-
user,
|
|
27
|
-
token,
|
|
28
|
-
error,
|
|
29
|
-
session,
|
|
30
|
-
initializing,
|
|
31
|
-
isExpired,
|
|
32
|
-
timeLeft,
|
|
33
|
-
login,
|
|
34
|
-
cancel
|
|
35
|
-
} = useQidAuth(sdk);
|
|
36
|
-
|
|
37
|
-
const [appError, setAppError] = React.useState(false);
|
|
38
|
-
|
|
39
|
-
// Watch for success
|
|
40
|
-
React.useEffect(() => {
|
|
41
|
-
if (user && token) {
|
|
42
|
-
onSuccess(user, token);
|
|
43
|
-
}
|
|
44
|
-
}, [user, token, onSuccess]);
|
|
45
|
-
|
|
46
|
-
// Watch for errors
|
|
47
|
-
React.useEffect(() => {
|
|
48
|
-
if (error && onError) {
|
|
49
|
-
onError(error);
|
|
50
|
-
}
|
|
51
|
-
}, [error, onError]);
|
|
52
|
-
|
|
53
|
-
return (
|
|
54
|
-
<>
|
|
55
|
-
<button
|
|
56
|
-
onClick={login}
|
|
57
|
-
disabled={initializing || !!session}
|
|
58
|
-
className={className || 'qid-signin-btn'}
|
|
59
|
-
style={{
|
|
60
|
-
backgroundColor: '#00e5ff',
|
|
61
|
-
color: '#000',
|
|
62
|
-
padding: '12px 24px',
|
|
63
|
-
borderRadius: '12px',
|
|
64
|
-
border: 'none',
|
|
65
|
-
fontWeight: '900',
|
|
66
|
-
cursor: (initializing || !!session) ? 'not-allowed' : 'pointer',
|
|
67
|
-
display: 'flex',
|
|
68
|
-
alignItems: 'center',
|
|
69
|
-
gap: '10px',
|
|
70
|
-
textTransform: 'uppercase',
|
|
71
|
-
fontSize: '0.85rem',
|
|
72
|
-
letterSpacing: '0.5px',
|
|
73
|
-
boxShadow: '0 4px 15px rgba(0, 229, 255, 0.3)',
|
|
74
|
-
transition: 'all 0.2s'
|
|
75
|
-
}}
|
|
76
|
-
>
|
|
77
|
-
<div style={{
|
|
78
|
-
width: '8px',
|
|
79
|
-
height: '8px',
|
|
80
|
-
borderRadius: '50%',
|
|
81
|
-
backgroundColor: (initializing || !!session) ? '#333' : '#000',
|
|
82
|
-
boxShadow: (initializing || !!session) ? 'none' : '0 0 8px rgba(0,0,0,0.5)'
|
|
83
|
-
}} />
|
|
84
|
-
{initializing ? 'Preparing Handshake...' : (session ? 'Awaiting Scan...' : buttonText)}
|
|
85
|
-
</button>
|
|
86
|
-
|
|
87
|
-
{session && (
|
|
88
|
-
<div className="qid-modal-overlay" style={{
|
|
89
|
-
position: 'fixed',
|
|
90
|
-
top: 0, left: 0, right: 0, bottom: 0,
|
|
91
|
-
backgroundColor: 'rgba(0,0,0,0.92)',
|
|
92
|
-
display: 'flex',
|
|
93
|
-
justifyContent: 'center',
|
|
94
|
-
alignItems: 'center',
|
|
95
|
-
zIndex: 9999,
|
|
96
|
-
backdropFilter: 'blur(8px)'
|
|
97
|
-
}}>
|
|
98
|
-
<div className="qid-modal-content" style={{
|
|
99
|
-
backgroundColor: '#0a0a0a',
|
|
100
|
-
padding: '40px',
|
|
101
|
-
borderRadius: '28px',
|
|
102
|
-
textAlign: 'center',
|
|
103
|
-
color: '#fff',
|
|
104
|
-
maxWidth: '420px',
|
|
105
|
-
width: '90%',
|
|
106
|
-
border: '1px solid rgba(0, 229, 255, 0.2)',
|
|
107
|
-
boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.5)'
|
|
108
|
-
}}>
|
|
109
|
-
<div style={{ marginBottom: '25px' }}>
|
|
110
|
-
<div style={{
|
|
111
|
-
display: 'inline-block',
|
|
112
|
-
padding: '12px 20px',
|
|
113
|
-
borderRadius: '30px',
|
|
114
|
-
backgroundColor: 'rgba(0, 229, 255, 0.1)',
|
|
115
|
-
border: '1px solid rgba(0, 229, 255, 0.3)',
|
|
116
|
-
marginBottom: '15px'
|
|
117
|
-
}}>
|
|
118
|
-
<span style={{ color: '#00e5ff', fontSize: '0.7rem', fontWeight: '900', letterSpacing: '2px' }}>PQC ENCLAVE SECURED</span>
|
|
119
|
-
</div>
|
|
120
|
-
<h2 style={{ fontSize: '1.75rem', fontWeight: '900', marginBottom: '10px', letterSpacing: '-0.5px' }}>Identity Handshake</h2>
|
|
121
|
-
<p style={{ fontSize: '0.95rem', color: '#94a3b8', lineHeight: '1.6' }}>
|
|
122
|
-
Scan this encrypted gateway with your QidCloud app to authorize access.
|
|
123
|
-
</p>
|
|
124
|
-
</div>
|
|
125
|
-
|
|
126
|
-
<div style={{
|
|
127
|
-
backgroundColor: '#fff',
|
|
128
|
-
padding: '24px',
|
|
129
|
-
display: 'inline-block',
|
|
130
|
-
borderRadius: '24px',
|
|
131
|
-
marginBottom: '30px',
|
|
132
|
-
boxShadow: '0 10px 40px rgba(0,0,0,0.3)'
|
|
133
|
-
}}>
|
|
134
|
-
<QRCodeSVG
|
|
135
|
-
value={session.qrData}
|
|
136
|
-
size={220}
|
|
137
|
-
level="H"
|
|
138
|
-
includeMargin={false}
|
|
139
|
-
style={{ opacity: isExpired ? 0.1 : 1, transition: 'opacity 0.3s' }}
|
|
140
|
-
imageSettings={{
|
|
141
|
-
src: "https://api.qidcloud.com/favicon.ico",
|
|
142
|
-
x: undefined,
|
|
143
|
-
y: undefined,
|
|
144
|
-
height: 40,
|
|
145
|
-
width: 40,
|
|
146
|
-
excavate: true,
|
|
147
|
-
}}
|
|
148
|
-
/>
|
|
149
|
-
{isExpired && (
|
|
150
|
-
<div style={{
|
|
151
|
-
position: 'absolute',
|
|
152
|
-
top: '50%',
|
|
153
|
-
left: '50%',
|
|
154
|
-
transform: 'translate(-50%, -50%)',
|
|
155
|
-
width: '100%'
|
|
156
|
-
}}>
|
|
157
|
-
<button
|
|
158
|
-
onClick={login}
|
|
159
|
-
style={{
|
|
160
|
-
backgroundColor: '#00e5ff',
|
|
161
|
-
color: '#000',
|
|
162
|
-
border: 'none',
|
|
163
|
-
padding: '12px 20px',
|
|
164
|
-
borderRadius: '12px',
|
|
165
|
-
fontWeight: '900',
|
|
166
|
-
cursor: 'pointer',
|
|
167
|
-
boxShadow: '0 4px 15px rgba(0, 229, 255, 0.4)',
|
|
168
|
-
fontSize: '0.8rem'
|
|
169
|
-
}}
|
|
170
|
-
>
|
|
171
|
-
REFRESH QR
|
|
172
|
-
</button>
|
|
173
|
-
</div>
|
|
174
|
-
)}
|
|
175
|
-
</div>
|
|
176
|
-
|
|
177
|
-
{isExpired && (
|
|
178
|
-
<p style={{ color: '#ef4444', fontSize: '0.85rem', marginBottom: '20px', fontWeight: '600' }}>
|
|
179
|
-
Session expired. Please refresh.
|
|
180
|
-
</p>
|
|
181
|
-
)}
|
|
182
|
-
|
|
183
|
-
<div style={{ marginBottom: '20px' }}>
|
|
184
|
-
<p style={{ fontSize: '0.85rem', color: '#94a3b8', marginBottom: '8px' }}>
|
|
185
|
-
Are you on mobile?
|
|
186
|
-
</p>
|
|
187
|
-
<p style={{ fontSize: '0.75rem', color: '#64748b' }}>
|
|
188
|
-
Use <strong>One-Click Auth</strong> for instant access.
|
|
189
|
-
</p>
|
|
190
|
-
</div>
|
|
191
|
-
|
|
192
|
-
<div style={{ display: 'flex', gap: '12px', marginTop: '10px' }}>
|
|
193
|
-
<button onClick={cancel} style={{
|
|
194
|
-
flex: 2,
|
|
195
|
-
padding: '16px',
|
|
196
|
-
backgroundColor: 'transparent',
|
|
197
|
-
color: '#64748b',
|
|
198
|
-
border: '1px solid rgba(148, 163, 184, 0.2)',
|
|
199
|
-
borderRadius: '16px',
|
|
200
|
-
cursor: 'pointer',
|
|
201
|
-
fontWeight: '700',
|
|
202
|
-
transition: 'all 0.2s',
|
|
203
|
-
fontSize: '0.9rem'
|
|
204
|
-
}}>
|
|
205
|
-
Cancel
|
|
206
|
-
</button>
|
|
207
|
-
|
|
208
|
-
{/* Mobile Deep Link Button */}
|
|
209
|
-
<button
|
|
210
|
-
onClick={() => {
|
|
211
|
-
setAppError(false);
|
|
212
|
-
const deepLink = `qidcloud://authorize?sessionId=${session.sessionId}`; // Corrected format per docs
|
|
213
|
-
|
|
214
|
-
// Timeout to check if app opened
|
|
215
|
-
const start = Date.now();
|
|
216
|
-
window.location.href = deepLink;
|
|
217
|
-
|
|
218
|
-
setTimeout(() => {
|
|
219
|
-
if (Date.now() - start < 1500) {
|
|
220
|
-
setAppError(true);
|
|
221
|
-
}
|
|
222
|
-
}, 1000);
|
|
223
|
-
}}
|
|
224
|
-
style={{
|
|
225
|
-
flex: 1,
|
|
226
|
-
padding: '16px',
|
|
227
|
-
backgroundColor: 'rgba(0, 229, 255, 0.1)',
|
|
228
|
-
color: '#00e5ff',
|
|
229
|
-
border: '1px solid rgba(0, 229, 255, 0.3)',
|
|
230
|
-
borderRadius: '16px',
|
|
231
|
-
cursor: 'pointer',
|
|
232
|
-
display: 'flex',
|
|
233
|
-
alignItems: 'center',
|
|
234
|
-
justifyContent: 'center',
|
|
235
|
-
transition: 'all 0.2s'
|
|
236
|
-
}}
|
|
237
|
-
title="Authenticate with QidCloud App"
|
|
238
|
-
>
|
|
239
|
-
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
240
|
-
<rect x="5" y="2" width="14" height="20" rx="2" ry="2" />
|
|
241
|
-
<line x1="12" y1="18" x2="12.01" y2="18" />
|
|
242
|
-
</svg>
|
|
243
|
-
</button>
|
|
244
|
-
</div>
|
|
245
|
-
|
|
246
|
-
{appError && (
|
|
247
|
-
<p style={{ color: '#f59e0b', fontSize: '0.75rem', marginTop: '12px', fontWeight: '600' }}>
|
|
248
|
-
QidCloud App not found on device
|
|
249
|
-
</p>
|
|
250
|
-
)}
|
|
251
|
-
|
|
252
|
-
<div style={{ marginTop: '25px', fontSize: '0.75rem', color: '#475569' }}>
|
|
253
|
-
Session ID: {session.sessionId.slice(0, 8)}...
|
|
254
|
-
</div>
|
|
255
|
-
</div>
|
|
256
|
-
</div>
|
|
257
|
-
)}
|
|
258
|
-
</>
|
|
259
|
-
);
|
|
260
|
-
};
|
package/src/hooks/useQidAuth.ts
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
2
|
-
import { QidCloud } from '../index';
|
|
3
|
-
import { QidUser, QidAuthSession } from '../types';
|
|
4
|
-
|
|
5
|
-
export interface UseQidAuthReturn {
|
|
6
|
-
user: QidUser | null;
|
|
7
|
-
token: string | null;
|
|
8
|
-
loading: boolean;
|
|
9
|
-
error: string | null;
|
|
10
|
-
session: QidAuthSession | null;
|
|
11
|
-
initializing: boolean;
|
|
12
|
-
isExpired: boolean;
|
|
13
|
-
timeLeft: number;
|
|
14
|
-
login: () => Promise<void>;
|
|
15
|
-
logout: () => void;
|
|
16
|
-
cancel: () => void;
|
|
17
|
-
setAuthenticated: (user: QidUser, token: string) => void;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* A React hook for managing QidCloud authentication lifecycle.
|
|
22
|
-
* Handles handshake initialization, WebSocket listeners, and profile fetching.
|
|
23
|
-
*/
|
|
24
|
-
export function useQidAuth(sdk: QidCloud): UseQidAuthReturn {
|
|
25
|
-
const [user, setUser] = useState<QidUser | null>(null);
|
|
26
|
-
const [token, setToken] = useState<string | null>(null);
|
|
27
|
-
const [session, setSession] = useState<QidAuthSession | null>(null);
|
|
28
|
-
const [loading, setLoading] = useState(false);
|
|
29
|
-
const [initializing, setInitializing] = useState(false);
|
|
30
|
-
const [error, setError] = useState<string | null>(null);
|
|
31
|
-
|
|
32
|
-
const [timeLeft, setTimeLeft] = useState<number>(0);
|
|
33
|
-
const [isExpired, setIsExpired] = useState(false);
|
|
34
|
-
|
|
35
|
-
// Use ref to track if we should still be listening (to avoid state updates after unmount/cancel)
|
|
36
|
-
const activeSessionId = useRef<string | null>(null);
|
|
37
|
-
const timerRef = useRef<NodeJS.Timeout | null>(null);
|
|
38
|
-
|
|
39
|
-
const clearTimer = useCallback(() => {
|
|
40
|
-
if (timerRef.current) {
|
|
41
|
-
clearInterval(timerRef.current);
|
|
42
|
-
timerRef.current = null;
|
|
43
|
-
}
|
|
44
|
-
}, []);
|
|
45
|
-
|
|
46
|
-
const STORAGE_KEY = 'qid_auth_token';
|
|
47
|
-
|
|
48
|
-
// Helper to fetch profile
|
|
49
|
-
const fetchProfile = useCallback(async (authToken: string) => {
|
|
50
|
-
setLoading(true);
|
|
51
|
-
try {
|
|
52
|
-
const profile = await sdk.auth.getProfile(authToken);
|
|
53
|
-
setToken(authToken);
|
|
54
|
-
setUser(profile);
|
|
55
|
-
localStorage.setItem(STORAGE_KEY, authToken);
|
|
56
|
-
} catch (err: any) {
|
|
57
|
-
console.error('Failed to restore session:', err);
|
|
58
|
-
setError('Session expired');
|
|
59
|
-
localStorage.removeItem(STORAGE_KEY);
|
|
60
|
-
setToken(null);
|
|
61
|
-
setUser(null);
|
|
62
|
-
} finally {
|
|
63
|
-
setLoading(false);
|
|
64
|
-
setInitializing(false);
|
|
65
|
-
}
|
|
66
|
-
}, [sdk]);
|
|
67
|
-
|
|
68
|
-
// Init: Check local storage
|
|
69
|
-
useEffect(() => {
|
|
70
|
-
const storedToken = localStorage.getItem(STORAGE_KEY);
|
|
71
|
-
if (storedToken) {
|
|
72
|
-
fetchProfile(storedToken);
|
|
73
|
-
}
|
|
74
|
-
}, [fetchProfile]);
|
|
75
|
-
|
|
76
|
-
const logout = useCallback(async () => {
|
|
77
|
-
if (token) {
|
|
78
|
-
await sdk.auth.logout(token);
|
|
79
|
-
} else {
|
|
80
|
-
sdk.auth.disconnect();
|
|
81
|
-
}
|
|
82
|
-
localStorage.removeItem(STORAGE_KEY);
|
|
83
|
-
setUser(null);
|
|
84
|
-
setToken(null);
|
|
85
|
-
setSession(null);
|
|
86
|
-
setIsExpired(false);
|
|
87
|
-
clearTimer();
|
|
88
|
-
}, [sdk, token, clearTimer]);
|
|
89
|
-
|
|
90
|
-
const cancel = useCallback(() => {
|
|
91
|
-
activeSessionId.current = null;
|
|
92
|
-
setSession(null);
|
|
93
|
-
setInitializing(false);
|
|
94
|
-
setIsExpired(false);
|
|
95
|
-
clearTimer();
|
|
96
|
-
sdk.auth.disconnect();
|
|
97
|
-
}, [sdk, clearTimer]);
|
|
98
|
-
|
|
99
|
-
const login = useCallback(async () => {
|
|
100
|
-
setInitializing(true);
|
|
101
|
-
setError(null);
|
|
102
|
-
setIsExpired(false);
|
|
103
|
-
clearTimer();
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
const newSession = await sdk.auth.createSession();
|
|
107
|
-
setSession(newSession);
|
|
108
|
-
activeSessionId.current = newSession.sessionId;
|
|
109
|
-
|
|
110
|
-
// Start Timer
|
|
111
|
-
const expiresAt = newSession.expiresAt || (Date.now() + 120000); // Fallback 2m
|
|
112
|
-
setTimeLeft(Math.max(0, Math.floor((expiresAt - Date.now()) / 1000)));
|
|
113
|
-
|
|
114
|
-
timerRef.current = setInterval(() => {
|
|
115
|
-
const remaining = Math.max(0, Math.floor((expiresAt - Date.now()) / 1000));
|
|
116
|
-
setTimeLeft(remaining);
|
|
117
|
-
|
|
118
|
-
if (remaining <= 0) {
|
|
119
|
-
setIsExpired(true);
|
|
120
|
-
clearTimer();
|
|
121
|
-
// Don't nullify session immediately, let UI show expiry state
|
|
122
|
-
}
|
|
123
|
-
}, 1000);
|
|
124
|
-
|
|
125
|
-
sdk.auth.listen(
|
|
126
|
-
newSession.sessionId,
|
|
127
|
-
async (receivedToken: string) => {
|
|
128
|
-
// Safety check: is this still the active session?
|
|
129
|
-
if (activeSessionId.current !== newSession.sessionId) return;
|
|
130
|
-
|
|
131
|
-
clearTimer();
|
|
132
|
-
setSession(null); // Clear QR session
|
|
133
|
-
|
|
134
|
-
// Fetch profile and persist
|
|
135
|
-
await fetchProfile(receivedToken);
|
|
136
|
-
},
|
|
137
|
-
(err: string) => {
|
|
138
|
-
if (activeSessionId.current !== newSession.sessionId) return;
|
|
139
|
-
setError(err);
|
|
140
|
-
setInitializing(false);
|
|
141
|
-
setSession(null);
|
|
142
|
-
clearTimer();
|
|
143
|
-
}
|
|
144
|
-
);
|
|
145
|
-
} catch (err: any) {
|
|
146
|
-
setError(err.message || 'Failed to initiate login handshake');
|
|
147
|
-
setInitializing(false);
|
|
148
|
-
clearTimer();
|
|
149
|
-
}
|
|
150
|
-
}, [sdk, clearTimer, fetchProfile]);
|
|
151
|
-
|
|
152
|
-
// Cleanup on unmount
|
|
153
|
-
useEffect(() => {
|
|
154
|
-
return () => {
|
|
155
|
-
clearTimer();
|
|
156
|
-
sdk.auth.disconnect();
|
|
157
|
-
};
|
|
158
|
-
}, [sdk, clearTimer]);
|
|
159
|
-
|
|
160
|
-
return {
|
|
161
|
-
user,
|
|
162
|
-
token,
|
|
163
|
-
loading,
|
|
164
|
-
error,
|
|
165
|
-
session,
|
|
166
|
-
initializing,
|
|
167
|
-
isExpired,
|
|
168
|
-
timeLeft,
|
|
169
|
-
login,
|
|
170
|
-
logout,
|
|
171
|
-
cancel,
|
|
172
|
-
setAuthenticated: (user: QidUser, token: string) => {
|
|
173
|
-
setUser(user);
|
|
174
|
-
setToken(token);
|
|
175
|
-
localStorage.setItem(STORAGE_KEY, token);
|
|
176
|
-
setInitializing(false);
|
|
177
|
-
}
|
|
178
|
-
};
|
|
179
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import axios, { AxiosInstance } from 'axios';
|
|
2
|
-
import { QidConfig } from './types';
|
|
3
|
-
import { AuthModule } from './modules/auth';
|
|
4
|
-
import { DbModule } from './modules/db';
|
|
5
|
-
import { EdgeModule } from './modules/edge';
|
|
6
|
-
import { VaultModule } from './modules/vault';
|
|
7
|
-
import { LogsModule } from './modules/logs';
|
|
8
|
-
|
|
9
|
-
export class QidCloud {
|
|
10
|
-
private config: QidConfig;
|
|
11
|
-
public api: AxiosInstance;
|
|
12
|
-
|
|
13
|
-
public auth: AuthModule;
|
|
14
|
-
public db: DbModule;
|
|
15
|
-
public edge: EdgeModule;
|
|
16
|
-
public vault: VaultModule;
|
|
17
|
-
public logs: LogsModule;
|
|
18
|
-
|
|
19
|
-
constructor(config: QidConfig) {
|
|
20
|
-
this.config = {
|
|
21
|
-
baseUrl: 'https://api.qidcloud.com',
|
|
22
|
-
...config,
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
this.api = axios.create({
|
|
26
|
-
baseURL: this.config.baseUrl,
|
|
27
|
-
headers: {
|
|
28
|
-
'x-api-key': this.config.apiKey,
|
|
29
|
-
'Content-Type': 'application/json',
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
this.auth = new AuthModule(this);
|
|
34
|
-
this.db = new DbModule(this);
|
|
35
|
-
this.edge = new EdgeModule(this);
|
|
36
|
-
this.vault = new VaultModule(this);
|
|
37
|
-
this.logs = new LogsModule(this);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Get the current project configuration
|
|
42
|
-
*/
|
|
43
|
-
getConfig(): QidConfig {
|
|
44
|
-
return { ...this.config };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Modules will be initialized here
|
|
48
|
-
// public auth = new AuthModule(this);
|
|
49
|
-
// public db = new DbModule(this);
|
|
50
|
-
// public storage = new StorageModule(this);
|
|
51
|
-
// public edge = new EdgeModule(this);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export default QidCloud;
|
|
55
|
-
export * from './types';
|
|
56
|
-
export { QidSignInButton } from './components/QidSignInButton';
|
|
57
|
-
export { QidCloudLogin } from './components/QidCloudLogin';
|
|
58
|
-
export { useQidAuth } from './hooks/useQidAuth';
|
package/src/modules/auth.ts
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { io, Socket } from 'socket.io-client';
|
|
2
|
-
import { QidCloud } from '../index';
|
|
3
|
-
import { QidAuthSession, QidUser } from '../types';
|
|
4
|
-
|
|
5
|
-
export class AuthModule {
|
|
6
|
-
private sdk: QidCloud;
|
|
7
|
-
private socket: Socket | null = null;
|
|
8
|
-
|
|
9
|
-
constructor(sdk: QidCloud) {
|
|
10
|
-
this.sdk = sdk;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Initialize a new handshake session for QR login
|
|
15
|
-
*/
|
|
16
|
-
async createSession(): Promise<QidAuthSession> {
|
|
17
|
-
const resp = await this.sdk.api.post('/api/initiate-generic');
|
|
18
|
-
const { sessionId, nonce } = resp.data;
|
|
19
|
-
|
|
20
|
-
// Construct QR data (JSON format for mobile app)
|
|
21
|
-
const qrData = JSON.stringify({
|
|
22
|
-
sessionId,
|
|
23
|
-
nonce,
|
|
24
|
-
action: 'login-handshake',
|
|
25
|
-
timestamp: Date.now()
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
sessionId,
|
|
30
|
-
qrData,
|
|
31
|
-
expiresAt: Date.now() + 120000 // 2 minutes
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Listen for authorization events for a specific session
|
|
37
|
-
*/
|
|
38
|
-
listen(sessionId: string, onAuthorized: (token: string) => void, onDenied?: (msg: string) => void) {
|
|
39
|
-
if (!this.socket) {
|
|
40
|
-
const baseUrl = this.sdk.getConfig().baseUrl || 'https://api.qidcloud.com';
|
|
41
|
-
this.socket = io(baseUrl);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
this.socket.emit('join', sessionId);
|
|
45
|
-
|
|
46
|
-
this.socket.on('authenticated', (token: string) => {
|
|
47
|
-
onAuthorized(token);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
this.socket.on('identity_authorized', (data: any) => {
|
|
51
|
-
// Some backend routes wrap the token in an object
|
|
52
|
-
const token = typeof data === 'string' ? data : data.token;
|
|
53
|
-
onAuthorized(token);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
this.socket.on('identity_denied', (data: any) => {
|
|
57
|
-
if (onDenied) onDenied(data.message || 'Authorization denied');
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Terminate the session on the server
|
|
63
|
-
*/
|
|
64
|
-
async logout(token: string): Promise<void> {
|
|
65
|
-
if (!token) return;
|
|
66
|
-
try {
|
|
67
|
-
await this.sdk.api.post('/api/logout', {}, {
|
|
68
|
-
headers: { 'Authorization': `Bearer ${token}` }
|
|
69
|
-
});
|
|
70
|
-
} catch (e) {
|
|
71
|
-
console.warn('Logout failed or session already expired');
|
|
72
|
-
} finally {
|
|
73
|
-
this.disconnect();
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Stop listening and disconnect socket
|
|
79
|
-
*/
|
|
80
|
-
disconnect() {
|
|
81
|
-
if (this.socket) {
|
|
82
|
-
this.socket.disconnect();
|
|
83
|
-
this.socket = null;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Fetch user profile using a session token
|
|
89
|
-
*/
|
|
90
|
-
async getProfile(token: string): Promise<QidUser> {
|
|
91
|
-
const resp = await this.sdk.api.get('/api/me', {
|
|
92
|
-
headers: {
|
|
93
|
-
'Authorization': `Bearer ${token}`
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
const data = resp.data;
|
|
97
|
-
return {
|
|
98
|
-
userId: data.regUserId,
|
|
99
|
-
regUserId: data.regUserId,
|
|
100
|
-
username: data.username,
|
|
101
|
-
email: data.email,
|
|
102
|
-
role: data.role
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|