@erikey/react 0.4.26 → 0.4.27
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/package.json +2 -1
- package/src/__tests__/auth-client.test.ts +105 -0
- package/src/__tests__/security/localStorage-encryption.test.ts +171 -0
- package/src/auth-client.ts +158 -0
- package/src/dashboard-client.ts +60 -0
- package/src/index.ts +88 -0
- package/src/kv-client.ts +316 -0
- package/src/lib/cross-origin-auth.ts +99 -0
- package/src/stubs/captcha.ts +24 -0
- package/src/stubs/hashes.ts +16 -0
- package/src/stubs/index.ts +17 -0
- package/src/stubs/passkey.ts +12 -0
- package/src/stubs/qr-code.ts +10 -0
- package/src/stubs/query.ts +16 -0
- package/src/stubs/realtime.ts +17 -0
- package/src/stubs/use-sync-external-store.ts +12 -0
- package/src/styles.css +141 -0
- package/src/types.ts +14 -0
- package/src/ui/components/auth/auth-callback.tsx +36 -0
- package/src/ui/components/auth/auth-form.tsx +310 -0
- package/src/ui/components/auth/auth-view.tsx +435 -0
- package/src/ui/components/auth/email-otp-button.tsx +53 -0
- package/src/ui/components/auth/forms/email-otp-form.tsx +312 -0
- package/src/ui/components/auth/forms/email-verification-form.tsx +271 -0
- package/src/ui/components/auth/forms/forgot-password-form.tsx +173 -0
- package/src/ui/components/auth/forms/magic-link-form.tsx +196 -0
- package/src/ui/components/auth/forms/recover-account-form.tsx +143 -0
- package/src/ui/components/auth/forms/reset-password-form.tsx +220 -0
- package/src/ui/components/auth/forms/sign-in-form.tsx +323 -0
- package/src/ui/components/auth/forms/sign-up-form.tsx +820 -0
- package/src/ui/components/auth/forms/two-factor-form.tsx +381 -0
- package/src/ui/components/auth/magic-link-button.tsx +54 -0
- package/src/ui/components/auth/one-tap.tsx +53 -0
- package/src/ui/components/auth/otp-input-group.tsx +65 -0
- package/src/ui/components/auth/passkey-button.tsx +91 -0
- package/src/ui/components/auth/provider-button.tsx +155 -0
- package/src/ui/components/auth/sign-out.tsx +25 -0
- package/src/ui/components/auth/wallet-button.tsx +192 -0
- package/src/ui/components/auth-loading.tsx +21 -0
- package/src/ui/components/captcha/captcha.tsx +91 -0
- package/src/ui/components/captcha/recaptcha-badge.tsx +61 -0
- package/src/ui/components/captcha/recaptcha-v2.tsx +58 -0
- package/src/ui/components/captcha/recaptcha-v3.tsx +73 -0
- package/src/ui/components/email/email-template.tsx +216 -0
- package/src/ui/components/form-error.tsx +27 -0
- package/src/ui/components/password-input.tsx +56 -0
- package/src/ui/components/provider-icons.tsx +404 -0
- package/src/ui/components/redirect-to-sign-in.tsx +16 -0
- package/src/ui/components/redirect-to-sign-up.tsx +16 -0
- package/src/ui/components/signed-in.tsx +20 -0
- package/src/ui/components/signed-out.tsx +20 -0
- package/src/ui/components/ui/alert.tsx +66 -0
- package/src/ui/components/ui/button.tsx +70 -0
- package/src/ui/components/ui/card.tsx +92 -0
- package/src/ui/components/ui/checkbox.tsx +66 -0
- package/src/ui/components/ui/field.tsx +248 -0
- package/src/ui/components/ui/form.tsx +165 -0
- package/src/ui/components/ui/input-otp.tsx +77 -0
- package/src/ui/components/ui/input.tsx +21 -0
- package/src/ui/components/ui/label.tsx +23 -0
- package/src/ui/components/ui/separator.tsx +34 -0
- package/src/ui/components/ui/skeleton.tsx +13 -0
- package/src/ui/components/ui/textarea.tsx +18 -0
- package/src/ui/components/user-avatar.tsx +151 -0
- package/src/ui/hooks/use-auth-data.ts +193 -0
- package/src/ui/hooks/use-authenticate.ts +64 -0
- package/src/ui/hooks/use-captcha.tsx +151 -0
- package/src/ui/hooks/use-hydrated.ts +13 -0
- package/src/ui/hooks/use-lang.ts +32 -0
- package/src/ui/hooks/use-success-transition.ts +41 -0
- package/src/ui/hooks/use-theme.ts +39 -0
- package/src/ui/index.ts +46 -0
- package/src/ui/instantdb.ts +1 -0
- package/src/ui/lib/auth-data-cache.ts +90 -0
- package/src/ui/lib/auth-ui-provider.tsx +769 -0
- package/src/ui/lib/gravatar-utils.ts +58 -0
- package/src/ui/lib/image-utils.ts +55 -0
- package/src/ui/lib/instantdb/model-names.ts +24 -0
- package/src/ui/lib/instantdb/use-instant-options.ts +98 -0
- package/src/ui/lib/instantdb/use-list-accounts.ts +38 -0
- package/src/ui/lib/instantdb/use-list-sessions.ts +53 -0
- package/src/ui/lib/instantdb/use-session.ts +55 -0
- package/src/ui/lib/social-providers.ts +150 -0
- package/src/ui/lib/tanstack/auth-ui-provider-tanstack.tsx +49 -0
- package/src/ui/lib/tanstack/use-tanstack-options.ts +112 -0
- package/src/ui/lib/triplit/model-names.ts +24 -0
- package/src/ui/lib/triplit/use-conditional-query.ts +82 -0
- package/src/ui/lib/triplit/use-list-accounts.ts +31 -0
- package/src/ui/lib/triplit/use-list-sessions.ts +33 -0
- package/src/ui/lib/triplit/use-session.ts +42 -0
- package/src/ui/lib/triplit/use-triplit-hooks.ts +68 -0
- package/src/ui/lib/triplit/use-triplit-token.ts +44 -0
- package/src/ui/lib/utils.ts +119 -0
- package/src/ui/lib/view-paths.ts +61 -0
- package/src/ui/lib/wallet.ts +129 -0
- package/src/ui/localization/admin-error-codes.ts +20 -0
- package/src/ui/localization/anonymous-error-codes.ts +6 -0
- package/src/ui/localization/api-key-error-codes.ts +32 -0
- package/src/ui/localization/auth-localization.ts +865 -0
- package/src/ui/localization/base-error-codes.ts +27 -0
- package/src/ui/localization/captcha-error-codes.ts +17 -0
- package/src/ui/localization/email-otp-error-codes.ts +7 -0
- package/src/ui/localization/generic-oauth-error-codes.ts +3 -0
- package/src/ui/localization/haveibeenpwned-error-codes.ts +4 -0
- package/src/ui/localization/multi-session-error-codes.ts +3 -0
- package/src/ui/localization/organization-error-codes.ts +57 -0
- package/src/ui/localization/passkey-error-codes.ts +10 -0
- package/src/ui/localization/phone-number-error-codes.ts +10 -0
- package/src/ui/localization/stripe-localization.ts +12 -0
- package/src/ui/localization/team-error-codes.ts +12 -0
- package/src/ui/localization/two-factor-error-codes.ts +12 -0
- package/src/ui/localization/username-error-codes.ts +9 -0
- package/src/ui/server.ts +4 -0
- package/src/ui/style.css +146 -0
- package/src/ui/tanstack.ts +1 -0
- package/src/ui/triplit.ts +1 -0
- package/src/ui/types/account-options.ts +35 -0
- package/src/ui/types/additional-fields.ts +21 -0
- package/src/ui/types/any-auth-client.ts +6 -0
- package/src/ui/types/api-key.ts +9 -0
- package/src/ui/types/auth-client.ts +41 -0
- package/src/ui/types/auth-hooks.ts +81 -0
- package/src/ui/types/auth-mutators.ts +21 -0
- package/src/ui/types/avatar-options.ts +29 -0
- package/src/ui/types/captcha-options.ts +32 -0
- package/src/ui/types/captcha-provider.ts +7 -0
- package/src/ui/types/credentials-options.ts +38 -0
- package/src/ui/types/delete-user-options.ts +7 -0
- package/src/ui/types/email-verification-options.ts +7 -0
- package/src/ui/types/fetch-error.ts +6 -0
- package/src/ui/types/generic-oauth-options.ts +16 -0
- package/src/ui/types/gravatar-options.ts +21 -0
- package/src/ui/types/image.ts +7 -0
- package/src/ui/types/invitation.ts +10 -0
- package/src/ui/types/link.ts +7 -0
- package/src/ui/types/organization-options.ts +106 -0
- package/src/ui/types/password-validation.ts +16 -0
- package/src/ui/types/profile.ts +15 -0
- package/src/ui/types/refetch.ts +1 -0
- package/src/ui/types/render-toast.ts +9 -0
- package/src/ui/types/sign-up-options.ts +7 -0
- package/src/ui/types/social-options.ts +16 -0
- package/src/ui/types/team-options.ts +47 -0
package/src/kv-client.ts
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @erikey/react - KV Store Client
|
|
3
|
+
*
|
|
4
|
+
* Erikey-specific key-value storage for end-users.
|
|
5
|
+
* Stores data per-user, scoped to the project.
|
|
6
|
+
*
|
|
7
|
+
* This is NOT part of better-auth - it's an Erikey-specific feature.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
shouldUseBearerAuth,
|
|
12
|
+
getStoredToken,
|
|
13
|
+
} from './lib/cross-origin-auth';
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Types
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
export interface KvClientConfig {
|
|
20
|
+
/**
|
|
21
|
+
* Your Erikey project ID (pk_live_xxx or pk_test_xxx)
|
|
22
|
+
*/
|
|
23
|
+
projectId: string;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Base URL for the auth API
|
|
27
|
+
* @default 'https://auth.erikey.com'
|
|
28
|
+
*/
|
|
29
|
+
baseUrl?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface KvPair {
|
|
33
|
+
key: string;
|
|
34
|
+
value: any;
|
|
35
|
+
createdAt: string;
|
|
36
|
+
updatedAt: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface KvBulkSetInput {
|
|
40
|
+
key: string;
|
|
41
|
+
value: any;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface SetValueResponse {
|
|
45
|
+
success: boolean;
|
|
46
|
+
data?: { key: string; value: any };
|
|
47
|
+
error?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface GetValueResponse {
|
|
51
|
+
success: boolean;
|
|
52
|
+
data?: KvPair;
|
|
53
|
+
error?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface GetValuesResponse {
|
|
57
|
+
success: boolean;
|
|
58
|
+
data?: { kvPairs: KvPair[]; total: number };
|
|
59
|
+
error?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface DeleteValueResponse {
|
|
63
|
+
success: boolean;
|
|
64
|
+
data?: { message: string };
|
|
65
|
+
error?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface DeleteValuesResponse {
|
|
69
|
+
success: boolean;
|
|
70
|
+
data?: { deleted: string[]; failed: Array<{ key: string; error: string }> };
|
|
71
|
+
error?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface SetValuesResponse {
|
|
75
|
+
success: boolean;
|
|
76
|
+
data?: { results: Array<{ key: string; success: boolean }>; total: number };
|
|
77
|
+
error?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ============================================================================
|
|
81
|
+
// KV Client
|
|
82
|
+
// ============================================================================
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Create a KV store client for per-user data storage
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```tsx
|
|
89
|
+
* import { createKvClient } from '@erikey/react';
|
|
90
|
+
*
|
|
91
|
+
* const kv = createKvClient({
|
|
92
|
+
* projectId: 'pk_live_xxx',
|
|
93
|
+
* });
|
|
94
|
+
*
|
|
95
|
+
* // Store a value
|
|
96
|
+
* await kv.setValue('preferences', { theme: 'dark' });
|
|
97
|
+
*
|
|
98
|
+
* // Get a value
|
|
99
|
+
* const { data } = await kv.getValue('preferences');
|
|
100
|
+
*
|
|
101
|
+
* // Get all values
|
|
102
|
+
* const { data: all } = await kv.getValues();
|
|
103
|
+
*
|
|
104
|
+
* // Delete a value
|
|
105
|
+
* await kv.deleteValue('preferences');
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export function createKvClient(config: KvClientConfig) {
|
|
109
|
+
const { projectId, baseUrl = 'https://auth.erikey.com' } = config;
|
|
110
|
+
const useBearerAuth = shouldUseBearerAuth(baseUrl);
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Internal helper for making authenticated requests
|
|
114
|
+
*/
|
|
115
|
+
const fetchWithAuth = async <T>(
|
|
116
|
+
endpoint: string,
|
|
117
|
+
options?: RequestInit
|
|
118
|
+
): Promise<T> => {
|
|
119
|
+
const headers: Record<string, string> = {
|
|
120
|
+
'Content-Type': 'application/json',
|
|
121
|
+
'X-Project-Id': projectId,
|
|
122
|
+
...(options?.headers as Record<string, string> || {}),
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Add Bearer token for cross-origin contexts
|
|
126
|
+
if (useBearerAuth) {
|
|
127
|
+
const token = getStoredToken(projectId);
|
|
128
|
+
if (token) {
|
|
129
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const response = await fetch(`${baseUrl}/api/auth${endpoint}`, {
|
|
134
|
+
...options,
|
|
135
|
+
credentials: useBearerAuth ? 'omit' : 'include',
|
|
136
|
+
headers,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const data = await response.json() as any;
|
|
140
|
+
|
|
141
|
+
if (!response.ok) {
|
|
142
|
+
return {
|
|
143
|
+
success: false,
|
|
144
|
+
error: data?.error?.message || data?.message || 'Request failed',
|
|
145
|
+
} as T;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
success: true,
|
|
150
|
+
data,
|
|
151
|
+
} as T;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
/**
|
|
156
|
+
* Set a single key-value pair
|
|
157
|
+
*/
|
|
158
|
+
setValue: async (key: string, value: any): Promise<SetValueResponse> => {
|
|
159
|
+
return fetchWithAuth<SetValueResponse>(`/key-value/${encodeURIComponent(key)}`, {
|
|
160
|
+
method: 'PUT',
|
|
161
|
+
body: JSON.stringify({ value }),
|
|
162
|
+
});
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get a single key-value pair
|
|
167
|
+
*/
|
|
168
|
+
getValue: async (key: string): Promise<GetValueResponse> => {
|
|
169
|
+
const result = await fetchWithAuth<{ success: boolean; data?: any; error?: string }>(
|
|
170
|
+
`/key-value/${encodeURIComponent(key)}`,
|
|
171
|
+
{ method: 'GET' }
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
if (!result.success) {
|
|
175
|
+
return { success: false, error: result.error };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
success: true,
|
|
180
|
+
data: {
|
|
181
|
+
key: result.data.key,
|
|
182
|
+
value: result.data.value,
|
|
183
|
+
createdAt: result.data.createdAt,
|
|
184
|
+
updatedAt: result.data.updatedAt,
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Get all key-value pairs for the authenticated user
|
|
191
|
+
*/
|
|
192
|
+
getValues: async (): Promise<GetValuesResponse> => {
|
|
193
|
+
const result = await fetchWithAuth<{ success: boolean; data?: any; error?: string }>(
|
|
194
|
+
'/key-value',
|
|
195
|
+
{ method: 'GET' }
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
if (!result.success) {
|
|
199
|
+
return { success: false, error: result.error };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
success: true,
|
|
204
|
+
data: {
|
|
205
|
+
kvPairs: result.data.kvPairs,
|
|
206
|
+
total: result.data.total,
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Delete a single key-value pair
|
|
213
|
+
*/
|
|
214
|
+
deleteValue: async (key: string): Promise<DeleteValueResponse> => {
|
|
215
|
+
const result = await fetchWithAuth<{ success: boolean; data?: any; error?: string }>(
|
|
216
|
+
`/key-value/${encodeURIComponent(key)}`,
|
|
217
|
+
{ method: 'DELETE' }
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
if (!result.success) {
|
|
221
|
+
return { success: false, error: result.error };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
success: true,
|
|
226
|
+
data: {
|
|
227
|
+
message: result.data.message || 'KV pair deleted',
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Delete multiple key-value pairs
|
|
234
|
+
*/
|
|
235
|
+
deleteValues: async (keys: string[]): Promise<DeleteValuesResponse> => {
|
|
236
|
+
if (keys.length === 0) {
|
|
237
|
+
return {
|
|
238
|
+
success: true,
|
|
239
|
+
data: {
|
|
240
|
+
deleted: [],
|
|
241
|
+
failed: [],
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const results = await Promise.allSettled(
|
|
247
|
+
keys.map((key) =>
|
|
248
|
+
fetchWithAuth<{ success: boolean; error?: string }>(
|
|
249
|
+
`/key-value/${encodeURIComponent(key)}`,
|
|
250
|
+
{ method: 'DELETE' }
|
|
251
|
+
).then((result) => ({ key, result }))
|
|
252
|
+
)
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
const deleted: string[] = [];
|
|
256
|
+
const failed: Array<{ key: string; error: string }> = [];
|
|
257
|
+
|
|
258
|
+
results.forEach((result, index) => {
|
|
259
|
+
if (result.status === 'fulfilled') {
|
|
260
|
+
const { key, result: deleteResult } = result.value;
|
|
261
|
+
if (deleteResult.success) {
|
|
262
|
+
deleted.push(key);
|
|
263
|
+
} else {
|
|
264
|
+
failed.push({
|
|
265
|
+
key,
|
|
266
|
+
error: deleteResult.error || 'Delete failed',
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
failed.push({
|
|
271
|
+
key: keys[index],
|
|
272
|
+
error: result.reason?.message || 'Request failed',
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
success: failed.length === 0,
|
|
279
|
+
data: {
|
|
280
|
+
deleted,
|
|
281
|
+
failed,
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Set multiple key-value pairs in bulk (max 100 pairs)
|
|
288
|
+
*/
|
|
289
|
+
setValues: async (kvPairs: KvBulkSetInput[]): Promise<SetValuesResponse> => {
|
|
290
|
+
const result = await fetchWithAuth<{ success: boolean; data?: any; error?: string }>(
|
|
291
|
+
'/key-value/bulk',
|
|
292
|
+
{
|
|
293
|
+
method: 'POST',
|
|
294
|
+
body: JSON.stringify({ kvPairs }),
|
|
295
|
+
}
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
if (!result.success) {
|
|
299
|
+
return { success: false, error: result.error };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
success: true,
|
|
304
|
+
data: {
|
|
305
|
+
results: result.data.results,
|
|
306
|
+
total: result.data.total,
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Type helper for inferring the KV client type
|
|
315
|
+
*/
|
|
316
|
+
export type KvClient = ReturnType<typeof createKvClient>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-origin authentication utilities
|
|
3
|
+
*
|
|
4
|
+
* Handles authentication in cross-origin contexts where cookies don't work:
|
|
5
|
+
* - Sandpack preview iframes
|
|
6
|
+
* - Deployed customer sites (e.g., mysite.com -> auth.erikey.com)
|
|
7
|
+
*
|
|
8
|
+
* Uses localStorage to store bearer tokens as a fallback.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Types
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
export interface StoredSession {
|
|
16
|
+
id: string;
|
|
17
|
+
token: string;
|
|
18
|
+
expiresAt: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Detection
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Detect if we should use Bearer token authentication
|
|
27
|
+
*
|
|
28
|
+
* Returns true when the current origin differs from the auth API origin,
|
|
29
|
+
* meaning cookies won't work and we need Bearer tokens.
|
|
30
|
+
*/
|
|
31
|
+
export function shouldUseBearerAuth(authApiUrl: string): boolean {
|
|
32
|
+
if (typeof window === 'undefined') {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const currentOrigin = window.location.origin;
|
|
38
|
+
const authOrigin = new URL(authApiUrl).origin;
|
|
39
|
+
return currentOrigin !== authOrigin;
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Storage
|
|
47
|
+
// ============================================================================
|
|
48
|
+
|
|
49
|
+
function getStorageKey(projectId: string): string {
|
|
50
|
+
return `erikey.session.${projectId}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Store session token in localStorage for cross-origin contexts
|
|
55
|
+
*/
|
|
56
|
+
export function storeToken(projectId: string, session: StoredSession): void {
|
|
57
|
+
if (typeof window === 'undefined') return;
|
|
58
|
+
|
|
59
|
+
const key = getStorageKey(projectId);
|
|
60
|
+
localStorage.setItem(key, JSON.stringify(session));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get stored token from localStorage
|
|
65
|
+
* Returns null if not found or expired
|
|
66
|
+
*/
|
|
67
|
+
export function getStoredToken(projectId: string): string | null {
|
|
68
|
+
if (typeof window === 'undefined') return null;
|
|
69
|
+
|
|
70
|
+
const key = getStorageKey(projectId);
|
|
71
|
+
const stored = localStorage.getItem(key);
|
|
72
|
+
|
|
73
|
+
if (!stored) return null;
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const session: StoredSession = JSON.parse(stored);
|
|
77
|
+
|
|
78
|
+
// Check if expired
|
|
79
|
+
if (new Date(session.expiresAt) < new Date()) {
|
|
80
|
+
localStorage.removeItem(key);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return session.token;
|
|
85
|
+
} catch {
|
|
86
|
+
localStorage.removeItem(key);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Clear stored token from localStorage
|
|
93
|
+
*/
|
|
94
|
+
export function clearToken(projectId: string): void {
|
|
95
|
+
if (typeof window === 'undefined') return;
|
|
96
|
+
|
|
97
|
+
const key = getStorageKey(projectId);
|
|
98
|
+
localStorage.removeItem(key);
|
|
99
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Captcha Provider Stubs
|
|
3
|
+
*
|
|
4
|
+
* No-op implementations for captcha providers. These are optional features
|
|
5
|
+
* that most users won't need. If real captcha is required, the user should
|
|
6
|
+
* configure it separately in their application.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// @wojtekmaj/react-recaptcha-v3
|
|
10
|
+
export const GoogleReCaptchaProvider = ({ children }: { children?: React.ReactNode }) => children;
|
|
11
|
+
export const useGoogleReCaptcha = () => ({ executeRecaptcha: null });
|
|
12
|
+
|
|
13
|
+
// @captchafox/react
|
|
14
|
+
export const CaptchaFox = () => null;
|
|
15
|
+
|
|
16
|
+
// @hcaptcha/react-hcaptcha
|
|
17
|
+
export const HCaptcha = () => null;
|
|
18
|
+
export default HCaptcha;
|
|
19
|
+
|
|
20
|
+
// @marsidev/react-turnstile
|
|
21
|
+
export const Turnstile = () => null;
|
|
22
|
+
|
|
23
|
+
// react-google-recaptcha
|
|
24
|
+
export const ReCAPTCHA = () => null;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Noble Hashes Stubs
|
|
3
|
+
*
|
|
4
|
+
* No-op implementations for @noble/hashes.
|
|
5
|
+
* Used for cryptographic operations in passkey/WebAuthn flows.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// @noble/hashes
|
|
9
|
+
export const sha256 = () => new Uint8Array(32);
|
|
10
|
+
|
|
11
|
+
// @noble/hashes/sha2
|
|
12
|
+
export { sha256 as sha256Hash };
|
|
13
|
+
|
|
14
|
+
// @noble/hashes/utils
|
|
15
|
+
export const bytesToHex = (_bytes: Uint8Array) => '';
|
|
16
|
+
export const hexToBytes = (_hex: string) => new Uint8Array(0);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stubs for Optional Dependencies
|
|
3
|
+
*
|
|
4
|
+
* These provide no-op implementations for optional features like captcha providers,
|
|
5
|
+
* passkey support, QR codes, etc. This allows the SDK to be fully self-contained
|
|
6
|
+
* without requiring consumers to install packages they don't use.
|
|
7
|
+
*
|
|
8
|
+
* If a user wants to use a real implementation, they can configure it in their app.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Re-export all stubs
|
|
12
|
+
export * from './captcha';
|
|
13
|
+
export * from './passkey';
|
|
14
|
+
export * from './qr-code';
|
|
15
|
+
export * from './hashes';
|
|
16
|
+
export * from './query';
|
|
17
|
+
export * from './realtime';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Passkey Stubs
|
|
3
|
+
*
|
|
4
|
+
* No-op implementations for @better-auth/passkey.
|
|
5
|
+
* Passkey support is optional and requires WebAuthn-capable browsers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// @better-auth/passkey
|
|
9
|
+
export const passkey = () => ({});
|
|
10
|
+
|
|
11
|
+
// @better-auth/passkey/client
|
|
12
|
+
export const passkeyClient = () => ({});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query Library Stubs
|
|
3
|
+
*
|
|
4
|
+
* No-op implementations for optional query/state management libraries.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// @tanstack/react-query
|
|
8
|
+
export const QueryClient = class {
|
|
9
|
+
constructor() {}
|
|
10
|
+
};
|
|
11
|
+
export const QueryClientProvider = ({ children }: { children?: React.ReactNode }) => children;
|
|
12
|
+
export const useQuery = () => ({ data: undefined, isLoading: false, error: null });
|
|
13
|
+
export const useMutation = () => ({ mutate: () => {}, isLoading: false, error: null });
|
|
14
|
+
|
|
15
|
+
// @daveyplate/better-auth-tanstack
|
|
16
|
+
export const createAuthTanstack = () => ({});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Realtime Database Stubs
|
|
3
|
+
*
|
|
4
|
+
* No-op implementations for optional realtime database libraries.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// @instantdb/react
|
|
8
|
+
export const init = () => ({});
|
|
9
|
+
export const useQuery = () => ({ data: undefined, isLoading: false, error: null });
|
|
10
|
+
|
|
11
|
+
// @triplit/client
|
|
12
|
+
export const TriplitClient = class {
|
|
13
|
+
constructor() {}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// @triplit/react
|
|
17
|
+
export const useTriplit = () => ({ client: null });
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* use-sync-external-store stub
|
|
3
|
+
*
|
|
4
|
+
* Redirects to React 18's built-in useSyncExternalStore.
|
|
5
|
+
* The npm package is a CJS polyfill that fails in browser ESM.
|
|
6
|
+
* Since we require React 18+, we use the native implementation.
|
|
7
|
+
*/
|
|
8
|
+
import { useSyncExternalStore } from 'react';
|
|
9
|
+
|
|
10
|
+
export { useSyncExternalStore };
|
|
11
|
+
export { useSyncExternalStore as useSyncExternalStoreWithSelector };
|
|
12
|
+
export default useSyncExternalStore;
|
package/src/styles.css
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
* Animation CSS for SDK components
|
|
7
|
+
*
|
|
8
|
+
* These styles are bundled with the SDK to ensure they work regardless of
|
|
9
|
+
* consumer's Tailwind configuration. Placed AFTER @tailwind utilities so
|
|
10
|
+
* these styles take precedence over purged Tailwind output.
|
|
11
|
+
*
|
|
12
|
+
* This solves the problem where consumer's Tailwind purges animation classes
|
|
13
|
+
* because they only exist in the SDK's minified bundle.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/* Keyframes for enter/exit animations (from tailwindcss-animate) */
|
|
17
|
+
@keyframes enter {
|
|
18
|
+
from {
|
|
19
|
+
opacity: var(--tw-enter-opacity, 1);
|
|
20
|
+
transform: translate3d(var(--tw-enter-translate-x, 0), var(--tw-enter-translate-y, 0), 0)
|
|
21
|
+
scale3d(var(--tw-enter-scale, 1), var(--tw-enter-scale, 1), var(--tw-enter-scale, 1))
|
|
22
|
+
rotate(var(--tw-enter-rotate, 0));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@keyframes exit {
|
|
27
|
+
to {
|
|
28
|
+
opacity: var(--tw-exit-opacity, 1);
|
|
29
|
+
transform: translate3d(var(--tw-exit-translate-x, 0), var(--tw-exit-translate-y, 0), 0)
|
|
30
|
+
scale3d(var(--tw-exit-scale, 1), var(--tw-exit-scale, 1), var(--tw-exit-scale, 1))
|
|
31
|
+
rotate(var(--tw-exit-rotate, 0));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Animation base classes */
|
|
36
|
+
.animate-in {
|
|
37
|
+
animation-name: enter;
|
|
38
|
+
animation-duration: 150ms;
|
|
39
|
+
--tw-enter-opacity: initial;
|
|
40
|
+
--tw-enter-scale: initial;
|
|
41
|
+
--tw-enter-rotate: initial;
|
|
42
|
+
--tw-enter-translate-x: initial;
|
|
43
|
+
--tw-enter-translate-y: initial;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.animate-out {
|
|
47
|
+
animation-name: exit;
|
|
48
|
+
animation-duration: 150ms;
|
|
49
|
+
--tw-exit-opacity: initial;
|
|
50
|
+
--tw-exit-scale: initial;
|
|
51
|
+
--tw-exit-rotate: initial;
|
|
52
|
+
--tw-exit-translate-x: initial;
|
|
53
|
+
--tw-exit-translate-y: initial;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Opacity variants */
|
|
57
|
+
.fade-in-0 { --tw-enter-opacity: 0; }
|
|
58
|
+
.fade-out-0 { --tw-exit-opacity: 0; }
|
|
59
|
+
|
|
60
|
+
/* Scale variants */
|
|
61
|
+
.zoom-in-95 { --tw-enter-scale: 0.95; }
|
|
62
|
+
.zoom-out-95 { --tw-exit-scale: 0.95; }
|
|
63
|
+
|
|
64
|
+
/* Slide variants */
|
|
65
|
+
.slide-in-from-top-2 { --tw-enter-translate-y: -0.5rem; }
|
|
66
|
+
.slide-in-from-bottom-2 { --tw-enter-translate-y: 0.5rem; }
|
|
67
|
+
.slide-in-from-left-2 { --tw-enter-translate-x: -0.5rem; }
|
|
68
|
+
.slide-in-from-right-2 { --tw-enter-translate-x: 0.5rem; }
|
|
69
|
+
|
|
70
|
+
/* Data attribute variants for Radix UI components */
|
|
71
|
+
[data-state="open"].animate-in {
|
|
72
|
+
animation-name: enter;
|
|
73
|
+
animation-duration: 150ms;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
[data-state="closed"].animate-out {
|
|
77
|
+
animation-name: exit;
|
|
78
|
+
animation-duration: 150ms;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
[data-state="open"].fade-in-0,
|
|
82
|
+
.data-\[state\=open\]\:fade-in-0[data-state="open"] {
|
|
83
|
+
--tw-enter-opacity: 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
[data-state="closed"].fade-out-0,
|
|
87
|
+
.data-\[state\=closed\]\:fade-out-0[data-state="closed"] {
|
|
88
|
+
--tw-exit-opacity: 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
[data-state="open"].zoom-in-95,
|
|
92
|
+
.data-\[state\=open\]\:zoom-in-95[data-state="open"] {
|
|
93
|
+
--tw-enter-scale: 0.95;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
[data-state="closed"].zoom-out-95,
|
|
97
|
+
.data-\[state\=closed\]\:zoom-out-95[data-state="closed"] {
|
|
98
|
+
--tw-exit-scale: 0.95;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Slide variants with data-side for dropdown positioning */
|
|
102
|
+
[data-side="bottom"].slide-in-from-top-2,
|
|
103
|
+
.data-\[side\=bottom\]\:slide-in-from-top-2[data-side="bottom"] {
|
|
104
|
+
--tw-enter-translate-y: -0.5rem;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
[data-side="top"].slide-in-from-bottom-2,
|
|
108
|
+
.data-\[side\=top\]\:slide-in-from-bottom-2[data-side="top"] {
|
|
109
|
+
--tw-enter-translate-y: 0.5rem;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
[data-side="left"].slide-in-from-right-2,
|
|
113
|
+
.data-\[side\=left\]\:slide-in-from-right-2[data-side="left"] {
|
|
114
|
+
--tw-enter-translate-x: 0.5rem;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
[data-side="right"].slide-in-from-left-2,
|
|
118
|
+
.data-\[side\=right\]\:slide-in-from-left-2[data-side="right"] {
|
|
119
|
+
--tw-enter-translate-x: -0.5rem;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Tailwind's data attribute variant syntax support */
|
|
123
|
+
.data-\[state\=open\]\:animate-in[data-state="open"] {
|
|
124
|
+
animation-name: enter;
|
|
125
|
+
animation-duration: 150ms;
|
|
126
|
+
--tw-enter-opacity: initial;
|
|
127
|
+
--tw-enter-scale: initial;
|
|
128
|
+
--tw-enter-rotate: initial;
|
|
129
|
+
--tw-enter-translate-x: initial;
|
|
130
|
+
--tw-enter-translate-y: initial;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.data-\[state\=closed\]\:animate-out[data-state="closed"] {
|
|
134
|
+
animation-name: exit;
|
|
135
|
+
animation-duration: 150ms;
|
|
136
|
+
--tw-exit-opacity: initial;
|
|
137
|
+
--tw-exit-scale: initial;
|
|
138
|
+
--tw-exit-rotate: initial;
|
|
139
|
+
--tw-exit-translate-x: initial;
|
|
140
|
+
--tw-exit-translate-y: initial;
|
|
141
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @erikey/react types
|
|
3
|
+
*
|
|
4
|
+
* Re-exports shared types from @erikey/core for dashboard client compatibility.
|
|
5
|
+
* For end-user auth types, see auth-client.ts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Re-export dashboard-related types from core
|
|
9
|
+
export type {
|
|
10
|
+
SessionData,
|
|
11
|
+
Organization,
|
|
12
|
+
APIKey,
|
|
13
|
+
Permission,
|
|
14
|
+
} from '@erikey/core/types';
|