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.
- package/dist/auth/auth.d.ts +0 -2
- package/dist/auth/auth.js +0 -31
- package/dist/auth/presence.svelte.d.ts +74 -0
- package/dist/auth/presence.svelte.js +331 -0
- package/dist/firestore/doc.svelte.js +3 -1
- package/dist/firestore/document-mutations.svelte.d.ts +21 -12
- package/dist/firestore/document-mutations.svelte.js +137 -48
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/realtime/realtime.svelte.js +116 -0
- package/package.json +1 -1
package/dist/auth/auth.d.ts
CHANGED
|
@@ -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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
timestamps
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
+
}
|