svelte-firekit 0.0.18 → 0.0.20

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.
@@ -4,7 +4,6 @@ declare class FirekitAuth {
4
4
  private firestore;
5
5
  private constructor();
6
6
  static getInstance(): FirekitAuth;
7
- getDeviceIdentifier(): string;
8
7
  signInWithGoogle(): Promise<void>;
9
8
  signInWithEmail(email: string, password: string): Promise<void>;
10
9
  registerWithEmail(email: string, password: string, displayName: string): Promise<void>;
@@ -30,7 +29,6 @@ declare class FirekitAuth {
30
29
  success: boolean;
31
30
  message: string;
32
31
  }>;
33
- updateUserSession(user: any): Promise<void>;
34
32
  }
35
33
  export declare const firekitAuth: FirekitAuth;
36
34
  export {};
package/dist/auth/auth.js CHANGED
@@ -14,19 +14,14 @@ class FirekitAuth {
14
14
  }
15
15
  return FirekitAuth.instance;
16
16
  }
17
- getDeviceIdentifier() {
18
- return navigator.userAgent; // Puedes personalizar esto según tu preferencia.
19
- }
20
17
  async signInWithGoogle() {
21
18
  const provider = new GoogleAuthProvider();
22
19
  const result = await signInWithPopup(this.auth, provider);
23
20
  await this.updateUserInFirestore(result.user);
24
- await this.updateUserSession(result.user);
25
21
  }
26
22
  async signInWithEmail(email, password) {
27
23
  const result = await signInWithEmailAndPassword(this.auth, email, password);
28
24
  await this.updateUserInFirestore(result.user);
29
- await this.updateUserSession(result.user);
30
25
  }
31
26
  async registerWithEmail(email, password, displayName) {
32
27
  const result = await createUserWithEmailAndPassword(this.auth, email, password);
@@ -34,7 +29,6 @@ class FirekitAuth {
34
29
  if (user) {
35
30
  await updateProfile(user, { displayName });
36
31
  await this.updateUserInFirestore(user);
37
- await this.updateUserSession(user);
38
32
  await sendEmailVerification(user);
39
33
  }
40
34
  }
@@ -110,30 +104,5 @@ class FirekitAuth {
110
104
  throw new Error(error.message);
111
105
  }
112
106
  }
113
- async updateUserSession(user) {
114
- let nav = this.getDeviceIdentifier().replace(/[ /]/g, '');
115
- const sessionId = `${user.uid}_${nav}`;
116
- const db = firebaseService.getDatabaseInstance();
117
- const userSessionsRef = ref(db, `sessions/${user.uid}`);
118
- const userSessionsSnap = await get(userSessionsRef);
119
- let sessionDatas = [];
120
- if (userSessionsSnap.exists()) {
121
- sessionDatas = userSessionsSnap.val().sessionDatas || [];
122
- if (sessionDatas.some(session => session.uid === sessionId)) {
123
- console.log('Session already registered from this device.');
124
- return;
125
- }
126
- }
127
- const newSessionData = {
128
- uid: sessionId,
129
- userId: user.uid,
130
- device: this.getDeviceIdentifier(),
131
- createdAt: new Date().toISOString(),
132
- last_changed: new Date().toISOString(),
133
- status: "online"
134
- };
135
- sessionDatas.push(newSessionData);
136
- await set(userSessionsRef, { sessionDatas });
137
- }
138
107
  }
139
108
  export const firekitAuth = FirekitAuth.getInstance();
