@techfinityedge/koolbase-react-native 4.0.0 → 4.2.1
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 +14 -3
- package/dist/index.js +1 -1
- package/dist/realtime.d.ts +9 -1
- package/dist/realtime.js +117 -29
- package/dist/types.d.ts +2 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -297,15 +297,26 @@ await Koolbase.storage.delete('avatars', `user-${userId}.jpg`);
|
|
|
297
297
|
|
|
298
298
|
## Realtime
|
|
299
299
|
|
|
300
|
-
|
|
300
|
+
Subscribe to live changes on a collection. Uses the signed-in user's session, so
|
|
301
|
+
subscribe after login. Streams `created`, `updated`, and `deleted` events for
|
|
302
|
+
collections whose read rule is `public` or `authenticated`.
|
|
303
|
+
|
|
304
|
+
```ts
|
|
301
305
|
const unsubscribe = Koolbase.realtime.subscribe('messages', (event) => {
|
|
302
|
-
|
|
306
|
+
// event.type -> 'created' | 'updated' | 'deleted'
|
|
307
|
+
if (event.type === 'deleted') {
|
|
308
|
+
console.log('deleted', event.recordId); // recordId on deletes
|
|
309
|
+
} else {
|
|
310
|
+
console.log(event.type, event.record!.data); // record on created/updated
|
|
311
|
+
}
|
|
303
312
|
});
|
|
304
313
|
|
|
305
|
-
// Cleanup
|
|
306
314
|
unsubscribe();
|
|
307
315
|
```
|
|
308
316
|
|
|
317
|
+
The socket opens lazily, is shared, and reconnects automatically. The project is
|
|
318
|
+
taken from the user's session..
|
|
319
|
+
|
|
309
320
|
---
|
|
310
321
|
|
|
311
322
|
## 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,146 @@
|
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
88
|
+
raw = JSON.parse(event.data);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
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)
|
|
98
|
+
return;
|
|
99
|
+
let msg;
|
|
100
|
+
if (mapped === 'deleted') {
|
|
101
|
+
msg = { type: 'deleted', collection: payload.collection, recordId: payload.record_id };
|
|
102
|
+
}
|
|
103
|
+
else if (payload.record) {
|
|
104
|
+
msg = { type: mapped, collection: payload.collection, record: (0, record_1.recordFromWire)(payload.record) };
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
return;
|
|
45
108
|
}
|
|
46
|
-
|
|
109
|
+
(this.listeners.get(payload.collection) ?? []).forEach((cb) => cb(msg));
|
|
47
110
|
};
|
|
48
|
-
|
|
49
|
-
this.
|
|
111
|
+
ws.onclose = () => {
|
|
112
|
+
this.connecting = false;
|
|
113
|
+
if (this.ws === ws)
|
|
114
|
+
this.ws = null;
|
|
115
|
+
this.scheduleReconnect();
|
|
50
116
|
};
|
|
117
|
+
ws.onerror = () => { };
|
|
118
|
+
}
|
|
119
|
+
sendSubscribe(collection) {
|
|
120
|
+
if (!this.projectId || !this.ws || this.ws.readyState !== WebSocket.OPEN)
|
|
121
|
+
return;
|
|
122
|
+
this.ws.send(JSON.stringify({ action: 'subscribe', project_id: this.projectId, collection }));
|
|
123
|
+
}
|
|
124
|
+
sendUnsubscribe(collection) {
|
|
125
|
+
if (!this.projectId || !this.ws || this.ws.readyState !== WebSocket.OPEN)
|
|
126
|
+
return;
|
|
127
|
+
this.ws.send(JSON.stringify({ action: 'unsubscribe', project_id: this.projectId, collection }));
|
|
128
|
+
}
|
|
129
|
+
scheduleReconnect() {
|
|
130
|
+
if (this.listeners.size === 0 || this.reconnectTimer)
|
|
131
|
+
return;
|
|
132
|
+
this.reconnectTimer = setTimeout(() => {
|
|
133
|
+
this.reconnectTimer = null;
|
|
134
|
+
void this.connect();
|
|
135
|
+
}, 3000);
|
|
51
136
|
}
|
|
52
137
|
disconnect() {
|
|
53
|
-
if (this.reconnectTimer)
|
|
138
|
+
if (this.reconnectTimer) {
|
|
54
139
|
clearTimeout(this.reconnectTimer);
|
|
140
|
+
this.reconnectTimer = null;
|
|
141
|
+
}
|
|
55
142
|
this.ws?.close();
|
|
56
143
|
this.ws = null;
|
|
144
|
+
this.projectId = null;
|
|
57
145
|
this.listeners.clear();
|
|
58
146
|
}
|
|
59
147
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -160,7 +160,8 @@ export interface UploadOptions {
|
|
|
160
160
|
export interface RealtimeEvent {
|
|
161
161
|
type: 'created' | 'updated' | 'deleted';
|
|
162
162
|
collection: string;
|
|
163
|
-
record
|
|
163
|
+
record?: KoolbaseRecord;
|
|
164
|
+
recordId?: string;
|
|
164
165
|
}
|
|
165
166
|
export type RealtimeCallback = (event: RealtimeEvent) => void;
|
|
166
167
|
export interface BootstrapPayload {
|
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.1",
|
|
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": [
|