@flowerforce/flowerbase-client 0.1.1-beta.9 → 0.3.1-beta.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 +19 -0
- package/dist/app.d.ts +2 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +39 -24
- package/dist/session.d.ts +9 -2
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +66 -62
- package/dist/storage.d.ts +11 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +44 -0
- package/dist/storage.native.d.ts +11 -0
- package/dist/storage.native.d.ts.map +1 -0
- package/dist/storage.native.js +42 -0
- package/package.json +1 -1
- package/src/app.ts +45 -24
- package/src/session.ts +63 -54
- package/src/storage.native.ts +49 -0
- package/src/storage.ts +57 -0
- package/dist/session.native.d.ts +0 -14
- package/dist/session.native.d.ts.map +0 -1
- package/dist/session.native.js +0 -76
- package/src/session.native.ts +0 -89
package/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
## 0.3.0 (2026-03-20)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### 🚀 Features
|
|
5
|
+
|
|
6
|
+
- add MongoDB Client-Side Field Level Encryption support ([#35](https://github.com/flowerforce/flowerbase/pull/35))
|
|
7
|
+
|
|
8
|
+
- **monitoring:** Add collapsable editor ([#34](https://github.com/flowerforce/flowerbase/pull/34))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### 🩹 Fixes
|
|
12
|
+
|
|
13
|
+
- watch operationType delete ([afcbec2](https://github.com/flowerforce/flowerbase/commit/afcbec2))
|
|
14
|
+
|
|
15
|
+
- propagate runAsSystem ([#41](https://github.com/flowerforce/flowerbase/pull/41))
|
|
16
|
+
|
|
17
|
+
- add setHeader ([#44](https://github.com/flowerforce/flowerbase/pull/44))
|
|
18
|
+
|
|
19
|
+
- 401 login ([fd13ff0](https://github.com/flowerforce/flowerbase/commit/fd13ff0))
|
package/dist/app.d.ts
CHANGED
|
@@ -48,6 +48,8 @@ export declare class App {
|
|
|
48
48
|
get allUsers(): Readonly<Record<string, User>>;
|
|
49
49
|
private persistSessionsByUser;
|
|
50
50
|
private persistUsersOrder;
|
|
51
|
+
private restorePersistedUsers;
|
|
52
|
+
private restoreCurrentSession;
|
|
51
53
|
private touchUser;
|
|
52
54
|
private removeUserFromOrder;
|
|
53
55
|
private setSessionForUser;
|
package/dist/app.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,eAAe,EAAuB,WAAW,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACnG,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAc7B,qBAAa,GAAG;IACd,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAA0B;IAC1D,MAAM,CAAC,QAAQ,CAAC,WAAW,qBAAc;IAEzC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAgB;IAC/C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0B;IACpD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAiC;IAClE,OAAO,CAAC,UAAU,CAAe;IACjC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAiC;IAClE,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAe;IACvD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAElD,iBAAiB,EAAE;QACjB,YAAY,EAAE,CAAC,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;QAC9E,WAAW,EAAE,CAAC,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;QAC5E,uBAAuB,EAAE,CAAC,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;QACvE,uBAAuB,EAAE,CAAC,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;QACvE,sBAAsB,EAAE,CAAC,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,GAAG,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;QAC/E,yBAAyB,EAAE,CACzB,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,GAAG,MAAM,EACnD,aAAa,CAAC,EAAE,MAAM,EACtB,GAAG,IAAI,EAAE,OAAO,EAAE,KACf,OAAO,CAAC,OAAO,CAAC,CAAA;QACrB,aAAa,EAAE,CAAC,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;KACjG,CAAA;gBAEW,UAAU,EAAE,MAAM,GAAG,SAAS;
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,eAAe,EAAuB,WAAW,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACnG,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAc7B,qBAAa,GAAG;IACd,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAA0B;IAC1D,MAAM,CAAC,QAAQ,CAAC,WAAW,qBAAc;IAEzC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAgB;IAC/C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0B;IACpD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAiC;IAClE,OAAO,CAAC,UAAU,CAAe;IACjC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAiC;IAClE,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAe;IACvD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAElD,iBAAiB,EAAE;QACjB,YAAY,EAAE,CAAC,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;QAC9E,WAAW,EAAE,CAAC,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;QAC5E,uBAAuB,EAAE,CAAC,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;QACvE,uBAAuB,EAAE,CAAC,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;QACvE,sBAAsB,EAAE,CAAC,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,GAAG,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;QAC/E,yBAAyB,EAAE,CACzB,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,GAAG,MAAM,EACnD,aAAa,CAAC,EAAE,MAAM,EACtB,GAAG,IAAI,EAAE,OAAO,EAAE,KACf,OAAO,CAAC,OAAO,CAAC,CAAA;QACrB,aAAa,EAAE,CAAC,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;KACjG,CAAA;gBAEW,UAAU,EAAE,MAAM,GAAG,SAAS;IA2C1C,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS;IAU/C,IAAI,WAAW,gBAQd;IAED,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAiB7C;IAED,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,qBAAqB;IAyB7B,OAAO,CAAC,qBAAqB;IAa7B,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,mBAAmB;IAK3B,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,mBAAmB;IAK3B,OAAO,CAAC,0BAA0B;IAWlC,OAAO,CAAC,eAAe;IAcvB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,YAAY;YAIN,aAAa;YASb,sBAAsB;YAsBtB,yBAAyB;YAIzB,eAAe;IAsB7B,OAAO,CAAC,eAAe;IAUjB,KAAK,CAAC,WAAW,EAAE,eAAe;IA4BxC,UAAU,CAAC,QAAQ,EAAE,IAAI;IAanB,UAAU,CAAC,IAAI,EAAE,IAAI;IAgBrB,UAAU,CAAC,IAAI,EAAE,IAAI;IAa3B,iBAAiB,CAAC,MAAM,CAAC,EAAE,MAAM;IASjC,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM;IAO1B,OAAO,CAAC,MAAM,EAAE,MAAM;IAItB,kBAAkB,CAAC,MAAM,EAAE,MAAM;IAI3B,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;YAS1D,sBAAsB;IAc9B,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM;IAqB3D,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;IAqDzG,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,OAAO,SAAkB,EAAE,MAAM,CAAC,EAAE,MAAM;IAoBrF,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAgBjD,kBAAkB,CAAC,MAAM,CAAC,EAAE,MAAM;IAuBlC,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM;IAoBhC,WAAW,CAAC,QAAQ,EAAE,MAAM,IAAI;IAIhC,cAAc,CAAC,QAAQ,EAAE,MAAM,IAAI;IAInC,kBAAkB;CAGnB"}
|
package/dist/app.js
CHANGED
|
@@ -19,29 +19,8 @@ class App {
|
|
|
19
19
|
this.baseUrl = (config.baseUrl ?? '').replace(/\/$/, '');
|
|
20
20
|
this.timeout = config.timeout ?? 10000;
|
|
21
21
|
this.sessionManager = new session_1.SessionManager(this.id);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
this.sessionsByUserId.set(userId, session);
|
|
25
|
-
}
|
|
26
|
-
this.usersOrder = this.sessionManager.getUsersOrder();
|
|
27
|
-
for (const userId of this.sessionsByUserId.keys()) {
|
|
28
|
-
if (!this.usersOrder.includes(userId)) {
|
|
29
|
-
this.usersOrder.push(userId);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
for (const userId of this.usersOrder) {
|
|
33
|
-
this.getOrCreateUser(userId);
|
|
34
|
-
}
|
|
35
|
-
const currentSession = this.sessionManager.get();
|
|
36
|
-
if (currentSession?.userId) {
|
|
37
|
-
this.sessionsByUserId.set(currentSession.userId, currentSession);
|
|
38
|
-
this.getOrCreateUser(currentSession.userId);
|
|
39
|
-
this.touchUser(currentSession.userId);
|
|
40
|
-
this.persistSessionsByUser();
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
this.setCurrentSessionFromOrder();
|
|
44
|
-
}
|
|
22
|
+
this.restorePersistedUsers();
|
|
23
|
+
this.restoreCurrentSession();
|
|
45
24
|
this.sessionBootstrapPromise = this.bootstrapSessionOnLoad();
|
|
46
25
|
this.emailPasswordAuth = {
|
|
47
26
|
registerUser: ({ email, password }) => this.postProvider('/local-userpass/register', { email, password }),
|
|
@@ -109,6 +88,39 @@ class App {
|
|
|
109
88
|
persistUsersOrder() {
|
|
110
89
|
this.sessionManager.setUsersOrder(this.usersOrder);
|
|
111
90
|
}
|
|
91
|
+
restorePersistedUsers() {
|
|
92
|
+
const persistedSessionsByUser = this.sessionManager.getSessionsByUser();
|
|
93
|
+
for (const [userId, session] of Object.entries(persistedSessionsByUser)) {
|
|
94
|
+
this.sessionsByUserId.set(userId, session);
|
|
95
|
+
}
|
|
96
|
+
const persistedOrder = this.sessionManager.getUsersOrder();
|
|
97
|
+
const nextUsersOrder = [];
|
|
98
|
+
for (const userId of persistedOrder) {
|
|
99
|
+
if (!nextUsersOrder.includes(userId)) {
|
|
100
|
+
nextUsersOrder.push(userId);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
for (const userId of this.sessionsByUserId.keys()) {
|
|
104
|
+
if (!nextUsersOrder.includes(userId)) {
|
|
105
|
+
nextUsersOrder.push(userId);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
this.usersOrder = nextUsersOrder;
|
|
109
|
+
for (const userId of this.usersOrder) {
|
|
110
|
+
this.getOrCreateUser(userId);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
restoreCurrentSession() {
|
|
114
|
+
const currentSession = this.sessionManager.get();
|
|
115
|
+
if (currentSession?.userId) {
|
|
116
|
+
this.sessionsByUserId.set(currentSession.userId, currentSession);
|
|
117
|
+
this.getOrCreateUser(currentSession.userId);
|
|
118
|
+
this.touchUser(currentSession.userId);
|
|
119
|
+
this.persistSessionsByUser();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
this.setCurrentSessionFromOrder();
|
|
123
|
+
}
|
|
112
124
|
touchUser(userId) {
|
|
113
125
|
this.usersOrder = [userId, ...this.usersOrder.filter((id) => id !== userId)];
|
|
114
126
|
this.persistUsersOrder();
|
|
@@ -167,8 +179,11 @@ class App {
|
|
|
167
179
|
});
|
|
168
180
|
}
|
|
169
181
|
async bootstrapSessionOnLoad() {
|
|
182
|
+
await this.sessionManager.whenReady();
|
|
183
|
+
this.restorePersistedUsers();
|
|
184
|
+
this.restoreCurrentSession();
|
|
170
185
|
const session = this.sessionManager.get();
|
|
171
|
-
if (!session ||
|
|
186
|
+
if (!session || !this.sessionManager.hasPersistentStorage()) {
|
|
172
187
|
return;
|
|
173
188
|
}
|
|
174
189
|
try {
|
package/dist/session.d.ts
CHANGED
|
@@ -4,15 +4,22 @@ export declare class SessionManager {
|
|
|
4
4
|
private readonly usersKey;
|
|
5
5
|
private readonly sessionsKey;
|
|
6
6
|
private readonly storage;
|
|
7
|
+
private readonly hydrationPromise;
|
|
7
8
|
private session;
|
|
9
|
+
private usersOrder;
|
|
10
|
+
private sessionsByUser;
|
|
8
11
|
constructor(appId: string);
|
|
9
|
-
|
|
12
|
+
private hydrate;
|
|
13
|
+
whenReady(): Promise<void>;
|
|
14
|
+
hasPersistentStorage(): boolean;
|
|
10
15
|
get(): SessionData | null;
|
|
11
16
|
set(session: SessionData): void;
|
|
12
17
|
clear(): void;
|
|
13
18
|
getUsersOrder(): string[];
|
|
14
19
|
setUsersOrder(order: string[]): void;
|
|
15
|
-
getSessionsByUser():
|
|
20
|
+
getSessionsByUser(): {
|
|
21
|
+
[x: string]: SessionData;
|
|
22
|
+
};
|
|
16
23
|
setSessionsByUser(sessionsByUser: Record<string, SessionData>): void;
|
|
17
24
|
}
|
|
18
25
|
//# sourceMappingURL=session.d.ts.map
|
package/dist/session.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAgDrC,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAQ;IAC5B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkB;IAC1C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAe;IAChD,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,UAAU,CAAe;IACjC,OAAO,CAAC,cAAc,CAAkC;gBAE5C,KAAK,EAAE,MAAM;YAUX,OAAO;IAOrB,SAAS;IAIT,oBAAoB;IAIpB,GAAG;IAIH,GAAG,CAAC,OAAO,EAAE,WAAW;IAKxB,KAAK;IAKL,aAAa;IAIb,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE;IAS7B,iBAAiB;;;IAIjB,iBAAiB,CAAC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC;CAQ9D"}
|
package/dist/session.js
CHANGED
|
@@ -1,44 +1,76 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SessionManager = void 0;
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
if (
|
|
7
|
-
return
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
4
|
+
const storage_1 = require("./storage");
|
|
5
|
+
const parseSession = (raw) => {
|
|
6
|
+
if (!raw)
|
|
7
|
+
return null;
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(raw);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const parseUsersOrder = (raw) => {
|
|
16
|
+
if (!raw)
|
|
17
|
+
return [];
|
|
18
|
+
try {
|
|
19
|
+
const parsed = JSON.parse(raw);
|
|
20
|
+
if (!Array.isArray(parsed))
|
|
21
|
+
return [];
|
|
22
|
+
return parsed.filter((item) => typeof item === 'string');
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const parseSessionsByUser = (raw) => {
|
|
29
|
+
if (!raw)
|
|
30
|
+
return {};
|
|
31
|
+
try {
|
|
32
|
+
const parsed = JSON.parse(raw);
|
|
33
|
+
const normalized = {};
|
|
34
|
+
for (const [userId, session] of Object.entries(parsed)) {
|
|
35
|
+
if (session &&
|
|
36
|
+
typeof session === 'object' &&
|
|
37
|
+
typeof session.accessToken === 'string' &&
|
|
38
|
+
typeof session.refreshToken === 'string' &&
|
|
39
|
+
typeof session.userId === 'string') {
|
|
40
|
+
normalized[userId] = session;
|
|
41
|
+
}
|
|
20
42
|
}
|
|
21
|
-
|
|
43
|
+
return normalized;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
22
48
|
};
|
|
23
49
|
class SessionManager {
|
|
24
50
|
constructor(appId) {
|
|
25
|
-
this.storage =
|
|
51
|
+
this.storage = (0, storage_1.createStorage)();
|
|
26
52
|
this.session = null;
|
|
53
|
+
this.usersOrder = [];
|
|
54
|
+
this.sessionsByUser = {};
|
|
27
55
|
this.key = `flowerbase:${appId}:session`;
|
|
28
56
|
this.usersKey = `flowerbase:${appId}:users`;
|
|
29
57
|
this.sessionsKey = `flowerbase:${appId}:sessions`;
|
|
30
|
-
this.session = this.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
58
|
+
this.session = parseSession(this.storage.getItem(this.key));
|
|
59
|
+
this.usersOrder = parseUsersOrder(this.storage.getItem(this.usersKey));
|
|
60
|
+
this.sessionsByUser = parseSessionsByUser(this.storage.getItem(this.sessionsKey));
|
|
61
|
+
this.hydrationPromise = this.hydrate();
|
|
62
|
+
}
|
|
63
|
+
async hydrate() {
|
|
64
|
+
const hydrated = await this.storage.hydrate([this.key, this.usersKey, this.sessionsKey]);
|
|
65
|
+
this.session = parseSession(hydrated[this.key] ?? null);
|
|
66
|
+
this.usersOrder = parseUsersOrder(hydrated[this.usersKey] ?? null);
|
|
67
|
+
this.sessionsByUser = parseSessionsByUser(hydrated[this.sessionsKey] ?? null);
|
|
68
|
+
}
|
|
69
|
+
whenReady() {
|
|
70
|
+
return this.hydrationPromise;
|
|
71
|
+
}
|
|
72
|
+
hasPersistentStorage() {
|
|
73
|
+
return this.storage.isPersistent;
|
|
42
74
|
}
|
|
43
75
|
get() {
|
|
44
76
|
return this.session;
|
|
@@ -52,20 +84,10 @@ class SessionManager {
|
|
|
52
84
|
this.storage.removeItem(this.key);
|
|
53
85
|
}
|
|
54
86
|
getUsersOrder() {
|
|
55
|
-
|
|
56
|
-
if (!raw)
|
|
57
|
-
return [];
|
|
58
|
-
try {
|
|
59
|
-
const parsed = JSON.parse(raw);
|
|
60
|
-
if (!Array.isArray(parsed))
|
|
61
|
-
return [];
|
|
62
|
-
return parsed.filter((item) => typeof item === 'string');
|
|
63
|
-
}
|
|
64
|
-
catch {
|
|
65
|
-
return [];
|
|
66
|
-
}
|
|
87
|
+
return [...this.usersOrder];
|
|
67
88
|
}
|
|
68
89
|
setUsersOrder(order) {
|
|
90
|
+
this.usersOrder = [...order];
|
|
69
91
|
if (order.length === 0) {
|
|
70
92
|
this.storage.removeItem(this.usersKey);
|
|
71
93
|
return;
|
|
@@ -73,28 +95,10 @@ class SessionManager {
|
|
|
73
95
|
this.storage.setItem(this.usersKey, JSON.stringify(order));
|
|
74
96
|
}
|
|
75
97
|
getSessionsByUser() {
|
|
76
|
-
|
|
77
|
-
if (!raw)
|
|
78
|
-
return {};
|
|
79
|
-
try {
|
|
80
|
-
const parsed = JSON.parse(raw);
|
|
81
|
-
const normalized = {};
|
|
82
|
-
for (const [userId, session] of Object.entries(parsed)) {
|
|
83
|
-
if (session &&
|
|
84
|
-
typeof session === 'object' &&
|
|
85
|
-
typeof session.accessToken === 'string' &&
|
|
86
|
-
typeof session.refreshToken === 'string' &&
|
|
87
|
-
typeof session.userId === 'string') {
|
|
88
|
-
normalized[userId] = session;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return normalized;
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
return {};
|
|
95
|
-
}
|
|
98
|
+
return { ...this.sessionsByUser };
|
|
96
99
|
}
|
|
97
100
|
setSessionsByUser(sessionsByUser) {
|
|
101
|
+
this.sessionsByUser = { ...sessionsByUser };
|
|
98
102
|
if (Object.keys(sessionsByUser).length === 0) {
|
|
99
103
|
this.storage.removeItem(this.sessionsKey);
|
|
100
104
|
return;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type StorageSnapshot = Record<string, string | null>;
|
|
2
|
+
export type PersistedStorage = {
|
|
3
|
+
isPersistent: boolean;
|
|
4
|
+
getItem: (key: string) => string | null;
|
|
5
|
+
setItem: (key: string, value: string) => void;
|
|
6
|
+
removeItem: (key: string) => void;
|
|
7
|
+
hydrate: (keys: string[]) => Promise<StorageSnapshot>;
|
|
8
|
+
};
|
|
9
|
+
export declare const createStorage: () => PersistedStorage;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA,KAAK,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAA;AAEpD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,YAAY,EAAE,OAAO,CAAA;IACrB,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAA;IACvC,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7C,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,eAAe,CAAC,CAAA;CACtD,CAAA;AA8BD,eAAO,MAAM,aAAa,QAAO,gBAkBhC,CAAA"}
|
package/dist/storage.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createStorage = void 0;
|
|
4
|
+
const memoryStore = new Map();
|
|
5
|
+
const getStorage = () => {
|
|
6
|
+
const browserStorage = globalThis.localStorage;
|
|
7
|
+
if (browserStorage &&
|
|
8
|
+
typeof browserStorage.getItem === 'function' &&
|
|
9
|
+
typeof browserStorage.setItem === 'function' &&
|
|
10
|
+
typeof browserStorage.removeItem === 'function') {
|
|
11
|
+
return {
|
|
12
|
+
getItem: (key) => browserStorage.getItem(key),
|
|
13
|
+
setItem: (key, value) => browserStorage.setItem(key, value),
|
|
14
|
+
removeItem: (key) => browserStorage.removeItem(key)
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
getItem: (key) => memoryStore.get(key) ?? null,
|
|
19
|
+
setItem: (key, value) => {
|
|
20
|
+
memoryStore.set(key, value);
|
|
21
|
+
},
|
|
22
|
+
removeItem: (key) => {
|
|
23
|
+
memoryStore.delete(key);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
const createStorage = () => {
|
|
28
|
+
const browserStorage = globalThis.localStorage;
|
|
29
|
+
const isPersistent = !!browserStorage &&
|
|
30
|
+
typeof browserStorage.getItem === 'function' &&
|
|
31
|
+
typeof browserStorage.setItem === 'function' &&
|
|
32
|
+
typeof browserStorage.removeItem === 'function';
|
|
33
|
+
const storage = getStorage();
|
|
34
|
+
return {
|
|
35
|
+
isPersistent,
|
|
36
|
+
getItem: storage.getItem,
|
|
37
|
+
setItem: storage.setItem,
|
|
38
|
+
removeItem: storage.removeItem,
|
|
39
|
+
async hydrate(keys) {
|
|
40
|
+
return Object.fromEntries(keys.map((key) => [key, storage.getItem(key)]));
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
exports.createStorage = createStorage;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type StorageSnapshot = Record<string, string | null>;
|
|
2
|
+
export type PersistedStorage = {
|
|
3
|
+
isPersistent: boolean;
|
|
4
|
+
getItem: (key: string) => string | null;
|
|
5
|
+
setItem: (key: string, value: string) => void;
|
|
6
|
+
removeItem: (key: string) => void;
|
|
7
|
+
hydrate: (keys: string[]) => Promise<StorageSnapshot>;
|
|
8
|
+
};
|
|
9
|
+
export declare const createStorage: () => PersistedStorage;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=storage.native.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.native.d.ts","sourceRoot":"","sources":["../src/storage.native.ts"],"names":[],"mappings":"AAEA,KAAK,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAA;AAEpD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,YAAY,EAAE,OAAO,CAAA;IACrB,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAA;IACvC,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7C,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,eAAe,CAAC,CAAA;CACtD,CAAA;AAOD,eAAO,MAAM,aAAa,QAAO,gBA+B/B,CAAA"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createStorage = void 0;
|
|
7
|
+
const async_storage_1 = __importDefault(require("@react-native-async-storage/async-storage"));
|
|
8
|
+
const memoryStore = new Map();
|
|
9
|
+
const getSnapshot = (keys) => Object.fromEntries(keys.map((key) => [key, memoryStore.get(key) ?? null]));
|
|
10
|
+
const createStorage = () => ({
|
|
11
|
+
isPersistent: true,
|
|
12
|
+
getItem: (key) => memoryStore.get(key) ?? null,
|
|
13
|
+
setItem: (key, value) => {
|
|
14
|
+
memoryStore.set(key, value);
|
|
15
|
+
void async_storage_1.default.setItem(key, value).catch(() => {
|
|
16
|
+
// Ignore write failures and keep the in-memory cache alive.
|
|
17
|
+
});
|
|
18
|
+
},
|
|
19
|
+
removeItem: (key) => {
|
|
20
|
+
memoryStore.delete(key);
|
|
21
|
+
void async_storage_1.default.removeItem(key).catch(() => {
|
|
22
|
+
// Ignore delete failures and keep the in-memory cache alive.
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
async hydrate(keys) {
|
|
26
|
+
try {
|
|
27
|
+
const entries = await async_storage_1.default.multiGet(keys);
|
|
28
|
+
for (const [key, value] of entries) {
|
|
29
|
+
if (value === null) {
|
|
30
|
+
memoryStore.delete(key);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
memoryStore.set(key, value);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Ignore storage read failures and keep the in-memory cache alive.
|
|
38
|
+
}
|
|
39
|
+
return getSnapshot(keys);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
exports.createStorage = createStorage;
|
package/package.json
CHANGED
package/src/app.ts
CHANGED
|
@@ -52,29 +52,8 @@ export class App {
|
|
|
52
52
|
this.baseUrl = (config.baseUrl ?? '').replace(/\/$/, '')
|
|
53
53
|
this.timeout = config.timeout ?? 10000
|
|
54
54
|
this.sessionManager = new SessionManager(this.id)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
this.sessionsByUserId.set(userId, session)
|
|
58
|
-
}
|
|
59
|
-
this.usersOrder = this.sessionManager.getUsersOrder()
|
|
60
|
-
for (const userId of this.sessionsByUserId.keys()) {
|
|
61
|
-
if (!this.usersOrder.includes(userId)) {
|
|
62
|
-
this.usersOrder.push(userId)
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
for (const userId of this.usersOrder) {
|
|
66
|
-
this.getOrCreateUser(userId)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const currentSession = this.sessionManager.get()
|
|
70
|
-
if (currentSession?.userId) {
|
|
71
|
-
this.sessionsByUserId.set(currentSession.userId, currentSession)
|
|
72
|
-
this.getOrCreateUser(currentSession.userId)
|
|
73
|
-
this.touchUser(currentSession.userId)
|
|
74
|
-
this.persistSessionsByUser()
|
|
75
|
-
} else {
|
|
76
|
-
this.setCurrentSessionFromOrder()
|
|
77
|
-
}
|
|
55
|
+
this.restorePersistedUsers()
|
|
56
|
+
this.restoreCurrentSession()
|
|
78
57
|
this.sessionBootstrapPromise = this.bootstrapSessionOnLoad()
|
|
79
58
|
|
|
80
59
|
this.emailPasswordAuth = {
|
|
@@ -157,6 +136,44 @@ export class App {
|
|
|
157
136
|
this.sessionManager.setUsersOrder(this.usersOrder)
|
|
158
137
|
}
|
|
159
138
|
|
|
139
|
+
private restorePersistedUsers() {
|
|
140
|
+
const persistedSessionsByUser = this.sessionManager.getSessionsByUser()
|
|
141
|
+
for (const [userId, session] of Object.entries(persistedSessionsByUser)) {
|
|
142
|
+
this.sessionsByUserId.set(userId, session)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const persistedOrder = this.sessionManager.getUsersOrder()
|
|
146
|
+
const nextUsersOrder: string[] = []
|
|
147
|
+
for (const userId of persistedOrder) {
|
|
148
|
+
if (!nextUsersOrder.includes(userId)) {
|
|
149
|
+
nextUsersOrder.push(userId)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
for (const userId of this.sessionsByUserId.keys()) {
|
|
153
|
+
if (!nextUsersOrder.includes(userId)) {
|
|
154
|
+
nextUsersOrder.push(userId)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
this.usersOrder = nextUsersOrder
|
|
158
|
+
|
|
159
|
+
for (const userId of this.usersOrder) {
|
|
160
|
+
this.getOrCreateUser(userId)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private restoreCurrentSession() {
|
|
165
|
+
const currentSession = this.sessionManager.get()
|
|
166
|
+
if (currentSession?.userId) {
|
|
167
|
+
this.sessionsByUserId.set(currentSession.userId, currentSession)
|
|
168
|
+
this.getOrCreateUser(currentSession.userId)
|
|
169
|
+
this.touchUser(currentSession.userId)
|
|
170
|
+
this.persistSessionsByUser()
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
this.setCurrentSessionFromOrder()
|
|
175
|
+
}
|
|
176
|
+
|
|
160
177
|
private touchUser(userId: string) {
|
|
161
178
|
this.usersOrder = [userId, ...this.usersOrder.filter((id) => id !== userId)]
|
|
162
179
|
this.persistUsersOrder()
|
|
@@ -225,8 +242,12 @@ export class App {
|
|
|
225
242
|
}
|
|
226
243
|
|
|
227
244
|
private async bootstrapSessionOnLoad(): Promise<void> {
|
|
245
|
+
await this.sessionManager.whenReady()
|
|
246
|
+
this.restorePersistedUsers()
|
|
247
|
+
this.restoreCurrentSession()
|
|
248
|
+
|
|
228
249
|
const session = this.sessionManager.get()
|
|
229
|
-
if (!session ||
|
|
250
|
+
if (!session || !this.sessionManager.hasPersistentStorage()) {
|
|
230
251
|
return
|
|
231
252
|
}
|
|
232
253
|
|
package/src/session.ts
CHANGED
|
@@ -1,24 +1,48 @@
|
|
|
1
1
|
import { SessionData } from './types'
|
|
2
|
+
import { createStorage } from './storage'
|
|
2
3
|
|
|
3
|
-
const
|
|
4
|
+
const parseSession = (raw: string | null): SessionData | null => {
|
|
5
|
+
if (!raw) return null
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(raw) as SessionData
|
|
9
|
+
} catch {
|
|
10
|
+
return null
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const parseUsersOrder = (raw: string | null) => {
|
|
15
|
+
if (!raw) return []
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const parsed = JSON.parse(raw)
|
|
19
|
+
if (!Array.isArray(parsed)) return []
|
|
20
|
+
return parsed.filter((item): item is string => typeof item === 'string')
|
|
21
|
+
} catch {
|
|
22
|
+
return []
|
|
12
23
|
}
|
|
24
|
+
}
|
|
13
25
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
const parseSessionsByUser = (raw: string | null) => {
|
|
27
|
+
if (!raw) return {} as Record<string, SessionData>
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const parsed = JSON.parse(raw) as Record<string, SessionData>
|
|
31
|
+
const normalized: Record<string, SessionData> = {}
|
|
32
|
+
for (const [userId, session] of Object.entries(parsed)) {
|
|
33
|
+
if (
|
|
34
|
+
session &&
|
|
35
|
+
typeof session === 'object' &&
|
|
36
|
+
typeof session.accessToken === 'string' &&
|
|
37
|
+
typeof session.refreshToken === 'string' &&
|
|
38
|
+
typeof session.userId === 'string'
|
|
39
|
+
) {
|
|
40
|
+
normalized[userId] = session
|
|
41
|
+
}
|
|
21
42
|
}
|
|
43
|
+
return normalized
|
|
44
|
+
} catch {
|
|
45
|
+
return {} as Record<string, SessionData>
|
|
22
46
|
}
|
|
23
47
|
}
|
|
24
48
|
|
|
@@ -26,25 +50,35 @@ export class SessionManager {
|
|
|
26
50
|
private readonly key: string
|
|
27
51
|
private readonly usersKey: string
|
|
28
52
|
private readonly sessionsKey: string
|
|
29
|
-
private readonly storage =
|
|
53
|
+
private readonly storage = createStorage()
|
|
54
|
+
private readonly hydrationPromise: Promise<void>
|
|
30
55
|
private session: SessionData | null = null
|
|
56
|
+
private usersOrder: string[] = []
|
|
57
|
+
private sessionsByUser: Record<string, SessionData> = {}
|
|
31
58
|
|
|
32
59
|
constructor(appId: string) {
|
|
33
60
|
this.key = `flowerbase:${appId}:session`
|
|
34
61
|
this.usersKey = `flowerbase:${appId}:users`
|
|
35
62
|
this.sessionsKey = `flowerbase:${appId}:sessions`
|
|
36
|
-
this.session = this.
|
|
63
|
+
this.session = parseSession(this.storage.getItem(this.key))
|
|
64
|
+
this.usersOrder = parseUsersOrder(this.storage.getItem(this.usersKey))
|
|
65
|
+
this.sessionsByUser = parseSessionsByUser(this.storage.getItem(this.sessionsKey))
|
|
66
|
+
this.hydrationPromise = this.hydrate()
|
|
37
67
|
}
|
|
38
68
|
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
69
|
+
private async hydrate() {
|
|
70
|
+
const hydrated = await this.storage.hydrate([this.key, this.usersKey, this.sessionsKey])
|
|
71
|
+
this.session = parseSession(hydrated[this.key] ?? null)
|
|
72
|
+
this.usersOrder = parseUsersOrder(hydrated[this.usersKey] ?? null)
|
|
73
|
+
this.sessionsByUser = parseSessionsByUser(hydrated[this.sessionsKey] ?? null)
|
|
74
|
+
}
|
|
42
75
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
76
|
+
whenReady() {
|
|
77
|
+
return this.hydrationPromise
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
hasPersistentStorage() {
|
|
81
|
+
return this.storage.isPersistent
|
|
48
82
|
}
|
|
49
83
|
|
|
50
84
|
get() {
|
|
@@ -62,18 +96,11 @@ export class SessionManager {
|
|
|
62
96
|
}
|
|
63
97
|
|
|
64
98
|
getUsersOrder() {
|
|
65
|
-
|
|
66
|
-
if (!raw) return []
|
|
67
|
-
try {
|
|
68
|
-
const parsed = JSON.parse(raw)
|
|
69
|
-
if (!Array.isArray(parsed)) return []
|
|
70
|
-
return parsed.filter((item): item is string => typeof item === 'string')
|
|
71
|
-
} catch {
|
|
72
|
-
return []
|
|
73
|
-
}
|
|
99
|
+
return [...this.usersOrder]
|
|
74
100
|
}
|
|
75
101
|
|
|
76
102
|
setUsersOrder(order: string[]) {
|
|
103
|
+
this.usersOrder = [...order]
|
|
77
104
|
if (order.length === 0) {
|
|
78
105
|
this.storage.removeItem(this.usersKey)
|
|
79
106
|
return
|
|
@@ -82,29 +109,11 @@ export class SessionManager {
|
|
|
82
109
|
}
|
|
83
110
|
|
|
84
111
|
getSessionsByUser() {
|
|
85
|
-
|
|
86
|
-
if (!raw) return {} as Record<string, SessionData>
|
|
87
|
-
try {
|
|
88
|
-
const parsed = JSON.parse(raw) as Record<string, SessionData>
|
|
89
|
-
const normalized: Record<string, SessionData> = {}
|
|
90
|
-
for (const [userId, session] of Object.entries(parsed)) {
|
|
91
|
-
if (
|
|
92
|
-
session &&
|
|
93
|
-
typeof session === 'object' &&
|
|
94
|
-
typeof session.accessToken === 'string' &&
|
|
95
|
-
typeof session.refreshToken === 'string' &&
|
|
96
|
-
typeof session.userId === 'string'
|
|
97
|
-
) {
|
|
98
|
-
normalized[userId] = session
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return normalized
|
|
102
|
-
} catch {
|
|
103
|
-
return {} as Record<string, SessionData>
|
|
104
|
-
}
|
|
112
|
+
return { ...this.sessionsByUser }
|
|
105
113
|
}
|
|
106
114
|
|
|
107
115
|
setSessionsByUser(sessionsByUser: Record<string, SessionData>) {
|
|
116
|
+
this.sessionsByUser = { ...sessionsByUser }
|
|
108
117
|
if (Object.keys(sessionsByUser).length === 0) {
|
|
109
118
|
this.storage.removeItem(this.sessionsKey)
|
|
110
119
|
return
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import AsyncStorage from '@react-native-async-storage/async-storage'
|
|
2
|
+
|
|
3
|
+
type StorageSnapshot = Record<string, string | null>
|
|
4
|
+
|
|
5
|
+
export type PersistedStorage = {
|
|
6
|
+
isPersistent: boolean
|
|
7
|
+
getItem: (key: string) => string | null
|
|
8
|
+
setItem: (key: string, value: string) => void
|
|
9
|
+
removeItem: (key: string) => void
|
|
10
|
+
hydrate: (keys: string[]) => Promise<StorageSnapshot>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const memoryStore = new Map<string, string>()
|
|
14
|
+
|
|
15
|
+
const getSnapshot = (keys: string[]): StorageSnapshot =>
|
|
16
|
+
Object.fromEntries(keys.map((key) => [key, memoryStore.get(key) ?? null]))
|
|
17
|
+
|
|
18
|
+
export const createStorage = (): PersistedStorage => ({
|
|
19
|
+
isPersistent: true,
|
|
20
|
+
getItem: (key) => memoryStore.get(key) ?? null,
|
|
21
|
+
setItem: (key, value) => {
|
|
22
|
+
memoryStore.set(key, value)
|
|
23
|
+
void AsyncStorage.setItem(key, value).catch(() => {
|
|
24
|
+
// Ignore write failures and keep the in-memory cache alive.
|
|
25
|
+
})
|
|
26
|
+
},
|
|
27
|
+
removeItem: (key) => {
|
|
28
|
+
memoryStore.delete(key)
|
|
29
|
+
void AsyncStorage.removeItem(key).catch(() => {
|
|
30
|
+
// Ignore delete failures and keep the in-memory cache alive.
|
|
31
|
+
})
|
|
32
|
+
},
|
|
33
|
+
async hydrate(keys) {
|
|
34
|
+
try {
|
|
35
|
+
const entries = await AsyncStorage.multiGet(keys)
|
|
36
|
+
for (const [key, value] of entries) {
|
|
37
|
+
if (value === null) {
|
|
38
|
+
memoryStore.delete(key)
|
|
39
|
+
continue
|
|
40
|
+
}
|
|
41
|
+
memoryStore.set(key, value)
|
|
42
|
+
}
|
|
43
|
+
} catch {
|
|
44
|
+
// Ignore storage read failures and keep the in-memory cache alive.
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return getSnapshot(keys)
|
|
48
|
+
}
|
|
49
|
+
})
|
package/src/storage.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
type StorageSnapshot = Record<string, string | null>
|
|
2
|
+
|
|
3
|
+
export type PersistedStorage = {
|
|
4
|
+
isPersistent: boolean
|
|
5
|
+
getItem: (key: string) => string | null
|
|
6
|
+
setItem: (key: string, value: string) => void
|
|
7
|
+
removeItem: (key: string) => void
|
|
8
|
+
hydrate: (keys: string[]) => Promise<StorageSnapshot>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const memoryStore = new Map<string, string>()
|
|
12
|
+
|
|
13
|
+
const getStorage = () => {
|
|
14
|
+
const browserStorage = globalThis.localStorage
|
|
15
|
+
if (
|
|
16
|
+
browserStorage &&
|
|
17
|
+
typeof browserStorage.getItem === 'function' &&
|
|
18
|
+
typeof browserStorage.setItem === 'function' &&
|
|
19
|
+
typeof browserStorage.removeItem === 'function'
|
|
20
|
+
) {
|
|
21
|
+
return {
|
|
22
|
+
getItem: (key: string) => browserStorage.getItem(key),
|
|
23
|
+
setItem: (key: string, value: string) => browserStorage.setItem(key, value),
|
|
24
|
+
removeItem: (key: string) => browserStorage.removeItem(key)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
getItem: (key: string) => memoryStore.get(key) ?? null,
|
|
30
|
+
setItem: (key: string, value: string) => {
|
|
31
|
+
memoryStore.set(key, value)
|
|
32
|
+
},
|
|
33
|
+
removeItem: (key: string) => {
|
|
34
|
+
memoryStore.delete(key)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const createStorage = (): PersistedStorage => {
|
|
40
|
+
const browserStorage = globalThis.localStorage
|
|
41
|
+
const isPersistent =
|
|
42
|
+
!!browserStorage &&
|
|
43
|
+
typeof browserStorage.getItem === 'function' &&
|
|
44
|
+
typeof browserStorage.setItem === 'function' &&
|
|
45
|
+
typeof browserStorage.removeItem === 'function'
|
|
46
|
+
const storage = getStorage()
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
isPersistent,
|
|
50
|
+
getItem: storage.getItem,
|
|
51
|
+
setItem: storage.setItem,
|
|
52
|
+
removeItem: storage.removeItem,
|
|
53
|
+
async hydrate(keys) {
|
|
54
|
+
return Object.fromEntries(keys.map((key) => [key, storage.getItem(key)]))
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
package/dist/session.native.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { SessionData } from './types';
|
|
2
|
-
export declare class SessionManager {
|
|
3
|
-
private readonly key;
|
|
4
|
-
private session;
|
|
5
|
-
private readonly asyncStorage;
|
|
6
|
-
private hydrationPromise;
|
|
7
|
-
constructor(appId: string);
|
|
8
|
-
private hydrateFromAsyncStorage;
|
|
9
|
-
load(): SessionData | null;
|
|
10
|
-
get(): SessionData | null;
|
|
11
|
-
set(session: SessionData): void;
|
|
12
|
-
clear(): void;
|
|
13
|
-
}
|
|
14
|
-
//# sourceMappingURL=session.native.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"session.native.d.ts","sourceRoot":"","sources":["../src/session.native.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAsBrC,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAQ;IAC5B,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoB;IACjD,OAAO,CAAC,gBAAgB,CAA6B;gBAEzC,KAAK,EAAE,MAAM;IAMzB,OAAO,CAAC,uBAAuB;IAwB/B,IAAI,IAAI,WAAW,GAAG,IAAI;IAI1B,GAAG;IAIH,GAAG,CAAC,OAAO,EAAE,WAAW;IAYxB,KAAK;CAUN"}
|
package/dist/session.native.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.SessionManager = void 0;
|
|
7
|
-
const async_storage_1 = __importDefault(require("@react-native-async-storage/async-storage"));
|
|
8
|
-
const memoryStore = new Map();
|
|
9
|
-
const getAsyncStorage = () => async_storage_1.default;
|
|
10
|
-
const parseSession = (raw) => {
|
|
11
|
-
if (!raw)
|
|
12
|
-
return null;
|
|
13
|
-
try {
|
|
14
|
-
return JSON.parse(raw);
|
|
15
|
-
}
|
|
16
|
-
catch {
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
class SessionManager {
|
|
21
|
-
constructor(appId) {
|
|
22
|
-
this.session = null;
|
|
23
|
-
this.asyncStorage = getAsyncStorage();
|
|
24
|
-
this.hydrationPromise = null;
|
|
25
|
-
this.key = `flowerbase:${appId}:session`;
|
|
26
|
-
this.session = this.load();
|
|
27
|
-
void this.hydrateFromAsyncStorage();
|
|
28
|
-
}
|
|
29
|
-
hydrateFromAsyncStorage() {
|
|
30
|
-
if (!this.asyncStorage) {
|
|
31
|
-
return Promise.resolve();
|
|
32
|
-
}
|
|
33
|
-
if (this.hydrationPromise) {
|
|
34
|
-
return this.hydrationPromise;
|
|
35
|
-
}
|
|
36
|
-
this.hydrationPromise = this.asyncStorage
|
|
37
|
-
.getItem(this.key)
|
|
38
|
-
.then((raw) => {
|
|
39
|
-
const parsed = parseSession(raw);
|
|
40
|
-
if (!parsed)
|
|
41
|
-
return;
|
|
42
|
-
this.session = parsed;
|
|
43
|
-
memoryStore.set(this.key, JSON.stringify(parsed));
|
|
44
|
-
})
|
|
45
|
-
.catch(() => {
|
|
46
|
-
// Ignore storage read failures and keep memory fallback.
|
|
47
|
-
});
|
|
48
|
-
return this.hydrationPromise;
|
|
49
|
-
}
|
|
50
|
-
load() {
|
|
51
|
-
return parseSession(memoryStore.get(this.key) ?? null);
|
|
52
|
-
}
|
|
53
|
-
get() {
|
|
54
|
-
return this.session;
|
|
55
|
-
}
|
|
56
|
-
set(session) {
|
|
57
|
-
this.session = session;
|
|
58
|
-
const raw = JSON.stringify(session);
|
|
59
|
-
memoryStore.set(this.key, raw);
|
|
60
|
-
if (this.asyncStorage) {
|
|
61
|
-
void this.asyncStorage.setItem(this.key, raw).catch(() => {
|
|
62
|
-
// Ignore write failures and keep memory fallback.
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
clear() {
|
|
67
|
-
this.session = null;
|
|
68
|
-
memoryStore.delete(this.key);
|
|
69
|
-
if (this.asyncStorage) {
|
|
70
|
-
void this.asyncStorage.removeItem(this.key).catch(() => {
|
|
71
|
-
// Ignore delete failures and keep memory fallback.
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
exports.SessionManager = SessionManager;
|
package/src/session.native.ts
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { SessionData } from './types'
|
|
2
|
-
import AsyncStorage from '@react-native-async-storage/async-storage'
|
|
3
|
-
|
|
4
|
-
type StorageLike = {
|
|
5
|
-
getItem: (key: string) => Promise<string | null>
|
|
6
|
-
setItem: (key: string, value: string) => Promise<void>
|
|
7
|
-
removeItem: (key: string) => Promise<void>
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const memoryStore = new Map<string, string>()
|
|
11
|
-
|
|
12
|
-
const getAsyncStorage = (): StorageLike => AsyncStorage
|
|
13
|
-
|
|
14
|
-
const parseSession = (raw: string | null): SessionData | null => {
|
|
15
|
-
if (!raw) return null
|
|
16
|
-
try {
|
|
17
|
-
return JSON.parse(raw) as SessionData
|
|
18
|
-
} catch {
|
|
19
|
-
return null
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export class SessionManager {
|
|
24
|
-
private readonly key: string
|
|
25
|
-
private session: SessionData | null = null
|
|
26
|
-
private readonly asyncStorage = getAsyncStorage()
|
|
27
|
-
private hydrationPromise: Promise<void> | null = null
|
|
28
|
-
|
|
29
|
-
constructor(appId: string) {
|
|
30
|
-
this.key = `flowerbase:${appId}:session`
|
|
31
|
-
this.session = this.load()
|
|
32
|
-
void this.hydrateFromAsyncStorage()
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
private hydrateFromAsyncStorage() {
|
|
36
|
-
if (!this.asyncStorage) {
|
|
37
|
-
return Promise.resolve()
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (this.hydrationPromise) {
|
|
41
|
-
return this.hydrationPromise
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
this.hydrationPromise = this.asyncStorage
|
|
45
|
-
.getItem(this.key)
|
|
46
|
-
.then((raw) => {
|
|
47
|
-
const parsed = parseSession(raw)
|
|
48
|
-
if (!parsed) return
|
|
49
|
-
this.session = parsed
|
|
50
|
-
memoryStore.set(this.key, JSON.stringify(parsed))
|
|
51
|
-
})
|
|
52
|
-
.catch(() => {
|
|
53
|
-
// Ignore storage read failures and keep memory fallback.
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
return this.hydrationPromise
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
load(): SessionData | null {
|
|
60
|
-
return parseSession(memoryStore.get(this.key) ?? null)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
get() {
|
|
64
|
-
return this.session
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
set(session: SessionData) {
|
|
68
|
-
this.session = session
|
|
69
|
-
const raw = JSON.stringify(session)
|
|
70
|
-
memoryStore.set(this.key, raw)
|
|
71
|
-
|
|
72
|
-
if (this.asyncStorage) {
|
|
73
|
-
void this.asyncStorage.setItem(this.key, raw).catch(() => {
|
|
74
|
-
// Ignore write failures and keep memory fallback.
|
|
75
|
-
})
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
clear() {
|
|
80
|
-
this.session = null
|
|
81
|
-
memoryStore.delete(this.key)
|
|
82
|
-
|
|
83
|
-
if (this.asyncStorage) {
|
|
84
|
-
void this.asyncStorage.removeItem(this.key).catch(() => {
|
|
85
|
-
// Ignore delete failures and keep memory fallback.
|
|
86
|
-
})
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|