@koolbase/js 10.0.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 +50 -0
- package/README.md +287 -0
- package/dist/cjs/auth-storage.d.ts +6 -0
- package/dist/cjs/auth-storage.js +39 -0
- package/dist/cjs/index.d.ts +18 -0
- package/dist/cjs/index.js +80 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/platform.d.ts +2 -0
- package/dist/cjs/platform.js +145 -0
- package/dist/esm/auth-storage.d.ts +6 -0
- package/dist/esm/auth-storage.js +35 -0
- package/dist/esm/index.d.ts +18 -0
- package/dist/esm/index.js +61 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/platform.d.ts +2 -0
- package/dist/esm/platform.js +142 -0
- package/package.json +48 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@koolbase/js` are documented in this file. The format
|
|
4
|
+
is based on [Keep a Changelog][kac], and this project adheres to
|
|
5
|
+
[Semantic Versioning][semver].
|
|
6
|
+
|
|
7
|
+
[kac]: https://keepachangelog.com/en/1.1.0/
|
|
8
|
+
[semver]: https://semver.org/
|
|
9
|
+
|
|
10
|
+
## 10.0.0
|
|
11
|
+
|
|
12
|
+
The first release. Numbered to match `@koolbase/react-native` and
|
|
13
|
+
`@koolbase/core`, which share one version and one core: this package is that
|
|
14
|
+
core composed for a browser, with sixty behavioural tests run against the
|
|
15
|
+
browser adapter before it shipped.
|
|
16
|
+
|
|
17
|
+
### What is here
|
|
18
|
+
|
|
19
|
+
- **Auth** — email and password, phone + OTP, session persistence across
|
|
20
|
+
reloads, `restoreSession()`, `onAuthStateChange`, password reset, email
|
|
21
|
+
verification. Google and Apple sign-in through their web OAuth flows,
|
|
22
|
+
passing the ID token to the same `signInWithGoogle` / `signInWithApple`.
|
|
23
|
+
- **Database** — insert, query, update, delete, upsert, bulk delete, atomic
|
|
24
|
+
batches, populate, and semantic / lexical / hybrid search.
|
|
25
|
+
- **Offline** — cached reads, a durable write queue with baselines,
|
|
26
|
+
`pendingWrites()`, and conflicts you resolve four ways. Same semantics as
|
|
27
|
+
React Native, same tests.
|
|
28
|
+
- **Storage** — presigned upload and download, safe-by-default paths, bucket
|
|
29
|
+
limits as typed errors, public CDN URLs with image transforms, versioning.
|
|
30
|
+
- **Realtime** — one shared WebSocket, backoff on reconnect, events filtered
|
|
31
|
+
by the collection's read rule.
|
|
32
|
+
- **Functions**, **feature flags**, **remote config**, **version
|
|
33
|
+
enforcement**, **analytics**.
|
|
34
|
+
|
|
35
|
+
### What is not, by design
|
|
36
|
+
|
|
37
|
+
- **Code push** — a native-bundle concept; the web ships on deploy.
|
|
38
|
+
- **Push messaging** — FCM tokens come from a native module. Use Web Push
|
|
39
|
+
through a service worker and your backend.
|
|
40
|
+
- **Native Google / Apple sign-in** — use the web OAuth flows.
|
|
41
|
+
|
|
42
|
+
### Known limitations in this release
|
|
43
|
+
|
|
44
|
+
- **Single tab.** Two tabs share one IndexedDB and one write queue. Inserts
|
|
45
|
+
are idempotent so the damage is bounded, but a conflict resolved in one tab
|
|
46
|
+
can be re-resolved in another. Multi-tab coordination is next.
|
|
47
|
+
- **Session storage is not a keychain.** IndexedDB is readable by any script
|
|
48
|
+
on the page. Inject your own `KoolbaseAuthStorage` if your threat model
|
|
49
|
+
needs an httpOnly cookie or similar; the README says more.
|
|
50
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# @koolbase/js
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@koolbase/js)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
**From idea to app. And everything after.** Design your app, power it with a
|
|
7
|
+
complete backend, and keep shipping after release.
|
|
8
|
+
|
|
9
|
+
Koolbase for the browser: auth, database, storage, realtime, functions,
|
|
10
|
+
feature flags, remote config, and an offline write queue with conflict
|
|
11
|
+
resolution — one package, one `initialize()` call, TypeScript throughout.
|
|
12
|
+
|
|
13
|
+
Same core as [`@koolbase/react-native`](https://www.npmjs.com/package/@koolbase/react-native).
|
|
14
|
+
Same behaviour, proven by the same test suite on both hosts.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Get started
|
|
19
|
+
|
|
20
|
+
1. Create a free account at [app.koolbase.com](https://app.koolbase.com)
|
|
21
|
+
2. Create a project and copy your public key from Environments
|
|
22
|
+
3. Install:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install @koolbase/js
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
4. Initialize once at startup:
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { Koolbase } from '@koolbase/js';
|
|
32
|
+
|
|
33
|
+
await Koolbase.initialize({
|
|
34
|
+
publicKey: 'pk_live_xxxx',
|
|
35
|
+
baseUrl: 'https://api.koolbase.com',
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
No native modules, no build step, nothing to configure. Works in any modern
|
|
40
|
+
browser and in frameworks that render in one — React, Vue, Svelte, Next.js on
|
|
41
|
+
the client side.
|
|
42
|
+
|
|
43
|
+
> **Auth is automatic.** Database, storage and function calls authenticate as
|
|
44
|
+
> the signed-in user — nothing to pass. Sign in (or restore a session) and every
|
|
45
|
+
> request carries that identity.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Authentication
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
await Koolbase.auth.register({ email: 'user@example.com', password: 'password' });
|
|
53
|
+
|
|
54
|
+
const session = await Koolbase.auth.login({
|
|
55
|
+
email: 'user@example.com',
|
|
56
|
+
password: 'password',
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const me = Koolbase.auth.currentUser;
|
|
60
|
+
|
|
61
|
+
await Koolbase.auth.logout();
|
|
62
|
+
|
|
63
|
+
await Koolbase.auth.forgotPassword('user@example.com');
|
|
64
|
+
|
|
65
|
+
const unsubscribe = Koolbase.auth.onAuthStateChange((user) => {
|
|
66
|
+
console.log(user ? 'signed in' : 'signed out');
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Sessions across page loads
|
|
71
|
+
|
|
72
|
+
Sessions persist in IndexedDB by default, so a refresh does not sign the user
|
|
73
|
+
out. Restore before rendering:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { RestoreResult } from '@koolbase/js';
|
|
77
|
+
|
|
78
|
+
const result = await Koolbase.auth.restoreSession();
|
|
79
|
+
|
|
80
|
+
switch (result) {
|
|
81
|
+
case RestoreResult.Restored: showApp(); break;
|
|
82
|
+
case RestoreResult.Offline: showApp(); break; // optimistic, no network yet
|
|
83
|
+
case RestoreResult.Expired: showLogin(); break;
|
|
84
|
+
case RestoreResult.NoSession: showLogin(); break;
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### What browser storage does and does not protect
|
|
89
|
+
|
|
90
|
+
There is no keychain in a browser. IndexedDB, localStorage and every other
|
|
91
|
+
store a page can read are readable by any script running on that page — so an
|
|
92
|
+
XSS vulnerability in your app exposes the session. This is true of every
|
|
93
|
+
browser SDK from every vendor. The defences are a content-security policy and
|
|
94
|
+
short session lifetimes, not a storage backend.
|
|
95
|
+
|
|
96
|
+
If your app needs a different trade-off — an httpOnly cookie set by your own
|
|
97
|
+
backend, say — implement `KoolbaseAuthStorage` and pass it as
|
|
98
|
+
`config.authStorage`. The SDK will use yours instead of its own.
|
|
99
|
+
|
|
100
|
+
### Phone + OTP
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
await Koolbase.auth.sendOtp({ phoneNumber: '+233200000000' });
|
|
104
|
+
await Koolbase.auth.verifyOtp({ phoneNumber: '+233200000000', code: '123456' });
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Configure your SMS provider (Twilio, Africa's Talking, or Hubtel) in the
|
|
108
|
+
dashboard under Phone Auth.
|
|
109
|
+
|
|
110
|
+
### Google and Apple sign-in
|
|
111
|
+
|
|
112
|
+
The native sign-in flows are React Native features. In the browser, run the
|
|
113
|
+
provider's web OAuth flow yourself and pass the resulting ID token to
|
|
114
|
+
`Koolbase.auth.signInWithGoogle({ idToken })` or
|
|
115
|
+
`signInWithApple({ identityToken })` — the server-side verification is the
|
|
116
|
+
same.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Database
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
await Koolbase.db.insert('posts', { title: 'Hello', published: true });
|
|
124
|
+
|
|
125
|
+
const { records, total } = await Koolbase.db.query('posts', {
|
|
126
|
+
filters: { published: true },
|
|
127
|
+
limit: 10,
|
|
128
|
+
orderBy: 'created_at',
|
|
129
|
+
orderDesc: true,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const post = records[0];
|
|
133
|
+
console.log(post.data.title); // your fields live under .data
|
|
134
|
+
console.log(post.id, post.collection); // metadata
|
|
135
|
+
|
|
136
|
+
await Koolbase.db.update('record-id', { title: 'Updated' });
|
|
137
|
+
await Koolbase.db.delete('record-id');
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`total` is the size of the set you are authorized to read, on every page, so
|
|
141
|
+
pagination is exact.
|
|
142
|
+
|
|
143
|
+
Upsert, bulk delete, atomic batches, populate for related records, and
|
|
144
|
+
semantic / lexical / hybrid search over vectors all work exactly as in
|
|
145
|
+
`@koolbase/react-native` — see its README for the full reference; the API is
|
|
146
|
+
identical.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Offline
|
|
151
|
+
|
|
152
|
+
Reads come from a local cache when the network is unavailable. `insert`,
|
|
153
|
+
`update` and `delete` queue and send when it returns.
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
const { records, isFromCache } = await Koolbase.db.query('posts', { limit: 20 });
|
|
157
|
+
|
|
158
|
+
await Koolbase.db.update(id, { title: 'Corrected' }); // queued if offline
|
|
159
|
+
await Koolbase.db.syncPendingWrites(); // or wait for reconnect
|
|
160
|
+
|
|
161
|
+
const pending = await Koolbase.db.pendingWrites(); // for a sync badge
|
|
162
|
+
const conflicts = await Koolbase.db.conflicts(); // writes the server refused
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
A write the server refuses on replay — because the record changed meanwhile —
|
|
166
|
+
becomes a conflict you resolve: `resolveWithLocal()`, `resolveWithServer()`,
|
|
167
|
+
`resolveWithMerge({...})`, or `abandon()`. Conflicts survive reloads and do not
|
|
168
|
+
expire; surface them if you support offline editing.
|
|
169
|
+
|
|
170
|
+
**Single tab, in this release.** Two tabs share one IndexedDB and one queue,
|
|
171
|
+
and both may replay the same write. Inserts are idempotent so the damage is
|
|
172
|
+
bounded, but a conflict resolved in one tab can be re-resolved in another.
|
|
173
|
+
Multi-tab coordination is on the roadmap; until then, treat the offline queue
|
|
174
|
+
as belonging to one tab.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Storage
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
const { object, downloadUrl } = await Koolbase.storage.upload({
|
|
182
|
+
bucket: 'avatars',
|
|
183
|
+
path: `user-${userId}.jpg`,
|
|
184
|
+
file: fileFromInput, // a File or Blob
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const url = await Koolbase.storage.getDownloadUrl('avatars', `user-${userId}.jpg`);
|
|
188
|
+
await Koolbase.storage.delete('avatars', `user-${userId}.jpg`);
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Safe-by-default: uploading to a path that is already taken throws
|
|
192
|
+
`KoolbaseStorageConflictError` unless you pass `overwrite: true`. Bucket size
|
|
193
|
+
caps, per-file caps and content-type allowlists arrive as typed errors. Public
|
|
194
|
+
buckets have stable CDN URLs with edge image transforms; versioned buckets keep
|
|
195
|
+
history. Same API as React Native.
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Realtime
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
const unsubscribe = Koolbase.realtime.subscribe('messages', (event) => {
|
|
203
|
+
if (event.type === 'deleted') console.log('deleted', event.recordId);
|
|
204
|
+
else console.log(event.type, event.record!.data);
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
One WebSocket, shared across subscriptions, reconnecting with backoff. Events
|
|
209
|
+
are filtered server-side by the collection's read rule, so a subscriber sees
|
|
210
|
+
only what a query would return them.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Functions
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
const result = await Koolbase.functions.invoke('send-welcome-email', { userId });
|
|
218
|
+
if (result.success) console.log(result.data);
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
The signed-in user's token is forwarded automatically; the function reads the
|
|
222
|
+
caller on `ctx.auth`. Failures are typed: `FunctionNotFoundError`,
|
|
223
|
+
`FunctionPermissionError`, `FunctionValidationError`,
|
|
224
|
+
`FunctionQuotaExceededError`, `FunctionExecutionError`.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Feature flags and remote config
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
if (Koolbase.isEnabled('new_checkout')) { /* ... */ }
|
|
232
|
+
|
|
233
|
+
const timeout = Koolbase.configNumber('timeout_seconds', 30);
|
|
234
|
+
const apiUrl = Koolbase.configString('api_url', 'https://api.myapp.com');
|
|
235
|
+
const dark = Koolbase.configBool('force_dark_mode', false);
|
|
236
|
+
|
|
237
|
+
const v = Koolbase.checkVersion('1.2.3');
|
|
238
|
+
if (v.status === 'force_update') { /* block and prompt */ }
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Analytics
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
Koolbase.analytics.track('purchase', { value: 1200, currency: 'GHS' });
|
|
247
|
+
Koolbase.analytics.screenView('checkout');
|
|
248
|
+
Koolbase.analytics.identify(user.id);
|
|
249
|
+
Koolbase.analytics.reset(); // on sign-out
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Events batch and flush every 30 seconds, when the tab is hidden, and on
|
|
253
|
+
`pagehide`.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Not in the browser package
|
|
258
|
+
|
|
259
|
+
Stated here so nothing is discovered as a method that fails:
|
|
260
|
+
|
|
261
|
+
- **Code push** — a native-bundle concept. The web already ships on deploy.
|
|
262
|
+
- **Push messaging** — FCM device tokens come from a native module. Use Web
|
|
263
|
+
Push through your own service worker and your backend.
|
|
264
|
+
- **Native Google / Apple sign-in** — use the web OAuth flows, above.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Error handling
|
|
269
|
+
|
|
270
|
+
Every error the SDK raises extends `KoolbaseError`. Authentication failures
|
|
271
|
+
(401) are `KoolbaseUnauthenticatedError` from any subsystem, and the user is
|
|
272
|
+
already signed out by the time you catch one. The full error reference is in
|
|
273
|
+
the [`@koolbase/react-native` README](https://www.npmjs.com/package/@koolbase/react-native#error-handling);
|
|
274
|
+
the classes are identical.
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Documentation
|
|
279
|
+
|
|
280
|
+
Full documentation at [docs.koolbase.com](https://docs.koolbase.com) ·
|
|
281
|
+
Dashboard at [app.koolbase.com](https://app.koolbase.com) ·
|
|
282
|
+
Issues at [github.com/koolbase/koolbase-react-native](https://github.com/koolbase/koolbase-react-native/issues) ·
|
|
283
|
+
Email <dev@koolbase.com>
|
|
284
|
+
|
|
285
|
+
## License
|
|
286
|
+
|
|
287
|
+
MIT
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { KoolbaseAuthStorage, KoolbaseSession } from '@koolbase/core';
|
|
2
|
+
export declare class BrowserAuthStorage implements KoolbaseAuthStorage {
|
|
3
|
+
readSession(): Promise<KoolbaseSession | null>;
|
|
4
|
+
saveSession(session: KoolbaseSession): Promise<void>;
|
|
5
|
+
clear(): Promise<void>;
|
|
6
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BrowserAuthStorage = void 0;
|
|
4
|
+
const core_1 = require("@koolbase/core");
|
|
5
|
+
// Session persistence for the browser.
|
|
6
|
+
//
|
|
7
|
+
// This is the default when an app injects nothing through
|
|
8
|
+
// KoolbaseConfig.authStorage. It keeps the session in the platform's storage
|
|
9
|
+
// — IndexedDB — so a page refresh does not sign the user out.
|
|
10
|
+
//
|
|
11
|
+
// What it is not: secure the way a keychain is. IndexedDB, localStorage and
|
|
12
|
+
// every other store a page can read are readable by any script running on
|
|
13
|
+
// that page, so an XSS vulnerability in the app exposes the session. That is
|
|
14
|
+
// true of every browser SDK from every vendor; the mitigations are an
|
|
15
|
+
// application's content-security policy and short session lifetimes, not a
|
|
16
|
+
// storage backend. An app that needs a different trade-off — an httpOnly
|
|
17
|
+
// cookie set by its own backend, say — injects its own KoolbaseAuthStorage.
|
|
18
|
+
const KEY = 'koolbase_session_v1';
|
|
19
|
+
class BrowserAuthStorage {
|
|
20
|
+
async readSession() {
|
|
21
|
+
const raw = await (0, core_1.getPlatform)().storage.getItem(KEY);
|
|
22
|
+
if (!raw)
|
|
23
|
+
return null;
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(raw);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
await this.clear();
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async saveSession(session) {
|
|
33
|
+
await (0, core_1.getPlatform)().storage.setItem(KEY, JSON.stringify(session));
|
|
34
|
+
}
|
|
35
|
+
async clear() {
|
|
36
|
+
await (0, core_1.getPlatform)().storage.removeItem(KEY);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.BrowserAuthStorage = BrowserAuthStorage;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export * from '@koolbase/core';
|
|
2
|
+
export { BrowserAuthStorage } from './auth-storage.js';
|
|
3
|
+
export { browserPlatform } from './platform.js';
|
|
4
|
+
import { KoolbaseAuth, KoolbaseDatabase, KoolbaseStorage, KoolbaseRealtime, KoolbaseFunctions, KoolbaseAnalytics, type KoolbaseConfig, type VersionCheckResult } from '@koolbase/core';
|
|
5
|
+
export declare const Koolbase: {
|
|
6
|
+
initialize(config: KoolbaseConfig): Promise<void>;
|
|
7
|
+
readonly auth: KoolbaseAuth;
|
|
8
|
+
readonly db: KoolbaseDatabase;
|
|
9
|
+
readonly storage: KoolbaseStorage;
|
|
10
|
+
readonly realtime: KoolbaseRealtime;
|
|
11
|
+
readonly functions: KoolbaseFunctions;
|
|
12
|
+
readonly analytics: KoolbaseAnalytics;
|
|
13
|
+
isEnabled(key: string): boolean;
|
|
14
|
+
configString(key: string, fallback?: string): string;
|
|
15
|
+
configNumber(key: string, fallback?: number): number;
|
|
16
|
+
configBool(key: string, fallback?: boolean): boolean;
|
|
17
|
+
checkVersion(currentVersion: string): VersionCheckResult;
|
|
18
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// @koolbase/js — composes @koolbase/core for the browser.
|
|
3
|
+
//
|
|
4
|
+
// The same surface as @koolbase/react-native where the browser can honour it.
|
|
5
|
+
// What is deliberately absent, so it is not discovered as a method that
|
|
6
|
+
// fails: code push (a native-bundle concept; the web already has one), the
|
|
7
|
+
// logic engine (its flows arrived only through code-push bundles), push
|
|
8
|
+
// messaging (FCM tokens come from a native module), and Apple/Google native
|
|
9
|
+
// sign-in (use the web OAuth flows against the same auth endpoints).
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
22
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
23
|
+
};
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.Koolbase = exports.browserPlatform = exports.BrowserAuthStorage = void 0;
|
|
26
|
+
__exportStar(require("@koolbase/core"), exports);
|
|
27
|
+
var auth_storage_js_1 = require("./auth-storage.js");
|
|
28
|
+
Object.defineProperty(exports, "BrowserAuthStorage", { enumerable: true, get: function () { return auth_storage_js_1.BrowserAuthStorage; } });
|
|
29
|
+
var platform_js_1 = require("./platform.js");
|
|
30
|
+
Object.defineProperty(exports, "browserPlatform", { enumerable: true, get: function () { return platform_js_1.browserPlatform; } });
|
|
31
|
+
const core_1 = require("@koolbase/core");
|
|
32
|
+
const platform_js_2 = require("./platform.js");
|
|
33
|
+
let _auth = null;
|
|
34
|
+
let _db = null;
|
|
35
|
+
let _storage = null;
|
|
36
|
+
let _realtime = null;
|
|
37
|
+
let _functions = null;
|
|
38
|
+
let _flags = null;
|
|
39
|
+
let _analytics = null;
|
|
40
|
+
let _initialized = false;
|
|
41
|
+
function ensureInitialized() {
|
|
42
|
+
if (!_initialized) {
|
|
43
|
+
throw new Error('Koolbase not initialized. Call Koolbase.initialize(config) first.');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exports.Koolbase = {
|
|
47
|
+
async initialize(config) {
|
|
48
|
+
if (_initialized)
|
|
49
|
+
return;
|
|
50
|
+
(0, core_1.setPlatform)(config.platform ?? (0, platform_js_2.browserPlatform)());
|
|
51
|
+
_auth = new core_1.KoolbaseAuth(config);
|
|
52
|
+
_db = new core_1.KoolbaseDatabase(config, () => _auth?.currentUser?.id ?? null, () => _auth?.validAccessToken() ?? Promise.resolve(null), async () => { await _auth?.clearStoredSession(); });
|
|
53
|
+
_storage = new core_1.KoolbaseStorage(config, () => _auth?.validAccessToken() ?? Promise.resolve(null), async () => { await _auth?.clearStoredSession(); });
|
|
54
|
+
_realtime = new core_1.KoolbaseRealtime(config, () => _auth?.validAccessToken() ?? Promise.resolve(null), () => _auth?.currentUser?.id ?? null);
|
|
55
|
+
_functions = new core_1.KoolbaseFunctions(config, () => _auth?.validAccessToken() ?? Promise.resolve(null), async () => { await _auth?.clearStoredSession(); });
|
|
56
|
+
const deviceId = await (0, core_1.getOrCreateDeviceId)();
|
|
57
|
+
_flags = new core_1.KoolbaseFlags(config, deviceId);
|
|
58
|
+
if (config.analyticsEnabled !== false) {
|
|
59
|
+
_analytics = new core_1.KoolbaseAnalytics(config);
|
|
60
|
+
await _analytics.init(config.appVersion);
|
|
61
|
+
}
|
|
62
|
+
_initialized = true;
|
|
63
|
+
},
|
|
64
|
+
get auth() { ensureInitialized(); return _auth; },
|
|
65
|
+
get db() { ensureInitialized(); return _db; },
|
|
66
|
+
get storage() { ensureInitialized(); return _storage; },
|
|
67
|
+
get realtime() { ensureInitialized(); return _realtime; },
|
|
68
|
+
get functions() { ensureInitialized(); return _functions; },
|
|
69
|
+
get analytics() {
|
|
70
|
+
ensureInitialized();
|
|
71
|
+
if (!_analytics)
|
|
72
|
+
throw new Error('Analytics is disabled (analyticsEnabled: false).');
|
|
73
|
+
return _analytics;
|
|
74
|
+
},
|
|
75
|
+
isEnabled(key) { ensureInitialized(); return _flags.isEnabled(key); },
|
|
76
|
+
configString(key, fallback = '') { ensureInitialized(); return _flags.getString(key, fallback); },
|
|
77
|
+
configNumber(key, fallback = 0) { ensureInitialized(); return _flags.getNumber(key, fallback); },
|
|
78
|
+
configBool(key, fallback = false) { ensureInitialized(); return _flags.getBool(key, fallback); },
|
|
79
|
+
checkVersion(currentVersion) { ensureInitialized(); return _flags.checkVersion(currentVersion); },
|
|
80
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.browserPlatform = browserPlatform;
|
|
4
|
+
const auth_storage_js_1 = require("./auth-storage.js");
|
|
5
|
+
// The browser host, expressed through the platform seam.
|
|
6
|
+
//
|
|
7
|
+
// Storage is IndexedDB behind the same four calls the core makes — get, set,
|
|
8
|
+
// remove, list. One object store, string keys, string values, one operation
|
|
9
|
+
// per transaction: exactly the contract the offline queue and conflict store
|
|
10
|
+
// were verified against, with nothing the RN adapter does not also do.
|
|
11
|
+
//
|
|
12
|
+
// What this adapter does NOT do, stated so it is not discovered later:
|
|
13
|
+
//
|
|
14
|
+
// - Multi-tab coordination. Two tabs share one IndexedDB and one write
|
|
15
|
+
// queue; both may replay the same pending write. Inserts are idempotent
|
|
16
|
+
// on the server (ids are UUIDs from birth), so the damage is bounded, but
|
|
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.
|
|
19
|
+
//
|
|
20
|
+
// - Secure token storage. There is no keychain in a browser. Anything
|
|
21
|
+
// JavaScript can read, a script injected by XSS can read. The core's
|
|
22
|
+
// KoolbaseAuthStorage is the injection point; the default here persists
|
|
23
|
+
// the session in IndexedDB so a refresh does not sign the user out, and
|
|
24
|
+
// the README says plainly what that means.
|
|
25
|
+
const DB_NAME = 'koolbase';
|
|
26
|
+
const STORE = 'kv';
|
|
27
|
+
function openDB() {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const req = indexedDB.open(DB_NAME, 1);
|
|
30
|
+
req.onupgradeneeded = () => {
|
|
31
|
+
const db = req.result;
|
|
32
|
+
if (!db.objectStoreNames.contains(STORE))
|
|
33
|
+
db.createObjectStore(STORE);
|
|
34
|
+
};
|
|
35
|
+
req.onsuccess = () => resolve(req.result);
|
|
36
|
+
req.onerror = () => reject(req.error ?? new Error('indexedDB open failed'));
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function tx(db, mode, run) {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
const t = db.transaction(STORE, mode);
|
|
42
|
+
const req = run(t.objectStore(STORE));
|
|
43
|
+
req.onsuccess = () => resolve(req.result);
|
|
44
|
+
req.onerror = () => reject(req.error ?? new Error('indexedDB request failed'));
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function indexedDBStorage() {
|
|
48
|
+
let dbp = null;
|
|
49
|
+
const db = () => (dbp ?? (dbp = openDB()));
|
|
50
|
+
return {
|
|
51
|
+
// Not part of PlatformStorage. Test harnesses call it between cases so
|
|
52
|
+
// deleteDatabase is not blocked by a live connection.
|
|
53
|
+
close: async () => {
|
|
54
|
+
if (!dbp)
|
|
55
|
+
return;
|
|
56
|
+
(await dbp).close();
|
|
57
|
+
dbp = null;
|
|
58
|
+
},
|
|
59
|
+
getItem: async (k) => {
|
|
60
|
+
const v = await tx(await db(), 'readonly', s => s.get(k));
|
|
61
|
+
return v ?? null;
|
|
62
|
+
},
|
|
63
|
+
setItem: async (k, v) => { await tx(await db(), 'readwrite', s => s.put(v, k)); },
|
|
64
|
+
removeItem: async (k) => { await tx(await db(), 'readwrite', s => s.delete(k)); },
|
|
65
|
+
getAllKeys: async () => {
|
|
66
|
+
const keys = await tx(await db(), 'readonly', s => s.getAllKeys());
|
|
67
|
+
return keys.map(String);
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// localStorage is synchronous and small, but present in more environments
|
|
72
|
+
// than IndexedDB (some private modes disable IDB). Used only when IDB is
|
|
73
|
+
// absent, so the common path keeps the larger, asynchronous store.
|
|
74
|
+
function localStorageStorage() {
|
|
75
|
+
return {
|
|
76
|
+
getItem: async (k) => localStorage.getItem(k),
|
|
77
|
+
setItem: async (k, v) => { localStorage.setItem(k, v); },
|
|
78
|
+
removeItem: async (k) => { localStorage.removeItem(k); },
|
|
79
|
+
getAllKeys: async () => {
|
|
80
|
+
const out = [];
|
|
81
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
82
|
+
const k = localStorage.key(i);
|
|
83
|
+
if (k !== null)
|
|
84
|
+
out.push(k);
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function browserVersion() {
|
|
91
|
+
if (typeof navigator === 'undefined')
|
|
92
|
+
return '';
|
|
93
|
+
const ua = navigator.userAgent;
|
|
94
|
+
const m = /(Chrome|Firefox|Safari|Edg)\/([\d.]+)/.exec(ua);
|
|
95
|
+
return m ? `${m[1]} ${m[2]}` : '';
|
|
96
|
+
}
|
|
97
|
+
function browserPlatform() {
|
|
98
|
+
const hasIDB = typeof indexedDB !== 'undefined';
|
|
99
|
+
return {
|
|
100
|
+
storage: hasIDB ? indexedDBStorage() : localStorageStorage(),
|
|
101
|
+
network: {
|
|
102
|
+
onChange: (cb) => {
|
|
103
|
+
// navigator.onLine is a hint that the interface is up, not that
|
|
104
|
+
// Koolbase is reachable. The sync engine treats a false positive as
|
|
105
|
+
// a failed request, the same as any other; a false negative is only
|
|
106
|
+
// a delayed flush until the next event or manual sync.
|
|
107
|
+
// No window in a worker or during SSR: no events, and the sync
|
|
108
|
+
// engine falls back to discovering reachability by trying.
|
|
109
|
+
if (typeof window === 'undefined')
|
|
110
|
+
return () => { };
|
|
111
|
+
const on = () => cb(true);
|
|
112
|
+
const off = () => cb(false);
|
|
113
|
+
window.addEventListener('online', on);
|
|
114
|
+
window.addEventListener('offline', off);
|
|
115
|
+
return () => {
|
|
116
|
+
window.removeEventListener('online', on);
|
|
117
|
+
window.removeEventListener('offline', off);
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
lifecycle: {
|
|
122
|
+
onBackground: (cb) => {
|
|
123
|
+
if (typeof document === 'undefined' || typeof window === 'undefined')
|
|
124
|
+
return () => { };
|
|
125
|
+
const handler = () => { if (document.visibilityState === 'hidden')
|
|
126
|
+
cb(); };
|
|
127
|
+
document.addEventListener('visibilitychange', handler);
|
|
128
|
+
// pagehide is the reliable "tab is going away" signal on mobile
|
|
129
|
+
// browsers, where visibilitychange may not fire before unload.
|
|
130
|
+
window.addEventListener('pagehide', cb);
|
|
131
|
+
return () => {
|
|
132
|
+
document.removeEventListener('visibilitychange', handler);
|
|
133
|
+
window.removeEventListener('pagehide', cb);
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
info: {
|
|
138
|
+
os: 'web',
|
|
139
|
+
version: browserVersion(),
|
|
140
|
+
},
|
|
141
|
+
// IndexedDB-backed; see auth-storage.ts for what that does and does not
|
|
142
|
+
// protect against.
|
|
143
|
+
authStorage: () => new auth_storage_js_1.BrowserAuthStorage(),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { KoolbaseAuthStorage, KoolbaseSession } from '@koolbase/core';
|
|
2
|
+
export declare class BrowserAuthStorage implements KoolbaseAuthStorage {
|
|
3
|
+
readSession(): Promise<KoolbaseSession | null>;
|
|
4
|
+
saveSession(session: KoolbaseSession): Promise<void>;
|
|
5
|
+
clear(): Promise<void>;
|
|
6
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { getPlatform } from '@koolbase/core';
|
|
2
|
+
// Session persistence for the browser.
|
|
3
|
+
//
|
|
4
|
+
// This is the default when an app injects nothing through
|
|
5
|
+
// KoolbaseConfig.authStorage. It keeps the session in the platform's storage
|
|
6
|
+
// — IndexedDB — so a page refresh does not sign the user out.
|
|
7
|
+
//
|
|
8
|
+
// What it is not: secure the way a keychain is. IndexedDB, localStorage and
|
|
9
|
+
// every other store a page can read are readable by any script running on
|
|
10
|
+
// that page, so an XSS vulnerability in the app exposes the session. That is
|
|
11
|
+
// true of every browser SDK from every vendor; the mitigations are an
|
|
12
|
+
// application's content-security policy and short session lifetimes, not a
|
|
13
|
+
// storage backend. An app that needs a different trade-off — an httpOnly
|
|
14
|
+
// cookie set by its own backend, say — injects its own KoolbaseAuthStorage.
|
|
15
|
+
const KEY = 'koolbase_session_v1';
|
|
16
|
+
export class BrowserAuthStorage {
|
|
17
|
+
async readSession() {
|
|
18
|
+
const raw = await getPlatform().storage.getItem(KEY);
|
|
19
|
+
if (!raw)
|
|
20
|
+
return null;
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(raw);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
await this.clear();
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async saveSession(session) {
|
|
30
|
+
await getPlatform().storage.setItem(KEY, JSON.stringify(session));
|
|
31
|
+
}
|
|
32
|
+
async clear() {
|
|
33
|
+
await getPlatform().storage.removeItem(KEY);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export * from '@koolbase/core';
|
|
2
|
+
export { BrowserAuthStorage } from './auth-storage.js';
|
|
3
|
+
export { browserPlatform } from './platform.js';
|
|
4
|
+
import { KoolbaseAuth, KoolbaseDatabase, KoolbaseStorage, KoolbaseRealtime, KoolbaseFunctions, KoolbaseAnalytics, type KoolbaseConfig, type VersionCheckResult } from '@koolbase/core';
|
|
5
|
+
export declare const Koolbase: {
|
|
6
|
+
initialize(config: KoolbaseConfig): Promise<void>;
|
|
7
|
+
readonly auth: KoolbaseAuth;
|
|
8
|
+
readonly db: KoolbaseDatabase;
|
|
9
|
+
readonly storage: KoolbaseStorage;
|
|
10
|
+
readonly realtime: KoolbaseRealtime;
|
|
11
|
+
readonly functions: KoolbaseFunctions;
|
|
12
|
+
readonly analytics: KoolbaseAnalytics;
|
|
13
|
+
isEnabled(key: string): boolean;
|
|
14
|
+
configString(key: string, fallback?: string): string;
|
|
15
|
+
configNumber(key: string, fallback?: number): number;
|
|
16
|
+
configBool(key: string, fallback?: boolean): boolean;
|
|
17
|
+
checkVersion(currentVersion: string): VersionCheckResult;
|
|
18
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// @koolbase/js — composes @koolbase/core for the browser.
|
|
2
|
+
//
|
|
3
|
+
// The same surface as @koolbase/react-native where the browser can honour it.
|
|
4
|
+
// What is deliberately absent, so it is not discovered as a method that
|
|
5
|
+
// fails: code push (a native-bundle concept; the web already has one), the
|
|
6
|
+
// logic engine (its flows arrived only through code-push bundles), push
|
|
7
|
+
// messaging (FCM tokens come from a native module), and Apple/Google native
|
|
8
|
+
// sign-in (use the web OAuth flows against the same auth endpoints).
|
|
9
|
+
export * from '@koolbase/core';
|
|
10
|
+
export { BrowserAuthStorage } from './auth-storage.js';
|
|
11
|
+
export { browserPlatform } from './platform.js';
|
|
12
|
+
import { KoolbaseAuth, KoolbaseDatabase, KoolbaseStorage, KoolbaseRealtime, KoolbaseFunctions, KoolbaseFlags, KoolbaseAnalytics, getOrCreateDeviceId, setPlatform, } from '@koolbase/core';
|
|
13
|
+
import { browserPlatform } from './platform.js';
|
|
14
|
+
let _auth = null;
|
|
15
|
+
let _db = null;
|
|
16
|
+
let _storage = null;
|
|
17
|
+
let _realtime = null;
|
|
18
|
+
let _functions = null;
|
|
19
|
+
let _flags = null;
|
|
20
|
+
let _analytics = null;
|
|
21
|
+
let _initialized = false;
|
|
22
|
+
function ensureInitialized() {
|
|
23
|
+
if (!_initialized) {
|
|
24
|
+
throw new Error('Koolbase not initialized. Call Koolbase.initialize(config) first.');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export const Koolbase = {
|
|
28
|
+
async initialize(config) {
|
|
29
|
+
if (_initialized)
|
|
30
|
+
return;
|
|
31
|
+
setPlatform(config.platform ?? browserPlatform());
|
|
32
|
+
_auth = new KoolbaseAuth(config);
|
|
33
|
+
_db = new KoolbaseDatabase(config, () => _auth?.currentUser?.id ?? null, () => _auth?.validAccessToken() ?? Promise.resolve(null), async () => { await _auth?.clearStoredSession(); });
|
|
34
|
+
_storage = new KoolbaseStorage(config, () => _auth?.validAccessToken() ?? Promise.resolve(null), async () => { await _auth?.clearStoredSession(); });
|
|
35
|
+
_realtime = new KoolbaseRealtime(config, () => _auth?.validAccessToken() ?? Promise.resolve(null), () => _auth?.currentUser?.id ?? null);
|
|
36
|
+
_functions = new KoolbaseFunctions(config, () => _auth?.validAccessToken() ?? Promise.resolve(null), async () => { await _auth?.clearStoredSession(); });
|
|
37
|
+
const deviceId = await getOrCreateDeviceId();
|
|
38
|
+
_flags = new KoolbaseFlags(config, deviceId);
|
|
39
|
+
if (config.analyticsEnabled !== false) {
|
|
40
|
+
_analytics = new KoolbaseAnalytics(config);
|
|
41
|
+
await _analytics.init(config.appVersion);
|
|
42
|
+
}
|
|
43
|
+
_initialized = true;
|
|
44
|
+
},
|
|
45
|
+
get auth() { ensureInitialized(); return _auth; },
|
|
46
|
+
get db() { ensureInitialized(); return _db; },
|
|
47
|
+
get storage() { ensureInitialized(); return _storage; },
|
|
48
|
+
get realtime() { ensureInitialized(); return _realtime; },
|
|
49
|
+
get functions() { ensureInitialized(); return _functions; },
|
|
50
|
+
get analytics() {
|
|
51
|
+
ensureInitialized();
|
|
52
|
+
if (!_analytics)
|
|
53
|
+
throw new Error('Analytics is disabled (analyticsEnabled: false).');
|
|
54
|
+
return _analytics;
|
|
55
|
+
},
|
|
56
|
+
isEnabled(key) { ensureInitialized(); return _flags.isEnabled(key); },
|
|
57
|
+
configString(key, fallback = '') { ensureInitialized(); return _flags.getString(key, fallback); },
|
|
58
|
+
configNumber(key, fallback = 0) { ensureInitialized(); return _flags.getNumber(key, fallback); },
|
|
59
|
+
configBool(key, fallback = false) { ensureInitialized(); return _flags.getBool(key, fallback); },
|
|
60
|
+
checkVersion(currentVersion) { ensureInitialized(); return _flags.checkVersion(currentVersion); },
|
|
61
|
+
};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { BrowserAuthStorage } from './auth-storage.js';
|
|
2
|
+
// The browser host, expressed through the platform seam.
|
|
3
|
+
//
|
|
4
|
+
// Storage is IndexedDB behind the same four calls the core makes — get, set,
|
|
5
|
+
// remove, list. One object store, string keys, string values, one operation
|
|
6
|
+
// per transaction: exactly the contract the offline queue and conflict store
|
|
7
|
+
// were verified against, with nothing the RN adapter does not also do.
|
|
8
|
+
//
|
|
9
|
+
// What this adapter does NOT do, stated so it is not discovered later:
|
|
10
|
+
//
|
|
11
|
+
// - Multi-tab coordination. Two tabs share one IndexedDB and one write
|
|
12
|
+
// queue; both may replay the same pending write. Inserts are idempotent
|
|
13
|
+
// on the server (ids are UUIDs from birth), so the damage is bounded, but
|
|
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.
|
|
16
|
+
//
|
|
17
|
+
// - Secure token storage. There is no keychain in a browser. Anything
|
|
18
|
+
// JavaScript can read, a script injected by XSS can read. The core's
|
|
19
|
+
// KoolbaseAuthStorage is the injection point; the default here persists
|
|
20
|
+
// the session in IndexedDB so a refresh does not sign the user out, and
|
|
21
|
+
// the README says plainly what that means.
|
|
22
|
+
const DB_NAME = 'koolbase';
|
|
23
|
+
const STORE = 'kv';
|
|
24
|
+
function openDB() {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const req = indexedDB.open(DB_NAME, 1);
|
|
27
|
+
req.onupgradeneeded = () => {
|
|
28
|
+
const db = req.result;
|
|
29
|
+
if (!db.objectStoreNames.contains(STORE))
|
|
30
|
+
db.createObjectStore(STORE);
|
|
31
|
+
};
|
|
32
|
+
req.onsuccess = () => resolve(req.result);
|
|
33
|
+
req.onerror = () => reject(req.error ?? new Error('indexedDB open failed'));
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function tx(db, mode, run) {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
const t = db.transaction(STORE, mode);
|
|
39
|
+
const req = run(t.objectStore(STORE));
|
|
40
|
+
req.onsuccess = () => resolve(req.result);
|
|
41
|
+
req.onerror = () => reject(req.error ?? new Error('indexedDB request failed'));
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function indexedDBStorage() {
|
|
45
|
+
let dbp = null;
|
|
46
|
+
const db = () => (dbp ?? (dbp = openDB()));
|
|
47
|
+
return {
|
|
48
|
+
// Not part of PlatformStorage. Test harnesses call it between cases so
|
|
49
|
+
// deleteDatabase is not blocked by a live connection.
|
|
50
|
+
close: async () => {
|
|
51
|
+
if (!dbp)
|
|
52
|
+
return;
|
|
53
|
+
(await dbp).close();
|
|
54
|
+
dbp = null;
|
|
55
|
+
},
|
|
56
|
+
getItem: async (k) => {
|
|
57
|
+
const v = await tx(await db(), 'readonly', s => s.get(k));
|
|
58
|
+
return v ?? null;
|
|
59
|
+
},
|
|
60
|
+
setItem: async (k, v) => { await tx(await db(), 'readwrite', s => s.put(v, k)); },
|
|
61
|
+
removeItem: async (k) => { await tx(await db(), 'readwrite', s => s.delete(k)); },
|
|
62
|
+
getAllKeys: async () => {
|
|
63
|
+
const keys = await tx(await db(), 'readonly', s => s.getAllKeys());
|
|
64
|
+
return keys.map(String);
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// localStorage is synchronous and small, but present in more environments
|
|
69
|
+
// than IndexedDB (some private modes disable IDB). Used only when IDB is
|
|
70
|
+
// absent, so the common path keeps the larger, asynchronous store.
|
|
71
|
+
function localStorageStorage() {
|
|
72
|
+
return {
|
|
73
|
+
getItem: async (k) => localStorage.getItem(k),
|
|
74
|
+
setItem: async (k, v) => { localStorage.setItem(k, v); },
|
|
75
|
+
removeItem: async (k) => { localStorage.removeItem(k); },
|
|
76
|
+
getAllKeys: async () => {
|
|
77
|
+
const out = [];
|
|
78
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
79
|
+
const k = localStorage.key(i);
|
|
80
|
+
if (k !== null)
|
|
81
|
+
out.push(k);
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function browserVersion() {
|
|
88
|
+
if (typeof navigator === 'undefined')
|
|
89
|
+
return '';
|
|
90
|
+
const ua = navigator.userAgent;
|
|
91
|
+
const m = /(Chrome|Firefox|Safari|Edg)\/([\d.]+)/.exec(ua);
|
|
92
|
+
return m ? `${m[1]} ${m[2]}` : '';
|
|
93
|
+
}
|
|
94
|
+
export function browserPlatform() {
|
|
95
|
+
const hasIDB = typeof indexedDB !== 'undefined';
|
|
96
|
+
return {
|
|
97
|
+
storage: hasIDB ? indexedDBStorage() : localStorageStorage(),
|
|
98
|
+
network: {
|
|
99
|
+
onChange: (cb) => {
|
|
100
|
+
// navigator.onLine is a hint that the interface is up, not that
|
|
101
|
+
// Koolbase is reachable. The sync engine treats a false positive as
|
|
102
|
+
// a failed request, the same as any other; a false negative is only
|
|
103
|
+
// a delayed flush until the next event or manual sync.
|
|
104
|
+
// No window in a worker or during SSR: no events, and the sync
|
|
105
|
+
// engine falls back to discovering reachability by trying.
|
|
106
|
+
if (typeof window === 'undefined')
|
|
107
|
+
return () => { };
|
|
108
|
+
const on = () => cb(true);
|
|
109
|
+
const off = () => cb(false);
|
|
110
|
+
window.addEventListener('online', on);
|
|
111
|
+
window.addEventListener('offline', off);
|
|
112
|
+
return () => {
|
|
113
|
+
window.removeEventListener('online', on);
|
|
114
|
+
window.removeEventListener('offline', off);
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
lifecycle: {
|
|
119
|
+
onBackground: (cb) => {
|
|
120
|
+
if (typeof document === 'undefined' || typeof window === 'undefined')
|
|
121
|
+
return () => { };
|
|
122
|
+
const handler = () => { if (document.visibilityState === 'hidden')
|
|
123
|
+
cb(); };
|
|
124
|
+
document.addEventListener('visibilitychange', handler);
|
|
125
|
+
// pagehide is the reliable "tab is going away" signal on mobile
|
|
126
|
+
// browsers, where visibilitychange may not fire before unload.
|
|
127
|
+
window.addEventListener('pagehide', cb);
|
|
128
|
+
return () => {
|
|
129
|
+
document.removeEventListener('visibilitychange', handler);
|
|
130
|
+
window.removeEventListener('pagehide', cb);
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
info: {
|
|
135
|
+
os: 'web',
|
|
136
|
+
version: browserVersion(),
|
|
137
|
+
},
|
|
138
|
+
// IndexedDB-backed; see auth-storage.ts for what that does and does not
|
|
139
|
+
// protect against.
|
|
140
|
+
authStorage: () => new BrowserAuthStorage(),
|
|
141
|
+
};
|
|
142
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@koolbase/js",
|
|
3
|
+
"version": "10.0.0",
|
|
4
|
+
"description": "Koolbase SDK for the browser \u2014 auth, database, storage, realtime, functions, flags and offline sync in one package.",
|
|
5
|
+
"main": "./dist/cjs/index.js",
|
|
6
|
+
"types": "./dist/esm/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"CHANGELOG.md"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "node ../../scripts/build-package.js js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"koolbase",
|
|
16
|
+
"baas",
|
|
17
|
+
"firebase-alternative",
|
|
18
|
+
"supabase-alternative",
|
|
19
|
+
"browser",
|
|
20
|
+
"typescript"
|
|
21
|
+
],
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/koolbase/koolbase-react-native"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@koolbase/core": "10.0.0"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"module": "./dist/esm/index.js",
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"import": {
|
|
37
|
+
"types": "./dist/esm/index.d.ts",
|
|
38
|
+
"default": "./dist/esm/index.js"
|
|
39
|
+
},
|
|
40
|
+
"require": {
|
|
41
|
+
"types": "./dist/cjs/index.d.ts",
|
|
42
|
+
"default": "./dist/cjs/index.js"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"./package.json": "./package.json"
|
|
46
|
+
},
|
|
47
|
+
"sideEffects": false
|
|
48
|
+
}
|