@koolbase/js 10.0.1 → 10.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/CHANGELOG.md +52 -0
- package/README.md +7 -5
- package/dist/cjs/platform.d.ts +24 -1
- package/dist/cjs/platform.js +124 -8
- package/dist/esm/platform.d.ts +24 -1
- package/dist/esm/platform.js +124 -8
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,58 @@ is based on [Keep a Changelog][kac], and this project adheres to
|
|
|
7
7
|
[kac]: https://keepachangelog.com/en/1.1.0/
|
|
8
8
|
[semver]: https://semver.org/
|
|
9
9
|
|
|
10
|
+
## 10.2.0
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Multiple tabs are coordinated.** Tabs on one origin share one IndexedDB
|
|
15
|
+
and one offline queue. Two locks, through the Web Locks API, now keep them
|
|
16
|
+
honest: a short exclusive lock around every read-modify-write of the
|
|
17
|
+
offline state, so two tabs enqueueing at once cannot overwrite each other;
|
|
18
|
+
and a lease on replaying the queue, held for a whole flush including its
|
|
19
|
+
HTTP calls, so two tabs coming online together send each queued write once
|
|
20
|
+
rather than once each. A tab that cannot take the lease skips its pass and
|
|
21
|
+
rechecks once the holder is done, so a write queued during another tab's
|
|
22
|
+
flush is not stranded. A tab that closes mid-flush releases its lock
|
|
23
|
+
automatically. In a browser without Web Locks — none current — offline
|
|
24
|
+
queueing refuses with an explicit error rather than risking a double send.
|
|
25
|
+
|
|
26
|
+
Proven with a contended fake lock manager: two tabs, one queued write, one
|
|
27
|
+
HTTP request. Exercised in Chrome with real Web Locks.
|
|
28
|
+
|
|
29
|
+
## 10.1.0
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- **Uploads could not work from a browser.** `upload()` required
|
|
34
|
+
`{ uri, name, type }` — a React Native shape — and fetched that URI to get
|
|
35
|
+
the bytes. A browser `File` has no `uri`, so every upload from the web
|
|
36
|
+
failed before it reached the network, while the README showed a file input
|
|
37
|
+
as if it worked. `file` now accepts a `Blob` or `File` directly and the
|
|
38
|
+
React Native form is unchanged. Round-tripped in a browser: upload,
|
|
39
|
+
download, byte-for-byte comparison.
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- **`storageTier()`** on the browser adapter — `'indexeddb'`,
|
|
44
|
+
`'localstorage'` or `'memory'`. Storage is now chosen by *trying* each
|
|
45
|
+
store rather than checking whether the API exists: Safari in private
|
|
46
|
+
browsing exposes `indexedDB` and may refuse to open it, and a browser with
|
|
47
|
+
site data blocked exposes `localStorage` and throws on write. A failure at
|
|
48
|
+
any tier falls to the next, ending in memory with one console warning. An
|
|
49
|
+
app can read the tier and tell the user their session will not survive a
|
|
50
|
+
reload.
|
|
51
|
+
|
|
52
|
+
## 10.0.2
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- **A Node process that initialized the SDK never exited.** The analytics
|
|
57
|
+
flush interval, and a pending realtime reconnect, counted as work on Node's
|
|
58
|
+
event loop, so a CLI, a test runner or an SSR build step hung after its last
|
|
59
|
+
line. Both timers are now unref'd where the runtime supports it. A browser
|
|
60
|
+
is unaffected: the page keeps itself alive regardless.
|
|
61
|
+
|
|
10
62
|
## 10.0.1
|
|
11
63
|
|
|
12
64
|
### Fixed
|
package/README.md
CHANGED
|
@@ -168,11 +168,13 @@ becomes a conflict you resolve: `resolveWithLocal()`, `resolveWithServer()`,
|
|
|
168
168
|
`resolveWithMerge({...})`, or `abandon()`. Conflicts survive reloads and do not
|
|
169
169
|
expire; surface them if you support offline editing.
|
|
170
170
|
|
|
171
|
-
**
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
171
|
+
**Multiple tabs are coordinated.** Tabs share one IndexedDB and one queue, and
|
|
172
|
+
the SDK uses the Web Locks API so that only one tab replays a queued write and
|
|
173
|
+
state changes from different tabs cannot overwrite each other. A tab that
|
|
174
|
+
closes mid-flush releases its lock automatically; the next tab picks up
|
|
175
|
+
whatever remains. Every current browser has Web Locks; in one that does not,
|
|
176
|
+
offline queueing is disabled with an explicit error rather than risking a
|
|
177
|
+
write being sent twice.
|
|
176
178
|
|
|
177
179
|
---
|
|
178
180
|
|
package/dist/cjs/platform.d.ts
CHANGED
|
@@ -1,2 +1,25 @@
|
|
|
1
1
|
import type { PlatformAdapter } from '@koolbase/core';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* What a browser is actually willing to persist, decided by trying.
|
|
4
|
+
*
|
|
5
|
+
* Feature detection is not capability detection. Safari in private browsing
|
|
6
|
+
* exposes `indexedDB` and then refuses to open a database; a browser with
|
|
7
|
+
* site data blocked exposes `localStorage` and throws on write; a storage
|
|
8
|
+
* quota can be exhausted at any point afterwards. A selector that checks
|
|
9
|
+
* `typeof` commits to a store that may never work.
|
|
10
|
+
*
|
|
11
|
+
* So the store is resolved on first use, by using it, and the result is
|
|
12
|
+
* cached. A failure at any tier falls to the next, ending in memory — which
|
|
13
|
+
* always works and persists nothing.
|
|
14
|
+
*/
|
|
15
|
+
type Tier = 'indexeddb' | 'localstorage' | 'memory';
|
|
16
|
+
/**
|
|
17
|
+
* The browser adapter, plus what only a browser needs: which storage tier it
|
|
18
|
+
* settled on. Not on PlatformAdapter, because no other host has tiers — React
|
|
19
|
+
* Native's keychain either works or its package is absent.
|
|
20
|
+
*/
|
|
21
|
+
export interface BrowserPlatformAdapter extends PlatformAdapter {
|
|
22
|
+
storageTier(): Promise<Tier>;
|
|
23
|
+
}
|
|
24
|
+
export declare function browserPlatform(): BrowserPlatformAdapter;
|
|
25
|
+
export {};
|
package/dist/cjs/platform.js
CHANGED
|
@@ -11,11 +11,9 @@ const auth_storage_js_1 = require("./auth-storage.js");
|
|
|
11
11
|
//
|
|
12
12
|
// What this adapter does NOT do, stated so it is not discovered later:
|
|
13
13
|
//
|
|
14
|
-
// -
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// a conflict resolved in one tab can be re-resolved in another. v1 is
|
|
18
|
-
// single-tab; a leader election is the fix and belongs in its own change.
|
|
14
|
+
// - Coordinate across origins. Tabs on one origin share a queue and are
|
|
15
|
+
// coordinated through Web Locks (see webLocks below); nothing coordinates
|
|
16
|
+
// two different origins, which is correct — they are different apps.
|
|
19
17
|
//
|
|
20
18
|
// - Secure token storage. There is no keychain in a browser. Anything
|
|
21
19
|
// JavaScript can read, a script injected by XSS can read. The core's
|
|
@@ -107,11 +105,128 @@ function memoryStorage() {
|
|
|
107
105
|
getAllKeys: async () => Array.from(m.keys()),
|
|
108
106
|
};
|
|
109
107
|
}
|
|
108
|
+
async function probe(store) {
|
|
109
|
+
const k = '__koolbase_probe__';
|
|
110
|
+
try {
|
|
111
|
+
await store.setItem(k, '1');
|
|
112
|
+
const v = await store.getItem(k);
|
|
113
|
+
await store.removeItem(k);
|
|
114
|
+
return v === '1';
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function makeResolver() {
|
|
121
|
+
// Per adapter, not per module: two adapters must not share a store, or a
|
|
122
|
+
// server that built one client per request would hand one request's
|
|
123
|
+
// session to the next.
|
|
124
|
+
let resolved = null;
|
|
125
|
+
let resolving = null;
|
|
126
|
+
return () => {
|
|
127
|
+
if (resolved)
|
|
128
|
+
return Promise.resolve(resolved);
|
|
129
|
+
resolving ?? (resolving = (async () => {
|
|
130
|
+
if (typeof indexedDB !== 'undefined') {
|
|
131
|
+
const s = indexedDBStorage();
|
|
132
|
+
if (await probe(s))
|
|
133
|
+
return { tier: 'indexeddb', store: s };
|
|
134
|
+
}
|
|
135
|
+
if (typeof localStorage !== 'undefined') {
|
|
136
|
+
const s = localStorageStorage();
|
|
137
|
+
if (await probe(s))
|
|
138
|
+
return { tier: 'localstorage', store: s };
|
|
139
|
+
}
|
|
140
|
+
// eslint-disable-next-line no-console
|
|
141
|
+
console.warn('[Koolbase] No persistent storage available in this browser ' +
|
|
142
|
+
'(private browsing, blocked site data, or an exhausted quota). ' +
|
|
143
|
+
'The session and the offline queue will not survive a reload.');
|
|
144
|
+
return { tier: 'memory', store: memoryStorage() };
|
|
145
|
+
})());
|
|
146
|
+
return resolving.then((r) => { resolved = r; return r; });
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Web Locks. One holder per name across every tab on the origin, with
|
|
151
|
+
* ownership released when a tab closes or crashes — which is why this and
|
|
152
|
+
* not a BroadcastChannel election with heartbeats and a dead-leader timeout.
|
|
153
|
+
*
|
|
154
|
+
* Where the API is absent there is deliberately no fallback to running the
|
|
155
|
+
* function unlocked: that is exactly the uncoordinated behaviour the lock
|
|
156
|
+
* exists to prevent, and doing it silently would be worse than refusing.
|
|
157
|
+
* Every browser current enough to run this SDK has Web Locks (Safari 15.4+,
|
|
158
|
+
* Chrome 69+, Firefox 96+), so this is a guard, not a limitation.
|
|
159
|
+
*/
|
|
160
|
+
function webLocks() {
|
|
161
|
+
// Tabs are what need coordinating, and a tab has a window. No window means
|
|
162
|
+
// server rendering, a build step, a Node tool, a worker — nothing else can
|
|
163
|
+
// be sharing this store, so run the work. Not navigator: Node 22 ships a
|
|
164
|
+
// navigator object without locks, and testing for it would refuse every
|
|
165
|
+
// server environment the memory fallback exists to support.
|
|
166
|
+
if (typeof window === 'undefined') {
|
|
167
|
+
return {
|
|
168
|
+
exclusive: (_name, fn) => fn(),
|
|
169
|
+
tryExclusive: async (_name, fn) => { await fn(); return { ran: true }; },
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
// A browser that has navigator but no locks is a browser too old to
|
|
173
|
+
// coordinate. That one refuses: tabs exist, and running unlocked is the
|
|
174
|
+
// duplicate-replay risk this lock was added to remove.
|
|
175
|
+
if (typeof navigator === 'undefined' || !('locks' in navigator)) {
|
|
176
|
+
const refuse = () => {
|
|
177
|
+
throw new Error('[Koolbase] This browser has no Web Locks API, so the SDK cannot ' +
|
|
178
|
+
'coordinate offline writes between tabs. Offline queueing is ' +
|
|
179
|
+
'disabled rather than risking a write being replayed twice.');
|
|
180
|
+
};
|
|
181
|
+
return {
|
|
182
|
+
exclusive: async () => refuse(),
|
|
183
|
+
tryExclusive: async () => refuse(),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
const locks = navigator.locks;
|
|
187
|
+
return {
|
|
188
|
+
// request() resolves with whatever the callback resolves to; the DOM
|
|
189
|
+
// typings describe the callback's return as the result, so an async
|
|
190
|
+
// callback types as Promise<Promise<T>>. The runtime awaits it.
|
|
191
|
+
exclusive: (name, fn) => locks.request(name, fn),
|
|
192
|
+
tryExclusive: async (name, fn) => {
|
|
193
|
+
let ran = false;
|
|
194
|
+
await locks.request(name, { ifAvailable: true }, async (lock) => {
|
|
195
|
+
// A null lock means another tab holds it. Skip: it is already
|
|
196
|
+
// replaying the same queue, and waiting would only duplicate the
|
|
197
|
+
// wait, not the work.
|
|
198
|
+
if (!lock)
|
|
199
|
+
return;
|
|
200
|
+
ran = true;
|
|
201
|
+
await fn();
|
|
202
|
+
});
|
|
203
|
+
return { ran };
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
110
207
|
function browserPlatform() {
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
208
|
+
const resolve = makeResolver();
|
|
209
|
+
// Every call resolves first, so a browser that only reveals its refusal on
|
|
210
|
+
// use is handled here rather than by the caller.
|
|
211
|
+
const storage = {
|
|
212
|
+
getItem: async (k) => (await resolve()).store.getItem(k),
|
|
213
|
+
setItem: async (k, v) => { await (await resolve()).store.setItem(k, v); },
|
|
214
|
+
removeItem: async (k) => { await (await resolve()).store.removeItem(k); },
|
|
215
|
+
getAllKeys: async () => (await resolve()).store.getAllKeys(),
|
|
216
|
+
// Reaches through to the resolved tier. Only IndexedDB holds a
|
|
217
|
+
// connection worth closing; the test harness needs it released before
|
|
218
|
+
// it can delete the database between cases, and without this the
|
|
219
|
+
// façade hid the handle and every browser test hung on its hook.
|
|
220
|
+
close: async () => {
|
|
221
|
+
const r = await resolve();
|
|
222
|
+
await r.store.close?.();
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
// Which tier this adapter settled on: 'indexeddb', 'localstorage' or
|
|
226
|
+
// 'memory'. An app that cares can warn the user before they lose a session.
|
|
227
|
+
const storageTier = async () => (await resolve()).tier;
|
|
114
228
|
return {
|
|
229
|
+
storageTier,
|
|
115
230
|
storage,
|
|
116
231
|
network: {
|
|
117
232
|
onChange: (cb) => {
|
|
@@ -153,6 +268,7 @@ function browserPlatform() {
|
|
|
153
268
|
os: 'web',
|
|
154
269
|
version: browserVersion(),
|
|
155
270
|
},
|
|
271
|
+
locks: webLocks(),
|
|
156
272
|
// IndexedDB-backed; see auth-storage.ts for what that does and does not
|
|
157
273
|
// protect against.
|
|
158
274
|
authStorage: () => new auth_storage_js_1.BrowserAuthStorage(),
|
package/dist/esm/platform.d.ts
CHANGED
|
@@ -1,2 +1,25 @@
|
|
|
1
1
|
import type { PlatformAdapter } from '@koolbase/core';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* What a browser is actually willing to persist, decided by trying.
|
|
4
|
+
*
|
|
5
|
+
* Feature detection is not capability detection. Safari in private browsing
|
|
6
|
+
* exposes `indexedDB` and then refuses to open a database; a browser with
|
|
7
|
+
* site data blocked exposes `localStorage` and throws on write; a storage
|
|
8
|
+
* quota can be exhausted at any point afterwards. A selector that checks
|
|
9
|
+
* `typeof` commits to a store that may never work.
|
|
10
|
+
*
|
|
11
|
+
* So the store is resolved on first use, by using it, and the result is
|
|
12
|
+
* cached. A failure at any tier falls to the next, ending in memory — which
|
|
13
|
+
* always works and persists nothing.
|
|
14
|
+
*/
|
|
15
|
+
type Tier = 'indexeddb' | 'localstorage' | 'memory';
|
|
16
|
+
/**
|
|
17
|
+
* The browser adapter, plus what only a browser needs: which storage tier it
|
|
18
|
+
* settled on. Not on PlatformAdapter, because no other host has tiers — React
|
|
19
|
+
* Native's keychain either works or its package is absent.
|
|
20
|
+
*/
|
|
21
|
+
export interface BrowserPlatformAdapter extends PlatformAdapter {
|
|
22
|
+
storageTier(): Promise<Tier>;
|
|
23
|
+
}
|
|
24
|
+
export declare function browserPlatform(): BrowserPlatformAdapter;
|
|
25
|
+
export {};
|
package/dist/esm/platform.js
CHANGED
|
@@ -8,11 +8,9 @@ import { BrowserAuthStorage } from './auth-storage.js';
|
|
|
8
8
|
//
|
|
9
9
|
// What this adapter does NOT do, stated so it is not discovered later:
|
|
10
10
|
//
|
|
11
|
-
// -
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// a conflict resolved in one tab can be re-resolved in another. v1 is
|
|
15
|
-
// single-tab; a leader election is the fix and belongs in its own change.
|
|
11
|
+
// - Coordinate across origins. Tabs on one origin share a queue and are
|
|
12
|
+
// coordinated through Web Locks (see webLocks below); nothing coordinates
|
|
13
|
+
// two different origins, which is correct — they are different apps.
|
|
16
14
|
//
|
|
17
15
|
// - Secure token storage. There is no keychain in a browser. Anything
|
|
18
16
|
// JavaScript can read, a script injected by XSS can read. The core's
|
|
@@ -104,11 +102,128 @@ function memoryStorage() {
|
|
|
104
102
|
getAllKeys: async () => Array.from(m.keys()),
|
|
105
103
|
};
|
|
106
104
|
}
|
|
105
|
+
async function probe(store) {
|
|
106
|
+
const k = '__koolbase_probe__';
|
|
107
|
+
try {
|
|
108
|
+
await store.setItem(k, '1');
|
|
109
|
+
const v = await store.getItem(k);
|
|
110
|
+
await store.removeItem(k);
|
|
111
|
+
return v === '1';
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function makeResolver() {
|
|
118
|
+
// Per adapter, not per module: two adapters must not share a store, or a
|
|
119
|
+
// server that built one client per request would hand one request's
|
|
120
|
+
// session to the next.
|
|
121
|
+
let resolved = null;
|
|
122
|
+
let resolving = null;
|
|
123
|
+
return () => {
|
|
124
|
+
if (resolved)
|
|
125
|
+
return Promise.resolve(resolved);
|
|
126
|
+
resolving ?? (resolving = (async () => {
|
|
127
|
+
if (typeof indexedDB !== 'undefined') {
|
|
128
|
+
const s = indexedDBStorage();
|
|
129
|
+
if (await probe(s))
|
|
130
|
+
return { tier: 'indexeddb', store: s };
|
|
131
|
+
}
|
|
132
|
+
if (typeof localStorage !== 'undefined') {
|
|
133
|
+
const s = localStorageStorage();
|
|
134
|
+
if (await probe(s))
|
|
135
|
+
return { tier: 'localstorage', store: s };
|
|
136
|
+
}
|
|
137
|
+
// eslint-disable-next-line no-console
|
|
138
|
+
console.warn('[Koolbase] No persistent storage available in this browser ' +
|
|
139
|
+
'(private browsing, blocked site data, or an exhausted quota). ' +
|
|
140
|
+
'The session and the offline queue will not survive a reload.');
|
|
141
|
+
return { tier: 'memory', store: memoryStorage() };
|
|
142
|
+
})());
|
|
143
|
+
return resolving.then((r) => { resolved = r; return r; });
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Web Locks. One holder per name across every tab on the origin, with
|
|
148
|
+
* ownership released when a tab closes or crashes — which is why this and
|
|
149
|
+
* not a BroadcastChannel election with heartbeats and a dead-leader timeout.
|
|
150
|
+
*
|
|
151
|
+
* Where the API is absent there is deliberately no fallback to running the
|
|
152
|
+
* function unlocked: that is exactly the uncoordinated behaviour the lock
|
|
153
|
+
* exists to prevent, and doing it silently would be worse than refusing.
|
|
154
|
+
* Every browser current enough to run this SDK has Web Locks (Safari 15.4+,
|
|
155
|
+
* Chrome 69+, Firefox 96+), so this is a guard, not a limitation.
|
|
156
|
+
*/
|
|
157
|
+
function webLocks() {
|
|
158
|
+
// Tabs are what need coordinating, and a tab has a window. No window means
|
|
159
|
+
// server rendering, a build step, a Node tool, a worker — nothing else can
|
|
160
|
+
// be sharing this store, so run the work. Not navigator: Node 22 ships a
|
|
161
|
+
// navigator object without locks, and testing for it would refuse every
|
|
162
|
+
// server environment the memory fallback exists to support.
|
|
163
|
+
if (typeof window === 'undefined') {
|
|
164
|
+
return {
|
|
165
|
+
exclusive: (_name, fn) => fn(),
|
|
166
|
+
tryExclusive: async (_name, fn) => { await fn(); return { ran: true }; },
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
// A browser that has navigator but no locks is a browser too old to
|
|
170
|
+
// coordinate. That one refuses: tabs exist, and running unlocked is the
|
|
171
|
+
// duplicate-replay risk this lock was added to remove.
|
|
172
|
+
if (typeof navigator === 'undefined' || !('locks' in navigator)) {
|
|
173
|
+
const refuse = () => {
|
|
174
|
+
throw new Error('[Koolbase] This browser has no Web Locks API, so the SDK cannot ' +
|
|
175
|
+
'coordinate offline writes between tabs. Offline queueing is ' +
|
|
176
|
+
'disabled rather than risking a write being replayed twice.');
|
|
177
|
+
};
|
|
178
|
+
return {
|
|
179
|
+
exclusive: async () => refuse(),
|
|
180
|
+
tryExclusive: async () => refuse(),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
const locks = navigator.locks;
|
|
184
|
+
return {
|
|
185
|
+
// request() resolves with whatever the callback resolves to; the DOM
|
|
186
|
+
// typings describe the callback's return as the result, so an async
|
|
187
|
+
// callback types as Promise<Promise<T>>. The runtime awaits it.
|
|
188
|
+
exclusive: (name, fn) => locks.request(name, fn),
|
|
189
|
+
tryExclusive: async (name, fn) => {
|
|
190
|
+
let ran = false;
|
|
191
|
+
await locks.request(name, { ifAvailable: true }, async (lock) => {
|
|
192
|
+
// A null lock means another tab holds it. Skip: it is already
|
|
193
|
+
// replaying the same queue, and waiting would only duplicate the
|
|
194
|
+
// wait, not the work.
|
|
195
|
+
if (!lock)
|
|
196
|
+
return;
|
|
197
|
+
ran = true;
|
|
198
|
+
await fn();
|
|
199
|
+
});
|
|
200
|
+
return { ran };
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
107
204
|
export function browserPlatform() {
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
205
|
+
const resolve = makeResolver();
|
|
206
|
+
// Every call resolves first, so a browser that only reveals its refusal on
|
|
207
|
+
// use is handled here rather than by the caller.
|
|
208
|
+
const storage = {
|
|
209
|
+
getItem: async (k) => (await resolve()).store.getItem(k),
|
|
210
|
+
setItem: async (k, v) => { await (await resolve()).store.setItem(k, v); },
|
|
211
|
+
removeItem: async (k) => { await (await resolve()).store.removeItem(k); },
|
|
212
|
+
getAllKeys: async () => (await resolve()).store.getAllKeys(),
|
|
213
|
+
// Reaches through to the resolved tier. Only IndexedDB holds a
|
|
214
|
+
// connection worth closing; the test harness needs it released before
|
|
215
|
+
// it can delete the database between cases, and without this the
|
|
216
|
+
// façade hid the handle and every browser test hung on its hook.
|
|
217
|
+
close: async () => {
|
|
218
|
+
const r = await resolve();
|
|
219
|
+
await r.store.close?.();
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
// Which tier this adapter settled on: 'indexeddb', 'localstorage' or
|
|
223
|
+
// 'memory'. An app that cares can warn the user before they lose a session.
|
|
224
|
+
const storageTier = async () => (await resolve()).tier;
|
|
111
225
|
return {
|
|
226
|
+
storageTier,
|
|
112
227
|
storage,
|
|
113
228
|
network: {
|
|
114
229
|
onChange: (cb) => {
|
|
@@ -150,6 +265,7 @@ export function browserPlatform() {
|
|
|
150
265
|
os: 'web',
|
|
151
266
|
version: browserVersion(),
|
|
152
267
|
},
|
|
268
|
+
locks: webLocks(),
|
|
153
269
|
// IndexedDB-backed; see auth-storage.ts for what that does and does not
|
|
154
270
|
// protect against.
|
|
155
271
|
authStorage: () => new BrowserAuthStorage(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koolbase/js",
|
|
3
|
-
"version": "10.0
|
|
3
|
+
"version": "10.2.0",
|
|
4
4
|
"description": "Koolbase SDK for the browser \u2014 auth, database, storage, realtime, functions, flags and offline sync in one package.",
|
|
5
5
|
"main": "./dist/cjs/index.js",
|
|
6
6
|
"types": "./dist/esm/index.d.ts",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"url": "https://github.com/koolbase/koolbase-react-native"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@koolbase/core": "10.0
|
|
28
|
+
"@koolbase/core": "10.2.0"
|
|
29
29
|
},
|
|
30
30
|
"publishConfig": {
|
|
31
31
|
"access": "public"
|