@techfinityedge/koolbase-react-native 3.1.0 → 4.2.0
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/README.md +38 -4
- package/dist/database-errors.d.ts +5 -4
- package/dist/database-errors.js +5 -4
- package/dist/database.d.ts +25 -0
- package/dist/database.js +74 -38
- package/dist/index.js +1 -1
- package/dist/realtime.d.ts +9 -1
- package/dist/realtime.js +112 -29
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -257,6 +257,28 @@ const results = await Koolbase.db.batch([
|
|
|
257
257
|
|
|
258
258
|
---
|
|
259
259
|
|
|
260
|
+
### Handling write conflicts
|
|
261
|
+
|
|
262
|
+
`insert`, `update`, and `upsert` are online-first: when the server is reachable they throw a typed error on rejection. Catch `KoolbaseConflictError` to handle unique-constraint violations (e.g. a duplicate email):
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
import { KoolbaseConflictError } from '@techfinityedge/koolbase-react-native';
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
await Koolbase.db.insert('users', { email, name });
|
|
269
|
+
} catch (e) {
|
|
270
|
+
if (e instanceof KoolbaseConflictError) {
|
|
271
|
+
showError(`That ${e.field ?? 'value'} is already in use.`);
|
|
272
|
+
} else {
|
|
273
|
+
throw e;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
When the device is offline, these writes are queued and synced automatically when connectivity returns.
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
260
282
|
## Storage
|
|
261
283
|
|
|
262
284
|
```typescript
|
|
@@ -275,15 +297,27 @@ await Koolbase.storage.delete('avatars', `user-${userId}.jpg`);
|
|
|
275
297
|
|
|
276
298
|
## Realtime
|
|
277
299
|
|
|
278
|
-
|
|
300
|
+
Subscribe to live changes on a collection. Realtime uses the signed-in user's
|
|
301
|
+
session, so subscribe after login. It streams `created` and `updated` events for
|
|
302
|
+
collections whose read rule is `public` or `authenticated`.
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
import { Koolbase } from '@techfinityedge/koolbase-react-native';
|
|
306
|
+
|
|
279
307
|
const unsubscribe = Koolbase.realtime.subscribe('messages', (event) => {
|
|
280
|
-
|
|
308
|
+
// event.type -> 'created' | 'updated'
|
|
309
|
+
// event.collection -> 'messages'
|
|
310
|
+
// event.record -> KoolbaseRecord
|
|
311
|
+
console.log(event.type, event.record.data);
|
|
281
312
|
});
|
|
282
313
|
|
|
283
|
-
//
|
|
284
|
-
unsubscribe();
|
|
314
|
+
unsubscribe(); // stop listening
|
|
285
315
|
```
|
|
286
316
|
|
|
317
|
+
The socket opens lazily on first `subscribe`, is shared across all subscriptions,
|
|
318
|
+
and reconnects automatically. The project is taken from the user's session — you
|
|
319
|
+
don't pass it.
|
|
320
|
+
|
|
287
321
|
---
|
|
288
322
|
|
|
289
323
|
## Functions
|
|
@@ -20,10 +20,11 @@ export declare class KoolbaseDataError extends Error {
|
|
|
20
20
|
* (`details.field`) — useful when a collection has more than one unique
|
|
21
21
|
* constraint and you need to know which value clashed.
|
|
22
22
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
23
|
+
* Surfaced by `insert`, `update`, and `upsert` whenever the server is
|
|
24
|
+
* reachable and rejects the write with a 409. These writes are online-first:
|
|
25
|
+
* a server-side conflict throws immediately. Only a genuine network failure
|
|
26
|
+
* falls back to the offline queue, where a conflict that surfaces at sync
|
|
27
|
+
* time is handled by the sync engine rather than thrown here.
|
|
27
28
|
*
|
|
28
29
|
* @example
|
|
29
30
|
* try {
|
package/dist/database-errors.js
CHANGED
|
@@ -29,10 +29,11 @@ exports.KoolbaseDataError = KoolbaseDataError;
|
|
|
29
29
|
* (`details.field`) — useful when a collection has more than one unique
|
|
30
30
|
* constraint and you need to know which value clashed.
|
|
31
31
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
32
|
+
* Surfaced by `insert`, `update`, and `upsert` whenever the server is
|
|
33
|
+
* reachable and rejects the write with a 409. These writes are online-first:
|
|
34
|
+
* a server-side conflict throws immediately. Only a genuine network failure
|
|
35
|
+
* falls back to the offline queue, where a conflict that surfaces at sync
|
|
36
|
+
* time is handled by the sync engine rather than thrown here.
|
|
36
37
|
*
|
|
37
38
|
* @example
|
|
38
39
|
* try {
|
package/dist/database.d.ts
CHANGED
|
@@ -9,6 +9,18 @@ export declare class KoolbaseDatabase {
|
|
|
9
9
|
private request;
|
|
10
10
|
private runQuery;
|
|
11
11
|
query(collection: string, options?: QueryOptions): Promise<QueryResult>;
|
|
12
|
+
/**
|
|
13
|
+
* Insert a new record into a collection.
|
|
14
|
+
*
|
|
15
|
+
* Online-first: awaits the server so a server-side rejection (unique
|
|
16
|
+
* violation, validation error, permission denial) surfaces as the typed
|
|
17
|
+
* `KoolbaseDataError` subclass — `insert` now throws `KoolbaseConflictError`
|
|
18
|
+
* with the offending field on a 409, matching `upsert` and `update`.
|
|
19
|
+
*
|
|
20
|
+
* On genuine network failure (server unreachable, timeout) the write is
|
|
21
|
+
* accepted optimistically: saved to the local cache and queued for sync
|
|
22
|
+
* when connectivity returns.
|
|
23
|
+
*/
|
|
12
24
|
insert(collection: string, data: Record<string, unknown>): Promise<KoolbaseRecord>;
|
|
13
25
|
/**
|
|
14
26
|
* Insert a record, or update the existing one matching `match`.
|
|
@@ -63,6 +75,19 @@ export declare class KoolbaseDatabase {
|
|
|
63
75
|
*/
|
|
64
76
|
batch(operations: BatchOp[]): Promise<BatchResult[]>;
|
|
65
77
|
get(recordId: string): Promise<KoolbaseRecord>;
|
|
78
|
+
/**
|
|
79
|
+
* Update a record's fields by id.
|
|
80
|
+
*
|
|
81
|
+
* Online-first: awaits the server so a server-side rejection (unique
|
|
82
|
+
* violation, not found, permission denial) surfaces as the typed
|
|
83
|
+
* `KoolbaseDataError` subclass. An update that would violate a unique
|
|
84
|
+
* constraint now throws `KoolbaseConflictError` with the offending field —
|
|
85
|
+
* same shape as `insert` and `upsert`.
|
|
86
|
+
*
|
|
87
|
+
* On genuine network failure the update is queued for sync and a partial
|
|
88
|
+
* optimistic record is returned so the UI can re-render the new fields
|
|
89
|
+
* immediately.
|
|
90
|
+
*/
|
|
66
91
|
update(recordId: string, data: Record<string, unknown>): Promise<KoolbaseRecord>;
|
|
67
92
|
delete(recordId: string): Promise<void>;
|
|
68
93
|
syncPendingWrites(): Promise<void>;
|
package/dist/database.js
CHANGED
|
@@ -84,39 +84,56 @@ class KoolbaseDatabase {
|
|
|
84
84
|
await (0, cache_store_1.setCached)(userId, collection, queryHash, result);
|
|
85
85
|
return { ...result, isFromCache: false };
|
|
86
86
|
}
|
|
87
|
-
// ─── Insert (
|
|
87
|
+
// ─── Insert (online-first with offline fallback) ───────────────────────────
|
|
88
|
+
/**
|
|
89
|
+
* Insert a new record into a collection.
|
|
90
|
+
*
|
|
91
|
+
* Online-first: awaits the server so a server-side rejection (unique
|
|
92
|
+
* violation, validation error, permission denial) surfaces as the typed
|
|
93
|
+
* `KoolbaseDataError` subclass — `insert` now throws `KoolbaseConflictError`
|
|
94
|
+
* with the offending field on a 409, matching `upsert` and `update`.
|
|
95
|
+
*
|
|
96
|
+
* On genuine network failure (server unreachable, timeout) the write is
|
|
97
|
+
* accepted optimistically: saved to the local cache and queued for sync
|
|
98
|
+
* when connectivity returns.
|
|
99
|
+
*/
|
|
88
100
|
async insert(collection, data) {
|
|
89
101
|
const userId = this.getUserId() ?? 'anonymous';
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
data
|
|
95
|
-
|
|
96
|
-
updatedAt: new Date().toISOString(),
|
|
97
|
-
};
|
|
98
|
-
// Write to local cache immediately
|
|
99
|
-
await (0, cache_store_1.optimisticallyInsert)(userId, collection, optimisticRecord);
|
|
100
|
-
// Add to write queue
|
|
101
|
-
await (0, cache_store_1.addToWriteQueue)(userId, {
|
|
102
|
-
id: generateId(),
|
|
103
|
-
type: 'insert',
|
|
104
|
-
collection,
|
|
105
|
-
data,
|
|
106
|
-
});
|
|
107
|
-
// Try network in background
|
|
108
|
-
this.request('POST', '/v1/sdk/db/insert', {
|
|
109
|
-
collection,
|
|
110
|
-
data,
|
|
111
|
-
})
|
|
112
|
-
.then(async (serverRecord) => {
|
|
113
|
-
// Invalidate cache so next query gets real data
|
|
102
|
+
try {
|
|
103
|
+
// Online path: await the server and return the authoritative record
|
|
104
|
+
// (with the server-assigned id). Refresh the collection cache so the
|
|
105
|
+
// next query sees real data instead of a stale optimistic copy.
|
|
106
|
+
const raw = await this.request('POST', '/v1/sdk/db/insert', { collection, data });
|
|
107
|
+
const record = (0, record_1.recordFromWire)(raw);
|
|
114
108
|
await (0, cache_store_1.invalidateCache)(userId, collection);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
109
|
+
return record;
|
|
110
|
+
}
|
|
111
|
+
catch (e) {
|
|
112
|
+
// Server-reachable rejection: the server saw the request and refused.
|
|
113
|
+
// Surface to the caller without writing optimistic state or queuing —
|
|
114
|
+
// the server has already decided it will not accept this write, and
|
|
115
|
+
// queuing it would just spin SyncEngine until max retries.
|
|
116
|
+
if (e instanceof database_errors_1.KoolbaseDataError)
|
|
117
|
+
throw e;
|
|
118
|
+
// Genuine network failure → offline path: save to local cache and
|
|
119
|
+
// queue for SyncEngine to retry when online. Return the optimistic
|
|
120
|
+
// record so the UI has something to render in the meantime.
|
|
121
|
+
const optimisticRecord = {
|
|
122
|
+
id: generateId(),
|
|
123
|
+
createdBy: userId,
|
|
124
|
+
data,
|
|
125
|
+
createdAt: new Date().toISOString(),
|
|
126
|
+
updatedAt: new Date().toISOString(),
|
|
127
|
+
};
|
|
128
|
+
await (0, cache_store_1.optimisticallyInsert)(userId, collection, optimisticRecord);
|
|
129
|
+
await (0, cache_store_1.addToWriteQueue)(userId, {
|
|
130
|
+
id: generateId(),
|
|
131
|
+
type: 'insert',
|
|
132
|
+
collection,
|
|
133
|
+
data,
|
|
134
|
+
});
|
|
135
|
+
return optimisticRecord;
|
|
136
|
+
}
|
|
120
137
|
}
|
|
121
138
|
// ─── Upsert (online-only) ─────────────────────────────────────────────────
|
|
122
139
|
/**
|
|
@@ -246,20 +263,39 @@ class KoolbaseDatabase {
|
|
|
246
263
|
const raw = await this.request('GET', `/v1/sdk/db/records/${recordId}`);
|
|
247
264
|
return (0, record_1.recordFromWire)(raw);
|
|
248
265
|
}
|
|
249
|
-
// ─── Update
|
|
266
|
+
// ─── Update (online-first with offline fallback) ───────────────────────────
|
|
267
|
+
/**
|
|
268
|
+
* Update a record's fields by id.
|
|
269
|
+
*
|
|
270
|
+
* Online-first: awaits the server so a server-side rejection (unique
|
|
271
|
+
* violation, not found, permission denial) surfaces as the typed
|
|
272
|
+
* `KoolbaseDataError` subclass. An update that would violate a unique
|
|
273
|
+
* constraint now throws `KoolbaseConflictError` with the offending field —
|
|
274
|
+
* same shape as `insert` and `upsert`.
|
|
275
|
+
*
|
|
276
|
+
* On genuine network failure the update is queued for sync and a partial
|
|
277
|
+
* optimistic record is returned so the UI can re-render the new fields
|
|
278
|
+
* immediately.
|
|
279
|
+
*/
|
|
250
280
|
async update(recordId, data) {
|
|
251
281
|
const userId = this.getUserId() ?? 'anonymous';
|
|
252
|
-
await (0, cache_store_1.addToWriteQueue)(userId, {
|
|
253
|
-
id: generateId(),
|
|
254
|
-
type: 'update',
|
|
255
|
-
recordId,
|
|
256
|
-
data,
|
|
257
|
-
});
|
|
258
282
|
try {
|
|
259
283
|
const raw = await this.request('PATCH', `/v1/sdk/db/records/${recordId}`, { data });
|
|
260
284
|
return (0, record_1.recordFromWire)(raw);
|
|
261
285
|
}
|
|
262
|
-
catch {
|
|
286
|
+
catch (e) {
|
|
287
|
+
// Server-reachable rejection: surface to caller without queuing — the
|
|
288
|
+
// server already refused the write and will refuse it again on retry.
|
|
289
|
+
if (e instanceof database_errors_1.KoolbaseDataError)
|
|
290
|
+
throw e;
|
|
291
|
+
// Genuine network failure → queue for sync and return an optimistic
|
|
292
|
+
// partial record so the UI reflects the update immediately.
|
|
293
|
+
await (0, cache_store_1.addToWriteQueue)(userId, {
|
|
294
|
+
id: generateId(),
|
|
295
|
+
type: 'update',
|
|
296
|
+
recordId,
|
|
297
|
+
data,
|
|
298
|
+
});
|
|
263
299
|
return {
|
|
264
300
|
id: recordId,
|
|
265
301
|
data,
|
package/dist/index.js
CHANGED
|
@@ -69,7 +69,7 @@ exports.Koolbase = {
|
|
|
69
69
|
_auth = new auth_1.KoolbaseAuth(config);
|
|
70
70
|
_db = new database_1.KoolbaseDatabase(config, () => _auth?.currentUser?.id ?? null, () => _auth?.validAccessToken() ?? Promise.resolve(null));
|
|
71
71
|
_storage = new storage_1.KoolbaseStorage(config, () => _auth?.validAccessToken() ?? Promise.resolve(null));
|
|
72
|
-
_realtime = new realtime_1.KoolbaseRealtime(config);
|
|
72
|
+
_realtime = new realtime_1.KoolbaseRealtime(config, () => _auth?.validAccessToken() ?? Promise.resolve(null));
|
|
73
73
|
_functions = new functions_1.KoolbaseFunctions(config, () => _auth?.validAccessToken() ?? Promise.resolve(null));
|
|
74
74
|
_flags = new flags_1.KoolbaseFlags(config, 'rn-device');
|
|
75
75
|
_codePush = new code_push_1.KoolbaseCodePush(config, config.codePushChannel ?? 'stable');
|
package/dist/realtime.d.ts
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { KoolbaseConfig, RealtimeCallback } from './types';
|
|
2
|
+
type TokenProvider = () => Promise<string | null>;
|
|
2
3
|
export declare class KoolbaseRealtime {
|
|
3
4
|
private config;
|
|
5
|
+
private getToken;
|
|
4
6
|
private ws;
|
|
7
|
+
private projectId;
|
|
5
8
|
private listeners;
|
|
6
9
|
private reconnectTimer;
|
|
7
|
-
|
|
10
|
+
private connecting;
|
|
11
|
+
constructor(config: KoolbaseConfig, getToken: TokenProvider);
|
|
8
12
|
subscribe(collection: string, callback: RealtimeCallback): () => void;
|
|
9
13
|
private connect;
|
|
14
|
+
private sendSubscribe;
|
|
15
|
+
private sendUnsubscribe;
|
|
16
|
+
private scheduleReconnect;
|
|
10
17
|
disconnect(): void;
|
|
11
18
|
}
|
|
19
|
+
export {};
|
package/dist/realtime.js
CHANGED
|
@@ -2,58 +2,141 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.KoolbaseRealtime = void 0;
|
|
4
4
|
const record_1 = require("./record");
|
|
5
|
+
const EVENT_TYPE_MAP = {
|
|
6
|
+
'db.record.created': 'created',
|
|
7
|
+
'db.record.updated': 'updated',
|
|
8
|
+
'db.record.deleted': 'deleted',
|
|
9
|
+
};
|
|
10
|
+
function projectIdFromToken(token) {
|
|
11
|
+
try {
|
|
12
|
+
const part = token.split('.')[1];
|
|
13
|
+
if (!part)
|
|
14
|
+
return null;
|
|
15
|
+
const b64 = part.replace(/-/g, '+').replace(/_/g, '/');
|
|
16
|
+
const g = globalThis;
|
|
17
|
+
let json;
|
|
18
|
+
if (typeof g.atob === 'function') {
|
|
19
|
+
const bin = g.atob(b64);
|
|
20
|
+
json = decodeURIComponent(bin.split('').map((c) => '%' + c.charCodeAt(0).toString(16).padStart(2, '0')).join(''));
|
|
21
|
+
}
|
|
22
|
+
else if (g.Buffer) {
|
|
23
|
+
json = g.Buffer.from(b64, 'base64').toString('utf8');
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return JSON.parse(json).project_id ?? null;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
5
34
|
class KoolbaseRealtime {
|
|
6
|
-
constructor(config) {
|
|
35
|
+
constructor(config, getToken) {
|
|
7
36
|
this.ws = null;
|
|
37
|
+
this.projectId = null;
|
|
8
38
|
this.listeners = new Map();
|
|
9
39
|
this.reconnectTimer = null;
|
|
40
|
+
this.connecting = false;
|
|
10
41
|
this.config = config;
|
|
42
|
+
this.getToken = getToken;
|
|
11
43
|
}
|
|
12
44
|
subscribe(collection, callback) {
|
|
13
|
-
if (!this.listeners.has(collection))
|
|
45
|
+
if (!this.listeners.has(collection))
|
|
14
46
|
this.listeners.set(collection, []);
|
|
15
|
-
}
|
|
16
47
|
this.listeners.get(collection).push(callback);
|
|
17
|
-
if (
|
|
18
|
-
this.
|
|
48
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
49
|
+
this.sendSubscribe(collection);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
void this.connect();
|
|
19
53
|
}
|
|
20
|
-
// Return unsubscribe function
|
|
21
54
|
return () => {
|
|
22
55
|
const callbacks = this.listeners.get(collection) ?? [];
|
|
23
|
-
const
|
|
24
|
-
if (
|
|
25
|
-
callbacks.splice(
|
|
56
|
+
const i = callbacks.indexOf(callback);
|
|
57
|
+
if (i > -1)
|
|
58
|
+
callbacks.splice(i, 1);
|
|
59
|
+
if (callbacks.length === 0) {
|
|
60
|
+
this.listeners.delete(collection);
|
|
61
|
+
this.sendUnsubscribe(collection);
|
|
62
|
+
}
|
|
26
63
|
};
|
|
27
64
|
}
|
|
28
|
-
connect() {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
65
|
+
async connect() {
|
|
66
|
+
if (this.connecting)
|
|
67
|
+
return;
|
|
68
|
+
if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING))
|
|
69
|
+
return;
|
|
70
|
+
const token = await this.getToken();
|
|
71
|
+
if (!token) {
|
|
72
|
+
this.scheduleReconnect(); // sign-in may be in flight
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
this.projectId = projectIdFromToken(token);
|
|
76
|
+
this.connecting = true;
|
|
77
|
+
const wsUrl = this.config.baseUrl.replace('https://', 'wss://').replace('http://', 'ws://');
|
|
78
|
+
const ws = new WebSocket(`${wsUrl}/v1/realtime/ws?token=${encodeURIComponent(token)}`);
|
|
79
|
+
this.ws = ws;
|
|
80
|
+
ws.onopen = () => {
|
|
81
|
+
this.connecting = false;
|
|
82
|
+
for (const collection of this.listeners.keys())
|
|
83
|
+
this.sendSubscribe(collection); // (re)subscribe all
|
|
84
|
+
};
|
|
85
|
+
ws.onmessage = (event) => {
|
|
86
|
+
let raw;
|
|
34
87
|
try {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
type: raw.type,
|
|
40
|
-
collection: raw.collection,
|
|
41
|
-
record: (0, record_1.recordFromWire)(raw.record),
|
|
42
|
-
};
|
|
43
|
-
const callbacks = this.listeners.get(msg.collection) ?? [];
|
|
44
|
-
callbacks.forEach((cb) => cb(msg));
|
|
88
|
+
raw = JSON.parse(event.data);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return;
|
|
45
92
|
}
|
|
46
|
-
|
|
93
|
+
const mapped = EVENT_TYPE_MAP[raw?.type];
|
|
94
|
+
if (!mapped)
|
|
95
|
+
return; // ignore subscribed / unsubscribed / error / unknown
|
|
96
|
+
const payload = raw.payload;
|
|
97
|
+
if (!payload || !payload.collection || !payload.record)
|
|
98
|
+
return; // created/updated carry a record
|
|
99
|
+
const msg = {
|
|
100
|
+
type: mapped,
|
|
101
|
+
collection: payload.collection,
|
|
102
|
+
record: (0, record_1.recordFromWire)(payload.record),
|
|
103
|
+
};
|
|
104
|
+
(this.listeners.get(payload.collection) ?? []).forEach((cb) => cb(msg));
|
|
47
105
|
};
|
|
48
|
-
|
|
49
|
-
this.
|
|
106
|
+
ws.onclose = () => {
|
|
107
|
+
this.connecting = false;
|
|
108
|
+
if (this.ws === ws)
|
|
109
|
+
this.ws = null;
|
|
110
|
+
this.scheduleReconnect();
|
|
50
111
|
};
|
|
112
|
+
ws.onerror = () => { };
|
|
113
|
+
}
|
|
114
|
+
sendSubscribe(collection) {
|
|
115
|
+
if (!this.projectId || !this.ws || this.ws.readyState !== WebSocket.OPEN)
|
|
116
|
+
return;
|
|
117
|
+
this.ws.send(JSON.stringify({ action: 'subscribe', project_id: this.projectId, collection }));
|
|
118
|
+
}
|
|
119
|
+
sendUnsubscribe(collection) {
|
|
120
|
+
if (!this.projectId || !this.ws || this.ws.readyState !== WebSocket.OPEN)
|
|
121
|
+
return;
|
|
122
|
+
this.ws.send(JSON.stringify({ action: 'unsubscribe', project_id: this.projectId, collection }));
|
|
123
|
+
}
|
|
124
|
+
scheduleReconnect() {
|
|
125
|
+
if (this.listeners.size === 0 || this.reconnectTimer)
|
|
126
|
+
return;
|
|
127
|
+
this.reconnectTimer = setTimeout(() => {
|
|
128
|
+
this.reconnectTimer = null;
|
|
129
|
+
void this.connect();
|
|
130
|
+
}, 3000);
|
|
51
131
|
}
|
|
52
132
|
disconnect() {
|
|
53
|
-
if (this.reconnectTimer)
|
|
133
|
+
if (this.reconnectTimer) {
|
|
54
134
|
clearTimeout(this.reconnectTimer);
|
|
135
|
+
this.reconnectTimer = null;
|
|
136
|
+
}
|
|
55
137
|
this.ws?.close();
|
|
56
138
|
this.ws = null;
|
|
139
|
+
this.projectId = null;
|
|
57
140
|
this.listeners.clear();
|
|
58
141
|
}
|
|
59
142
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@techfinityedge/koolbase-react-native",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "React Native SDK for Koolbase
|
|
3
|
+
"version": "4.2.0",
|
|
4
|
+
"description": "React Native SDK for Koolbase — auth, database, storage, realtime, feature flags, and functions in one package.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"files": [
|