@flowerforce/flowerbase-client 0.1.1-beta.2 → 0.1.1-beta.3

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.
Files changed (45) hide show
  1. package/dist/app.d.ts +55 -10
  2. package/dist/app.d.ts.map +1 -1
  3. package/dist/app.js +322 -47
  4. package/dist/credentials.d.ts +1 -0
  5. package/dist/credentials.d.ts.map +1 -1
  6. package/dist/credentials.js +6 -0
  7. package/dist/functions.d.ts +4 -1
  8. package/dist/functions.d.ts.map +1 -1
  9. package/dist/functions.js +7 -1
  10. package/dist/http.d.ts +24 -4
  11. package/dist/http.d.ts.map +1 -1
  12. package/dist/http.js +121 -25
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +3 -1
  16. package/dist/mongo.d.ts +1 -1
  17. package/dist/mongo.d.ts.map +1 -1
  18. package/dist/mongo.js +45 -4
  19. package/dist/session.d.ts +6 -0
  20. package/dist/session.d.ts.map +1 -1
  21. package/dist/session.js +52 -0
  22. package/dist/session.native.d.ts.map +1 -1
  23. package/dist/session.native.js +5 -10
  24. package/dist/types.d.ts +28 -4
  25. package/dist/types.d.ts.map +1 -1
  26. package/dist/user.d.ts +23 -4
  27. package/dist/user.d.ts.map +1 -1
  28. package/dist/user.js +96 -7
  29. package/package.json +12 -1
  30. package/src/__tests__/auth.test.ts +49 -0
  31. package/src/__tests__/compat.test.ts +10 -0
  32. package/src/__tests__/functions.test.ts +213 -0
  33. package/src/__tests__/mongo.test.ts +35 -0
  34. package/src/__tests__/session.test.ts +494 -0
  35. package/src/__tests__/watch.test.ts +74 -0
  36. package/src/app.ts +390 -63
  37. package/src/credentials.ts +7 -0
  38. package/src/functions.ts +16 -3
  39. package/src/http.ts +156 -27
  40. package/src/index.ts +1 -0
  41. package/src/mongo.ts +48 -4
  42. package/src/session.native.ts +2 -11
  43. package/src/session.ts +55 -0
  44. package/src/types.ts +34 -4
  45. package/src/user.ts +115 -11
package/dist/app.d.ts CHANGED
@@ -1,26 +1,59 @@
1
1
  import { AppConfig, CredentialsLike, ProfileData, SessionData } from './types';
2
+ import { Credentials } from './credentials';
2
3
  import { User } from './user';
