@koolbase/core 12.13.1 → 12.15.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/dist/cjs/auth.d.ts +31 -1
- package/dist/cjs/auth.js +167 -20
- package/dist/cjs/cache-store.d.ts +5 -0
- package/dist/cjs/cache-store.js +31 -0
- package/dist/cjs/connectivity.d.ts +25 -0
- package/dist/cjs/connectivity.js +59 -0
- package/dist/cjs/database.d.ts +32 -0
- package/dist/cjs/database.js +111 -16
- package/dist/cjs/fiscal.js +2 -2
- package/dist/cjs/functions.js +2 -2
- package/dist/cjs/index.d.ts +3 -1
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/network.d.ts +14 -0
- package/dist/cjs/network.js +60 -0
- package/dist/cjs/offline-state.d.ts +4 -0
- package/dist/cjs/offline-state.js +36 -0
- package/dist/cjs/platform.d.ts +16 -0
- package/dist/cjs/react/use-record.d.ts +1 -1
- package/dist/cjs/record-controller.d.ts +8 -1
- package/dist/cjs/record-controller.js +29 -7
- package/dist/cjs/shared.d.ts +1 -0
- package/dist/cjs/storage.js +11 -11
- package/dist/cjs/sync-engine.js +3 -3
- package/dist/cjs/types.d.ts +7 -0
- package/dist/esm/auth.d.ts +31 -1
- package/dist/esm/auth.js +168 -21
- package/dist/esm/cache-store.d.ts +5 -0
- package/dist/esm/cache-store.js +30 -0
- package/dist/esm/connectivity.d.ts +25 -0
- package/dist/esm/connectivity.js +55 -0
- package/dist/esm/database.d.ts +32 -0
- package/dist/esm/database.js +101 -6
- package/dist/esm/fiscal.js +3 -3
- package/dist/esm/functions.js +3 -3
- package/dist/esm/index.d.ts +3 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/network.d.ts +14 -0
- package/dist/esm/network.js +58 -0
- package/dist/esm/offline-state.d.ts +4 -0
- package/dist/esm/offline-state.js +35 -0
- package/dist/esm/platform.d.ts +16 -0
- package/dist/esm/react/use-record.d.ts +1 -1
- package/dist/esm/record-controller.d.ts +8 -1
- package/dist/esm/record-controller.js +29 -7
- package/dist/esm/shared.d.ts +1 -0
- package/dist/esm/storage.js +12 -12
- package/dist/esm/sync-engine.js +4 -4
- package/dist/esm/types.d.ts +7 -0
- package/package.json +1 -1
package/dist/cjs/auth.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AuthStateListener, KoolbaseConfig, KoolbaseSession, KoolbaseUser, LinkPhoneParams, LoginParams, OtpSendResult, PhoneVerifyResult, RegisterParams, RestoreResult, KoolbaseAuditPage, KoolbaseSessionInfo, ResendVerificationResult, SendOtpParams, SignUpResult, SignInWithAppleParams, VerifyOtpParams } from './types.js';
|
|
2
|
-
import type { SignInWithGoogleParams } from './types.js';
|
|
2
|
+
import type { SignInWithGoogleParams, SignOutReason } from './types.js';
|
|
3
3
|
export declare class KoolbaseAuth {
|
|
4
4
|
private config;
|
|
5
5
|
private storage;
|
|
@@ -9,6 +9,10 @@ export declare class KoolbaseAuth {
|
|
|
9
9
|
private timeoutMs;
|
|
10
10
|
private ongoingRefresh;
|
|
11
11
|
private listeners;
|
|
12
|
+
/** Why the last session ended (#104); null while signed in or never signed in. */
|
|
13
|
+
private signOutReason;
|
|
14
|
+
/** The last renewal failed for want of a connection, not because the server refused it (#96). */
|
|
15
|
+
private renewalUnavailable;
|
|
12
16
|
constructor(config: KoolbaseConfig);
|
|
13
17
|
/**
|
|
14
18
|
* Subscribe to authentication state changes. The listener fires:
|
|
@@ -82,6 +86,29 @@ export declare class KoolbaseAuth {
|
|
|
82
86
|
* need to.
|
|
83
87
|
*/
|
|
84
88
|
clearStoredSession(): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* Why the last session ended (#104): `'signed_out'` when the person signed
|
|
91
|
+
* out (or deleted their account), `'session_ended'` when it ended on its own
|
|
92
|
+
* -- revoked, past renewal, signed out on another device. null while signed
|
|
93
|
+
* in. Read it in an onAuthStateChange listener to say "You were signed out"
|
|
94
|
+
* only when they did not ask to be.
|
|
95
|
+
*/
|
|
96
|
+
get lastSignOutReason(): SignOutReason | null;
|
|
97
|
+
/**
|
|
98
|
+
* A data request carrying `refused` came back unauthenticated. Renews the
|
|
99
|
+
* session and returns the token to send again with, or null when there is
|
|
100
|
+
* none -- nobody is signed in, or the server refused the renewal too, which
|
|
101
|
+
* ends the session (#96).
|
|
102
|
+
*/
|
|
103
|
+
renewRejected(refused: string): Promise<string | null>;
|
|
104
|
+
/**
|
|
105
|
+
* A data request was refused as unauthenticated and renewing could not
|
|
106
|
+
* help: the session is over, so it ends here and every listener hears it.
|
|
107
|
+
* Unless the renewal failed only for want of a connection -- then the
|
|
108
|
+
* session may well be good, and signing someone out for a dropped network
|
|
109
|
+
* would be wrong (#96).
|
|
110
|
+
*/
|
|
111
|
+
sessionRejected(): Promise<void>;
|
|
85
112
|
private clearSessionInternal;
|
|
86
113
|
restoreSession(): Promise<RestoreResult>;
|
|
87
114
|
/**
|
|
@@ -164,6 +191,9 @@ export declare class KoolbaseAuth {
|
|
|
164
191
|
private parseAppleSessionResponse;
|
|
165
192
|
refresh(refreshToken?: string): Promise<KoolbaseSession>;
|
|
166
193
|
private _doRefresh;
|
|
194
|
+
private readStored;
|
|
195
|
+
/** A newer session another tab stored after renewing `spent`, made current; null if none. */
|
|
196
|
+
private adoptNewer;
|
|
167
197
|
logout(): Promise<boolean>;
|
|
168
198
|
forgotPassword(email: string): Promise<void>;
|
|
169
199
|
/**
|
package/dist/cjs/auth.js
CHANGED
|
@@ -11,6 +11,10 @@ class KoolbaseAuth {
|
|
|
11
11
|
this.session = null;
|
|
12
12
|
this.ongoingRefresh = null;
|
|
13
13
|
this.listeners = new Set();
|
|
14
|
+
/** Why the last session ended (#104); null while signed in or never signed in. */
|
|
15
|
+
this.signOutReason = null;
|
|
16
|
+
/** The last renewal failed for want of a connection, not because the server refused it (#96). */
|
|
17
|
+
this.renewalUnavailable = false;
|
|
14
18
|
this.config = config;
|
|
15
19
|
this.metadata = new device_metadata_js_1.DeviceMetadata(config.appVersion);
|
|
16
20
|
this.fetchFn = config.fetch ?? ((url, init) => (0, network_js_1.koolbaseFetch)(url, init));
|
|
@@ -152,6 +156,7 @@ class KoolbaseAuth {
|
|
|
152
156
|
}
|
|
153
157
|
async setSessionInternal(session) {
|
|
154
158
|
this.session = session;
|
|
159
|
+
this.signOutReason = null;
|
|
155
160
|
if (this.storage) {
|
|
156
161
|
try {
|
|
157
162
|
await this.storage.saveSession(session);
|
|
@@ -178,9 +183,52 @@ class KoolbaseAuth {
|
|
|
178
183
|
* need to.
|
|
179
184
|
*/
|
|
180
185
|
async clearStoredSession() {
|
|
181
|
-
await this.clearSessionInternal();
|
|
186
|
+
await this.clearSessionInternal('session_ended');
|
|
182
187
|
}
|
|
183
|
-
|
|
188
|
+
/**
|
|
189
|
+
* Why the last session ended (#104): `'signed_out'` when the person signed
|
|
190
|
+
* out (or deleted their account), `'session_ended'` when it ended on its own
|
|
191
|
+
* -- revoked, past renewal, signed out on another device. null while signed
|
|
192
|
+
* in. Read it in an onAuthStateChange listener to say "You were signed out"
|
|
193
|
+
* only when they did not ask to be.
|
|
194
|
+
*/
|
|
195
|
+
get lastSignOutReason() {
|
|
196
|
+
return this.signOutReason;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* A data request carrying `refused` came back unauthenticated. Renews the
|
|
200
|
+
* session and returns the token to send again with, or null when there is
|
|
201
|
+
* none -- nobody is signed in, or the server refused the renewal too, which
|
|
202
|
+
* ends the session (#96).
|
|
203
|
+
*/
|
|
204
|
+
async renewRejected(refused) {
|
|
205
|
+
if (!this.session)
|
|
206
|
+
return null;
|
|
207
|
+
// Another request renewed it while this one was out.
|
|
208
|
+
if (this.session.accessToken !== refused)
|
|
209
|
+
return this.session.accessToken;
|
|
210
|
+
try {
|
|
211
|
+
return (await this.refresh()).accessToken;
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* A data request was refused as unauthenticated and renewing could not
|
|
219
|
+
* help: the session is over, so it ends here and every listener hears it.
|
|
220
|
+
* Unless the renewal failed only for want of a connection -- then the
|
|
221
|
+
* session may well be good, and signing someone out for a dropped network
|
|
222
|
+
* would be wrong (#96).
|
|
223
|
+
*/
|
|
224
|
+
async sessionRejected() {
|
|
225
|
+
if (!this.session || this.renewalUnavailable)
|
|
226
|
+
return;
|
|
227
|
+
await this.clearSessionInternal('session_ended');
|
|
228
|
+
}
|
|
229
|
+
async clearSessionInternal(reason = 'session_ended') {
|
|
230
|
+
if (this.session)
|
|
231
|
+
this.signOutReason = reason;
|
|
184
232
|
this.session = null;
|
|
185
233
|
if (this.storage) {
|
|
186
234
|
try {
|
|
@@ -208,7 +256,28 @@ class KoolbaseAuth {
|
|
|
208
256
|
: 0;
|
|
209
257
|
const oneMinuteMs = 60 * 1000;
|
|
210
258
|
if (expiresAt > Date.now() + oneMinuteMs) {
|
|
211
|
-
|
|
259
|
+
// Valid by the clock is not valid: the session may have been signed out
|
|
260
|
+
// on another device, revoked, or the account disabled since. Ask the
|
|
261
|
+
// server before saying it is restored (#104). No answer -- offline, a
|
|
262
|
+
// timeout, a server error -- keeps the optimistic session: Offline.
|
|
263
|
+
try {
|
|
264
|
+
const res = await this.authRequest('/v1/sdk/auth/me', { method: 'GET', includeAuth: true });
|
|
265
|
+
if (res.ok) {
|
|
266
|
+
try {
|
|
267
|
+
await this.updateUserAndPersist(this.mapUser(await res.json()));
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
// The session is good; a user that will not parse keeps the stored one.
|
|
271
|
+
}
|
|
272
|
+
return types_js_1.RestoreResult.Restored;
|
|
273
|
+
}
|
|
274
|
+
if (res.status !== 401)
|
|
275
|
+
return types_js_1.RestoreResult.Offline;
|
|
276
|
+
// Refused: the access token is done. The refresh token decides.
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
return types_js_1.RestoreResult.Offline;
|
|
280
|
+
}
|
|
212
281
|
}
|
|
213
282
|
try {
|
|
214
283
|
await this.refresh(persisted.refreshToken);
|
|
@@ -224,8 +293,8 @@ class KoolbaseAuth {
|
|
|
224
293
|
// key or the request rather than the user's session — and deleting the
|
|
225
294
|
// refresh token on that reading signs someone out with no way back.
|
|
226
295
|
// Nothing reaches here with it today; it stays out so nothing does.
|
|
227
|
-
if (e
|
|
228
|
-
await this.clearSessionInternal();
|
|
296
|
+
if (isRejection(e)) {
|
|
297
|
+
await this.clearSessionInternal('session_ended');
|
|
229
298
|
return types_js_1.RestoreResult.Expired;
|
|
230
299
|
}
|
|
231
300
|
return types_js_1.RestoreResult.Offline;
|
|
@@ -498,17 +567,77 @@ class KoolbaseAuth {
|
|
|
498
567
|
return promise;
|
|
499
568
|
}
|
|
500
569
|
async _doRefresh(refreshToken) {
|
|
501
|
-
|
|
570
|
+
// The newest refresh token is the stored one: another tab of the same app
|
|
571
|
+
// may have renewed the session, and its in-memory copy here is spent.
|
|
572
|
+
const stored = refreshToken ? null : await this.readStored();
|
|
573
|
+
const token = refreshToken ?? stored?.refreshToken ?? this.session?.refreshToken;
|
|
502
574
|
if (!token) {
|
|
503
575
|
throw new auth_errors_js_1.SessionExpiredError();
|
|
504
576
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
577
|
+
try {
|
|
578
|
+
const res = await this.authRequest('/v1/sdk/auth/refresh', {
|
|
579
|
+
method: 'POST',
|
|
580
|
+
body: { refresh_token: token },
|
|
581
|
+
});
|
|
582
|
+
const session = await this.parseSessionResponse(res, true);
|
|
583
|
+
this.renewalUnavailable = false;
|
|
584
|
+
await this.setSessionInternal(session);
|
|
585
|
+
return session;
|
|
586
|
+
}
|
|
587
|
+
catch (e) {
|
|
588
|
+
if (!isRejection(e)) {
|
|
589
|
+
// No answer, or a server error: the session may be fine. Keep it.
|
|
590
|
+
this.renewalUnavailable = true;
|
|
591
|
+
throw e;
|
|
592
|
+
}
|
|
593
|
+
this.renewalUnavailable = false;
|
|
594
|
+
// Two tabs renewing at the same moment send the same token; the
|
|
595
|
+
// server renews one and refuses the other. The winner's session is in
|
|
596
|
+
// storage: take it rather than signing out (#96).
|
|
597
|
+
const adopted = await this.adoptNewer(token);
|
|
598
|
+
if (adopted)
|
|
599
|
+
return adopted;
|
|
600
|
+
await this.clearSessionInternal('session_ended');
|
|
601
|
+
throw e;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
async readStored() {
|
|
605
|
+
if (!this.storage)
|
|
606
|
+
return null;
|
|
607
|
+
try {
|
|
608
|
+
return await this.storage.readSession();
|
|
609
|
+
}
|
|
610
|
+
catch {
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
/** A newer session another tab stored after renewing `spent`, made current; null if none. */
|
|
615
|
+
async adoptNewer(spent) {
|
|
616
|
+
for (const wait of [0, 400]) {
|
|
617
|
+
if (wait)
|
|
618
|
+
await new Promise((r) => setTimeout(r, wait));
|
|
619
|
+
const stored = await this.readStored();
|
|
620
|
+
if (!stored || stored.refreshToken === spent)
|
|
621
|
+
continue;
|
|
622
|
+
const expiresAt = stored.expiresAt ? new Date(stored.expiresAt).getTime() : 0;
|
|
623
|
+
if (expiresAt > Date.now() + 60 * 1000) {
|
|
624
|
+
await this.setSessionInternal(stored);
|
|
625
|
+
return stored;
|
|
626
|
+
}
|
|
627
|
+
try {
|
|
628
|
+
const res = await this.authRequest('/v1/sdk/auth/refresh', {
|
|
629
|
+
method: 'POST',
|
|
630
|
+
body: { refresh_token: stored.refreshToken },
|
|
631
|
+
});
|
|
632
|
+
const session = await this.parseSessionResponse(res, true);
|
|
633
|
+
await this.setSessionInternal(session);
|
|
634
|
+
return session;
|
|
635
|
+
}
|
|
636
|
+
catch {
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
return null;
|
|
512
641
|
}
|
|
513
642
|
async logout() {
|
|
514
643
|
let serverSucceeded = true;
|
|
@@ -529,7 +658,7 @@ class KoolbaseAuth {
|
|
|
529
658
|
serverSucceeded = false;
|
|
530
659
|
}
|
|
531
660
|
finally {
|
|
532
|
-
await this.clearSessionInternal();
|
|
661
|
+
await this.clearSessionInternal('signed_out');
|
|
533
662
|
}
|
|
534
663
|
return serverSucceeded;
|
|
535
664
|
}
|
|
@@ -692,7 +821,7 @@ class KoolbaseAuth {
|
|
|
692
821
|
includeAuth: true,
|
|
693
822
|
});
|
|
694
823
|
await this.checkResponse(res);
|
|
695
|
-
await this.clearSessionInternal();
|
|
824
|
+
await this.clearSessionInternal('signed_out');
|
|
696
825
|
}
|
|
697
826
|
/**
|
|
698
827
|
* Every session the signed-in user has — a "where you're signed in" list.
|
|
@@ -815,8 +944,14 @@ class KoolbaseAuth {
|
|
|
815
944
|
try {
|
|
816
945
|
return await this._ensureValidToken();
|
|
817
946
|
}
|
|
818
|
-
catch {
|
|
819
|
-
|
|
947
|
+
catch (e) {
|
|
948
|
+
// Refused: the session has ended (and been cleared); go api-key-only.
|
|
949
|
+
if (isRejection(e))
|
|
950
|
+
return null;
|
|
951
|
+
// No answer: the session may be fine, so the request must not go out
|
|
952
|
+
// as a signed-out visitor (whose refusal would end it). It fails as
|
|
953
|
+
// the network failure it is, and the caller retries (#96).
|
|
954
|
+
throw e;
|
|
820
955
|
}
|
|
821
956
|
}
|
|
822
957
|
async setSession(session) {
|
|
@@ -1064,9 +1199,9 @@ class KoolbaseAuth {
|
|
|
1064
1199
|
return session.accessToken;
|
|
1065
1200
|
}
|
|
1066
1201
|
catch (e) {
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
throw
|
|
1202
|
+
// A refusal stays a refusal; a network failure stays one too, rather
|
|
1203
|
+
// than being reported as an ended session (#96).
|
|
1204
|
+
throw e;
|
|
1070
1205
|
}
|
|
1071
1206
|
}
|
|
1072
1207
|
mapUser(raw) {
|
|
@@ -1314,3 +1449,15 @@ class KoolbaseAuth {
|
|
|
1314
1449
|
}
|
|
1315
1450
|
}
|
|
1316
1451
|
exports.KoolbaseAuth = KoolbaseAuth;
|
|
1452
|
+
/**
|
|
1453
|
+
* Whether an error is the server refusing the session -- it ended -- rather
|
|
1454
|
+
* than no answer at all (#96). Only a refusal signs anyone out.
|
|
1455
|
+
*/
|
|
1456
|
+
function isRejection(e) {
|
|
1457
|
+
if (e instanceof auth_errors_js_1.SessionExpiredError || e instanceof auth_errors_js_1.TokenRevokedError || e instanceof auth_errors_js_1.UserDisabledError)
|
|
1458
|
+
return true;
|
|
1459
|
+
if (e instanceof auth_errors_js_1.KoolbaseAuthError) {
|
|
1460
|
+
return ['invalid_refresh_token', 'token_revoked', 'account_disabled', 'mfa_session_insufficient', 'unauthenticated'].includes(e.code ?? '');
|
|
1461
|
+
}
|
|
1462
|
+
return false;
|
|
1463
|
+
}
|
|
@@ -67,3 +67,8 @@ export declare function optimisticallyInsert(userId: string, collection: string,
|
|
|
67
67
|
* to get rid of one record.
|
|
68
68
|
*/
|
|
69
69
|
export declare function removeFromCachedQueries(userId: string, collection: string, recordId: string): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Applies an offline edit to the record wherever a cached result holds it, so
|
|
72
|
+
* a saved list shows what the user just changed rather than what it was.
|
|
73
|
+
*/
|
|
74
|
+
export declare function updateInCachedQueries(userId: string, collection: string, recordId: string, data: Record<string, unknown>): Promise<void>;
|
package/dist/cjs/cache-store.js
CHANGED
|
@@ -14,6 +14,7 @@ exports.removeFromWriteQueue = removeFromWriteQueue;
|
|
|
14
14
|
exports.incrementWriteRetry = incrementWriteRetry;
|
|
15
15
|
exports.optimisticallyInsert = optimisticallyInsert;
|
|
16
16
|
exports.removeFromCachedQueries = removeFromCachedQueries;
|
|
17
|
+
exports.updateInCachedQueries = updateInCachedQueries;
|
|
17
18
|
const platform_js_1 = require("./platform.js");
|
|
18
19
|
const CACHE_VERSION = 'v1';
|
|
19
20
|
function cacheKey(userId, collection, queryHash) {
|
|
@@ -244,3 +245,33 @@ async function removeFromCachedQueries(userId, collection, recordId) {
|
|
|
244
245
|
// ignore
|
|
245
246
|
}
|
|
246
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Applies an offline edit to the record wherever a cached result holds it, so
|
|
250
|
+
* a saved list shows what the user just changed rather than what it was.
|
|
251
|
+
*/
|
|
252
|
+
async function updateInCachedQueries(userId, collection, recordId, data) {
|
|
253
|
+
try {
|
|
254
|
+
const keys = await (0, platform_js_1.getPlatform)().storage.getAllKeys();
|
|
255
|
+
const prefix = `koolbase:${CACHE_VERSION}:${userId}:${collection}:`;
|
|
256
|
+
for (const key of keys.filter(k => k.startsWith(prefix))) {
|
|
257
|
+
const raw = await (0, platform_js_1.getPlatform)().storage.getItem(key);
|
|
258
|
+
if (!raw)
|
|
259
|
+
continue;
|
|
260
|
+
const cached = JSON.parse(raw);
|
|
261
|
+
if (!Array.isArray(cached.records))
|
|
262
|
+
continue;
|
|
263
|
+
let changed = false;
|
|
264
|
+
cached.records = cached.records.map(r => {
|
|
265
|
+
if (r.id !== recordId)
|
|
266
|
+
return r;
|
|
267
|
+
changed = true;
|
|
268
|
+
return { ...r, data: { ...(r.data ?? {}), ...data } };
|
|
269
|
+
});
|
|
270
|
+
if (changed)
|
|
271
|
+
await (0, platform_js_1.getPlatform)().storage.setItem(key, JSON.stringify(cached));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
// ignore
|
|
276
|
+
}
|
|
277
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether the device reports a network connection. A hint, not a promise that
|
|
3
|
+
* Koolbase is reachable: requests can still fail while online.
|
|
4
|
+
*
|
|
5
|
+
* 'unknown' until the platform first answers -- a real state, not a guess. An
|
|
6
|
+
* app should neither show "offline" nor run online-only logic on it.
|
|
7
|
+
*/
|
|
8
|
+
export type ConnectivityState = 'unknown' | 'online' | 'offline';
|
|
9
|
+
/**
|
|
10
|
+
* The device's connectivity, as state an app can render: getState() for now,
|
|
11
|
+
* subscribe() for changes (the shape React's useSyncExternalStore takes).
|
|
12
|
+
*/
|
|
13
|
+
export declare class KoolbaseConnectivity {
|
|
14
|
+
private state;
|
|
15
|
+
private readonly listeners;
|
|
16
|
+
private stop;
|
|
17
|
+
private heard;
|
|
18
|
+
/** Starts following the platform. Called once by Koolbase.initialize. */
|
|
19
|
+
start(): void;
|
|
20
|
+
getState(): ConnectivityState;
|
|
21
|
+
/** Called on every change. Returns the unsubscribe. */
|
|
22
|
+
subscribe(listener: () => void): () => void;
|
|
23
|
+
dispose(): void;
|
|
24
|
+
private set;
|
|
25
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KoolbaseConnectivity = void 0;
|
|
4
|
+
const platform_js_1 = require("./platform.js");
|
|
5
|
+
/**
|
|
6
|
+
* The device's connectivity, as state an app can render: getState() for now,
|
|
7
|
+
* subscribe() for changes (the shape React's useSyncExternalStore takes).
|
|
8
|
+
*/
|
|
9
|
+
class KoolbaseConnectivity {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.state = 'unknown';
|
|
12
|
+
this.listeners = new Set();
|
|
13
|
+
this.stop = null;
|
|
14
|
+
// A change event is fresher than the first read: once one arrives, a late
|
|
15
|
+
// answer to that read is ignored.
|
|
16
|
+
this.heard = false;
|
|
17
|
+
}
|
|
18
|
+
/** Starts following the platform. Called once by Koolbase.initialize. */
|
|
19
|
+
start() {
|
|
20
|
+
if (this.stop)
|
|
21
|
+
return;
|
|
22
|
+
const network = (0, platform_js_1.getPlatform)().network;
|
|
23
|
+
this.stop = network.onChange((online) => {
|
|
24
|
+
this.heard = true;
|
|
25
|
+
this.set(online ? 'online' : 'offline');
|
|
26
|
+
});
|
|
27
|
+
if (network.current) {
|
|
28
|
+
network.current().then((online) => {
|
|
29
|
+
if (!this.heard && online !== null && online !== undefined)
|
|
30
|
+
this.set(online ? 'online' : 'offline');
|
|
31
|
+
}, () => { });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
getState() {
|
|
35
|
+
return this.state;
|
|
36
|
+
}
|
|
37
|
+
/** Called on every change. Returns the unsubscribe. */
|
|
38
|
+
subscribe(listener) {
|
|
39
|
+
this.listeners.add(listener);
|
|
40
|
+
return () => { this.listeners.delete(listener); };
|
|
41
|
+
}
|
|
42
|
+
dispose() {
|
|
43
|
+
this.stop?.();
|
|
44
|
+
this.stop = null;
|
|
45
|
+
this.listeners.clear();
|
|
46
|
+
}
|
|
47
|
+
set(next) {
|
|
48
|
+
if (next === this.state)
|
|
49
|
+
return;
|
|
50
|
+
this.state = next;
|
|
51
|
+
for (const listener of [...this.listeners]) {
|
|
52
|
+
try {
|
|
53
|
+
listener();
|
|
54
|
+
}
|
|
55
|
+
catch { /* one broken listener must not stop the rest */ }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
exports.KoolbaseConnectivity = KoolbaseConnectivity;
|
package/dist/cjs/database.d.ts
CHANGED
|
@@ -119,6 +119,16 @@ export declare class KoolbaseDatabase {
|
|
|
119
119
|
*/
|
|
120
120
|
batch(operations: BatchOp[]): Promise<BatchResult[]>;
|
|
121
121
|
get(recordId: string): Promise<KoolbaseRecord>;
|
|
122
|
+
/**
|
|
123
|
+
* The device's saved copy of a record: what this device last read, with the
|
|
124
|
+
* changes queued on it applied. Never contacts the server. Null when there is
|
|
125
|
+
* none: never read here, or deleted on this device.
|
|
126
|
+
*
|
|
127
|
+
* For showing something at once while get() asks the server -- saved-first,
|
|
128
|
+
* never saved-only. Kept data and revision, not createdAt or createdBy, which
|
|
129
|
+
* the saved copy does not hold (empty strings here).
|
|
130
|
+
*/
|
|
131
|
+
getSaved(recordId: string): Promise<KoolbaseRecord | null>;
|
|
122
132
|
/**
|
|
123
133
|
* Writes that could not be applied, waiting for a decision.
|
|
124
134
|
*
|
|
@@ -134,6 +144,28 @@ export declare class KoolbaseDatabase {
|
|
|
134
144
|
* moment matters. Snapshot, not a live handle; per-user.
|
|
135
145
|
*/
|
|
136
146
|
pendingWrites(): Promise<PendingWrite[]>;
|
|
147
|
+
/**
|
|
148
|
+
* The pending writes now, and again whenever they change -- here, in another
|
|
149
|
+
* tab, or because the signed-in user changed. For a sync badge that reflects
|
|
150
|
+
* reality. null while signed out: per-user state has no answer then, and a
|
|
151
|
+
* fake empty list is how a badge says "all synced" while a user's changes
|
|
152
|
+
* sit queued. Returns the unsubscribe.
|
|
153
|
+
*/
|
|
154
|
+
watchPendingWrites(listener: (writes: PendingWrite[] | null) => void): () => void;
|
|
155
|
+
/** The unresolved conflicts now, and again whenever they change. null while signed out. */
|
|
156
|
+
watchConflicts(listener: (conflicts: KoolbaseConflict[] | null) => void): () => void;
|
|
157
|
+
/**
|
|
158
|
+
* The signed-in user changed: every watcher reads again, for the new user
|
|
159
|
+
* (or null, signed out). Koolbase.initialize wires it to auth changes.
|
|
160
|
+
*/
|
|
161
|
+
sessionChanged(): void;
|
|
162
|
+
private readonly stateWatchers;
|
|
163
|
+
private offStateChange;
|
|
164
|
+
/**
|
|
165
|
+
* One watcher: reads at once, then on every change to the signed-in user's
|
|
166
|
+
* offline state. Reads can finish out of order; only the latest delivers.
|
|
167
|
+
*/
|
|
168
|
+
private watchState;
|
|
137
169
|
/**
|
|
138
170
|
* Count and total over a whole collection, with the read rule applied
|
|
139
171
|
* inside the query.
|