Skip to content

Commit 38ea67e

Browse files
committed
- Created a download link component
- Created a storage list component
1 parent fc380cd commit 38ea67e

14 files changed

+232
-8
lines changed

Diff for: firebase.json

+6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
"firestore": {
77
"port": 8080
88
},
9+
"storage": {
10+
"port": 9199
11+
},
912
"hosting": {
1013
"port": 5000
1114
},
@@ -14,6 +17,9 @@
1417
},
1518
"singleProjectMode": true
1619
},
20+
"storage": {
21+
"rules": "storage.rules"
22+
},
1723
"hosting": {
1824
"public": "docs/dist",
1925
"ignore": [

Diff for: package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: src/lib/components/DownloadLink.svelte

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<script lang="ts">
2+
import { downloadUrlStore } from '$lib/stores/storage.js';
3+
import { getFirebaseContext } from '$lib/stores/sdk.js';
4+
import type { FirebaseStorage, StorageReference } from 'firebase/storage';
5+
6+
export let ref: string | StorageReference;
7+
8+
const { storage } = getFirebaseContext();
9+
const store = downloadUrlStore(storage!, ref);
10+
11+
interface $$Slots {
12+
default: { link: string | null; ref: StorageReference | null; storage?: FirebaseStorage },
13+
loading: {},
14+
}
15+
</script>
16+
17+
{#if $store !== undefined}
18+
<slot link={$store} ref={store.reference} {storage}/>
19+
{:else}
20+
<slot name="loading" />
21+
{/if}
22+

Diff for: src/lib/components/FirebaseApp.svelte

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
import { setFirebaseContext } from "$lib/stores/sdk.js";
33
import type { Auth } from "firebase/auth";
44
import type { Firestore } from "firebase/firestore";
5+
import type { FirebaseStorage } from "firebase/storage";
56
67
export let firestore: Firestore;
78
export let auth: Auth;
9+
export let storage: FirebaseStorage;
810
9-
setFirebaseContext({ firestore, auth });
11+
setFirebaseContext({ firestore, auth, storage });
1012
</script>
1113

1214
<slot />

Diff for: src/lib/components/StorageList.svelte

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<script lang="ts">
2+
import { storageListStore } from '$lib/stores/storage.js';
3+
import { getFirebaseContext } from '$lib/stores/sdk.js';
4+
import type { FirebaseStorage, ListResult, StorageReference } from 'firebase/storage';
5+
6+
export let ref: string | StorageReference;
7+
8+
const { storage } = getFirebaseContext();
9+
const listStore = storageListStore(storage!, ref);
10+
11+
interface $$Slots {
12+
default: { list: ListResult | null; ref: StorageReference | null; storage?: FirebaseStorage },
13+
loading: {},
14+
}
15+
</script>
16+
17+
{#if $listStore !== undefined}
18+
<slot list={$listStore} ref={listStore.reference} {storage} />
19+
{:else}
20+
<slot name="loading" />
21+
{/if}
22+

Diff for: src/lib/stores/sdk.ts

+2
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ import { writable } from "svelte/store";
22
import type { Firestore } from "firebase/firestore";
33
import type { Auth } from "firebase/auth";
44
import { getContext, setContext } from "svelte";
5+
import type { FirebaseStorage } from "firebase/storage";
56

67

78
export interface FirebaseSDKContext {
89
auth?: Auth;
910
firestore?: Firestore;
11+
storage?: FirebaseStorage;
1012
}
1113

1214
export const contextKey = "firebase";

Diff for: src/lib/stores/storage.ts

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { writable } from "svelte/store";
2+
import { getDownloadURL, list, ref } from "firebase/storage";
3+
4+
import type {
5+
StorageReference,
6+
FirebaseStorage,
7+
ListResult,
8+
} from "firebase/storage";
9+
10+
const defaultListResult: ListResult = {
11+
prefixes: [],
12+
items: [],
13+
};
14+
15+
interface StorageListStore {
16+
subscribe: (cb: (value: ListResult) => void) => void | (() => void);
17+
reference: StorageReference | null;
18+
}
19+
20+
/**
21+
* @param {FirebaseStorage} storage firebase storage instance
22+
* @param {string|StorageReference} reference file or storage item path or reference
23+
* @param {{prefixes:[], items:[]}} startWith optional default data
24+
* @returns a store with the list result
25+
*/
26+
export function storageListStore(
27+
storage: FirebaseStorage,
28+
reference: string | StorageReference,
29+
startWith: ListResult = defaultListResult
30+
): StorageListStore {
31+
32+
// Fallback for SSR
33+
if (!globalThis.window) {
34+
const { subscribe } = writable(startWith);
35+
return {
36+
subscribe,
37+
reference: null,
38+
};
39+
}
40+
41+
// Fallback for missing SDK
42+
if (!storage) {
43+
console.warn(
44+
"Cloud Storage is not initialized. Are you missing FirebaseApp as a parent component?"
45+
);
46+
const { subscribe } = writable(defaultListResult);
47+
return {
48+
subscribe,
49+
reference: null,
50+
};
51+
}
52+
53+
const storageRef = typeof reference === "string" ? ref(storage, reference) : reference;
54+
55+
const { subscribe } = writable(startWith, (set) => {
56+
list(storageRef).then((snapshot) => {
57+
set(snapshot);
58+
});
59+
});
60+
61+
return {
62+
subscribe,
63+
reference: storageRef,
64+
};
65+
}
66+
67+
interface DownloadUrlStore {
68+
subscribe: (cb: (value: string | null) => void) => void | (() => void);
69+
reference: StorageReference | null;
70+
}
71+
72+
/**
73+
* @param {FirebaseStorage} storage firebase storage instance
74+
* @param {string|StorageReference} reference file or storage item path or reference
75+
* @param {null} startWith optional default data
76+
* @returns a store with the list result
77+
*/
78+
export function downloadUrlStore(
79+
storage: FirebaseStorage,
80+
reference: string | StorageReference,
81+
startWith: string | null = null
82+
): DownloadUrlStore {
83+
84+
// Fallback for SSR
85+
if (!globalThis.window) {
86+
const { subscribe } = writable(startWith);
87+
return {
88+
subscribe,
89+
reference: null,
90+
};
91+
}
92+
93+
// Fallback for missing SDK
94+
if (!storage) {
95+
console.warn(
96+
"Cloud Storage is not initialized. Are you missing FirebaseApp as a parent component?"
97+
);
98+
const { subscribe } = writable(null);
99+
return {
100+
subscribe,
101+
reference: null,
102+
};
103+
}
104+
105+
const storageRef = typeof reference === "string" ? ref(storage, reference) : reference;
106+
107+
const { subscribe } = writable(startWith, (set) => {
108+
getDownloadURL(storageRef).then((snapshot) => {
109+
set(snapshot);
110+
});
111+
});
112+
113+
return {
114+
subscribe,
115+
reference: storageRef,
116+
};
117+
}
118+

Diff for: src/routes/+layout.svelte

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
<script lang="ts">
22
import FirebaseApp from '$lib/components/FirebaseApp.svelte';
3-
import { db as firestore, auth } from './firebase.js';
3+
import { db as firestore, auth, storage } from './firebase.js';
44
55
</script>
66

7-
<FirebaseApp {auth} {firestore}>
8-
7+
<FirebaseApp {auth} {firestore} {storage}>
98
<slot />
109
</FirebaseApp>

Diff for: src/routes/+page.svelte

+2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
<li><a href="/auth-test">Auth Test</a></li>
1313
<li><a href="/firestore-test">Firestore Test</a></li>
1414
<li><a href="/ssr-test">SSR Test</a></li>
15+
<li><a href="/storage-test">Storage Test</a></li>
1516
</ul>
1617
<ul>
1718
<li data-testid="auth">Auth Context: {!!ctx.auth}</li>
1819
<li data-testid="firestore">Firestore Context: {!!ctx.firestore}</li>
20+
<li data-testid="firestore">Storage Context: {!!ctx.storage}</li>
1921
</ul>

Diff for: src/routes/auth-test/+page.svelte

-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
import SignedIn from '$lib/components/SignedIn.svelte';
33
import SignedOut from '$lib/components/SignedOut.svelte';
44
import { signInAnonymously } from "firebase/auth";
5-
6-
75
</script>
86

97
<h1>Auth Test</h1>

Diff for: src/routes/firebase.ts

+16
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { initializeApp } from "firebase/app";
22
import { connectFirestoreEmulator, doc, getFirestore, setDoc } from "firebase/firestore";
33
import { connectAuthEmulator, getAuth } from "firebase/auth";
44
import { dev } from "$app/environment";
5+
import { connectStorageEmulator, getStorage, ref, uploadString } from "firebase/storage";
56

67

78
const firebaseConfig = {
@@ -18,15 +19,30 @@ const firebaseConfig = {
1819
export const app = initializeApp(firebaseConfig);
1920
export const db = getFirestore(app);
2021
export const auth = getAuth(app);
22+
export const storage = getStorage(app);
2123

2224
if (dev) {
2325
connectAuthEmulator(auth, "http://localhost:9099");
2426
connectFirestoreEmulator(db, "localhost", 8080);
27+
connectStorageEmulator(storage, "localhost", 9199);
2528

2629
// Seed Firestore
2730
setDoc(doc(db, "posts", "test"), {
2831
title: "Hi Mom",
2932
content: "this is a test"
3033
});
34+
35+
36+
// Create a reference to the file to create
37+
const fileRef = ref(storage, "test.txt");
38+
39+
// Upload a string to the file
40+
uploadString(fileRef, "Hello, world!", "raw")
41+
.then(() => {
42+
console.log("File created successfully!");
43+
})
44+
.catch((error) => {
45+
console.error("Error creating file:", error);
46+
});
3147
}
3248

Diff for: src/routes/storage-test/+page.svelte

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<script lang="ts">
2+
import DownloadLink from "$lib/components/DownloadLink.svelte";
3+
import StorageList from "$lib/components/StorageList.svelte";
4+
</script>
5+
6+
<h1>Storage Test</h1>
7+
8+
<StorageList ref="/" let:list>
9+
<ul>
10+
{#if list === null}
11+
<li>Loading...</li>
12+
{:else if list.prefixes.length === 0 && list.items.length === 0}
13+
<li>Empty</li>
14+
{:else}
15+
{#each list.items as item}
16+
<li>
17+
<DownloadLink ref={item} let:link let:ref>
18+
<a href="{link}" download>{ref?.name}</a>
19+
</DownloadLink>
20+
</li>
21+
{/each}
22+
{/if}
23+
</ul>
24+
</StorageList>

Diff for: storage.rules

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Firebase Storage Rules allow you to define how your data should be structured and when data can be read or written.
2+
// See https://firebase.google.com/docs/storage/security/start for more information.
3+
rules_version='2'
4+
// Anyone can read or write to the bucket, even non-users of your app.
5+
// Because it is shared with Google App Engine, this will also make files uploaded via App Engine public.
6+
service firebase.storage {
7+
match /b/{bucket}/o {
8+
match /{allPaths=**} {
9+
allow read, write: if true;
10+
}
11+
}
12+
}

Diff for: tests/main.test.ts

+1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ test('Firebase SDK context is defined via FirebaseApp component', async ({ page
99
await page.goto('/');
1010
await expect( page.getByTestId('auth')).toContainText('true');
1111
await expect( page.getByTestId('firestore')).toContainText('true');
12+
await expect( page.getByTestId('storage')).toContainText('true');
1213
});

0 commit comments

Comments
 (0)