@foony/chat 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,12 @@
3
3
  All notable changes to `@foony/chat`. Format loosely follows
4
4
  [Keep a Changelog](https://keepachangelog.com); versions are semver.
5
5
 
6
+ ## 0.3.1
7
+
8
+ ### Fixed
9
+
10
+ - Chat continues without its local cache when browser privacy settings block IndexedDB or the cache runs out of space.
11
+
6
12
  ## 0.3.0
7
13
 
8
14
  ### Added
package/lib/storage.d.ts CHANGED
@@ -23,8 +23,9 @@ export type ChatStorage = {
23
23
  remove(room: string): Promise<void>;
24
24
  };
25
25
  /**
26
- * A {@link ChatStorage} backed by IndexedDB, or null where IndexedDB does not exist (Node,
27
- * some webviews) so callers can pass the result straight to `new ChatClient(...)`.
26
+ * Create an optional IndexedDB cache. Return null when access is unavailable or blocked.
27
+ * If opening or using the database fails, reads resolve with null and writes resolve without
28
+ * saving. Cache failures never reject, so live chat can continue without persistence.
28
29
  *
29
30
  * @example
30
31
  * const chat = new ChatClient(realtime, { storage: indexedDbChatStorage() ?? undefined });
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD,uDAAuD;AACvD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,iFAAiF;IACjF,QAAQ,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,CAAC;IACzC,yFAAyF;IACzF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0FAA0F;IAC1F,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,kGAAkG;AAClG,MAAM,MAAM,WAAW,GAAG;IACxB,gEAAgE;IAChE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;IACvD,mCAAmC;IACnC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,qFAAqF;IACrF,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC,CAAC;AAKF;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,SAAe,GAAG,WAAW,GAAG,IAAI,CAqB9E"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD,uDAAuD;AACvD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,iFAAiF;IACjF,QAAQ,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,CAAC;IACzC,yFAAyF;IACzF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0FAA0F;IAC1F,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,kGAAkG;AAClG,MAAM,MAAM,WAAW,GAAG;IACxB,gEAAgE;IAChE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;IACvD,mCAAmC;IACnC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,qFAAqF;IACrF,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC,CAAC;AAKF;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,SAAe,GAAG,WAAW,GAAG,IAAI,CA6C9E"}
package/lib/storage.js CHANGED
@@ -6,36 +6,61 @@
6
6
  /** Object store holding one record per room inside the IndexedDB database. */
7
7
  const STORE = 'rooms';
8
8
  /**
9
- * A {@link ChatStorage} backed by IndexedDB, or null where IndexedDB does not exist (Node,
10
- * some webviews) so callers can pass the result straight to `new ChatClient(...)`.
9
+ * Create an optional IndexedDB cache. Return null when access is unavailable or blocked.
10
+ * If opening or using the database fails, reads resolve with null and writes resolve without
11
+ * saving. Cache failures never reject, so live chat can continue without persistence.
11
12
  *
12
13
  * @example
13
14
  * const chat = new ChatClient(realtime, { storage: indexedDbChatStorage() ?? undefined });
14
15
  */
15
16
  export function indexedDbChatStorage(dbName = 'foony-chat') {
16
- if (typeof indexedDB === 'undefined') {
17
+ let factory;
18
+ try {
19
+ factory = globalThis.indexedDB;
20
+ if (!factory) {
21
+ return null;
22
+ }
23
+ }
24
+ catch {
17
25
  return null;
18
26
  }
19
- const database = openDatabase(dbName);
27
+ // Attach the rejection handler now, before a room has asked to use the cache.
28
+ const database = openDatabase(factory, dbName).catch(() => null);
29
+ let failed = false;
30
+ async function withDatabase(operation, fallback) {
31
+ const db = await database;
32
+ if (!db || failed) {
33
+ return fallback;
34
+ }
35
+ try {
36
+ return await operation(db);
37
+ }
38
+ catch {
39
+ // Quota and privacy failures can persist for the whole session. Stop retrying this cache.
40
+ failed = true;
41
+ return fallback;
42
+ }
43
+ }
20
44
  return {
21
45
  async load(room) {
22
- const db = await database;
23
- return await requestOf(db.transaction(STORE, 'readonly').objectStore(STORE).get(room)) ?? null;
46
+ return withDatabase(async (db) => await requestOf(db.transaction(STORE, 'readonly').objectStore(STORE).get(room)) ?? null, null);
24
47
  },
25
48
  async save(room, state) {
26
- const db = await database;
27
- await requestOf(db.transaction(STORE, 'readwrite').objectStore(STORE).put(state, room));
49
+ return withDatabase(async (db) => {
50
+ await requestOf(db.transaction(STORE, 'readwrite').objectStore(STORE).put(state, room));
51
+ }, undefined);
28
52
  },
29
53
  async remove(room) {
30
- const db = await database;
31
- await requestOf(db.transaction(STORE, 'readwrite').objectStore(STORE).delete(room));
54
+ return withDatabase(async (db) => {
55
+ await requestOf(db.transaction(STORE, 'readwrite').objectStore(STORE).delete(room));
56
+ }, undefined);
32
57
  },
33
58
  };
34
59
  }
35
60
  /** Open (or create) the database with its single room store. */
36
- function openDatabase(dbName) {
61
+ function openDatabase(factory, dbName) {
37
62
  return new Promise((resolve, reject) => {
38
- const request = indexedDB.open(dbName, 1);
63
+ const request = factory.open(dbName, 1);
39
64
  request.onupgradeneeded = () => {
40
65
  request.result.createObjectStore(STORE);
41
66
  };
@@ -1 +1 @@
1
- {"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAwBH,8EAA8E;AAC9E,MAAM,KAAK,GAAG,OAAO,CAAC;AAEtB;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAM,GAAG,YAAY;IACxD,IAAI,OAAO,SAAS,KAAK,WAAW,EAAE,CAAC;QACrC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACtC,OAAO;QACL,KAAK,CAAC,IAAI,CAAC,IAAY;YACrB,MAAM,EAAE,GAAG,MAAM,QAAQ,CAAC;YAC1B,OAAO,MAAM,SAAS,CACpB,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAC/D,IAAI,IAAI,CAAC;QACZ,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,IAAY,EAAE,KAAyB;YAChD,MAAM,EAAE,GAAG,MAAM,QAAQ,CAAC;YAC1B,MAAM,SAAS,CAAC,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;QAC1F,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,IAAY;YACvB,MAAM,EAAE,GAAG,MAAM,QAAQ,CAAC;YAC1B,MAAM,SAAS,CAAC,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QACtF,CAAC;KACF,CAAC;AACJ,CAAC;AAED,gEAAgE;AAChE,SAAS,YAAY,CAAC,MAAc;IAClC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAC1C,OAAO,CAAC,eAAe,GAAG,GAAG,EAAE;YAC7B,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAC1C,CAAC,CAAC;QACF,OAAO,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAClD,OAAO,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC,CAAC;IACtF,CAAC,CAAC,CAAC;AACL,CAAC;AAED,gCAAgC;AAChC,SAAS,SAAS,CAAI,OAAsB;IAC1C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,OAAO,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAClD,OAAO,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC,CAAC;IACzF,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
@@ -9,36 +9,61 @@ exports.indexedDbChatStorage = indexedDbChatStorage;
9
9
  /** Object store holding one record per room inside the IndexedDB database. */
10
10
  const STORE = 'rooms';
11
11
  /**
12
- * A {@link ChatStorage} backed by IndexedDB, or null where IndexedDB does not exist (Node,
13
- * some webviews) so callers can pass the result straight to `new ChatClient(...)`.
12
+ * Create an optional IndexedDB cache. Return null when access is unavailable or blocked.
13
+ * If opening or using the database fails, reads resolve with null and writes resolve without
14
+ * saving. Cache failures never reject, so live chat can continue without persistence.
14
15
  *
15
16
  * @example
16
17
  * const chat = new ChatClient(realtime, { storage: indexedDbChatStorage() ?? undefined });
17
18
  */
18
19
  function indexedDbChatStorage(dbName = 'foony-chat') {
19
- if (typeof indexedDB === 'undefined') {
20
+ let factory;
21
+ try {
22
+ factory = globalThis.indexedDB;
23
+ if (!factory) {
24
+ return null;
25
+ }
26
+ }
27
+ catch {
20
28
  return null;
21
29
  }
22
- const database = openDatabase(dbName);
30
+ // Attach the rejection handler now, before a room has asked to use the cache.
31
+ const database = openDatabase(factory, dbName).catch(() => null);
32
+ let failed = false;
33
+ async function withDatabase(operation, fallback) {
34
+ const db = await database;
35
+ if (!db || failed) {
36
+ return fallback;
37
+ }
38
+ try {
39
+ return await operation(db);
40
+ }
41
+ catch {
42
+ // Quota and privacy failures can persist for the whole session. Stop retrying this cache.
43
+ failed = true;
44
+ return fallback;
45
+ }
46
+ }
23
47
  return {
24
48
  async load(room) {
25
- const db = await database;
26
- return await requestOf(db.transaction(STORE, 'readonly').objectStore(STORE).get(room)) ?? null;
49
+ return withDatabase(async (db) => await requestOf(db.transaction(STORE, 'readonly').objectStore(STORE).get(room)) ?? null, null);
27
50
  },
28
51
  async save(room, state) {
29
- const db = await database;
30
- await requestOf(db.transaction(STORE, 'readwrite').objectStore(STORE).put(state, room));
52
+ return withDatabase(async (db) => {
53
+ await requestOf(db.transaction(STORE, 'readwrite').objectStore(STORE).put(state, room));
54
+ }, undefined);
31
55
  },
32
56
  async remove(room) {
33
- const db = await database;
34
- await requestOf(db.transaction(STORE, 'readwrite').objectStore(STORE).delete(room));
57
+ return withDatabase(async (db) => {
58
+ await requestOf(db.transaction(STORE, 'readwrite').objectStore(STORE).delete(room));
59
+ }, undefined);
35
60
  },
36
61
  };
37
62
  }
38
63
  /** Open (or create) the database with its single room store. */
39
- function openDatabase(dbName) {
64
+ function openDatabase(factory, dbName) {
40
65
  return new Promise((resolve, reject) => {
41
- const request = indexedDB.open(dbName, 1);
66
+ const request = factory.open(dbName, 1);
42
67
  request.onupgradeneeded = () => {
43
68
  request.result.createObjectStore(STORE);
44
69
  };
@@ -1 +1 @@
1
- {"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;AAkCH,oDAqBC;AA/BD,8EAA8E;AAC9E,MAAM,KAAK,GAAG,OAAO,CAAC;AAEtB;;;;;;GAMG;AACH,SAAgB,oBAAoB,CAAC,MAAM,GAAG,YAAY;IACxD,IAAI,OAAO,SAAS,KAAK,WAAW,EAAE,CAAC;QACrC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACtC,OAAO;QACL,KAAK,CAAC,IAAI,CAAC,IAAY;YACrB,MAAM,EAAE,GAAG,MAAM,QAAQ,CAAC;YAC1B,OAAO,MAAM,SAAS,CACpB,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAC/D,IAAI,IAAI,CAAC;QACZ,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,IAAY,EAAE,KAAyB;YAChD,MAAM,EAAE,GAAG,MAAM,QAAQ,CAAC;YAC1B,MAAM,SAAS,CAAC,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;QAC1F,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,IAAY;YACvB,MAAM,EAAE,GAAG,MAAM,QAAQ,CAAC;YAC1B,MAAM,SAAS,CAAC,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QACtF,CAAC;KACF,CAAC;AACJ,CAAC;AAED,gEAAgE;AAChE,SAAS,YAAY,CAAC,MAAc;IAClC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAC1C,OAAO,CAAC,eAAe,GAAG,GAAG,EAAE;YAC7B,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAC1C,CAAC,CAAC;QACF,OAAO,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAClD,OAAO,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC,CAAC;IACtF,CAAC,CAAC,CAAC;AACL,CAAC;AAED,gCAAgC;AAChC,SAAS,SAAS,CAAI,OAAsB;IAC1C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,OAAO,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAClD,OAAO,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC,CAAC;IACzF,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foony/chat",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Chat API (rooms, messages, typing, reactions, presence, occupancy) built on @foony/realtime.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
package/src/storage.ts CHANGED
@@ -30,39 +30,64 @@ export type ChatStorage = {
30
30
  const STORE = 'rooms';
31
31
 
32
32
  /**
33
- * A {@link ChatStorage} backed by IndexedDB, or null where IndexedDB does not exist (Node,
34
- * some webviews) so callers can pass the result straight to `new ChatClient(...)`.
33
+ * Create an optional IndexedDB cache. Return null when access is unavailable or blocked.
34
+ * If opening or using the database fails, reads resolve with null and writes resolve without
35
+ * saving. Cache failures never reject, so live chat can continue without persistence.
35
36
  *
36
37
  * @example
37
38
  * const chat = new ChatClient(realtime, { storage: indexedDbChatStorage() ?? undefined });
38
39
  */
39
40
  export function indexedDbChatStorage(dbName = 'foony-chat'): ChatStorage | null {
40
- if (typeof indexedDB === 'undefined') {
41
+ let factory: IDBFactory;
42
+ try {
43
+ factory = globalThis.indexedDB;
44
+ if (!factory) {
45
+ return null;
46
+ }
47
+ } catch {
41
48
  return null;
42
49
  }
43
- const database = openDatabase(dbName);
50
+ // Attach the rejection handler now, before a room has asked to use the cache.
51
+ const database = openDatabase(factory, dbName).catch(() => null);
52
+ let failed = false;
53
+
54
+ async function withDatabase<T>(operation: (db: IDBDatabase) => Promise<T>, fallback: T): Promise<T> {
55
+ const db = await database;
56
+ if (!db || failed) {
57
+ return fallback;
58
+ }
59
+ try {
60
+ return await operation(db);
61
+ } catch {
62
+ // Quota and privacy failures can persist for the whole session. Stop retrying this cache.
63
+ failed = true;
64
+ return fallback;
65
+ }
66
+ }
67
+
44
68
  return {
45
69
  async load(room: string): Promise<PersistedRoomState | null> {
46
- const db = await database;
47
- return await requestOf<PersistedRoomState | undefined>(
70
+ return withDatabase(async db => await requestOf<PersistedRoomState | undefined>(
48
71
  db.transaction(STORE, 'readonly').objectStore(STORE).get(room),
49
- ) ?? null;
72
+ ) ?? null, null);
50
73
  },
51
74
  async save(room: string, state: PersistedRoomState): Promise<void> {
52
- const db = await database;
53
- await requestOf(db.transaction(STORE, 'readwrite').objectStore(STORE).put(state, room));
75
+ return withDatabase(async db => {
76
+ await requestOf(db.transaction(STORE, 'readwrite').objectStore(STORE).put(state, room));
77
+ }, undefined);
54
78
  },
55
79
  async remove(room: string): Promise<void> {
56
- const db = await database;
57
- await requestOf(db.transaction(STORE, 'readwrite').objectStore(STORE).delete(room));
80
+ return withDatabase(async db => {
81
+ await requestOf(db.transaction(STORE, 'readwrite').objectStore(STORE).delete(room));
82
+ }, undefined);
58
83
  },
59
84
  };
60
85
  }
61
86
 
62
87
  /** Open (or create) the database with its single room store. */
63
- function openDatabase(dbName: string): Promise<IDBDatabase> {
88
+ function openDatabase(factory: IDBFactory, dbName: string): Promise<IDBDatabase> {
64
89
  return new Promise((resolve, reject) => {
65
- const request = indexedDB.open(dbName, 1);
90
+ const request = factory.open(dbName, 1);
66
91
  request.onupgradeneeded = () => {
67
92
  request.result.createObjectStore(STORE);
68
93
  };