@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 +6 -0
- package/lib/storage.d.ts +3 -2
- package/lib/storage.d.ts.map +1 -1
- package/lib/storage.js +37 -12
- package/lib/storage.js.map +1 -1
- package/lib-cjs/storage.js +37 -12
- package/lib-cjs/storage.js.map +1 -1
- package/package.json +1 -1
- package/src/storage.ts +38 -13
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
|
-
*
|
|
27
|
-
*
|
|
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 });
|
package/lib/storage.d.ts.map
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
10
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
27
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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 =
|
|
63
|
+
const request = factory.open(dbName, 1);
|
|
39
64
|
request.onupgradeneeded = () => {
|
|
40
65
|
request.result.createObjectStore(STORE);
|
|
41
66
|
};
|
package/lib/storage.js.map
CHANGED
|
@@ -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
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
|
package/lib-cjs/storage.js
CHANGED
|
@@ -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
|
-
*
|
|
13
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
30
|
-
|
|
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
|
-
|
|
34
|
-
|
|
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 =
|
|
66
|
+
const request = factory.open(dbName, 1);
|
|
42
67
|
request.onupgradeneeded = () => {
|
|
43
68
|
request.result.createObjectStore(STORE);
|
|
44
69
|
};
|
package/lib-cjs/storage.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/storage.ts
CHANGED
|
@@ -30,39 +30,64 @@ export type ChatStorage = {
|
|
|
30
30
|
const STORE = 'rooms';
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
53
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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 =
|
|
90
|
+
const request = factory.open(dbName, 1);
|
|
66
91
|
request.onupgradeneeded = () => {
|
|
67
92
|
request.result.createObjectStore(STORE);
|
|
68
93
|
};
|