@koolbase/js 10.2.0 → 10.4.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 CHANGED
@@ -7,6 +7,44 @@ 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.4.0
11
+
12
+ ### Added
13
+
14
+ - **`auth.verifyEmail(token)`** — complete email verification with a token
15
+ from a verification link. The endpoint and the Flutter SDK have had this;
16
+ the TypeScript SDKs did not, so an app's verify-email page had nothing to
17
+ call.
18
+
19
+ - **`auth.resendVerificationEmail()`** — re-send the verification email to a
20
+ signed-in but unverified user. Returns `{ alreadyVerified, expiresAt,
21
+ cooldownUntil }`; an already-verified account is a no-op that says so
22
+ rather than an error, which is what a resend button needs when the user
23
+ verified in another tab.
24
+
25
+ The server throttles this, and the refusal now arrives typed:
26
+ `VerificationResendCooldownError` carries `cooldownUntil` so you can show a
27
+ countdown, and `VerificationResendDailyCapError` is separate because the
28
+ remedy differs — wait seconds versus wait until tomorrow.
29
+
30
+ ## 10.3.0
31
+
32
+ ### Fixed
33
+
34
+ - **Analytics events carried no user unless the app called `identify()`.**
35
+ Nothing errored when it never did, so every event landed anonymous and
36
+ retention, funnels and per-user analysis were quietly worthless — found in
37
+ a real project as 53 events, 8 registered users, and not one event carrying
38
+ a user id. The SDK already knows who is signed in; it now says so. The
39
+ Flutter SDK fixed this in 11.2.0 and the TypeScript SDKs did not, until now.
40
+
41
+ `identify()` still wins for an app with its own identity system, and
42
+ `reset()` releases that override and falls back to the Koolbase session.
43
+
44
+ If you have been calling `identify()` yourself, nothing changes. If you have
45
+ not, your events will start carrying users — which is the point, though it
46
+ means your analytics before and after this version are not comparable.
47
+
10
48
  ## 10.2.0
11
49
 
12
50
  ### Added
package/dist/cjs/index.js CHANGED
@@ -38,6 +38,15 @@ let _functions = null;
38
38
  let _flags = null;
39
39
  let _analytics = null;
40
40
  let _initialized = false;
