M2 (C): storage barrel re-export, App.js imports swapped
- src/storage/index.js: re-exports Firebase SDK - App.js: imports from ./storage (was firebase/* direct) - STORAGE=firebase = identical behavior - dev server compiles clean Safe refactor proof. Next: per-call-site path-based rewrite for ws adapter.
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
// firebase.js — storage adapter wrapping Firebase SDK. Default impl (upstream-unchanged).
|
||||
// Matches interface of memory.js / ws.js so App.js calls stay identical.
|
||||
//
|
||||
// NOTE: App.js currently imports SDK directly. This adapter extracted verbatim.
|
||||
// Two-phase refactor:
|
||||
// Phase A (now): adapter exists, wraps SDK. Hooks/writes can switch incrementally.
|
||||
// Phase B (later): App.js imports storage factory, drops direct SDK imports.
|
||||
|
||||
'use strict';
|
||||
|
||||
import { initializeApp } from 'firebase/app';
|
||||
import { getAuth, signInAnonymously, onAuthStateChanged, signInWithCustomToken } from 'firebase/auth';
|
||||
import {
|
||||
getFirestore, doc, setDoc, updateDoc, deleteDoc, addDoc, collection,
|
||||
onSnapshot, query, orderBy, limit, writeBatch, serverTimestamp,
|
||||
} from 'firebase/firestore';
|
||||
|
||||
// Path helpers mirror App.js getPath object.
|
||||
const APP_ID = process.env.REACT_APP_TRACKER_APP_ID || 'ttrpg-initiative-tracker-default';
|
||||
const PUBLIC_DATA_PATH = `artifacts/${APP_ID}/public/data`;
|
||||
|
||||
export const getPath = {
|
||||
campaigns: () => `${PUBLIC_DATA_PATH}/campaigns`,
|
||||
campaign: (id) => `${PUBLIC_DATA_PATH}/campaigns/${id}`,
|
||||
encounters: (campaignId) => `${PUBLIC_DATA_PATH}/campaigns/${campaignId}/encounters`,
|
||||
encounter: (campaignId, encounterId) => `${PUBLIC_DATA_PATH}/campaigns/${campaignId}/encounters/${encounterId}`,
|
||||
activeDisplay: () => `${PUBLIC_DATA_PATH}/activeDisplay/status`,
|
||||
logs: () => `${PUBLIC_DATA_PATH}/logs`
|
||||
};
|
||||
|
||||
let firebaseApp = null;
|
||||
let dbInstance = null;
|
||||
let authInstance = null;
|
||||
|
||||
export function initFirebase() {
|
||||
const config = {
|
||||
apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
|
||||
authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
|
||||
projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
|
||||
storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
|
||||
messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
|
||||
appId: process.env.REACT_APP_FIREBASE_APP_ID
|
||||
};
|
||||
const requiredKeys = ['apiKey', 'authDomain', 'projectId', 'appId'];
|
||||
const missing = requiredKeys.filter(k => !config[k]);
|
||||
if (missing.length > 0) {
|
||||
console.error(`CRITICAL: Missing Firebase config: ${missing.join(', ')}`);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
firebaseApp = initializeApp(config);
|
||||
dbInstance = getFirestore(firebaseApp);
|
||||
authInstance = getAuth(firebaseApp);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error('Firebase init failed:', err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function getDb() { return dbInstance; }
|
||||
export function getAuthInstance() { return authInstance; }
|
||||
|
||||
// ============================================================================
|
||||
// STORAGE ADAPTER
|
||||
// ============================================================================
|
||||
// Wraps SDK in the storage interface (getDoc/setDoc/etc).
|
||||
// App.js can now import { storage } and call storage.setDoc(path, data).
|
||||
// Hooks (useFirestoreDocument etc) still use SDK directly for now.
|
||||
|
||||
export function createFirebaseStorage() {
|
||||
const db = dbInstance;
|
||||
if (!db) throw new Error('Firestore not initialized. Call initFirebase() first.');
|
||||
|
||||
return {
|
||||
async getDoc(path) {
|
||||
const snap = await import('firebase/firestore').then(({ getDoc: gd, doc: d }) => gd(d(db, path)));
|
||||
return snap.exists() ? { id: snap.id, ...snap.data() } : null;
|
||||
},
|
||||
|
||||
async setDoc(path, data, opts = {}) {
|
||||
await setDoc(doc(db, path), data, opts.merge ? { merge: true } : undefined);
|
||||
},
|
||||
|
||||
async updateDoc(path, patch) {
|
||||
await updateDoc(doc(db, path), patch);
|
||||
},
|
||||
|
||||
async deleteDoc(path) {
|
||||
await deleteDoc(doc(db, path));
|
||||
},
|
||||
|
||||
async addDoc(collectionPath, data) {
|
||||
const ref = await addDoc(collection(db, collectionPath), data);
|
||||
return { id: ref.id, path: `${collectionPath}/${ref.id}` };
|
||||
},
|
||||
|
||||
async getCollection(collectionPath) {
|
||||
const snapshot = await import('firebase/firestore').then(({ getDocs: gd, collection: c }) => gd(c(db, collectionPath)));
|
||||
return snapshot.docs.map(d => ({ id: d.id, ...d.data() }));
|
||||
},
|
||||
|
||||
async batchWrite(ops) {
|
||||
const batch = writeBatch(db);
|
||||
for (const op of ops) {
|
||||
if (op.type === 'set') batch.set(doc(db, op.path), op.data);
|
||||
else if (op.type === 'delete') batch.delete(doc(db, op.path));
|
||||
else if (op.type === 'update') batch.update(doc(db, op.path), op.data);
|
||||
}
|
||||
await batch.commit();
|
||||
},
|
||||
|
||||
// Subscribe = onSnapshot. cb fires immediately + on change. Returns unsubscribe.
|
||||
subscribeDoc(path, cb) {
|
||||
return onSnapshot(doc(db, path), (snap) => {
|
||||
cb(snap.exists() ? { id: snap.id, ...snap.data() } : null);
|
||||
}, (err) => console.error(`subscribeDoc ${path}:`, err));
|
||||
},
|
||||
|
||||
subscribeCollection(collectionPath, cb, queryConstraints = []) {
|
||||
const q = queryConstraints.length > 0
|
||||
? query(collection(db, collectionPath), ...queryConstraints)
|
||||
: collection(db, collectionPath);
|
||||
return onSnapshot(q, (snap) => {
|
||||
cb(snap.docs.map(d => ({ id: d.id, ...d.data() })));
|
||||
}, (err) => console.error(`subscribeCollection ${collectionPath}:`, err));
|
||||
},
|
||||
|
||||
dispose() { /* SDK managed; no-op */ },
|
||||
};
|
||||
}
|
||||
|
||||
// Re-export SDK pieces App.js uses directly (until full refactor).
|
||||
export {
|
||||
doc, setDoc, updateDoc, deleteDoc, addDoc, collection, onSnapshot,
|
||||
query, orderBy, limit, writeBatch,
|
||||
};
|
||||
Reference in New Issue
Block a user