3
4
  export declare class App {
5
+ private static readonly appCache;
6
+ static readonly Credentials: typeof Credentials;
4
7
  readonly id: string;
5
8
  readonly baseUrl: string;
6
9
  readonly timeout: number;
7
10
  private readonly sessionManager;
8
- currentUser: User | null;
11
+ private readonly usersById;
12
+ private readonly sessionsByUserId;
13
+ private usersOrder;
14
+ private readonly profilesByUserId;
9
15
  private readonly sessionBootstrapPromise;
16
+ private readonly listeners;
10
17
  emailPasswordAuth: {
11
18
  registerUser: (input: {
12
19
  email: string;
13
20
  password: string;
14
21
  }) => Promise<unknown>;
15
- sendResetPasswordEmail: (email: string) => Promise<unknown>;
16
- callResetPasswordFunction: (email: string, password: string, ...args: unknown[]) => Promise<unknown>;
22
+ confirmUser: (input: {
23
+ token: string;
24
+ tokenId: string;
25
+ }) => Promise<unknown>;
26
+ resendConfirmationEmail: (input: {
27
+ email: string;
28
+ }) => Promise<unknown>;
29
+ retryCustomConfirmation: (input: {
30
+ email: string;
31
+ }) => Promise<unknown>;
32
+ sendResetPasswordEmail: (input: {
33
+ email: string;
34
+ } | string) => Promise<unknown>;
35
+ callResetPasswordFunction: (input: {
36
+ email: string;
37
+ password: string;
38
+ } | string, passwordOrArg?: string, ...args: unknown[]) => Promise<unknown>;
17
39
  resetPassword: (input: {
18
40
  token: string;
19
41
  tokenId: string;
20
42
  password: string;
21
43
  }) => Promise<unknown>;
22
44
  };
23
- constructor(config: AppConfig);
45
+ constructor(idOrConfig: string | AppConfig);
46
+ static getApp(appIdOrConfig: string | AppConfig): App;
47
+ get currentUser(): User | null;
48
+ get allUsers(): Readonly<Record<string, User>>;
49
+ private persistSessionsByUser;
50
+ private persistUsersOrder;
51
+ private touchUser;
52
+ private removeUserFromOrder;
53
+ private setSessionForUser;
54
+ private clearSessionForUser;
55
+ private setCurrentSessionFromOrder;
56
+ private notifyListeners;
24
57
  private providerUrl;
25
58
  private authUrl;
26
59
  private functionsUrl;
@@ -28,13 +61,25 @@ export declare class App {
28
61
  private bootstrapSessionOnLoad;
29
62
  private ensureSessionBootstrapped;
30
63
  private setLoggedInUser;
64
+ private getOrCreateUser;
31
65
  logIn(credentials: CredentialsLike): Promise<User>;
32
- getSessionOrThrow(): SessionData;
66
+ switchUser(nextUser: User): void;
67
+ removeUser(user: User): Promise<void>;
68
+ deleteUser(user: User): Promise<void>;
69
+ getSessionOrThrow(userId?: string): SessionData;
70
+ getSession(userId?: string): SessionData | null;
71
+ hasUser(userId: string): boolean;
72
+ getProfileSnapshot(userId: string): ProfileData | undefined;
33
73
  postProvider<T = unknown>(path: string, body: unknown): Promise<T>;
34
- callFunction(name: string, args: unknown[]): Promise<any>;
35
- callService(name: string, args: unknown[]): Promise<unknown>;
36
- getProfile(): Promise<ProfileData>;
37
- refreshAccessToken(): Promise<string>;
38
- logoutUser(): Promise<void>;
74
+ private requestWithAccessToken;
75
+ callFunction(name: string, args: unknown[], userId?: string): Promise<any>;
76
+ callFunctionStreaming(name: string, args: unknown[], userId?: string): Promise<AsyncIterable<Uint8Array>>;
77
+ callService(name: string, args: unknown[], service?: string, userId?: string): Promise<unknown>;
78
+ getProfile(userId?: string): Promise<ProfileData>;
79
+ refreshAccessToken(userId?: string): Promise<string>;
80
+ logoutUser(userId?: string): Promise<void>;
81
+ addListener(callback: () => void): void;
82
+ removeListener(callback: () => void): void;
83
+ removeAllListeners(): void;
39
84
  }
40
85
  //# sourceMappingURL=app.d.ts.map
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,IAAI,EAAE,MAAM,QAAQ,CAAA;AAc7B,qBAAa,GAAG;IACd,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,WAAW,EAAE,IAAI,GAAG,IAAI,CAAO;IAC/B,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAe;IAEvD,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,sBAAsB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;QAC3D,yBAAyB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;QACpG,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,MAAM,EAAE,SAAS;IAwB7B,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,YAAY;YAIN,aAAa;YASb,sBAAsB;YAkBtB,yBAAyB;YAIzB,eAAe;IAevB,KAAK,CAAC,WAAW,EAAE,eAAe;IAkBxC,iBAAiB;IAQX,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IASlE,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE;IAmB1C,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE;IAkBzC,UAAU,IAAI,OAAO,CAAC,WAAW,CAAC;IAWlC,kBAAkB;IAoBlB,UAAU;CAgBjB"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
package/dist/app.js CHANGED
@@ -4,27 +4,151 @@ exports.App = void 0;
4
4
  const functions_1 = require("./functions");
5
5
  const http_1 = require("./http");
6
6
  const session_1 = require("./session");
7
+ const credentials_1 = require("./credentials");
7
8
  const user_1 = require("./user");
8
9
  const API_PREFIX = '/api/client/v2.0';
9
10
  class App {
10
- constructor(config) {
11
- this.currentUser = null;
11
+ constructor(idOrConfig) {
12
+ this.usersById = new Map();
13
+ this.sessionsByUserId = new Map();
14
+ this.usersOrder = [];
15
+ this.profilesByUserId = new Map();
16
+ this.listeners = new Set();
17
+ const config = typeof idOrConfig === 'string' ? { id: idOrConfig } : idOrConfig;
12
18
  this.id = config.id;
13
- this.baseUrl = config.baseUrl.replace(/\/$/, '');
19
+ this.baseUrl = (config.baseUrl ?? '').replace(/\/$/, '');
14
20
  this.timeout = config.timeout ?? 10000;
15
21
  this.sessionManager = new session_1.SessionManager(this.id);
16
- const session = this.sessionManager.get();
17
- if (session?.userId) {
18
- this.currentUser = new user_1.User(this, session.userId);
22
+ const persistedSessionsByUser = this.sessionManager.getSessionsByUser();
23
+ for (const [userId, session] of Object.entries(persistedSessionsByUser)) {
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();
19
44
  }
20
45
  this.sessionBootstrapPromise = this.bootstrapSessionOnLoad();
21
46
  this.emailPasswordAuth = {
22
47
  registerUser: ({ email, password }) => this.postProvider('/local-userpass/register', { email, password }),
23
- sendResetPasswordEmail: (email) => this.postProvider('/local-userpass/reset/send', { email }),
24
- callResetPasswordFunction: (email, password, ...args) => this.postProvider('/local-userpass/reset/call', { email, password, arguments: args }),
48
+ confirmUser: ({ token, tokenId }) => this.postProvider('/local-userpass/confirm', { token, tokenId }),
49
+ resendConfirmationEmail: ({ email }) => this.postProvider('/local-userpass/confirm/send', { email }),
50
+ retryCustomConfirmation: ({ email }) => this.postProvider('/local-userpass/confirm/call', { email }),
51
+ sendResetPasswordEmail: (input) => this.postProvider('/local-userpass/reset/send', {
52
+ email: typeof input === 'string' ? input : input.email
53
+ }),
54
+ callResetPasswordFunction: (input, passwordOrArg, ...args) => {
55
+ if (typeof input === 'string') {
56
+ return this.postProvider('/local-userpass/reset/call', {
57
+ email: input,
58
+ password: passwordOrArg,
59
+ arguments: args
60
+ });
61
+ }
62
+ return this.postProvider('/local-userpass/reset/call', {
63
+ email: input.email,
64
+ password: input.password,
65
+ arguments: [passwordOrArg, ...args].filter((value) => value !== undefined)
66
+ });
67
+ },
25
68
  resetPassword: ({ token, tokenId, password }) => this.postProvider('/local-userpass/reset', { token, tokenId, password })
26
69
  };
27
70
  }
71
+ static getApp(appIdOrConfig) {
72
+ const appId = typeof appIdOrConfig === 'string' ? appIdOrConfig : appIdOrConfig.id;
73
+ if (appId in App.appCache) {
74
+ return App.appCache[appId];
75
+ }
76
+ const app = new App(appIdOrConfig);
77
+ App.appCache[appId] = app;
78
+ return app;
79
+ }
80
+ get currentUser() {
81
+ for (const userId of this.usersOrder) {
82
+ const user = this.usersById.get(userId);
83
+ if (user?.state === 'active') {
84
+ return user;
85
+ }
86
+ }
87
+ return null;
88
+ }
89
+ get allUsers() {
90
+ const activeUsers = [];
91
+ const loggedOutUsers = [];
92
+ for (const userId of this.usersOrder) {
93
+ const user = this.usersById.get(userId);
94
+ if (!user)
95
+ continue;
96
+ if (user.state === 'active') {
97
+ activeUsers.push(userId);
98
+ }
99
+ else if (user.state === 'logged-out') {
100
+ loggedOutUsers.push(userId);
101
+ }
102
+ }
103
+ const users = Object.fromEntries([...activeUsers, ...loggedOutUsers].map((userId) => [userId, this.usersById.get(userId)]));
104
+ return users;
105
+ }
106
+ persistSessionsByUser() {
107
+ this.sessionManager.setSessionsByUser(Object.fromEntries(this.sessionsByUserId.entries()));
108
+ }
109
+ persistUsersOrder() {
110
+ this.sessionManager.setUsersOrder(this.usersOrder);
111
+ }
112
+ touchUser(userId) {
113
+ this.usersOrder = [userId, ...this.usersOrder.filter((id) => id !== userId)];
114
+ this.persistUsersOrder();
115
+ }
116
+ removeUserFromOrder(userId) {
117
+ this.usersOrder = this.usersOrder.filter((id) => id !== userId);
118
+ this.persistUsersOrder();
119
+ }
120
+ setSessionForUser(session) {
121
+ this.sessionsByUserId.set(session.userId, session);
122
+ this.sessionManager.set(session);
123
+ this.persistSessionsByUser();
124
+ }
125
+ clearSessionForUser(userId) {
126
+ this.sessionsByUserId.delete(userId);
127
+ this.persistSessionsByUser();
128
+ }
129
+ setCurrentSessionFromOrder() {
130
+ for (const userId of this.usersOrder) {
131
+ const session = this.sessionsByUserId.get(userId);
132
+ if (session) {
133
+ this.sessionManager.set(session);
134
+ return;
135
+ }
136
+ }
137
+ this.sessionManager.clear();
138
+ }
139
+ notifyListeners(userId) {
140
+ for (const callback of Array.from(this.listeners)) {
141
+ try {
142
+ callback();
143
+ }
144
+ catch {
145
+ // Listener failures should not break auth/session lifecycle.
146
+ }
147
+ }
148
+ if (userId) {
149
+ this.usersById.get(userId)?.notifyListeners();
150
+ }
151
+ }
28
152
  providerUrl(path) {
29
153
  return `${this.baseUrl}${API_PREFIX}/app/${this.id}/auth/providers${path}`;
30
154
  }
@@ -49,32 +173,44 @@ class App {
49
173
  }
50
174
  try {
51
175
  const result = await this.createSession(session.refreshToken);
52
- this.sessionManager.set({
176
+ this.setSessionForUser({
53
177
  ...session,
54
178
  accessToken: result.access_token
55
179
  });
56
180
  }
57
181
  catch {
58
- this.sessionManager.clear();
59
- this.currentUser = null;
182
+ this.clearSessionForUser(session.userId);
183
+ this.setCurrentSessionFromOrder();
60
184
  }
61
185
  }
62
186
  async ensureSessionBootstrapped() {
63
187
  await this.sessionBootstrapPromise;
64
188
  }
65
- async setLoggedInUser(data, profileEmail) {
189
+ async setLoggedInUser(data, providerType, profileEmail) {
66
190
  const sessionResult = await this.createSession(data.refresh_token);
67
191
  const session = {
68
192
  accessToken: sessionResult.access_token,
69
193
  refreshToken: data.refresh_token,
70
194
  userId: data.user_id
71
195
  };
72
- this.sessionManager.set(session);
73
- this.currentUser = new user_1.User(this, data.user_id);
196
+ this.setSessionForUser(session);
197
+ const user = this.getOrCreateUser(data.user_id);
198
+ user.setProviderType(providerType);
199
+ this.touchUser(data.user_id);
74
200
  if (profileEmail) {
75
- this.currentUser.profile = { email: profileEmail };
201
+ user.profile = { email: profileEmail };
76
202
  }
77
- return this.currentUser;
203
+ this.notifyListeners(data.user_id);
204
+ return user;
205
+ }
206
+ getOrCreateUser(userId) {
207
+ const existing = this.usersById.get(userId);
208
+ if (existing) {
209
+ return existing;
210
+ }
211
+ const user = new user_1.User(this, userId);
212
+ this.usersById.set(userId, user);
213
+ return user;
78
214
  }
79
215
  async logIn(credentials) {
80
216
  if (credentials.provider === 'local-userpass') {
@@ -82,22 +218,79 @@ class App {
82
218
  username: credentials.email,
83
219
  password: credentials.password
84
220
  });
85
- return this.setLoggedInUser(result, credentials.email);
221
+ return this.setLoggedInUser(result, 'local-userpass', credentials.email);
86
222
  }
87
223
  if (credentials.provider === 'anon-user') {
88
224
  const result = await this.postProvider('/anon-user/login', {});
89
- return this.setLoggedInUser(result);
225
+ return this.setLoggedInUser(result, 'anon-user');
226
+ }
227
+ if (credentials.provider === 'custom-function') {
228
+ const result = await this.postProvider('/custom-function/login', credentials.payload);
229
+ return this.setLoggedInUser(result, 'custom-function');
90
230
  }
91
- const result = await this.postProvider('/custom-function/login', credentials.payload);
92
- return this.setLoggedInUser(result);
231
+ if (credentials.provider === 'custom-token') {
232
+ const result = await this.postProvider('/custom-token/login', { token: credentials.token });
233
+ return this.setLoggedInUser(result, 'custom-token');
234
+ }
235
+ const unsupportedProvider = credentials;
236
+ throw new Error(`Unsupported credentials provider: ${JSON.stringify(unsupportedProvider)}`);
93
237
  }
94
- getSessionOrThrow() {
95
- const session = this.sessionManager.get();
238
+ switchUser(nextUser) {
239
+ const knownUser = this.usersById.get(nextUser.id);
240
+ if (!knownUser) {
241
+ throw new Error('The user was never logged into this app');
242
+ }
243
+ this.touchUser(nextUser.id);
244
+ const session = this.sessionsByUserId.get(nextUser.id);
245
+ if (session) {
246
+ this.sessionManager.set(session);
247
+ }
248
+ this.notifyListeners(nextUser.id);
249
+ }
250
+ async removeUser(user) {
251
+ const knownUser = this.usersById.get(user.id);
252
+ if (!knownUser) {
253
+ throw new Error('The user was never logged into this app');
254
+ }
255
+ if (this.sessionsByUserId.has(user.id)) {
256
+ await this.logoutUser(user.id);
257
+ }
258
+ this.usersById.delete(user.id);
259
+ this.removeUserFromOrder(user.id);
260
+ this.profilesByUserId.delete(user.id);
261
+ this.clearSessionForUser(user.id);
262
+ this.setCurrentSessionFromOrder();
263
+ this.notifyListeners(user.id);
264
+ }
265
+ async deleteUser(user) {
266
+ await this.requestWithAccessToken((accessToken) => (0, http_1.requestJson)({
267
+ url: this.authUrl('/delete'),
268
+ method: 'DELETE',
269
+ bearerToken: accessToken,
270
+ timeout: this.timeout
271
+ }), user.id);
272
+ await this.removeUser(user);
273
+ }
274
+ getSessionOrThrow(userId) {
275
+ const targetUserId = userId ?? this.currentUser?.id;
276
+ const session = targetUserId ? this.sessionsByUserId.get(targetUserId) : this.sessionManager.get();
96
277
  if (!session) {
97
278
  throw new Error('User is not authenticated');
98
279
  }
99
280
  return session;
100
281
  }
282
+ getSession(userId) {
283
+ if (userId) {
284
+ return this.sessionsByUserId.get(userId) ?? null;
285
+ }
286
+ return this.sessionManager.get();
287
+ }
288
+ hasUser(userId) {
289
+ return this.usersById.has(userId);
290
+ }
291
+ getProfileSnapshot(userId) {
292
+ return this.profilesByUserId.get(userId);
293
+ }
101
294
  async postProvider(path, body) {
102
295
  return (0, http_1.requestJson)({
103
296
  url: this.providerUrl(path),
@@ -106,67 +299,135 @@ class App {
106
299
  timeout: this.timeout
107
300
  });
108
301
  }
109
- async callFunction(name, args) {
302
+ async requestWithAccessToken(operation, userId) {
303
+ const firstSession = this.getSessionOrThrow(userId);
304
+ try {
305
+ return await operation(firstSession.accessToken);
306
+ }
307
+ catch (error) {
308
+ if (!(error instanceof http_1.FlowerbaseHttpError) || error.status !== 401) {
309
+ throw error;
310
+ }
311
+ await this.refreshAccessToken(userId);
312
+ const refreshedSession = this.getSessionOrThrow(userId);
313
+ return operation(refreshedSession.accessToken);
314
+ }
315
+ }
316
+ async callFunction(name, args, userId) {
110
317
  await this.ensureSessionBootstrapped();
111
- const session = this.getSessionOrThrow();
112
318
  const payload = {
113
319
  name,
114
320
  arguments: args
115
321
  };
116
- const result = await (0, http_1.requestJson)({
322
+ const result = await this.requestWithAccessToken((accessToken) => (0, http_1.requestJson)({
117
323
  url: this.functionsUrl('/call'),
118
324
  method: 'POST',
119
325
  body: payload,
120
- bearerToken: session.accessToken,
326
+ bearerToken: accessToken,
121
327
  timeout: this.timeout
122
- });
328
+ }), userId);
123
329
  return (0, functions_1.normalizeFunctionResponse)(result);
124
330
  }
125
- async callService(name, args) {
331
+ async callFunctionStreaming(name, args, userId) {
126
332
  await this.ensureSessionBootstrapped();
127
- const session = this.getSessionOrThrow();
128
333
  const payload = {
129
334
  name,
130
- service: 'mongodb-atlas',
131
335
  arguments: args
132
336
  };
133
- return (0, http_1.requestJson)({
337
+ const resolveSession = () => this.getSessionOrThrow(userId);
338
+ const refreshSession = () => this.refreshAccessToken(userId);
339
+ const timeout = this.timeout;
340
+ const url = this.functionsUrl('/call');
341
+ return {
342
+ async *[Symbol.asyncIterator]() {
343
+ let didRefresh = false;
344
+ while (true) {
345
+ const session = resolveSession();
346
+ let stream;
347
+ try {
348
+ stream = await (0, http_1.requestStream)({
349
+ url,
350
+ method: 'POST',
351
+ body: payload,
352
+ bearerToken: session.accessToken,
353
+ timeout
354
+ });
355
+ }
356
+ catch (error) {
357
+ if (!didRefresh && error instanceof http_1.FlowerbaseHttpError && error.status === 401) {
358
+ await refreshSession();
359
+ didRefresh = true;
360
+ continue;
361
+ }
362
+ throw error;
363
+ }
364
+ try {
365
+ for await (const chunk of stream) {
366
+ yield chunk;
367
+ }
368
+ return;
369
+ }
370
+ catch (error) {
371
+ if (!didRefresh && error instanceof http_1.FlowerbaseHttpError && error.status === 401) {
372
+ await refreshSession();
373
+ didRefresh = true;
374
+ continue;
375
+ }
376
+ throw error;
377
+ }
378
+ }
379
+ }
380
+ };
381
+ }
382
+ async callService(name, args, service = 'mongodb-atlas', userId) {
383
+ await this.ensureSessionBootstrapped();
384
+ const payload = {
385
+ name,
386
+ service,
387
+ arguments: args
388
+ };
389
+ return this.requestWithAccessToken((accessToken) => (0, http_1.requestJson)({
134
390
  url: this.functionsUrl('/call'),
135
391
  method: 'POST',
136
392
  body: payload,
137
- bearerToken: session.accessToken,
393
+ bearerToken: accessToken,
138
394
  timeout: this.timeout
139
- });
395
+ }), userId);
140
396
  }
141
- async getProfile() {
397
+ async getProfile(userId) {
142
398
  await this.ensureSessionBootstrapped();
143
- const session = this.getSessionOrThrow();
144
- return (0, http_1.requestJson)({
399
+ const profile = await this.requestWithAccessToken((accessToken) => (0, http_1.requestJson)({
145
400
  url: this.authUrl('/profile'),
146
401
  method: 'GET',
147
- bearerToken: session.accessToken,
402
+ bearerToken: accessToken,
148
403
  timeout: this.timeout
149
- });
404
+ }), userId);
405
+ const session = this.getSessionOrThrow(userId);
406
+ this.profilesByUserId.set(session.userId, profile);
407
+ return profile;
150
408
  }
151
- async refreshAccessToken() {
409
+ async refreshAccessToken(userId) {
152
410
  await this.ensureSessionBootstrapped();
153
- const session = this.getSessionOrThrow();
411
+ const session = this.getSessionOrThrow(userId);
154
412
  try {
155
413
  const result = await this.createSession(session.refreshToken);
156
- this.sessionManager.set({
414
+ this.setSessionForUser({
157
415
  ...session,
158
416
  accessToken: result.access_token
159
417
  });
418
+ this.touchUser(session.userId);
419
+ this.notifyListeners(session.userId);
160
420
  return result.access_token;
161
421
  }
162
422
  catch (error) {
163
- this.sessionManager.clear();
164
- this.currentUser = null;
423
+ this.clearSessionForUser(session.userId);
424
+ this.setCurrentSessionFromOrder();
425
+ this.notifyListeners(session.userId);
165
426
  throw error;
166
427
  }
167
428
  }
168
- async logoutUser() {
169
- const session = this.sessionManager.get();
429
+ async logoutUser(userId) {
430
+ const session = this.getSession(userId ?? this.currentUser?.id);
170
431
  try {
171
432
  if (session) {
172
433
  await (0, http_1.requestJson)({
@@ -178,9 +439,23 @@ class App {
178
439
  }
179
440
  }
180
441
  finally {
181
- this.sessionManager.clear();
182
- this.currentUser = null;
442
+ if (session) {
443
+ this.clearSessionForUser(session.userId);
444
+ this.notifyListeners(session.userId);
445
+ }
446
+ this.setCurrentSessionFromOrder();
183
447
  }
184
448
  }
449
+ addListener(callback) {
450
+ this.listeners.add(callback);
451
+ }
452
+ removeListener(callback) {
453
+ this.listeners.delete(callback);
454
+ }
455
+ removeAllListeners() {
456
+ this.listeners.clear();
457
+ }
185
458
  }
186
459
  exports.App = App;
460
+ App.appCache = {};
461
+ App.Credentials = credentials_1.Credentials;
@@ -3,5 +3,6 @@ export declare class Credentials {
3
3
  static emailPassword(email: string, password: string): CredentialsLike;
4
4
  static anonymous(): CredentialsLike;
5
5
  static function(payload: Record<string, unknown>): CredentialsLike;
6
+ static jwt(token: string): CredentialsLike;
6
7
  }
7
8
  //# sourceMappingURL=credentials.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAEzC,qBAAa,WAAW;IACtB,MAAM,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,eAAe;IAQtE,MAAM,CAAC,SAAS,IAAI,eAAe;IAMnC,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,eAAe;CAMnE"}
1
+ {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAEzC,qBAAa,WAAW;IACtB,MAAM,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,eAAe;IAQtE,MAAM,CAAC,SAAS,IAAI,eAAe;IAMnC,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,eAAe;IAOlE,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe;CAM3C"}
@@ -20,5 +20,11 @@ class Credentials {
20
20
  payload
21
21
  };
22
22
  }
23
+ static jwt(token) {
24
+ return {
25
+ provider: 'custom-token',
26
+ token
27
+ };
28
+ }
23
29
  }
24
30
  exports.Credentials = Credentials;
@@ -1,3 +1,6 @@
1
1
  export declare const normalizeFunctionResponse: (value: unknown) => any;
2
- export declare const createFunctionsProxy: (callFunction: (name: string, args: unknown[]) => Promise<unknown>) => Record<string, (...args: unknown[]) => Promise<unknown>>;
2
+ export declare const createFunctionsProxy: (callFunction: (name: string, args: unknown[]) => Promise<unknown>, callFunctionStreaming: (name: string, args: unknown[]) => Promise<AsyncIterable<Uint8Array>>) => Record<string, (...args: unknown[]) => Promise<unknown>> & {
3
+ callFunction: (name: string, ...args: unknown[]) => Promise<unknown>;
4
+ callFunctionStreaming: (name: string, ...args: unknown[]) => Promise<AsyncIterable<Uint8Array>>;
5
+ };
3
6
  //# sourceMappingURL=functions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"functions.d.ts","sourceRoot":"","sources":["../src/functions.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,yBAAyB,UAAW,OAAO,QAWvD,CAAA;AAED,eAAO,MAAM,oBAAoB,iBACjB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,QAAQ,OAAO,CAAC,KAChE,OAAO,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,QAAQ,OAAO,CAAC,CASK,CAAA"}
1
+ {"version":3,"file":"functions.d.ts","sourceRoot":"","sources":["../src/functions.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,yBAAyB,UAAW,OAAO,QAWvD,CAAA;AAED,eAAO,MAAM,oBAAoB,iBACjB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,QAAQ,OAAO,CAAC,yBAC1C,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,QAAQ,cAAc,UAAU,CAAC,CAAC,KAC3F,OAAO,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,QAAQ,OAAO,CAAC,CAAC,GAAG;IAC5D,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,QAAQ,OAAO,CAAC,CAAA;IACpE,qBAAqB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,QAAQ,cAAc,UAAU,CAAC,CAAC,CAAA;CAmB9F,CAAA"}
package/dist/functions.js CHANGED
@@ -20,10 +20,16 @@ const normalizeFunctionResponse = (value) => {
20
20
  return deserialize(value);
21
21
  };
22
22
  exports.normalizeFunctionResponse = normalizeFunctionResponse;
23
- const createFunctionsProxy = (callFunction) => new Proxy({}, {
23
+ const createFunctionsProxy = (callFunction, callFunctionStreaming) => new Proxy({}, {
24
24
  get: (_, key) => {
25
25
  if (typeof key !== 'string')
26
26
  return undefined;
27
+ if (key === 'callFunction') {
28
+ return (name, ...args) => callFunction(name, args);
29
+ }
30
+ if (key === 'callFunctionStreaming') {
31
+ return (name, ...args) => callFunctionStreaming(name, args);
32
+ }
27
33
  return (...args) => callFunction(key, args);
28
34
  }
29
35
  });