local-first-auth-import-export 0.1.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.
@@ -0,0 +1,400 @@
1
+ import React$1 from 'react';
2
+
3
+ /**
4
+ * Local First Auth Import/Export Types
5
+ * Compatible with the Local First Auth Specification
6
+ */
7
+ interface SocialLink {
8
+ platform: SocialPlatform;
9
+ handle: string;
10
+ }
11
+ type SocialPlatform = 'INSTAGRAM' | 'X' | 'BLUESKY' | 'LINKEDIN' | 'YOUTUBE' | 'SPOTIFY' | 'TIKTOK' | 'SNAPCHAT' | 'GITHUB' | 'FACEBOOK' | 'REDDIT' | 'DISCORD' | 'TWITCH' | 'TELEGRAM' | 'PINTEREST' | 'TUMBLR' | 'SOUNDCLOUD' | 'BANDCAMP' | 'PATREON' | 'KO_FI' | 'MASTODON' | 'WEBSITE' | 'EMAIL';
12
+ interface Profile {
13
+ did: string;
14
+ name: string;
15
+ socials?: SocialLink[];
16
+ avatar?: string | null;
17
+ }
18
+ interface ProfileKeys {
19
+ did: string;
20
+ privateKey: string;
21
+ publicKey: string;
22
+ }
23
+ interface StoredProfile {
24
+ did: string;
25
+ name: string;
26
+ socials?: SocialLink[];
27
+ avatar?: string | null;
28
+ }
29
+ /**
30
+ * The magic string identifying a Local First Auth export file.
31
+ * Present as the `type` field so a consumer can cheaply confirm the format.
32
+ */
33
+ declare const EXPORT_FILE_TYPE: "local-first-auth:export";
34
+ /**
35
+ * The current export file schema version.
36
+ */
37
+ declare const EXPORT_FILE_VERSION: 1;
38
+ /**
39
+ * A Local First Auth profile export.
40
+ *
41
+ * This is the exact shape of the exported `.json` file. It is self-identifying
42
+ * (`type` + `version`) and internally consistent: `did` and `publicKey` MUST be
43
+ * re-derivable from `privateKey`, which lets any consumer validate the file
44
+ * without trusting its contents.
45
+ *
46
+ * SECURITY: this contains a plaintext private key. Anyone who has it can act as
47
+ * the user. Never transmit or log it.
48
+ */
49
+ interface ExportedProfile {
50
+ /** Fixed magic string identifying this as a Local First Auth export */
51
+ type: typeof EXPORT_FILE_TYPE;
52
+ /** Schema version, for forward compatibility */
53
+ version: typeof EXPORT_FILE_VERSION;
54
+ /** The user's DID (did:key:z...) */
55
+ did: string;
56
+ /** base64-encoded 32-byte Ed25519 public key */
57
+ publicKey: string;
58
+ /** base64-encoded 64-byte Ed25519 secret key */
59
+ privateKey: string;
60
+ /** Display name */
61
+ name?: string;
62
+ /** Social links */
63
+ socials?: SocialLink[];
64
+ /** Avatar as a data: URI */
65
+ avatar?: string | null;
66
+ /** ISO 8601 timestamp of when the file was exported */
67
+ exportedAt?: string;
68
+ }
69
+ /**
70
+ * Result of validating an untrusted value against the export file format.
71
+ */
72
+ interface ValidationResult {
73
+ valid: boolean;
74
+ errors: string[];
75
+ profile?: ExportedProfile;
76
+ }
77
+ /**
78
+ * Options for importing a profile.
79
+ */
80
+ interface ImportOptions {
81
+ /**
82
+ * Replace an existing profile with a different DID.
83
+ * Defaults to false — importing over a different identity throws unless this
84
+ * is explicitly set, to guard against accidentally destroying the user's keys.
85
+ */
86
+ overwrite?: boolean;
87
+ }
88
+ interface LocalFirstAuth {
89
+ getProfileDetails(): Promise<string>;
90
+ getAvatar(): Promise<string | null>;
91
+ getAppDetails(): AppDetails;
92
+ requestPermission(permission: string): Promise<boolean>;
93
+ close(): void;
94
+ }
95
+ interface AppDetails {
96
+ name: string;
97
+ version: string;
98
+ platform: 'ios' | 'android' | 'browser';
99
+ supportedPermissions: string[];
100
+ }
101
+ interface JWTHeader {
102
+ alg: 'EdDSA';
103
+ typ: 'JWT';
104
+ }
105
+ interface JWTPayload {
106
+ iss: string;
107
+ aud: string;
108
+ iat: number;
109
+ exp: number;
110
+ type: string;
111
+ data?: any;
112
+ }
113
+ interface CustomStyles {
114
+ primaryColor?: string;
115
+ backgroundColor?: string;
116
+ textColor?: string;
117
+ borderRadius?: string;
118
+ fontFamily?: string;
119
+ buttonRadius?: string;
120
+ inputRadius?: string;
121
+ inputTextColor?: string;
122
+ inputBackgroundColor?: string;
123
+ /** Scale factor for button press animation on mobile (0-1). Default: 0.95 */
124
+ mobileButtonPressScale?: number;
125
+ /** Color for mobile tap highlight. Default: 'transparent' */
126
+ mobileTapHighlightColor?: string;
127
+ /** Enable safe area insets for iOS notch / Android nav bars. Default: true */
128
+ useSafeAreaInsets?: boolean;
129
+ }
130
+ interface ExportProfileProps {
131
+ customStyles?: CustomStyles;
132
+ /** Called after the user downloads their profile backup */
133
+ onExport?: (profile: ExportedProfile) => void;
134
+ /** Rendered when no profile exists. Defaults to a short message. */
135
+ emptyState?: React.ReactNode;
136
+ }
137
+ interface ImportProfileProps {
138
+ customStyles?: CustomStyles;
139
+ /** Called with the imported profile on success */
140
+ onComplete?: (profile: Profile) => void;
141
+ onBack?: () => void;
142
+ }
143
+ interface ImportExportProps {
144
+ customStyles?: CustomStyles;
145
+ onComplete?: (profile: Profile) => void;
146
+ onExport?: (profile: ExportedProfile) => void;
147
+ /** Which tab is shown first. Default: 'import' */
148
+ defaultTab?: 'import' | 'export';
149
+ /** Hide the import tab */
150
+ skipImport?: boolean;
151
+ /** Hide the export tab */
152
+ skipExport?: boolean;
153
+ }
154
+
155
+ /**
156
+ * ImportProfile
157
+ * Lets the user restore an existing identity by uploading (or pasting) an
158
+ * exported profile file. No new keypair is generated — the DID is preserved.
159
+ */
160
+
161
+ declare function ImportProfile({ customStyles, onComplete, onBack }: ImportProfileProps): React$1.JSX.Element;
162
+
163
+ /**
164
+ * ExportProfile
165
+ * Lets the user download their profile (DID + keypair) as a portable JSON file.
166
+ */
167
+
168
+ declare function ExportProfile({ customStyles, onExport, emptyState }: ExportProfileProps): React$1.JSX.Element;
169
+
170
+ /**
171
+ * ImportExport
172
+ * Tabbed wrapper over <ImportProfile /> and <ExportProfile />.
173
+ */
174
+
175
+ declare function ImportExport({ customStyles, onComplete, onExport, defaultTab, skipImport, skipExport }: ImportExportProps): React$1.JSX.Element | null;
176
+
177
+ /**
178
+ * React hook for accessing the current profile
179
+ */
180
+
181
+ /**
182
+ * Hook to get the current profile.
183
+ *
184
+ * Updates when the profile changes in another tab (via the `storage` event) and
185
+ * exposes a `refresh()` for same-tab changes (import/clear), which the `storage`
186
+ * event does not fire for.
187
+ */
188
+ declare function useProfile(): {
189
+ profile: Profile | null;
190
+ refresh: () => void;
191
+ };
192
+
193
+ /**
194
+ * Export the current profile (including its keypair) as a portable JSON file.
195
+ *
196
+ * SECURITY: the exported file contains a plaintext private key. Anyone who has
197
+ * it can act as the user. Surface a clear warning in your UI, and never send the
198
+ * file to a server or log it.
199
+ */
200
+
201
+ /**
202
+ * Build the exportable representation of the current profile.
203
+ *
204
+ * The DID and public key are re-derived from the stored private key rather than
205
+ * read from storage, so the resulting file is always internally consistent.
206
+ *
207
+ * @returns The ExportedProfile, or null if there is no profile to export.
208
+ */
209
+ declare function exportProfile(): ExportedProfile | null;
210
+ /**
211
+ * Serialize the current profile to a JSON string.
212
+ *
213
+ * @param pretty - Pretty-print the JSON (default true)
214
+ * @returns The JSON string, or null if there is no profile to export.
215
+ */
216
+ declare function exportProfileToJSON(pretty?: boolean): string | null;
217
+ /**
218
+ * Build a default filename for a profile backup, e.g.
219
+ * `local-first-auth-profile-z6MkhaXg.json`
220
+ */
221
+ declare function defaultExportFilename(did: string): string;
222
+ /**
223
+ * Download the current profile as a .json file in the browser.
224
+ *
225
+ * @param filename - Optional filename. Defaults to `local-first-auth-profile-<key>.json`
226
+ * @returns The exported profile, or null if there was nothing to export.
227
+ */
228
+ declare function downloadProfile(filename?: string): ExportedProfile | null;
229
+
230
+ /**
231
+ * Import an existing profile (public + private keypair) from an export file.
232
+ *
233
+ * Importing adopts an identity the user already has — no new keypair is minted.
234
+ * The private key is the source of truth: the DID and public key are always
235
+ * re-derived from it, never trusted from the file.
236
+ */
237
+
238
+ /**
239
+ * Validate an untrusted value against the Local First Auth export file format.
240
+ *
241
+ * Checks, in order:
242
+ * 1. It parses as an object (a string is JSON.parse'd first)
243
+ * 2. `type` is the `local-first-auth:export` magic string
244
+ * 3. `version` is a supported schema version
245
+ * 4. `privateKey` is base64 that decodes to exactly 64 bytes
246
+ * 5. `did` / `publicKey` are re-derivable from `privateKey` (consistency check)
247
+ * 6. `socials`, if present, is a well-formed array
248
+ *
249
+ * This never throws — inspect `valid` and `errors`.
250
+ */
251
+ declare function validateExportedProfile(input: unknown): ValidationResult;
252
+ /**
253
+ * Normalize any supported import input into a valid ExportedProfile.
254
+ *
255
+ * Accepts:
256
+ * - an `ExportedProfile` object
257
+ * - a JSON string of an export file
258
+ * - a bare base64 private key (convenience — the envelope is synthesized)
259
+ *
260
+ * @throws Error with a human-readable message if the input is unusable.
261
+ */
262
+ declare function parseExportedProfile(input: string | ExportedProfile): ExportedProfile;
263
+ /**
264
+ * Import a profile, adopting its existing identity.
265
+ *
266
+ * Saves the profile and private key to storage (the same keys local-first-auth
267
+ * uses) and injects `window.localFirstAuth`, so the app is immediately
268
+ * authenticated as the imported user.
269
+ *
270
+ * If a profile with a *different* DID already exists, this throws unless
271
+ * `overwrite: true` is passed — importing would otherwise silently destroy the
272
+ * user's existing keys.
273
+ *
274
+ * @param input - An export file (object or JSON string), or a bare private key
275
+ * @param options - `{ overwrite }` to replace an existing, different identity
276
+ */
277
+ declare function importProfile(input: string | ExportedProfile, options?: ImportOptions): Promise<Profile>;
278
+ /**
279
+ * Import a profile from a File (e.g. from an <input type="file"> or a drop).
280
+ */
281
+ declare function importProfileFromFile(file: File, options?: ImportOptions): Promise<Profile>;
282
+
283
+ /**
284
+ * Cryptographic utilities for DID derivation and JWT signing.
285
+ *
286
+ * Mirrors local-first-auth's crypto layer so that keys/DIDs/JWTs produced here
287
+ * are byte-for-byte compatible, and adds the primitives needed to reconstruct an
288
+ * identity from an existing private key (rather than generating a new one).
289
+ */
290
+
291
+ /**
292
+ * Create a did:key DID from an Ed25519 public key
293
+ *
294
+ * @param publicKey - The Ed25519 public key as Uint8Array (32 bytes)
295
+ * @returns The did:key formatted DID string
296
+ */
297
+ declare function createDidFromPublicKey(publicKey: Uint8Array): string;
298
+ /**
299
+ * Validate that a string is a usable Ed25519 private key.
300
+ *
301
+ * A valid key is base64 that decodes to exactly 64 bytes (the Ed25519 secret
302
+ * key: a 32-byte seed followed by the 32-byte public key).
303
+ */
304
+ declare function validatePrivateKey(privateKey: unknown): {
305
+ valid: boolean;
306
+ error?: string;
307
+ };
308
+ /**
309
+ * Reconstruct a full identity (DID + public key) from an existing private key.
310
+ *
311
+ * This is the core of "import": an Ed25519 secret key already contains its
312
+ * public key in the final 32 bytes, so the private key alone is enough to
313
+ * recover the complete identity. No new keypair is generated.
314
+ *
315
+ * @param privateKey - base64-encoded 64-byte Ed25519 secret key
316
+ * @returns The derived { did, publicKey, privateKey }
317
+ * @throws If the private key is malformed
318
+ */
319
+ declare function deriveKeysFromPrivateKey(privateKey: string): ProfileKeys;
320
+ /**
321
+ * Decode a base64-encoded public key back into bytes (useful for verifyJWT)
322
+ */
323
+ declare function decodePublicKey(publicKey: string): Uint8Array;
324
+ /**
325
+ * Create and sign a JWT using Ed25519
326
+ *
327
+ * @param payload - JWT payload containing claims
328
+ * @param privateKey - base64-encoded 64-byte Ed25519 secret key
329
+ * @returns Signed JWT string
330
+ */
331
+ declare function createJWT(payload: JWTPayload, privateKey: string): Promise<string>;
332
+ /**
333
+ * Decode a JWT (for debugging - does NOT verify the signature)
334
+ */
335
+ declare function decodeJWT(jwt: string): {
336
+ header: JWTHeader;
337
+ payload: JWTPayload;
338
+ signature: string;
339
+ };
340
+ /**
341
+ * Verify a JWT signature against an Ed25519 public key
342
+ *
343
+ * @param jwt - The JWT string to verify
344
+ * @param publicKey - The Ed25519 public key (32 bytes)
345
+ */
346
+ declare function verifyJWT(jwt: string, publicKey: Uint8Array): boolean;
347
+
348
+ /**
349
+ * LocalStorage wrapper for profile storage.
350
+ *
351
+ * IMPORTANT: these storage keys are intentionally identical to the ones used by
352
+ * the `local-first-auth` package. That is what makes the two interoperable — a
353
+ * profile created by local-first-auth's <Onboarding> can be exported here, and a
354
+ * profile imported here is immediately usable by local-first-auth's hooks and
355
+ * window API. Do not namespace these differently.
356
+ */
357
+
358
+ declare const STORAGE_KEYS: {
359
+ readonly PROFILE: "local-first-auth:profile";
360
+ readonly PRIVATE_KEY: "local-first-auth:privateKey";
361
+ };
362
+ /**
363
+ * Get private key from LocalStorage
364
+ */
365
+ declare function getPrivateKey(): string | null;
366
+ /**
367
+ * Clear all stored profile data
368
+ */
369
+ declare function clearProfile(): void;
370
+ /**
371
+ * Check if a profile exists in storage
372
+ */
373
+ declare function hasProfile(): boolean;
374
+ /**
375
+ * Get the current profile (requires both the profile and its private key)
376
+ */
377
+ declare function getCurrentProfile(): Profile | null;
378
+
379
+ /**
380
+ * Local First Auth API implementation.
381
+ *
382
+ * Implements the LocalFirstAuth interface from the Local First Auth Specification
383
+ * and injects it at window.localFirstAuth. Mirrors local-first-auth's api.ts so an
384
+ * imported profile behaves exactly like one created through onboarding.
385
+ */
386
+
387
+ /**
388
+ * Inject the Local First Auth API into the window object
389
+ */
390
+ declare function injectLocalFirstAuthAPI(): void;
391
+ /**
392
+ * Remove the Local First Auth API from the window object
393
+ */
394
+ declare function removeLocalFirstAuthAPI(): void;
395
+ /**
396
+ * Check if the Local First Auth API is available
397
+ */
398
+ declare function hasLocalFirstAuthAPI(): boolean;
399
+
400
+ export { type AppDetails, type CustomStyles, EXPORT_FILE_TYPE, EXPORT_FILE_VERSION, ExportProfile, type ExportProfileProps, type ExportedProfile, ImportExport, type ImportExportProps, type ImportOptions, ImportProfile, type ImportProfileProps, type JWTHeader, type JWTPayload, type LocalFirstAuth, type Profile, type ProfileKeys, STORAGE_KEYS, type SocialLink, type SocialPlatform, type StoredProfile, type ValidationResult, clearProfile, createDidFromPublicKey, createJWT, decodeJWT, decodePublicKey, defaultExportFilename, deriveKeysFromPrivateKey, downloadProfile, exportProfile, exportProfileToJSON, getCurrentProfile, getPrivateKey, hasLocalFirstAuthAPI, hasProfile, importProfile, importProfileFromFile, injectLocalFirstAuthAPI, parseExportedProfile, removeLocalFirstAuthAPI, useProfile, validateExportedProfile, validatePrivateKey, verifyJWT };