svelte-firekit 0.0.24 → 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.
- package/README.md +445 -213
- package/dist/components/Collection.svelte +150 -0
- package/dist/components/Collection.svelte.d.ts +27 -0
- package/dist/components/Ddoc.svelte +131 -0
- package/dist/components/Ddoc.svelte.d.ts +28 -0
- package/dist/components/Node.svelte +97 -0
- package/dist/components/Node.svelte.d.ts +23 -0
- package/dist/components/auth-guard.svelte +89 -0
- package/dist/components/auth-guard.svelte.d.ts +26 -0
- package/dist/components/custom-guard.svelte +122 -0
- package/dist/components/custom-guard.svelte.d.ts +31 -0
- package/dist/components/download-url.svelte +92 -0
- package/dist/components/download-url.svelte.d.ts +19 -0
- package/dist/components/firebase-app.svelte +30 -0
- package/dist/components/firebase-app.svelte.d.ts +7 -0
- package/dist/components/node-list.svelte +102 -0
- package/dist/components/node-list.svelte.d.ts +27 -0
- package/dist/components/signed-in.svelte +42 -0
- package/dist/components/signed-in.svelte.d.ts +11 -0
- package/dist/components/signed-out.svelte +42 -0
- package/dist/components/signed-out.svelte.d.ts +11 -0
- package/dist/components/storage-list.svelte +97 -0
- package/dist/components/storage-list.svelte.d.ts +26 -0
- package/dist/components/upload-task.svelte +108 -0
- package/dist/components/upload-task.svelte.d.ts +24 -0
- package/dist/config.js +17 -39
- package/dist/firebase.d.ts +43 -21
- package/dist/firebase.js +121 -35
- package/dist/index.d.ts +21 -12
- package/dist/index.js +27 -14
- package/dist/services/auth.d.ts +389 -0
- package/dist/services/auth.js +824 -0
- package/dist/services/collection.svelte.d.ts +286 -0
- package/dist/services/collection.svelte.js +871 -0
- package/dist/services/document.svelte.d.ts +288 -0
- package/dist/services/document.svelte.js +555 -0
- package/dist/services/mutations.d.ts +336 -0
- package/dist/services/mutations.js +1079 -0
- package/dist/services/presence.svelte.d.ts +141 -0
- package/dist/services/presence.svelte.js +727 -0
- package/dist/{realtime → services}/realtime.svelte.d.ts +3 -1
- package/dist/{realtime → services}/realtime.svelte.js +13 -7
- package/dist/services/storage.svelte.d.ts +257 -0
- package/dist/services/storage.svelte.js +374 -0
- package/dist/services/user.svelte.d.ts +290 -0
- package/dist/services/user.svelte.js +533 -0
- package/dist/types/auth.d.ts +158 -0
- package/dist/types/auth.js +106 -0
- package/dist/types/collection.d.ts +360 -0
- package/dist/types/collection.js +167 -0
- package/dist/types/document.d.ts +342 -0
- package/dist/types/document.js +148 -0
- package/dist/types/firebase.d.ts +44 -0
- package/dist/types/firebase.js +33 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.js +4 -0
- package/dist/types/mutations.d.ts +387 -0
- package/dist/types/mutations.js +205 -0
- package/dist/types/presence.d.ts +282 -0
- package/dist/types/presence.js +80 -0
- package/dist/utils/errors.d.ts +21 -0
- package/dist/utils/errors.js +35 -0
- package/dist/utils/firestore.d.ts +9 -0
- package/dist/utils/firestore.js +33 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/providers.d.ts +16 -0
- package/dist/utils/providers.js +30 -0
- package/dist/utils/user.d.ts +8 -0
- package/dist/utils/user.js +29 -0
- package/package.json +65 -65
- package/dist/auth/auth.d.ts +0 -117
- package/dist/auth/auth.js +0 -194
- package/dist/auth/presence.svelte.d.ts +0 -139
- package/dist/auth/presence.svelte.js +0 -373
- package/dist/auth/user.svelte.d.ts +0 -112
- package/dist/auth/user.svelte.js +0 -155
- package/dist/firestore/awaitable-doc.svelte.d.ts +0 -141
- package/dist/firestore/awaitable-doc.svelte.js +0 -183
- package/dist/firestore/batch.svelte.d.ts +0 -140
- package/dist/firestore/batch.svelte.js +0 -218
- package/dist/firestore/collection-group.svelte.d.ts +0 -78
- package/dist/firestore/collection-group.svelte.js +0 -120
- package/dist/firestore/collection.svelte.d.ts +0 -96
- package/dist/firestore/collection.svelte.js +0 -137
- package/dist/firestore/doc.svelte.d.ts +0 -90
- package/dist/firestore/doc.svelte.js +0 -131
- package/dist/firestore/document-mutations.svelte.d.ts +0 -164
- package/dist/firestore/document-mutations.svelte.js +0 -273
- package/dist/storage/download-url.svelte.d.ts +0 -83
- package/dist/storage/download-url.svelte.js +0 -114
- package/dist/storage/storage-list.svelte.d.ts +0 -89
- package/dist/storage/storage-list.svelte.js +0 -123
- package/dist/storage/upload-task.svelte.d.ts +0 -94
- package/dist/storage/upload-task.svelte.js +0 -138
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { firekitCollection } from '../services/collection.svelte.js';
|
|
3
|
+
import { firebaseService } from '../firebase.js';
|
|
4
|
+
import { collection } from 'firebase/firestore';
|
|
5
|
+
import { browser } from '$app/environment';
|
|
6
|
+
import type {
|
|
7
|
+
CollectionReference,
|
|
8
|
+
Query,
|
|
9
|
+
DocumentData,
|
|
10
|
+
Firestore,
|
|
11
|
+
QueryConstraint
|
|
12
|
+
} from 'firebase/firestore';
|
|
13
|
+
import type { Snippet } from 'svelte';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Props for Collection component
|
|
17
|
+
*/
|
|
18
|
+
let {
|
|
19
|
+
ref,
|
|
20
|
+
startWith,
|
|
21
|
+
children,
|
|
22
|
+
loading,
|
|
23
|
+
queryConstraints = []
|
|
24
|
+
}: {
|
|
25
|
+
/**
|
|
26
|
+
* Firestore collection reference, query reference, or path string
|
|
27
|
+
*/
|
|
28
|
+
ref: CollectionReference | Query | string;
|
|
29
|
+
/**
|
|
30
|
+
* Initial value to use before collection is fetched
|
|
31
|
+
*/
|
|
32
|
+
startWith?: DocumentData[];
|
|
33
|
+
/**
|
|
34
|
+
* Content to render when collection is loaded
|
|
35
|
+
*/
|
|
36
|
+
children: Snippet<[DocumentData[], CollectionReference | Query, Firestore, number]>;
|
|
37
|
+
/**
|
|
38
|
+
* Content to render while loading
|
|
39
|
+
*/
|
|
40
|
+
loading?: Snippet<[]>;
|
|
41
|
+
/**
|
|
42
|
+
* Optional query constraints to apply to the collection
|
|
43
|
+
*/
|
|
44
|
+
queryConstraints?: QueryConstraint[];
|
|
45
|
+
} = $props();
|
|
46
|
+
|
|
47
|
+
// Get Firestore instance only in browser environment
|
|
48
|
+
let firestore: Firestore | null = $state(null);
|
|
49
|
+
let collectionRef: CollectionReference | Query | null = $state(null);
|
|
50
|
+
let collectionService: ReturnType<typeof firekitCollection> | null = $state(null);
|
|
51
|
+
|
|
52
|
+
// Track collection state
|
|
53
|
+
let collectionState = $state({
|
|
54
|
+
loading: true,
|
|
55
|
+
data: startWith ?? ([] as DocumentData[]),
|
|
56
|
+
error: null as Error | null,
|
|
57
|
+
count: startWith?.length ?? 0
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Subscribe to collection changes only if in browser and collection service exists
|
|
61
|
+
$effect(() => {
|
|
62
|
+
if (!browser) {
|
|
63
|
+
collectionState = {
|
|
64
|
+
loading: false,
|
|
65
|
+
data: startWith ?? [],
|
|
66
|
+
error: null,
|
|
67
|
+
count: startWith?.length ?? 0
|
|
68
|
+
};
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Initialize Firestore and collection service
|
|
73
|
+
firestore = firebaseService.getDbInstance();
|
|
74
|
+
if (!firestore) {
|
|
75
|
+
collectionState = {
|
|
76
|
+
loading: false,
|
|
77
|
+
data: [],
|
|
78
|
+
error: new Error('Firestore instance not available'),
|
|
79
|
+
count: 0
|
|
80
|
+
};
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Create collection reference if path string is provided
|
|
85
|
+
collectionRef =
|
|
86
|
+
typeof ref === 'string' ? collection(firestore, ref) : (ref as CollectionReference | Query);
|
|
87
|
+
|
|
88
|
+
// Create collection service
|
|
89
|
+
collectionService = firekitCollection(
|
|
90
|
+
typeof ref === 'string' ? ref : (ref as CollectionReference).path,
|
|
91
|
+
queryConstraints
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// Update state based on collection service state
|
|
95
|
+
collectionState = {
|
|
96
|
+
loading: collectionService.loading,
|
|
97
|
+
data: collectionService.data,
|
|
98
|
+
error: collectionService.error,
|
|
99
|
+
count: collectionService.size
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Set up event listener for real-time updates
|
|
103
|
+
const unsubscribe = collectionService.addEventListener((event) => {
|
|
104
|
+
if (event.type === 'data_changed') {
|
|
105
|
+
collectionState = {
|
|
106
|
+
loading: false,
|
|
107
|
+
data: event.data || [],
|
|
108
|
+
error: null,
|
|
109
|
+
count: event.data?.length || 0
|
|
110
|
+
};
|
|
111
|
+
} else if (event.type === 'error') {
|
|
112
|
+
collectionState = {
|
|
113
|
+
loading: false,
|
|
114
|
+
data: [],
|
|
115
|
+
error: event.error || null,
|
|
116
|
+
count: 0
|
|
117
|
+
};
|
|
118
|
+
} else if (event.type === 'loading_started') {
|
|
119
|
+
collectionState = {
|
|
120
|
+
...collectionState,
|
|
121
|
+
loading: true
|
|
122
|
+
};
|
|
123
|
+
} else if (event.type === 'loading_finished') {
|
|
124
|
+
collectionState = {
|
|
125
|
+
...collectionState,
|
|
126
|
+
loading: false
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return () => {
|
|
132
|
+
unsubscribe();
|
|
133
|
+
collectionService?.dispose();
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
</script>
|
|
137
|
+
|
|
138
|
+
{#if !browser}
|
|
139
|
+
{@render children(startWith ?? [], null as any, null as any, startWith?.length ?? 0)}
|
|
140
|
+
{:else if collectionState.loading}
|
|
141
|
+
{#if loading}
|
|
142
|
+
{@render loading()}
|
|
143
|
+
{:else}
|
|
144
|
+
<div>Loading...</div>
|
|
145
|
+
{/if}
|
|
146
|
+
{:else if collectionState.error}
|
|
147
|
+
<div class="error">Error: {collectionState.error.message}</div>
|
|
148
|
+
{:else}
|
|
149
|
+
{@render children(collectionState.data, collectionRef!, firestore!, collectionState.count)}
|
|
150
|
+
{/if}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { CollectionReference, Query, DocumentData, Firestore, QueryConstraint } from 'firebase/firestore';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
/**
|
|
5
|
+
* Firestore collection reference, query reference, or path string
|
|
6
|
+
*/
|
|
7
|
+
ref: CollectionReference | Query | string;
|
|
8
|
+
/**
|
|
9
|
+
* Initial value to use before collection is fetched
|
|
10
|
+
*/
|
|
11
|
+
startWith?: DocumentData[];
|
|
12
|
+
/**
|
|
13
|
+
* Content to render when collection is loaded
|
|
14
|
+
*/
|
|
15
|
+
children: Snippet<[DocumentData[], CollectionReference | Query, Firestore, number]>;
|
|
16
|
+
/**
|
|
17
|
+
* Content to render while loading
|
|
18
|
+
*/
|
|
19
|
+
loading?: Snippet<[]>;
|
|
20
|
+
/**
|
|
21
|
+
* Optional query constraints to apply to the collection
|
|
22
|
+
*/
|
|
23
|
+
queryConstraints?: QueryConstraint[];
|
|
24
|
+
};
|
|
25
|
+
declare const Collection: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
26
|
+
type Collection = ReturnType<typeof Collection>;
|
|
27
|
+
export default Collection;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { firekitDoc } from '../services/document.svelte.js';
|
|
3
|
+
import { firebaseService } from '../firebase.js';
|
|
4
|
+
import { doc } from 'firebase/firestore';
|
|
5
|
+
import { browser } from '$app/environment';
|
|
6
|
+
import { onDestroy } from 'svelte';
|
|
7
|
+
import type { DocumentReference, DocumentData, Firestore } from 'firebase/firestore';
|
|
8
|
+
import type { Snippet } from 'svelte';
|
|
9
|
+
import type { DocumentOptions } from '../types/document.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Props for Doc component
|
|
13
|
+
*/
|
|
14
|
+
let {
|
|
15
|
+
ref,
|
|
16
|
+
startWith,
|
|
17
|
+
children,
|
|
18
|
+
loading,
|
|
19
|
+
options = {}
|
|
20
|
+
}: {
|
|
21
|
+
/**
|
|
22
|
+
* Firestore document reference or path string
|
|
23
|
+
*/
|
|
24
|
+
ref: DocumentReference | string;
|
|
25
|
+
/**
|
|
26
|
+
* Initial value to use before document is fetched
|
|
27
|
+
*/
|
|
28
|
+
startWith?: DocumentData | null;
|
|
29
|
+
/**
|
|
30
|
+
* Content to render when document is loaded
|
|
31
|
+
*/
|
|
32
|
+
children: Snippet<[DocumentData | null, DocumentReference, Firestore]>;
|
|
33
|
+
/**
|
|
34
|
+
* Content to render while loading
|
|
35
|
+
*/
|
|
36
|
+
loading?: Snippet<[]>;
|
|
37
|
+
/**
|
|
38
|
+
* Document options for configuration
|
|
39
|
+
*/
|
|
40
|
+
options?: DocumentOptions;
|
|
41
|
+
} = $props();
|
|
42
|
+
|
|
43
|
+
// Get Firestore instance only in browser environment
|
|
44
|
+
let firestore: Firestore | null = $state(null);
|
|
45
|
+
let docRef: DocumentReference | null = $state(null);
|
|
46
|
+
let documentService: any = $state(null);
|
|
47
|
+
|
|
48
|
+
// Reactive document state
|
|
49
|
+
let componentState = $state({
|
|
50
|
+
loading: true,
|
|
51
|
+
data: null as DocumentData | null,
|
|
52
|
+
error: null as Error | null,
|
|
53
|
+
exists: false
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Initialize in browser environment
|
|
57
|
+
$effect(() => {
|
|
58
|
+
if (!browser) return;
|
|
59
|
+
|
|
60
|
+
firestore = firebaseService.getDbInstance();
|
|
61
|
+
if (!firestore) {
|
|
62
|
+
throw new Error('Firestore instance not available');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Create document reference if path string is provided
|
|
66
|
+
docRef = typeof ref === 'string' ? doc(firestore, ref) : ref;
|
|
67
|
+
|
|
68
|
+
// Create document service
|
|
69
|
+
documentService = firekitDoc(docRef.path, startWith ?? undefined, options);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Subscribe to document changes only if in browser and document service exists
|
|
73
|
+
$effect(() => {
|
|
74
|
+
if (!browser || !documentService) {
|
|
75
|
+
componentState = {
|
|
76
|
+
loading: false,
|
|
77
|
+
data: startWith ?? null,
|
|
78
|
+
error: null,
|
|
79
|
+
exists: !!startWith
|
|
80
|
+
};
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Update component state based on document service state
|
|
85
|
+
componentState = {
|
|
86
|
+
loading: documentService.loading,
|
|
87
|
+
data: documentService.data,
|
|
88
|
+
error: documentService.error,
|
|
89
|
+
exists: documentService.exists
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Cleanup on component destruction
|
|
94
|
+
onDestroy(() => {
|
|
95
|
+
if (documentService) {
|
|
96
|
+
documentService.dispose();
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
</script>
|
|
100
|
+
|
|
101
|
+
{#if !browser}
|
|
102
|
+
{@render children(startWith ?? null, null as any, null as any)}
|
|
103
|
+
{:else if componentState.loading}
|
|
104
|
+
{#if loading}
|
|
105
|
+
{@render loading()}
|
|
106
|
+
{:else}
|
|
107
|
+
<div class="flex items-center justify-center min-h-screen">
|
|
108
|
+
<div class="text-center">
|
|
109
|
+
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900 mx-auto"></div>
|
|
110
|
+
<p class="mt-2 text-gray-600">Loading document...</p>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
{/if}
|
|
114
|
+
{:else if componentState.error}
|
|
115
|
+
<div class="flex items-center justify-center min-h-screen">
|
|
116
|
+
<div class="text-center">
|
|
117
|
+
<div class="text-red-500 text-lg font-semibold mb-2">Error Loading Document</div>
|
|
118
|
+
<p class="text-gray-600 mb-4">{componentState.error.message}</p>
|
|
119
|
+
{#if documentService?.canRefresh}
|
|
120
|
+
<button
|
|
121
|
+
class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
|
|
122
|
+
onclick={() => documentService?.refresh()}
|
|
123
|
+
>
|
|
124
|
+
Retry
|
|
125
|
+
</button>
|
|
126
|
+
{/if}
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
{:else}
|
|
130
|
+
{@render children(componentState.data ?? null, docRef!, firestore!)}
|
|
131
|
+
{/if}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { DocumentReference, DocumentData, Firestore } from 'firebase/firestore';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { DocumentOptions } from '../types/document.js';
|
|
4
|
+
type $$ComponentProps = {
|
|
5
|
+
/**
|
|
6
|
+
* Firestore document reference or path string
|
|
7
|
+
*/
|
|
8
|
+
ref: DocumentReference | string;
|
|
9
|
+
/**
|
|
10
|
+
* Initial value to use before document is fetched
|
|
11
|
+
*/
|
|
12
|
+
startWith?: DocumentData | null;
|
|
13
|
+
/**
|
|
14
|
+
* Content to render when document is loaded
|
|
15
|
+
*/
|
|
16
|
+
children: Snippet<[DocumentData | null, DocumentReference, Firestore]>;
|
|
17
|
+
/**
|
|
18
|
+
* Content to render while loading
|
|
19
|
+
*/
|
|
20
|
+
loading?: Snippet<[]>;
|
|
21
|
+
/**
|
|
22
|
+
* Document options for configuration
|
|
23
|
+
*/
|
|
24
|
+
options?: DocumentOptions;
|
|
25
|
+
};
|
|
26
|
+
declare const Ddoc: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
27
|
+
type Ddoc = ReturnType<typeof Ddoc>;
|
|
28
|
+
export default Ddoc;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { ref, type DatabaseReference, type Database } from 'firebase/database';
|
|
3
|
+
import { firebaseService } from '../firebase.js';
|
|
4
|
+
import { firekitRealtimeDB } from '../services/realtime.svelte.js';
|
|
5
|
+
import { browser } from '$app/environment';
|
|
6
|
+
import type { Snippet } from 'svelte';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Props for Node component
|
|
10
|
+
*/
|
|
11
|
+
let {
|
|
12
|
+
path,
|
|
13
|
+
startWith,
|
|
14
|
+
children,
|
|
15
|
+
loading
|
|
16
|
+
}: {
|
|
17
|
+
/**
|
|
18
|
+
* Database reference path string (e.g. 'users/123')
|
|
19
|
+
*/
|
|
20
|
+
path: string;
|
|
21
|
+
/**
|
|
22
|
+
* Initial value to use before node is fetched
|
|
23
|
+
*/
|
|
24
|
+
startWith?: any;
|
|
25
|
+
/**
|
|
26
|
+
* Content to render when node is loaded
|
|
27
|
+
*/
|
|
28
|
+
children: Snippet<[any, DatabaseReference, Database]>;
|
|
29
|
+
/**
|
|
30
|
+
* Content to render while loading
|
|
31
|
+
*/
|
|
32
|
+
loading?: Snippet<[]>;
|
|
33
|
+
} = $props();
|
|
34
|
+
|
|
35
|
+
// Get Database instance and create references
|
|
36
|
+
let database: Database | null = $state(null);
|
|
37
|
+
let nodeRef: DatabaseReference | null = $state(null);
|
|
38
|
+
let nodeService: ReturnType<typeof firekitRealtimeDB> | null = $state(null);
|
|
39
|
+
|
|
40
|
+
// Track node state
|
|
41
|
+
let nodeState = $state({
|
|
42
|
+
loading: true,
|
|
43
|
+
data: startWith ?? null,
|
|
44
|
+
error: null as Error | null
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Subscribe to node changes
|
|
48
|
+
$effect(() => {
|
|
49
|
+
if (!browser) {
|
|
50
|
+
nodeState = {
|
|
51
|
+
loading: false,
|
|
52
|
+
data: startWith ?? null,
|
|
53
|
+
error: new Error('Realtime Database not available in SSR')
|
|
54
|
+
};
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Initialize database and service
|
|
59
|
+
database = firebaseService.getDatabaseInstance();
|
|
60
|
+
if (!database) {
|
|
61
|
+
nodeState = {
|
|
62
|
+
loading: false,
|
|
63
|
+
data: null,
|
|
64
|
+
error: new Error('Realtime Database instance not available')
|
|
65
|
+
};
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Create database reference
|
|
70
|
+
nodeRef = ref(database, path);
|
|
71
|
+
|
|
72
|
+
// Create node service
|
|
73
|
+
nodeService = firekitRealtimeDB(path, startWith);
|
|
74
|
+
|
|
75
|
+
// The service state is reactive, so we can directly access it
|
|
76
|
+
// The effect will re-run when the service state changes
|
|
77
|
+
nodeState = {
|
|
78
|
+
loading: nodeService.loading,
|
|
79
|
+
data: nodeService.data,
|
|
80
|
+
error: nodeService.error
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
{#if !browser}
|
|
86
|
+
{@render children(startWith ?? null, null as any, null as any)}
|
|
87
|
+
{:else if nodeState.loading}
|
|
88
|
+
{#if loading}
|
|
89
|
+
{@render loading()}
|
|
90
|
+
{:else}
|
|
91
|
+
<div>Loading...</div>
|
|
92
|
+
{/if}
|
|
93
|
+
{:else if nodeState.error}
|
|
94
|
+
<div class="error">Error: {nodeState.error.message}</div>
|
|
95
|
+
{:else}
|
|
96
|
+
{@render children(nodeState.data, nodeRef!, database!)}
|
|
97
|
+
{/if}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type DatabaseReference, type Database } from 'firebase/database';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
/**
|
|
5
|
+
* Database reference path string (e.g. 'users/123')
|
|
6
|
+
*/
|
|
7
|
+
path: string;
|
|
8
|
+
/**
|
|
9
|
+
* Initial value to use before node is fetched
|
|
10
|
+
*/
|
|
11
|
+
startWith?: any;
|
|
12
|
+
/**
|
|
13
|
+
* Content to render when node is loaded
|
|
14
|
+
*/
|
|
15
|
+
children: Snippet<[any, DatabaseReference, Database]>;
|
|
16
|
+
/**
|
|
17
|
+
* Content to render while loading
|
|
18
|
+
*/
|
|
19
|
+
loading?: Snippet<[]>;
|
|
20
|
+
};
|
|
21
|
+
declare const Node: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
22
|
+
type Node = ReturnType<typeof Node>;
|
|
23
|
+
export default Node;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { firekitAuth } from '../services/auth.js';
|
|
3
|
+
import { firebaseService } from '../firebase.js';
|
|
4
|
+
import { goto } from '$app/navigation';
|
|
5
|
+
import { onDestroy } from 'svelte';
|
|
6
|
+
import type { UserProfile } from '../types/auth.js';
|
|
7
|
+
import type { Auth } from 'firebase/auth';
|
|
8
|
+
import type { Snippet } from 'svelte';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Props for AuthGuard component
|
|
12
|
+
*/
|
|
13
|
+
let {
|
|
14
|
+
children,
|
|
15
|
+
requireAuth = true,
|
|
16
|
+
redirectTo = '/',
|
|
17
|
+
fallback
|
|
18
|
+
}: {
|
|
19
|
+
/**
|
|
20
|
+
* Children content to render when auth state matches requirements
|
|
21
|
+
*/
|
|
22
|
+
children: Snippet<[UserProfile, Auth, () => Promise<void>]>;
|
|
23
|
+
/**
|
|
24
|
+
* Whether authentication is required to view the content
|
|
25
|
+
* @default true
|
|
26
|
+
*/
|
|
27
|
+
requireAuth?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Path to redirect to if auth state doesn't match requirements
|
|
30
|
+
* @default '/'
|
|
31
|
+
*/
|
|
32
|
+
redirectTo?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Fallback content to show while checking auth state
|
|
35
|
+
*/
|
|
36
|
+
fallback?: Snippet<[]>;
|
|
37
|
+
} = $props();
|
|
38
|
+
|
|
39
|
+
// Get Firebase Auth instance
|
|
40
|
+
const auth = firebaseService.getAuthInstance();
|
|
41
|
+
if (!auth) {
|
|
42
|
+
throw new Error('Firebase Auth instance not available');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Reactive auth state
|
|
46
|
+
let authState = $state(firekitAuth.getState());
|
|
47
|
+
|
|
48
|
+
// Subscribe to auth state changes
|
|
49
|
+
const unsubscribe = firekitAuth.onAuthStateChanged((state) => {
|
|
50
|
+
authState = state;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Sign out function
|
|
54
|
+
async function signOut() {
|
|
55
|
+
await firekitAuth.signOut();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Check if current auth state matches requirements
|
|
59
|
+
$effect(() => {
|
|
60
|
+
if (authState.loading) return;
|
|
61
|
+
|
|
62
|
+
const isAuthenticated = firekitAuth.isAuthenticated();
|
|
63
|
+
const shouldRedirect = requireAuth ? !isAuthenticated : isAuthenticated;
|
|
64
|
+
|
|
65
|
+
if (shouldRedirect) {
|
|
66
|
+
goto(redirectTo);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Cleanup subscription on component destruction
|
|
71
|
+
onDestroy(() => {
|
|
72
|
+
unsubscribe();
|
|
73
|
+
});
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
{#if authState.loading}
|
|
77
|
+
{#if fallback}
|
|
78
|
+
{@render fallback()}
|
|
79
|
+
{:else}
|
|
80
|
+
<div class="flex items-center justify-center min-h-screen">
|
|
81
|
+
<div class="text-center">
|
|
82
|
+
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900 mx-auto"></div>
|
|
83
|
+
<p class="mt-2 text-gray-600">Loading...</p>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
{/if}
|
|
87
|
+
{:else if firekitAuth.isAuthenticated() === requireAuth}
|
|
88
|
+
{@render children(authState.user!, auth, signOut)}
|
|
89
|
+
{/if}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { UserProfile } from '../types/auth.js';
|
|
2
|
+
import type { Auth } from 'firebase/auth';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
type $$ComponentProps = {
|
|
5
|
+
/**
|
|
6
|
+
* Children content to render when auth state matches requirements
|
|
7
|
+
*/
|
|
8
|
+
children: Snippet<[UserProfile, Auth, () => Promise<void>]>;
|
|
9
|
+
/**
|
|
10
|
+
* Whether authentication is required to view the content
|
|
11
|
+
* @default true
|
|
12
|
+
*/
|
|
13
|
+
requireAuth?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Path to redirect to if auth state doesn't match requirements
|
|
16
|
+
* @default '/'
|
|
17
|
+
*/
|
|
18
|
+
redirectTo?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Fallback content to show while checking auth state
|
|
21
|
+
*/
|
|
22
|
+
fallback?: Snippet<[]>;
|
|
23
|
+
};
|
|
24
|
+
declare const AuthGuard: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
25
|
+
type AuthGuard = ReturnType<typeof AuthGuard>;
|
|
26
|
+
export default AuthGuard;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { firekitAuth } from '../services/auth.js';
|
|
3
|
+
import { firebaseService } from '../firebase.js';
|
|
4
|
+
import { goto } from '$app/navigation';
|
|
5
|
+
import { onDestroy } from 'svelte';
|
|
6
|
+
import type { UserProfile } from '../types/auth.js';
|
|
7
|
+
import type { Auth } from 'firebase/auth';
|
|
8
|
+
import type { Snippet } from 'svelte';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Props for CustomGuard component
|
|
12
|
+
*/
|
|
13
|
+
let {
|
|
14
|
+
children,
|
|
15
|
+
requireAuth = true,
|
|
16
|
+
redirectTo = '/',
|
|
17
|
+
fallback,
|
|
18
|
+
verificationChecks = []
|
|
19
|
+
}: {
|
|
20
|
+
/**
|
|
21
|
+
* Children content to render when all checks pass
|
|
22
|
+
*/
|
|
23
|
+
children: Snippet<[UserProfile, Auth, () => Promise<void>]>;
|
|
24
|
+
/**
|
|
25
|
+
* Whether authentication is required to view the content
|
|
26
|
+
* @default true
|
|
27
|
+
*/
|
|
28
|
+
requireAuth?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Path to redirect to if checks fail
|
|
31
|
+
* @default '/'
|
|
32
|
+
*/
|
|
33
|
+
redirectTo?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Fallback content to show while checking auth state
|
|
36
|
+
*/
|
|
37
|
+
fallback?: Snippet<[]>;
|
|
38
|
+
/**
|
|
39
|
+
* Array of verification functions that must return true to allow access
|
|
40
|
+
* Each function receives the user profile and auth instance
|
|
41
|
+
*/
|
|
42
|
+
verificationChecks?: ((user: UserProfile, auth: Auth) => boolean | Promise<boolean>)[];
|
|
43
|
+
} = $props();
|
|
44
|
+
|
|
45
|
+
// Get Firebase Auth instance
|
|
46
|
+
const auth = firebaseService.getAuthInstance();
|
|
47
|
+
if (!auth) {
|
|
48
|
+
throw new Error('Firebase Auth instance not available');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Reactive auth state
|
|
52
|
+
let authState = $state(firekitAuth.getState());
|
|
53
|
+
let verificationPassed = $state(true);
|
|
54
|
+
let isVerifying = $state(false);
|
|
55
|
+
|
|
56
|
+
// Subscribe to auth state changes
|
|
57
|
+
const unsubscribe = firekitAuth.onAuthStateChanged((state) => {
|
|
58
|
+
authState = state;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Sign out function
|
|
62
|
+
async function signOut() {
|
|
63
|
+
await firekitAuth.signOut();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check if current auth state and verification checks pass
|
|
67
|
+
$effect(() => {
|
|
68
|
+
if (authState.loading) return;
|
|
69
|
+
|
|
70
|
+
const isAuthenticated = firekitAuth.isAuthenticated();
|
|
71
|
+
const shouldRedirect = requireAuth ? !isAuthenticated : isAuthenticated;
|
|
72
|
+
|
|
73
|
+
if (shouldRedirect) {
|
|
74
|
+
goto(redirectTo);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// If authenticated and verification checks exist, run them
|
|
79
|
+
if (isAuthenticated && verificationChecks.length > 0) {
|
|
80
|
+
isVerifying = true;
|
|
81
|
+
Promise.all(verificationChecks.map((check) => check(authState.user!, auth)))
|
|
82
|
+
.then((results) => {
|
|
83
|
+
verificationPassed = results.every((result) => result === true);
|
|
84
|
+
if (!verificationPassed) {
|
|
85
|
+
goto(redirectTo);
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
.catch((error) => {
|
|
89
|
+
console.error('Verification check failed:', error);
|
|
90
|
+
verificationPassed = false;
|
|
91
|
+
goto(redirectTo);
|
|
92
|
+
})
|
|
93
|
+
.finally(() => {
|
|
94
|
+
isVerifying = false;
|
|
95
|
+
});
|
|
96
|
+
} else {
|
|
97
|
+
verificationPassed = true;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Cleanup subscription on component destruction
|
|
102
|
+
onDestroy(() => {
|
|
103
|
+
unsubscribe();
|
|
104
|
+
});
|
|
105
|
+
</script>
|
|
106
|
+
|
|
107
|
+
{#if authState.loading || isVerifying}
|
|
108
|
+
{#if fallback}
|
|
109
|
+
{@render fallback()}
|
|
110
|
+
{:else}
|
|
111
|
+
<div class="flex items-center justify-center min-h-screen">
|
|
112
|
+
<div class="text-center">
|
|
113
|
+
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900 mx-auto"></div>
|
|
114
|
+
<p class="mt-2 text-gray-600">
|
|
115
|
+
{isVerifying ? 'Verifying access...' : 'Loading...'}
|
|
116
|
+
</p>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
{/if}
|
|
120
|
+
{:else if firekitAuth.isAuthenticated() === requireAuth && verificationPassed}
|
|
121
|
+
{@render children(authState.user!, auth, signOut)}
|
|
122
|
+
{/if}
|