@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.
- package/dist/app.d.ts +55 -10
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +322 -47
- package/dist/credentials.d.ts +1 -0
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js +6 -0
- package/dist/functions.d.ts +4 -1
- package/dist/functions.d.ts.map +1 -1
- package/dist/functions.js +7 -1
- package/dist/http.d.ts +24 -4
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +121 -25
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/mongo.d.ts +1 -1
- package/dist/mongo.d.ts.map +1 -1
- package/dist/mongo.js +45 -4
- package/dist/session.d.ts +6 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +52 -0
- package/dist/session.native.d.ts.map +1 -1
- package/dist/session.native.js +5 -10
- package/dist/types.d.ts +28 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/user.d.ts +23 -4
- package/dist/user.d.ts.map +1 -1
- package/dist/user.js +96 -7
- package/package.json +12 -1
- package/src/__tests__/auth.test.ts +49 -0
- package/src/__tests__/compat.test.ts +10 -0
- package/src/__tests__/functions.test.ts +213 -0
- package/src/__tests__/mongo.test.ts +35 -0
- package/src/__tests__/session.test.ts +494 -0
- package/src/__tests__/watch.test.ts +74 -0
- package/src/app.ts +390 -63
- package/src/credentials.ts +7 -0
- package/src/functions.ts +16 -3
- package/src/http.ts +156 -27
- package/src/index.ts +1 -0
- package/src/mongo.ts +48 -4
- package/src/session.native.ts +2 -11
- package/src/session.ts +55 -0
- package/src/types.ts +34 -4
- 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
|
-
|
|
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
|
-
|
|
16
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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,
|
|
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;IAgE1C,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,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;YAkBtB,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
|
@@ -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(
|
|
11
|
-
this.
|
|
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
|
|
17
|
-
|
|
18
|
-
this.
|
|
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
|
-
|
|
24
|
-
|
|
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.
|
|
176
|
+
this.setSessionForUser({
|
|
53
177
|
...session,
|
|
54
178
|
accessToken: result.access_token
|
|
55
179
|
});
|
|
56
180
|
}
|
|
57
181
|
catch {
|
|
58
|
-
this.
|
|
59
|
-
this.
|
|
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.
|
|
73
|
-
|
|
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
|
-
|
|
201
|
+
user.profile = { email: profileEmail };
|
|
76
202
|
}
|
|
77
|
-
|
|
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
|
-
|
|
92
|
-
|
|
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
|
-
|
|
95
|
-
const
|
|
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
|
|
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:
|
|
326
|
+
bearerToken: accessToken,
|
|
121
327
|
timeout: this.timeout
|
|
122
|
-
});
|
|
328
|
+
}), userId);
|
|
123
329
|
return (0, functions_1.normalizeFunctionResponse)(result);
|
|
124
330
|
}
|
|
125
|
-
async
|
|
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
|
-
|
|
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:
|
|
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
|
|
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:
|
|
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.
|
|
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.
|
|
164
|
-
this.
|
|
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.
|
|
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
|
-
|
|
182
|
-
|
|
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;
|
package/dist/credentials.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/credentials.js
CHANGED
package/dist/functions.d.ts
CHANGED
|
@@ -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
|
|
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
|
package/dist/functions.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
});
|