@techfinityedge/koolbase-react-native 4.0.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 +16 -4
- 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
|
@@ -297,15 +297,27 @@ await Koolbase.storage.delete('avatars', `user-${userId}.jpg`);
|
|
|
297
297
|
|
|
298
298
|
## Realtime
|
|
299
299
|
|
|
300
|
-
|
|
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
|
+
|
|
301
307
|
const unsubscribe = Koolbase.realtime.subscribe('messages', (event) => {
|
|
302
|
-
|
|
308
|
+
// event.type -> 'created' | 'updated'
|
|
309
|
+
// event.collection -> 'messages'
|
|
310
|
+
// event.record -> KoolbaseRecord
|
|
311
|
+
console.log(event.type, event.record.data);
|
|
303
312
|
});
|
|
304
313
|
|
|
305
|
-
//
|
|
306
|
-
unsubscribe();
|
|
314
|
+
unsubscribe(); // stop listening
|
|
307
315
|
```
|
|
308
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
|
+
|
|
309
321
|
---
|
|
310
322
|
|
|
311
323
|
## Functions
|
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.
|
|
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": [
|