@@ -194,26 +194,47 @@ <h2>6. Raw POST to Callable (without SDK)</h2>
194194 < div id ="rawResult " class ="result " style ="display: none; "> </ div >
195195 </ div >
196196
197+ <!-- Storage Upload Demo -->
198+ < div class ="card ">
199+ < h2 > 7. Storage - File Upload (triggers onObjectFinalized)</ h2 >
200+ < p > Uploads a file to Cloud Storage via the Firebase SDK. The emulator automatically triggers < code > onObjectFinalized</ code > on the Dart function. Check the emulator logs to see the trigger fire.</ p >
201+
202+ < div >
203+ < input type ="file " id ="storageFile ">
204+ </ div >
205+ < div >
206+ < input type ="text " id ="storagePath " placeholder ="Upload path " value ="uploads/ " style ="width: 300px; ">
207+ < button onclick ="uploadFile() "> Upload to Storage</ button >
208+ < button onclick ="deleteFile() "> Delete Last Upload</ button >
209+ </ div >
210+
211+ < div id ="storageResult " class ="result " style ="display: none; "> </ div >
212+ </ div >
213+
197214 <!-- Firebase SDK -->
198215 < script type ="module ">
199216 // Import Firebase SDK
200217 import { initializeApp } from 'https://www.gstatic.com/firebasejs/12.8.0/firebase-app.js' ;
201218 import { getFunctions , httpsCallable , connectFunctionsEmulator } from 'https://www.gstatic.com/firebasejs/12.8.0/firebase-functions.js' ;
202219 import { getAuth , connectAuthEmulator , signInWithEmailAndPassword , createUserWithEmailAndPassword , signOut as firebaseSignOut , onAuthStateChanged } from 'https://www.gstatic.com/firebasejs/12.8.0/firebase-auth.js' ;
220+ import { getStorage , connectStorageEmulator , ref , uploadBytes , deleteObject , getMetadata } from 'https://www.gstatic.com/firebasejs/12.8.0/firebase-storage.js' ;
203221
204222 // Initialize Firebase (minimal config for Functions)
205223 const firebaseConfig = {
206224 projectId : 'demo-test' ,
207225 apiKey : 'fake-api-key' , // Not needed for emulator
226+ storageBucket : 'demo-test.firebasestorage.app' ,
208227 } ;
209228
210229 const app = initializeApp ( firebaseConfig ) ;
211230 const functions = getFunctions ( app , 'us-central1' ) ;
212231 const auth = getAuth ( app ) ;
232+ const storage = getStorage ( app ) ;
213233
214234 // Connect to emulators
215235 connectFunctionsEmulator ( functions , '127.0.0.1' , 5001 ) ;
216236 connectAuthEmulator ( auth , 'http://127.0.0.1:9099' , { disableWarnings : true } ) ;
237+ connectStorageEmulator ( storage , '127.0.0.1' , 9199 ) ;
217238
218239 // Track auth state
219240 onAuthStateChanged ( auth , ( user ) => {
@@ -234,8 +255,13 @@ <h2>6. Raw POST to Callable (without SDK)</h2>
234255 window . signInWithEmailAndPassword = signInWithEmailAndPassword ;
235256 window . createUserWithEmailAndPassword = createUserWithEmailAndPassword ;
236257 window . firebaseSignOut = firebaseSignOut ;
258+ window . storage = storage ;
259+ window . storageRef = ref ;
260+ window . uploadBytes = uploadBytes ;
261+ window . deleteObject = deleteObject ;
262+ window . getMetadata = getMetadata ;
237263
238- console . log ( 'Firebase initialized and connected to emulators (Functions + Auth)' ) ;
264+ console . log ( 'Firebase initialized and connected to emulators (Functions + Auth + Storage )' ) ;
239265 </ script >
240266
241267 < script >
@@ -465,6 +491,64 @@ <h2>6. Raw POST to Callable (without SDK)</h2>
465491 }
466492 }
467493 window . callRawCallable = callRawCallable ;
494+
495+ // 7. Storage - File upload (triggers onObjectFinalized)
496+ let lastUploadedRef = null ;
497+
498+ async function uploadFile ( ) {
499+ const fileInput = document . getElementById ( 'storageFile' ) ;
500+ const pathPrefix = document . getElementById ( 'storagePath' ) . value || 'uploads/' ;
501+
502+ if ( ! fileInput . files . length ) {
503+ showResult ( 'storageResult' , 'Please select a file first.' , true ) ;
504+ return ;
505+ }
506+
507+ const file = fileInput . files [ 0 ] ;
508+ const fullPath = pathPrefix + file . name ;
509+
510+ try {
511+ const fileRef = window . storageRef ( window . storage , fullPath ) ;
512+ const snapshot = await window . uploadBytes ( fileRef , file , {
513+ contentType : file . type ,
514+ } ) ;
515+
516+ lastUploadedRef = fileRef ;
517+ const metadata = await window . getMetadata ( fileRef ) ;
518+
519+ showResult ( 'storageResult' , {
520+ status : 'Upload complete - check emulator logs for onObjectFinalized trigger' ,
521+ path : fullPath ,
522+ contentType : metadata . contentType ,
523+ size : metadata . size ,
524+ bucket : metadata . bucket ,
525+ timeCreated : metadata . timeCreated ,
526+ } ) ;
527+ } catch ( error ) {
528+ showResult ( 'storageResult' , `Upload error: ${ error . message } ` , true ) ;
529+ }
530+ }
531+ window . uploadFile = uploadFile ;
532+
533+ async function deleteFile ( ) {
534+ if ( ! lastUploadedRef ) {
535+ showResult ( 'storageResult' , 'No file uploaded yet. Upload a file first.' , true ) ;
536+ return ;
537+ }
538+
539+ try {
540+ await window . deleteObject ( lastUploadedRef ) ;
541+
542+ showResult ( 'storageResult' , {
543+ status : 'File deleted - check emulator logs for onObjectDeleted trigger' ,
544+ path : lastUploadedRef . fullPath ,
545+ } ) ;
546+ lastUploadedRef = null ;
547+ } catch ( error ) {
548+ showResult ( 'storageResult' , `Delete error: ${ error . message } ` , true ) ;
549+ }
550+ }
551+ window . deleteFile = deleteFile ;
468552 </ script >
469553</ body >
470554</ html >
0 commit comments