41
+ // The in-flight initialize, so overlapping callers await the same one.
42
+ //
43
+ // A boolean guard is not enough: the check happens before the first await
44
+ // and the flag is set after the last, so two calls that overlap in that
45
+ // window both pass and both build a whole SDK — two of every client, two
46
+ // analytics flush timers, two sync engines over one queue. React strict
47
+ // mode does exactly this in development, and so does any app that
48
+ // initializes from two components.
49
+ let _initializing = null;
41
50
  function ensureInitialized() {
42
51
  if (!_initialized) {
43
52
  throw new Error('Koolbase not initialized. Call Koolbase.initialize(config) first.');
@@ -47,19 +56,31 @@ exports.Koolbase = {
47
56
  async initialize(config) {
48
57
  if (_initialized)
49
58
  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);
59
+ if (_initializing)
60
+ return _initializing;
61
+ _initializing = (async () => {
62
+ (0, core_1.setPlatform)(config.platform ?? (0, platform_js_2.browserPlatform)());
63
+ _auth = new core_1.KoolbaseAuth(config);
64
+ _db = new core_1.KoolbaseDatabase(config, () => _auth?.currentUser?.id ?? null, () => _auth?.validAccessToken() ?? Promise.resolve(null), async () => { await _auth?.clearStoredSession(); });
65
+ _storage = new core_1.KoolbaseStorage(config, () => _auth?.validAccessToken() ?? Promise.resolve(null), async () => { await _auth?.clearStoredSession(); });
66
+ _realtime = new core_1.KoolbaseRealtime(config, () => _auth?.validAccessToken() ?? Promise.resolve(null), () => _auth?.currentUser?.id ?? null);
67
+ _functions = new core_1.KoolbaseFunctions(config, () => _auth?.validAccessToken() ?? Promise.resolve(null), async () => { await _auth?.clearStoredSession(); });
68
+ const deviceId = await (0, core_1.getOrCreateDeviceId)();
69
+ _flags = new core_1.KoolbaseFlags(config, deviceId);
70
+ if (config.analyticsEnabled !== false) {
71
+ _analytics = new core_1.KoolbaseAnalytics(config, () => _auth?.currentUser?.id ?? null);
72
+ await _analytics.init(config.appVersion);
73
+ }
74
+ _initialized = true;
75
+ })();
76
+ try {
77
+ await _initializing;
78
+ }
79
+ finally {
80
+ // Cleared either way: a failed initialize must be retryable rather
81
+ // than leaving every later caller awaiting a rejected promise.
82
+ _initializing = null;
61
83
  }
62
- _initialized = true;
63
84
  },
64
85
  get auth() { ensureInitialized(); return _auth; },
65
86
  get db() { ensureInitialized(); return _db; },
package/dist/esm/index.js CHANGED
@@ -19,6 +19,15 @@ let _functions = null;
19
19
  let _flags = null;
20
20
  let _analytics = null;
21
21
  let _initialized = false;
22
+ // The in-flight initialize, so overlapping callers await the same one.
23
+ //
24
+ // A boolean guard is not enough: the check happens before the first await
25
+ // and the flag is set after the last, so two calls that overlap in that
26
+ // window both pass and both build a whole SDK — two of every client, two
27
+ // analytics flush timers, two sync engines over one queue. React strict
28
+ // mode does exactly this in development, and so does any app that
29
+ // initializes from two components.
30
+ let _initializing = null;
22
31
  function ensureInitialized() {
23
32
  if (!_initialized) {
24
33
  throw new Error('Koolbase not initialized. Call Koolbase.initialize(config) first.');
@@ -28,19 +37,31 @@ export const Koolbase = {
28
37
  async initialize(config) {
29
38
  if (_initialized)
30
39
  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);
40
+ if (_initializing)
41
+ return _initializing;
42
+ _initializing = (async () => {
43
+ setPlatform(config.platform ?? browserPlatform());
44
+ _auth = new KoolbaseAuth(config);
45
+ _db = new KoolbaseDatabase(config, () => _auth?.currentUser?.id ?? null, () => _auth?.validAccessToken() ?? Promise.resolve(null), async () => { await _auth?.clearStoredSession(); });
46
+ _storage = new KoolbaseStorage(config, () => _auth?.validAccessToken() ?? Promise.resolve(null), async () => { await _auth?.clearStoredSession(); });
47
+ _realtime = new KoolbaseRealtime(config, () => _auth?.validAccessToken() ?? Promise.resolve(null), () => _auth?.currentUser?.id ?? null);
48
+ _functions = new KoolbaseFunctions(config, () => _auth?.validAccessToken() ?? Promise.resolve(null), async () => { await _auth?.clearStoredSession(); });
49
+ const deviceId = await getOrCreateDeviceId();
50
+ _flags = new KoolbaseFlags(config, deviceId);
51
+ if (config.analyticsEnabled !== false) {
52
+ _analytics = new KoolbaseAnalytics(config, () => _auth?.currentUser?.id ?? null);
53
+ await _analytics.init(config.appVersion);
54
+ }
55
+ _initialized = true;
56
+ })();
57
+ try {
58
+ await _initializing;
59
+ }
60
+ finally {
61
+ // Cleared either way: a failed initialize must be retryable rather
62
+ // than leaving every later caller awaiting a rejected promise.
63
+ _initializing = null;
42
64
  }
43
- _initialized = true;
44
65
  },
45
66
  get auth() { ensureInitialized(); return _auth; },
46
67
  get db() { ensureInitialized(); return _db; },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koolbase/js",
3
- "version": "10.2.0",
3
+ "version": "10.4.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.2.0"
28
+ "@koolbase/core": "10.4.0"
29
29
  },
30
30
  "publishConfig": {
31
31
  "access": "public"