@@ -0,0 +1,74 @@
1
+ interface GeolocationConfig {
2
+ enabled: boolean;
3
+ type: 'browser' | 'ip' | 'custom';
4
+ customGeolocationFn?: () => Promise<{
5
+ latitude: number;
6
+ longitude: number;
7
+ }>;
8
+ ipServiceUrl?: string;
9
+ requireConsent?: boolean;
10
+ }
11
+ interface PresenceConfig {
12
+ geolocation?: GeolocationConfig;
13
+ sessionTTL?: number;
14
+ updateInterval?: number;
15
+ }
16
+ interface Location {
17
+ latitude: number | null;
18
+ longitude: number | null;
19
+ lastUpdated: string | null;
20
+ }
21
+ interface SessionData {
22
+ uid: string;
23
+ userId: string;
24
+ deviceId: string;
25
+ status: 'online' | 'offline' | 'away';
26
+ createdAt: string;
27
+ lastSeen: string;
28
+ location?: Location;
29
+ }
30
+ type PresenceEvent = {
31
+ type: 'status_change' | 'error' | 'init' | 'disconnect' | 'location_update';
32
+ data?: any;
33
+ error?: Error;
34
+ timestamp: number;
35
+ };
36
+ type PresenceEventCallback = (event: PresenceEvent) => void;
37
+ declare class PresenceService {
38
+ private static instance;
39
+ private connectedListener;
40
+ private locationWatcher;
41
+ private currentUser;
42
+ private eventListeners;
43
+ private config;
44
+ private initialized;
45
+ private locationConsent;
46
+ private _currentSession;
47
+ private _sessions;
48
+ private _status;
49
+ private _loading;
50
+ private _error;
51
+ private constructor();
52
+ static getInstance(): PresenceService;
53
+ get currentSession(): SessionData | null;
54
+ get sessions(): SessionData[];
55
+ get status(): "online" | "offline" | "away";
56
+ get loading(): boolean;
57
+ get error(): Error | null;
58
+ get isInitialized(): boolean;
59
+ get hasLocationConsent(): boolean;
60
+ private initializePresence;
61
+ private setupVisibilityListener;
62
+ private getDeviceInfo;
63
+ requestLocationConsent(): Promise<boolean>;
64
+ private getLocation;
65
+ initialize(user: any, config?: PresenceConfig): Promise<void>;
66
+ private setPresence;
67
+ private startLocationWatcher;
68
+ private stopLocationWatcher;
69
+ addEventListener(callback: PresenceEventCallback): () => boolean;
70
+ private emitEvent;
71
+ dispose(): void;
72
+ }
73
+ export declare const presenceService: PresenceService;
74
+ export {};
@@ -0,0 +1,331 @@
1
+ import { ref, onDisconnect, onValue, get, set } from 'firebase/database';
2
+ import { firebaseService } from '../firebase.js';
3
+ import { browser } from '$app/environment';
4
+ class PresenceService {
5
+ static instance;
6
+ connectedListener = null;
7
+ locationWatcher = null;
8
+ currentUser = null;
9
+ eventListeners = new Set();
10
+ config = {
11
+ geolocation: {
12
+ enabled: false,
13
+ type: 'browser',
14
+ requireConsent: true
15
+ },
16
+ sessionTTL: 30 * 60 * 1000, // 30 minutes
17
+ updateInterval: 60 * 1000 // 1 minute
18
+ };
19
+ initialized = $state(false);
20
+ locationConsent = $state(false);
21
+ _currentSession = $state(null);
22
+ _sessions = $state([]);
23
+ _status = $state('offline');
24
+ _loading = $state(true);
25
+ _error = $state(null);
26
+ constructor() {
27
+ if (browser) {
28
+ this.setupVisibilityListener();
29
+ }
30
+ }
31
+ static getInstance() {
32
+ if (!PresenceService.instance) {
33
+ PresenceService.instance = new PresenceService();
34
+ }
35
+ return PresenceService.instance;
36
+ }
37
+ // Getters
38
+ get currentSession() { return this._currentSession; }
39
+ get sessions() { return this._sessions; }
40
+ get status() { return this._status; }
41
+ get loading() { return this._loading; }
42
+ get error() { return this._error; }
43
+ get isInitialized() { return this.initialized; }
44
+ get hasLocationConsent() { return this.locationConsent; }
45
+ async initializePresence() {
46
+ const connectedRef = ref(firebaseService.getDatabaseInstance(), '.info/connected');
47
+ this.connectedListener = onValue(connectedRef, async (snapshot) => {
48
+ if (snapshot.val() === true) {
49
+ await this.setPresence('online');
50
+ const userStatusRef = ref(firebaseService.getDatabaseInstance(), `sessions/${this.currentUser.uid}`);
51
+ // Set up disconnect hook
52
+ await onDisconnect(userStatusRef).set({
53
+ sessionDatas: this._sessions.map(session => ({
54
+ ...session,
55
+ status: 'offline',
56
+ lastSeen: new Date().toISOString()
57
+ }))
58
+ });
59
+ }
60
+ else {
61
+ await this.setPresence('offline');
62
+ }
63
+ });
64
+ // Set up visibility change listener
65
+ if (browser) {
66
+ document.onvisibilitychange = async () => {
67
+ if (document.visibilityState === 'hidden') {
68
+ await this.setPresence('away');
69
+ }
70
+ else {
71
+ await this.setPresence('online');
72
+ }
73
+ };
74
+ }
75
+ }
76
+ setupVisibilityListener() {
77
+ if (browser) {
78
+ document.onvisibilitychange = async () => {
79
+ if (this.initialized) {
80
+ if (document.visibilityState === 'hidden') {
81
+ await this.setPresence('away');
82
+ }
83
+ else {
84
+ await this.setPresence('online');
85
+ }
86
+ }
87
+ };
88
+ }
89
+ }
90
+ getDeviceInfo() {
91
+ const userAgent = navigator.userAgent;
92
+ const platform = navigator.platform;
93
+ const browserInfo = userAgent.match(/(firefox|chrome|safari|opera|edge|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
94
+ const browser = browserInfo[1] || 'Unknown Browser';
95
+ return `${browser}-${platform}`.replace(/[^a-zA-Z0-9-]/g, '');
96
+ }
97
+ async requestLocationConsent() {
98
+ if (!this.config.geolocation?.enabled)
99
+ return false;
100
+ try {
101
+ if (this.config.geolocation.type === 'browser') {
102
+ const permission = await navigator.permissions.query({ name: 'geolocation' });
103
+ if (permission.state === 'granted') {
104
+ this.locationConsent = true;
105
+ return true;
106
+ }
107
+ const result = await new Promise((resolve) => {
108
+ navigator.geolocation.getCurrentPosition(() => resolve(true), () => resolve(false), { timeout: 10000 });
109
+ });
110
+ this.locationConsent = result;
111
+ return result;
112
+ }
113
+ return true;
114
+ }
115
+ catch (error) {
116
+ console.error('Error requesting location consent:', error);
117
+ return false;
118
+ }
119
+ }
120
+ async getLocation() {
121
+ if (!this.config.geolocation?.enabled ||
122
+ (this.config.geolocation.requireConsent && !this.locationConsent)) {
123
+ return null;
124
+ }
125
+ try {
126
+ switch (this.config.geolocation.type) {
127
+ case 'browser':
128
+ return new Promise((resolve) => {
129
+ navigator.geolocation.getCurrentPosition((position) => {
130
+ resolve({
131
+ latitude: position.coords.latitude,
132
+ longitude: position.coords.longitude,
133
+ lastUpdated: new Date().toISOString()
134
+ });
135
+ }, () => resolve(null), { timeout: 10000 });
136
+ });
137
+ case 'custom':
138
+ if (this.config.geolocation.customGeolocationFn) {
139
+ const result = await this.config.geolocation.customGeolocationFn();
140
+ return {
141
+ ...result,
142
+ lastUpdated: new Date().toISOString()
143
+ };
144
+ }
145
+ return null;
146
+ case 'ip':
147
+ if (this.config.geolocation.ipServiceUrl) {
148
+ const response = await fetch(this.config.geolocation.ipServiceUrl);
149
+ const data = await response.json();
150
+ return {
151
+ latitude: data.latitude,
152
+ longitude: data.longitude,
153
+ lastUpdated: new Date().toISOString()
154
+ };
155
+ }
156
+ return null;
157
+ default:
158
+ return null;
159
+ }
160
+ }
161
+ catch (error) {
162
+ console.error('Error getting location:', error);
163
+ return null;
164
+ }
165
+ }
166
+ async initialize(user, config) {
167
+ try {
168
+ if (!browser) {
169
+ throw new Error('Presence service can only be initialized in browser environment');
170
+ }
171
+ if (this.initialized) {
172
+ console.warn('Presence service is already initialized');
173
+ return;
174
+ }
175
+ this._loading = true;
176
+ this.currentUser = user;
177
+ this.config = { ...this.config, ...config };
178
+ // If geolocation is enabled and requires consent, request it
179
+ if (this.config.geolocation?.enabled && this.config.geolocation.requireConsent) {
180
+ await this.requestLocationConsent();
181
+ }
182
+ await this.initializePresence();
183
+ this.initialized = true;
184
+ // Start location watcher if enabled and has consent
185
+ if (this.config.geolocation?.enabled &&
186
+ (!this.config.geolocation.requireConsent || this.locationConsent)) {
187
+ this.startLocationWatcher();
188
+ }
189
+ this.emitEvent({
190
+ type: 'init',
191
+ data: { userId: user.uid },
192
+ timestamp: Date.now()
193
+ });
194
+ }
195
+ catch (error) {
196
+ this._error = error;
197
+ this.emitEvent({
198
+ type: 'error',
199
+ error: error,
200
+ timestamp: Date.now()
201
+ });
202
+ throw error;
203
+ }
204
+ finally {
205
+ this._loading = false;
206
+ }
207
+ }
208
+ async setPresence(status) {
209
+ try {
210
+ if (!this.currentUser) {
211
+ throw new Error('No authenticated user found');
212
+ }
213
+ const db = firebaseService.getDatabaseInstance();
214
+ const userSessionsRef = ref(db, `sessions/${this.currentUser.uid}`);
215
+ const deviceId = this.getDeviceInfo();
216
+ const sessionId = `${this.currentUser.uid}_${deviceId}`;
217
+ // Get location if enabled
218
+ const location = await this.getLocation();
219
+ const userSessionsSnap = await get(userSessionsRef);
220
+ let sessionDatas = [];
221
+ if (userSessionsSnap.exists()) {
222
+ sessionDatas = userSessionsSnap.val().sessionDatas || [];
223
+ const sessionIndex = sessionDatas.findIndex((session) => session.uid === sessionId);
224
+ if (sessionIndex !== -1) {
225
+ // Update existing session
226
+ sessionDatas[sessionIndex] = {
227
+ ...sessionDatas[sessionIndex],
228
+ status,
229
+ lastSeen: new Date().toISOString(),
230
+ ...(location && { location })
231
+ };
232
+ }
233
+ else {
234
+ // Create new session
235
+ sessionDatas.push({
236
+ uid: sessionId,
237
+ userId: this.currentUser.uid,
238
+ deviceId,
239
+ status,
240
+ createdAt: new Date().toISOString(),
241
+ lastSeen: new Date().toISOString(),
242
+ ...(location && { location })
243
+ });
244
+ }
245
+ }
246
+ else {
247
+ // First session for this user
248
+ sessionDatas = [{
249
+ uid: sessionId,
250
+ userId: this.currentUser.uid,
251
+ deviceId,
252
+ status,
253
+ createdAt: new Date().toISOString(),
254
+ lastSeen: new Date().toISOString(),
255
+ ...(location && { location })
256
+ }];
257
+ }
258
+ // Clean up stale sessions
259
+ if (this.config.sessionTTL) {
260
+ const cutoffTime = new Date(Date.now() - this.config.sessionTTL).toISOString();
261
+ sessionDatas = sessionDatas.filter(session => session.lastSeen >= cutoffTime);
262
+ }
263
+ await set(userSessionsRef, { sessionDatas });
264
+ this._sessions = sessionDatas;
265
+ this._currentSession = sessionDatas.find(session => session.uid === sessionId) || null;
266
+ this._status = status;
267
+ this.emitEvent({
268
+ type: 'status_change',
269
+ data: { status, sessionId, location },
270
+ timestamp: Date.now()
271
+ });
272
+ }
273
+ catch (error) {
274
+ this._error = error;
275
+ this.emitEvent({
276
+ type: 'error',
277
+ error: error,
278
+ timestamp: Date.now()
279
+ });
280
+ throw error;
281
+ }
282
+ }
283
+ startLocationWatcher() {
284
+ if (this.locationWatcher)
285
+ return;
286
+ const watchLocation = async () => {
287
+ const location = await this.getLocation();
288
+ if (location && this._currentSession) {
289
+ await this.setPresence(this._status);
290
+ this.emitEvent({
291
+ type: 'location_update',
292
+ data: { location },
293
+ timestamp: Date.now()
294
+ });
295
+ }
296
+ };
297
+ // Update location periodically
298
+ this.locationWatcher = window.setInterval(watchLocation, this.config.updateInterval);
299
+ }
300
+ stopLocationWatcher() {
301
+ if (this.locationWatcher) {
302
+ clearInterval(this.locationWatcher);
303
+ this.locationWatcher = null;
304
+ }
305
+ }
306
+ addEventListener(callback) {
307
+ this.eventListeners.add(callback);
308
+ return () => this.eventListeners.delete(callback);
309
+ }
310
+ emitEvent(event) {
311
+ this.eventListeners.forEach(callback => callback(event));
312
+ }
313
+ dispose() {
314
+ this.stopLocationWatcher();
315
+ if (this.connectedListener) {
316
+ this.connectedListener();
317
+ this.connectedListener = null;
318
+ }
319
+ this.initialized = false;
320
+ this._currentSession = null;
321
+ this._sessions = [];
322
+ this._status = 'offline';
323
+ this._loading = false;
324
+ this._error = null;
325
+ this.emitEvent({
326
+ type: 'disconnect',
327
+ timestamp: Date.now()
328
+ });
329
+ }
330
+ }
331
+ export const presenceService = PresenceService.getInstance();
@@ -15,7 +15,9 @@ class FirekitDoc {
15
15
  ? doc(firestore, ref)
16
16
  : ref;
17
17
  onSnapshot(this.docRef, (snapshot) => {
18
- this._data = snapshot.data() ?? null;
18
+ // this._data = (snapshot.data() as T) ?? null;
19
+ const data = snapshot.data();
20
+ this._data = data ? { ...data, id: snapshot.id } : null;
19
21
  this._loading = false;
20
22
  this._error = null;
21
23
  }, (error) => {
@@ -1,18 +1,27 @@
1
1
  import { type DocumentData, type WithFieldValue, type PartialWithFieldValue } from "firebase/firestore";
2
+ interface MutationResponse<T> {
3
+ success: boolean;
4
+ data?: T;
5
+ id?: string;
6
+ error?: {
7
+ code: string;
8
+ message: string;
9
+ };
10
+ }
11
+ interface MutationOptions {
12
+ timestamps?: boolean;
13
+ merge?: boolean;
14
+ customId?: string;
15
+ }
2
16
  declare class FirekitDocumentMutations {
3
- constructor();
4
- add<T extends DocumentData>(collectionPath: string, data: WithFieldValue<T>, options?: {
5
- timestamps?: boolean;
6
- }): Promise<import("@firebase/firestore").DocumentReference<DocumentData, DocumentData>>;
7
- set<T extends DocumentData>(path: string, data: WithFieldValue<T>, options?: {
8
- merge?: boolean;
9
- timestamps?: boolean;
10
- }): Promise<string>;
11
- update<T extends DocumentData>(path: string, data: PartialWithFieldValue<T>, options?: {
12
- timestamps?: boolean;
13
- }): Promise<void>;
14
- delete(path: string): Promise<void>;
17
+ private getTimestampData;
18
+ private handleError;
19
+ add<T extends DocumentData>(collectionPath: string, data: WithFieldValue<T>, options?: MutationOptions): Promise<MutationResponse<T>>;
20
+ set<T extends DocumentData>(path: string, data: WithFieldValue<T>, options?: MutationOptions): Promise<MutationResponse<T>>;
21
+ update<T extends DocumentData>(path: string, data: PartialWithFieldValue<T>, options?: MutationOptions): Promise<MutationResponse<Partial<T>>>;
22
+ delete(path: string): Promise<MutationResponse<void>>;
15
23
  exists(path: string): Promise<boolean>;
24
+ getDoc<T extends DocumentData>(path: string): Promise<MutationResponse<T>>;
16
25
  }
17
26
  export declare const firekitDocMutations: FirekitDocumentMutations;
18
27
  export {};
@@ -2,62 +2,151 @@ import { addDoc, setDoc, updateDoc, deleteDoc, doc, getDoc, collection, serverTi
2
2
  import { firebaseService } from "../firebase.js";
3
3
  import { firekitUser } from "../auth/user.svelte.js";
4
4
  class FirekitDocumentMutations {
5
- constructor() { }
6
- async add(collectionPath, data, options = { timestamps: true }) {
7
- const firestore = firebaseService.getDbInstance();
8
- const colRef = collection(firestore, collectionPath);
9
- const dataToAdd = {
10
- ...data,
11
- ...(options.timestamps && {
12
- createdAt: serverTimestamp(),
13
- createdBy: firekitUser.uid,
14
- updatedAt: serverTimestamp(),
15
- updatedBy: firekitUser.uid,
16
- }),
5
+ getTimestampData(isNew = true) {
6
+ const timestamps = {
7
+ updatedAt: serverTimestamp(),
8
+ updatedBy: firekitUser.uid,
17
9
  };
18
- return addDoc(colRef, dataToAdd);
19
- }
20
- async set(path, data, options = {
21
- merge: false,
22
- timestamps: true,
23
- }) {
24
- const firestore = firebaseService.getDbInstance();
25
- const docRef = doc(collection(firestore, path));
26
- const dataToSet = {
27
- ...data,
28
- ...(options.timestamps && {
29
- createdAt: serverTimestamp(),
30
- createdBy: firekitUser.uid,
31
- updatedAt: serverTimestamp(),
32
- updatedBy: firekitUser.uid,
33
- }),
34
- uid: docRef.id,
10
+ if (isNew) {
11
+ timestamps.createdAt = serverTimestamp();
12
+ timestamps.createdBy = firekitUser.uid;
13
+ }
14
+ return timestamps;
15
+ }
16
+ handleError(error) {
17
+ console.error('Firestore mutation error:', error);
18
+ return {
19
+ success: false,
20
+ error: {
21
+ code: error.code || 'unknown_error',
22
+ message: error.message || 'An unknown error occurred'
23
+ }
35
24
  };
36
- await setDoc(docRef, dataToSet, { merge: options.merge });
37
- return docRef.id;
25
+ }
26
+ async add(collectionPath, data, options = { timestamps: true }) {
27
+ try {
28
+ const firestore = firebaseService.getDbInstance();
29
+ const colRef = collection(firestore, collectionPath);
30
+ let dataToAdd = {
31
+ ...data,
32
+ ...(options.timestamps && this.getTimestampData()),
33
+ };
34
+ let docRef;
35
+ if (options.customId) {
36
+ docRef = doc(colRef, options.customId);
37
+ dataToAdd = { ...dataToAdd, id: docRef.id }; // Agregar el id al documento
38
+ await setDoc(docRef, dataToAdd);
39
+ }
40
+ else {
41
+ docRef = await addDoc(colRef, dataToAdd);
42
+ dataToAdd = { ...dataToAdd, id: docRef.id }; // Agregar el id al documento
43
+ await setDoc(docRef, dataToAdd);
44
+ }
45
+ return {
46
+ success: true,
47
+ id: docRef.id,
48
+ data: { ...dataToAdd, id: docRef.id }
49
+ };
50
+ }
51
+ catch (error) {
52
+ return this.handleError(error);
53
+ }
54
+ }
55
+ async set(path, data, options = { merge: false, timestamps: true }) {
56
+ try {
57
+ const firestore = firebaseService.getDbInstance();
58
+ let docRef;
59
+ if (path.includes('/')) {
60
+ docRef = doc(firestore, path);
61
+ }
62
+ else {
63
+ const [collectionPath, documentId] = path.split('/');
64
+ docRef = doc(collection(firestore, collectionPath), documentId);
65
+ }
66
+ const dataToSet = {
67
+ ...data,
68
+ ...(options.timestamps && this.getTimestampData()),
69
+ id: docRef.id,
70
+ };
71
+ await setDoc(docRef, dataToSet, { merge: options.merge });
72
+ return {
73
+ success: true,
74
+ id: docRef.id,
75
+ data: dataToSet
76
+ };
77
+ }
78
+ catch (error) {
79
+ return this.handleError(error);
80
+ }
38
81
  }
39
82
  async update(path, data, options = { timestamps: true }) {
40
- const firestore = firebaseService.getDbInstance();
41
- const docRef = doc(firestore, path);
42
- const dataToUpdate = {
43
- ...data,
44
- ...(options.timestamps && {
45
- updatedAt: serverTimestamp(),
46
- updatedBy: firekitUser.uid,
47
- }),
48
- };
49
- return updateDoc(docRef, dataToUpdate);
83
+ try {
84
+ const firestore = firebaseService.getDbInstance();
85
+ const docRef = doc(firestore, path);
86
+ const dataToUpdate = {
87
+ ...data,
88
+ ...(options.timestamps && this.getTimestampData(false)),
89
+ };
90
+ await updateDoc(docRef, dataToUpdate);
91
+ return {
92
+ success: true,
93
+ id: docRef.id,
94
+ data: dataToUpdate
95
+ };
96
+ }
97
+ catch (error) {
98
+ return this.handleError(error);
99
+ }
50
100
  }
51
101
  async delete(path) {
52
- const firestore = firebaseService.getDbInstance();
53
- const docRef = doc(firestore, path);
54
- return deleteDoc(docRef);
102
+ try {
103
+ const firestore = firebaseService.getDbInstance();
104
+ const docRef = doc(firestore, path);
105
+ await deleteDoc(docRef);
106
+ return {
107
+ success: true,
108
+ id: docRef.id
109
+ };
110
+ }
111
+ catch (error) {
112
+ return this.handleError(error);
113
+ }
55
114
  }
56
115
  async exists(path) {
57
- const firestore = firebaseService.getDbInstance();
58
- const docRef = doc(firestore, path);
59
- const docSnap = await getDoc(docRef);
60
- return docSnap.exists();
116
+ try {
117
+ const firestore = firebaseService.getDbInstance();
118
+ const docRef = doc(firestore, path);
119
+ const docSnap = await getDoc(docRef);
120
+ return docSnap.exists();
121
+ }
122
+ catch (error) {
123
+ console.error('Error checking document existence:', error);
124
+ return false;
125
+ }
126
+ }
127
+ async getDoc(path) {
128
+ try {
129
+ const firestore = firebaseService.getDbInstance();
130
+ const docRef = doc(firestore, path);
131
+ const docSnap = await getDoc(docRef);
132
+ if (!docSnap.exists()) {
133
+ return {
134
+ success: false,
135
+ error: {
136
+ code: 'not_found',
137
+ message: 'Document does not exist'
138
+ }
139
+ };
140
+ }
141
+ return {
142
+ success: true,
143
+ id: docSnap.id,
144
+ data: { id: docSnap.id, ...docSnap.data() }
145
+ };
146
+ }
147
+ catch (error) {
148
+ return this.handleError(error);
149
+ }
61
150
  }
62
151
  }
63
152
  export const firekitDocMutations = new FirekitDocumentMutations();
package/dist/index.d.ts CHANGED
@@ -2,10 +2,12 @@ export { firebaseConfig } from './config.js';
2
2
  export { firebaseService } from './firebase.js';
3
3
  export { firekitUser } from './auth/user.svelte.js';
4
4
  export { firekitAuth } from './auth/auth.js';
5
+ export { presenceService } from './auth/presence.svelte';
5
6
  export { firekitAwaitableDoc } from './firestore/awaitable-doc.svelte.js';
6
7
  export { firekitDocMutations } from './firestore/document-mutations.svelte';
7
8
  export { firekitCollection } from './firestore/collection.svelte.js';
8
9
  export { firekitDoc } from './firestore/doc.svelte.js';
10
+ export { firekitRealtimeDB } from './realtime/realtime.svelte.js';
9
11
  export { firekitDownloadUrl } from './storage/download-url.svelte.js';
10
12
  export { firekitStorageList } from './storage/storage-list.svelte.js';
11
13
  export { firekitUploadTask } from './storage/upload-task.svelte.js';
package/dist/index.js CHANGED
@@ -4,11 +4,14 @@ export { firebaseService } from './firebase.js';
4
4
  // auth services
5
5
  export { firekitUser } from './auth/user.svelte.js';
6
6
  export { firekitAuth } from './auth/auth.js';
7
+ export { presenceService } from './auth/presence.svelte';
7
8
  // firestore services
8
9
  export { firekitAwaitableDoc } from './firestore/awaitable-doc.svelte.js';
9
10
  export { firekitDocMutations } from './firestore/document-mutations.svelte';
10
11
  export { firekitCollection } from './firestore/collection.svelte.js';
11
12
  export { firekitDoc } from './firestore/doc.svelte.js';
13
+ // realtime services
14
+ export { firekitRealtimeDB } from './realtime/realtime.svelte.js';
12
15
  // Storage services
13
16
  export { firekitDownloadUrl } from './storage/download-url.svelte.js';
14
17
  export { firekitStorageList } from './storage/storage-list.svelte.js';
@@ -0,0 +1,116 @@
1
+ import { ref, onValue, push, set, update, remove } from "firebase/database";
2
+ import { firebaseService } from "../firebase.js";
3
+ import { browser } from "$app/environment";
4
+ class FirekitRealtimeDB {
5
+ _data = $state(null);
6
+ _loading = $state(true);
7
+ _error = $state(null);
8
+ dbRef = null;
9
+ unsubscribe = null;
10
+ constructor(path, startWith) {
11
+ this._data = startWith ?? null;
12
+ if (browser) {
13
+ this.initializeRealtimeDB(path);
14
+ }
15
+ }
16
+ initializeRealtimeDB(path) {
17
+ try {
18
+ const database = firebaseService.getDatabaseInstance();
19
+ this.dbRef = ref(database, path);
20
+ this.unsubscribe = onValue(this.dbRef, (snapshot) => {
21
+ this._data = snapshot.val();
22
+ this._loading = false;
23
+ this._error = null;
24
+ }, (error) => {
25
+ this._error = error;
26
+ this._loading = false;
27
+ });
28
+ }
29
+ catch (error) {
30
+ this._error = error;
31
+ this._loading = false;
32
+ }
33
+ }
34
+ // Push new data to the list
35
+ async push(data) {
36
+ if (!this.dbRef)
37
+ return null;
38
+ const newRef = push(this.dbRef);
39
+ await set(newRef, data);
40
+ return newRef.key;
41
+ }
42
+ // Set data at the reference
43
+ async set(data) {
44
+ if (!this.dbRef)
45
+ return;
46
+ await set(this.dbRef, data);
47
+ }
48
+ // Update data at the reference
49
+ async update(data) {
50
+ if (!this.dbRef)
51
+ return;
52
+ await update(this.dbRef, data);
53
+ }
54
+ // Remove data at the reference
55
+ async remove() {
56
+ if (!this.dbRef)
57
+ return;
58
+ await remove(this.dbRef);
59
+ }
60
+ // Getters for reactive state
61
+ get data() {
62
+ return this._data;
63
+ }
64
+ get loading() {
65
+ return this._loading;
66
+ }
67
+ get error() {
68
+ return this._error;
69
+ }
70
+ get ref() {
71
+ if (!this.dbRef) {
72
+ throw new Error("Database reference is not available");
73
+ }
74
+ return this.dbRef;
75
+ }
76
+ // Cleanup subscription
77
+ dispose() {
78
+ if (this.unsubscribe) {
79
+ this.unsubscribe();
80
+ }
81
+ }
82
+ }
83
+ /**
84
+ * Creates a reactive Realtime Database reference
85
+ * @param path Database path
86
+ * @param startWith Optional initial data
87
+ * @returns FirekitRealtimeDB instance
88
+ */
89
+ export function firekitRealtimeDB(path, startWith) {
90
+ return new FirekitRealtimeDB(path, startWith);
91
+ }
92
+ /**
93
+ * Creates a reactive Realtime Database list reference
94
+ * Automatically converts the data into an array format
95
+ * @param path Database path
96
+ * @param startWith Optional initial array data
97
+ * @returns FirekitRealtimeDB instance with array data
98
+ */
99
+ export function firekitRealtimeList(path, startWith = []) {
100
+ // Convert initial array to Record format
101
+ const startWithRecord = startWith.reduce((acc, item, index) => {
102
+ acc[`key${index}`] = item;
103
+ return acc;
104
+ }, {});
105
+ return new class extends FirekitRealtimeDB {
106
+ _list = $derived(this.data
107
+ ? Object.entries(this.data).map(([key, value]) => ({
108
+ id: key,
109
+ ...value,
110
+ }))
111
+ : []);
112
+ get list() {
113
+ return this._list;
114
+ }
115
+ }(path, startWithRecord);
116
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-firekit",
3
- "version": "0.0.18",
3
+ "version": "0.0.20",
4
4
  "license": "MIT",
5
5
  "scripts": {
6
6
  "dev": "vite dev",