@qmuse/cloud-client 0.0.1-dev.0 → 0.0.1-dev.2
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/README.md +62 -51
- package/dist/adapters.d.ts +39 -4
- package/dist/adapters.d.ts.map +1 -1
- package/dist/adapters.js +31 -18
- package/dist/adapters.js.map +1 -1
- package/dist/auth.d.ts +8 -3
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +149 -96
- package/dist/auth.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +12 -8
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +3 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -4
- package/dist/index.js.map +1 -1
- package/dist/miniapp.d.ts +20 -1
- package/dist/miniapp.d.ts.map +1 -1
- package/dist/miniapp.js +139 -0
- package/dist/miniapp.js.map +1 -1
- package/dist/singleton.d.ts +24 -0
- package/dist/singleton.d.ts.map +1 -0
- package/dist/singleton.js +104 -0
- package/dist/singleton.js.map +1 -0
- package/dist/types.d.ts +26 -26
- package/dist/types.d.ts.map +1 -1
- package/dist/web.d.ts +4 -1
- package/dist/web.d.ts.map +1 -1
- package/dist/web.js +20 -0
- package/dist/web.js.map +1 -1
- package/package.json +17 -2
package/README.md
CHANGED
|
@@ -6,54 +6,60 @@ its control plane while large file bytes use a platform-specific Storage data
|
|
|
6
6
|
plane inside this package.
|
|
7
7
|
|
|
8
8
|
The package intentionally does not expose AppBase clients, Functions, Storage,
|
|
9
|
-
TablesDB, endpoint, project ID, database ID, bucket ID, or
|
|
10
|
-
business modules.
|
|
11
|
-
|
|
9
|
+
TablesDB, endpoint, project ID, database ID, bucket ID, permissions, or a
|
|
10
|
+
platform-injection API to business modules. Platform implementations are fixed
|
|
11
|
+
by the `/web` and `/miniapp` package entry points.
|
|
12
|
+
|
|
13
|
+
The host initializes one client for the current JavaScript runtime before
|
|
14
|
+
mounting the application. Web and mini-program hosts may load the public
|
|
15
|
+
connection config differently while business modules always read the same
|
|
16
|
+
singleton:
|
|
12
17
|
|
|
13
18
|
```ts
|
|
14
|
-
import {
|
|
19
|
+
import { getQMuseClient } from '@qmuse/cloud-client';
|
|
20
|
+
import { initQMuseCloudClient } from '@qmuse/cloud-client/web';
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
await initQMuseCloudClient({
|
|
23
|
+
config: () => hostRuntime.appwrite,
|
|
18
24
|
});
|
|
19
25
|
|
|
20
|
-
const result = await
|
|
26
|
+
const result = await getQMuseClient().request<{ data: Order }>({
|
|
21
27
|
method: 'GET',
|
|
22
28
|
path: '/api/orders/order_1',
|
|
23
29
|
});
|
|
24
30
|
```
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
`initQMuseCloudClient` accepts a config object or a sync/async config loader. Calls
|
|
33
|
+
with the same normalized `endpoint`, `projectId`, and `functionId` are
|
|
34
|
+
idempotent and concurrent calls share the same initialization. Reinitializing
|
|
35
|
+
with different connection config is rejected.
|
|
36
|
+
|
|
37
|
+
Each generated application owns its own AppBase users, Sessions, roles and
|
|
38
|
+
policies. The AppBase Session is the only application login state. It supplies
|
|
39
|
+
Function runtime identity headers and Storage permissions; there is no second
|
|
40
|
+
application access/refresh-token layer.
|
|
30
41
|
|
|
31
|
-
The
|
|
32
|
-
|
|
42
|
+
The selected package entry point supplies the AppBase Account capability. Login receives a
|
|
43
|
+
short-lived custom-token secret from the Function and exchanges it immediately;
|
|
44
|
+
the secret is never returned from `login`, persisted, logged or put in business
|
|
45
|
+
state.
|
|
33
46
|
|
|
34
47
|
```ts
|
|
35
|
-
|
|
36
|
-
execute: input => runtime.executeCloudFunction(input),
|
|
37
|
-
auth: {
|
|
38
|
-
credentialStore: secureCredentialStore,
|
|
39
|
-
shadowSession: {
|
|
40
|
-
createSession: input => account.createSession(input),
|
|
41
|
-
deleteCurrentSession: () => account.deleteSession({ sessionId: 'current' }),
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
});
|
|
48
|
+
import { getQMuseClient } from '@qmuse/cloud-client';
|
|
45
49
|
|
|
46
|
-
|
|
50
|
+
const cloudClient = getQMuseClient();
|
|
51
|
+
await cloudClient.auth.login({ username, password });
|
|
47
52
|
const currentUser = await cloudClient.auth.getCurrentUser();
|
|
48
53
|
await cloudClient.auth.logout();
|
|
54
|
+
// Or revoke every AppBase Session for this user:
|
|
55
|
+
await cloudClient.auth.logoutAll();
|
|
49
56
|
```
|
|
50
57
|
|
|
51
|
-
`
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
tokens to `localStorage`.
|
|
58
|
+
`register` creates the user without changing the current Session. `login`
|
|
59
|
+
exchanges the custom token internally. Session refresh is single-flight and is
|
|
60
|
+
performed before business requests when expiry is within five minutes. Web
|
|
61
|
+
hosts use AppBase's secure cookie; mini-program hosts persist AppBase fallback
|
|
62
|
+
Session headers in storage scoped by endpoint and project ID.
|
|
57
63
|
|
|
58
64
|
Business file operations use a logical resource, never a bucket ID:
|
|
59
65
|
|
|
@@ -80,42 +86,47 @@ the backend must also expire abandoned intents. Preview/download access is
|
|
|
80
86
|
authorized by the Function and may return a short-lived File Token URL or an
|
|
81
87
|
authenticated URL that the Web transport converts to a disposable Blob URL.
|
|
82
88
|
|
|
83
|
-
|
|
89
|
+
Initialization is available only from platform-specific entry points:
|
|
84
90
|
|
|
85
91
|
```ts
|
|
86
|
-
|
|
87
|
-
import {
|
|
92
|
+
// Web build
|
|
93
|
+
import { initQMuseCloudClient } from '@qmuse/cloud-client/web';
|
|
94
|
+
|
|
95
|
+
await initQMuseCloudClient({
|
|
96
|
+
config: () => window.__MUSE__.appwrite,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Taro mini-program build
|
|
100
|
+
import { initQMuseCloudClient } from '@qmuse/cloud-client/miniapp';
|
|
101
|
+
|
|
102
|
+
await initQMuseCloudClient({
|
|
103
|
+
config: () => getMuseRuntime().appwrite,
|
|
104
|
+
});
|
|
88
105
|
```
|
|
89
106
|
|
|
90
|
-
|
|
91
|
-
|
|
107
|
+
The Web entry owns the Appwrite Web SDK and secure-cookie Session. The miniapp
|
|
108
|
+
entry owns Taro request, upload, storage, file-system and project-isolated
|
|
109
|
+
`X-Fallback-Cookies` Session handling. Both entries include the proven file
|
|
110
|
+
mechanics from the legacy `@qmuse/appwrite` implementation: Web `File`/`Blob`, Taro-style local paths, 5 MiB chunks,
|
|
92
111
|
`Content-Range`, `X-Appwrite-ID` continuation, progress, fallback Session
|
|
93
112
|
headers and mini-program temporary-file cleanup. They do not migrate TablesDB
|
|
94
113
|
CRUD or the legacy anonymous `Role.any()` update/delete permissions.
|
|
95
114
|
|
|
96
|
-
For Web hosts,
|
|
97
|
-
business modules:
|
|
115
|
+
For Web hosts, initialization therefore stays small:
|
|
98
116
|
|
|
99
117
|
```ts
|
|
100
|
-
import {
|
|
118
|
+
import { initQMuseCloudClient } from '@qmuse/cloud-client/web';
|
|
101
119
|
|
|
102
|
-
export const cloudClient =
|
|
103
|
-
|
|
120
|
+
export const cloudClient = await initQMuseCloudClient({
|
|
121
|
+
config: () => window.__MUSE__.appwrite,
|
|
104
122
|
});
|
|
105
123
|
```
|
|
106
124
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
```ts
|
|
110
|
-
import { createPortableCloudClient } from '@qmuse/cloud-client';
|
|
111
|
-
|
|
112
|
-
export const cloudClient = createPortableCloudClient(appwrite);
|
|
113
|
-
```
|
|
125
|
+
`functionId` is the public physical Function ID assigned by the AppBase project.
|
|
126
|
+
It is fixed during initialization and is not supplied by business requests.
|
|
114
127
|
|
|
115
|
-
New applications should
|
|
116
|
-
|
|
117
|
-
`@qmuse/appwrite-runtime-sdk`. The portable adapter only translates this
|
|
118
|
-
package's `path` field to the legacy `xpath` field for staged migration.
|
|
128
|
+
New applications should select one platform entry at build time and should not
|
|
129
|
+
depend on `@qmuse/appwrite` or `@qmuse/appwrite-runtime-sdk`.
|
|
119
130
|
|
|
120
131
|
Only `/api` paths and an allowlist of business headers are accepted. The client
|
|
121
132
|
uses synchronous Function execution, reads `responseStatusCode`, `responseBody`,
|
package/dist/adapters.d.ts
CHANGED
|
@@ -1,15 +1,50 @@
|
|
|
1
|
-
import type { CloudAuthOptions, CloudExecution, CloudExecutionInput, CloudFileTransport, QmuseCloudClient } from './types.js';
|
|
1
|
+
import type { CloudAccountCapability, CloudAuthOptions, CloudExecution, CloudExecutionInput, CloudFileTransport, QmuseCloudClient } from './types.js';
|
|
2
|
+
export type AppBaseExecutionInput = Omit<CloudExecutionInput, 'functionId'> & {
|
|
3
|
+
functionId: string;
|
|
4
|
+
};
|
|
2
5
|
export interface AppBaseFunctionsCapability {
|
|
3
|
-
createExecution(input:
|
|
6
|
+
createExecution(input: AppBaseExecutionInput): Promise<CloudExecution>;
|
|
4
7
|
}
|
|
5
|
-
export interface
|
|
8
|
+
export interface AppBaseFunctionsSdkCapability {
|
|
9
|
+
createExecution(...args: never[]): Promise<CloudExecution>;
|
|
10
|
+
}
|
|
11
|
+
export declare function createAppBaseFunctionsCapability(functions: AppBaseFunctionsSdkCapability): AppBaseFunctionsCapability;
|
|
12
|
+
export interface AppBaseAccountSdkCapability {
|
|
13
|
+
createSession(input: {
|
|
14
|
+
userId: string;
|
|
15
|
+
secret: string;
|
|
16
|
+
}): Promise<{
|
|
17
|
+
expire: string;
|
|
18
|
+
}>;
|
|
19
|
+
get(): Promise<{
|
|
20
|
+
$id: string;
|
|
21
|
+
email?: string;
|
|
22
|
+
labels?: string[];
|
|
23
|
+
}>;
|
|
24
|
+
getSession(input: {
|
|
25
|
+
sessionId: 'current';
|
|
26
|
+
}): Promise<{
|
|
27
|
+
expire: string;
|
|
28
|
+
}>;
|
|
29
|
+
updateSession(input: {
|
|
30
|
+
sessionId: 'current';
|
|
31
|
+
}): Promise<{
|
|
32
|
+
expire: string;
|
|
33
|
+
}>;
|
|
34
|
+
deleteSession(input: {
|
|
35
|
+
sessionId: 'current';
|
|
36
|
+
}): Promise<unknown>;
|
|
37
|
+
deleteSessions(): Promise<unknown>;
|
|
38
|
+
}
|
|
39
|
+
export declare function createAppBaseAccountCapability(account: AppBaseAccountSdkCapability): CloudAccountCapability;
|
|
40
|
+
export interface PortableExecutionInput extends Omit<AppBaseExecutionInput, 'path'> {
|
|
6
41
|
xpath: string;
|
|
7
42
|
}
|
|
8
43
|
export interface PortableAppwriteCapability {
|
|
9
44
|
executeFunction(input: PortableExecutionInput): Promise<CloudExecution>;
|
|
10
45
|
}
|
|
11
46
|
export interface HostAdapterOptions<FileSource = never> {
|
|
12
|
-
|
|
47
|
+
functionId?: string;
|
|
13
48
|
auth?: CloudAuthOptions;
|
|
14
49
|
fileTransport?: CloudFileTransport<FileSource>;
|
|
15
50
|
}
|
package/dist/adapters.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAChB,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,sBAAsB,EACtB,gBAAgB,EAChB,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAGpB,MAAM,MAAM,qBAAqB,GAAG,IAAI,CAAC,mBAAmB,EAAE,YAAY,CAAC,GAAG;IAC5E,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,WAAW,0BAA0B;IACzC,eAAe,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CACxE;AAED,MAAM,WAAW,6BAA6B;IAC5C,eAAe,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CAC5D;AAED,wBAAgB,gCAAgC,CAC9C,SAAS,EAAE,6BAA6B,GACvC,0BAA0B,CAU5B;AAED,MAAM,WAAW,2BAA2B;IAC1C,aAAa,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtF,GAAG,IAAI,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACnE,UAAU,CAAC,KAAK,EAAE;QAAE,SAAS,EAAE,SAAS,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzE,aAAa,CAAC,KAAK,EAAE;QAAE,SAAS,EAAE,SAAS,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5E,aAAa,CAAC,KAAK,EAAE;QAAE,SAAS,EAAE,SAAS,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACjE,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACpC;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,2BAA2B,GAAG,sBAAsB,CAY3G;AAED,MAAM,WAAW,sBAAuB,SAAQ,IAAI,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACjF,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,0BAA0B;IACzC,eAAe,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CACzE;AAED,MAAM,WAAW,kBAAkB,CAAC,UAAU,GAAG,KAAK;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,aAAa,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAC;CAChD;AAYD,wBAAgB,2BAA2B,CAAC,UAAU,GAAG,KAAK,EAC5D,SAAS,EAAE,0BAA0B,EACrC,OAAO,GAAE,kBAAkB,CAAC,UAAU,CAAM,GAC3C,gBAAgB,CAAC,UAAU,CAAC,CAS9B;AAED,wBAAgB,yBAAyB,CAAC,UAAU,GAAG,KAAK,EAC1D,QAAQ,EAAE,0BAA0B,EACpC,OAAO,GAAE,kBAAkB,CAAC,UAAU,CAAM,GAC3C,gBAAgB,CAAC,UAAU,CAAC,CAY9B"}
|
package/dist/adapters.js
CHANGED
|
@@ -1,28 +1,41 @@
|
|
|
1
1
|
import { createQmuseCloudClient } from './client.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
return [value.code, value.status, value.statusCode, value.response?.status, value.response?.statusCode]
|
|
7
|
-
.some(code => Number(code) === 401);
|
|
8
|
-
}
|
|
9
|
-
async function withTransportRetry(operation, refreshSession) {
|
|
10
|
-
try {
|
|
11
|
-
return await operation();
|
|
2
|
+
import { toCloudAuthUser } from './auth.js';
|
|
3
|
+
export function createAppBaseFunctionsCapability(functions) {
|
|
4
|
+
if (!functions || typeof functions.createExecution !== 'function') {
|
|
5
|
+
throw new TypeError('An AppBase Functions SDK capability is required');
|
|
12
6
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
7
|
+
return {
|
|
8
|
+
createExecution(input) {
|
|
9
|
+
const { path, ...sdkInput } = input;
|
|
10
|
+
return functions.createExecution({ ...sdkInput, xpath: path });
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function createAppBaseAccountCapability(account) {
|
|
15
|
+
if (!account || typeof account.createSession !== 'function' || typeof account.get !== 'function') {
|
|
16
|
+
throw new TypeError('An AppBase Account SDK capability is required');
|
|
18
17
|
}
|
|
18
|
+
return {
|
|
19
|
+
createSession: input => account.createSession(input),
|
|
20
|
+
getCurrentUser: async () => toCloudAuthUser(await account.get()),
|
|
21
|
+
getCurrentSession: () => account.getSession({ sessionId: 'current' }),
|
|
22
|
+
refreshCurrentSession: () => account.updateSession({ sessionId: 'current' }),
|
|
23
|
+
deleteCurrentSession: async () => { await account.deleteSession({ sessionId: 'current' }); },
|
|
24
|
+
deleteAllSessions: async () => { await account.deleteSessions(); },
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function executionInput(input, functionId) {
|
|
28
|
+
return {
|
|
29
|
+
...input,
|
|
30
|
+
functionId: functionId?.trim() || input.functionId,
|
|
31
|
+
};
|
|
19
32
|
}
|
|
20
33
|
export function createAppBaseWebCloudClient(functions, options = {}) {
|
|
21
34
|
if (!functions || typeof functions.createExecution !== 'function') {
|
|
22
35
|
throw new TypeError('An AppBase Functions capability is required');
|
|
23
36
|
}
|
|
24
37
|
return createQmuseCloudClient({
|
|
25
|
-
execute: input =>
|
|
38
|
+
execute: input => functions.createExecution(executionInput(input, options.functionId)),
|
|
26
39
|
...(options.auth ? { auth: options.auth } : {}),
|
|
27
40
|
...(options.fileTransport ? { fileTransport: options.fileTransport } : {}),
|
|
28
41
|
});
|
|
@@ -33,8 +46,8 @@ export function createPortableCloudClient(appwrite, options = {}) {
|
|
|
33
46
|
}
|
|
34
47
|
return createQmuseCloudClient({
|
|
35
48
|
execute: input => {
|
|
36
|
-
const { path, ...portableInput } = input;
|
|
37
|
-
return
|
|
49
|
+
const { path, ...portableInput } = executionInput(input, options.functionId);
|
|
50
|
+
return appwrite.executeFunction({ ...portableInput, xpath: path });
|
|
38
51
|
},
|
|
39
52
|
...(options.auth ? { auth: options.auth } : {}),
|
|
40
53
|
...(options.fileTransport ? { fileTransport: options.fileTransport } : {}),
|
package/dist/adapters.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapters.js","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"adapters.js","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"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"}
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import type { CloudAuthOptions, CloudRequest, CloudResponse, QmuseCloudAuth } from './types.js';
|
|
2
|
-
type AuthRequest = <T>(request: CloudRequest
|
|
1
|
+
import type { CloudAuthOptions, CloudAuthUser, CloudRequest, CloudResponse, QmuseCloudAuth } from './types.js';
|
|
2
|
+
type AuthRequest = <T>(request: CloudRequest) => Promise<CloudResponse<T>>;
|
|
3
3
|
export type CloudAuthRuntime = QmuseCloudAuth & {
|
|
4
|
-
|
|
4
|
+
prepareRequest(): Promise<void>;
|
|
5
5
|
};
|
|
6
|
+
export declare function toCloudAuthUser(value: {
|
|
7
|
+
$id: string;
|
|
8
|
+
email?: string;
|
|
9
|
+
labels?: string[];
|
|
10
|
+
}): CloudAuthUser;
|
|
6
11
|
export declare function createUnavailableCloudAuth(): CloudAuthRuntime;
|
|
7
12
|
export declare function createCloudAuth(options: CloudAuthOptions, request: AuthRequest): CloudAuthRuntime;
|
|
8
13
|
export {};
|
package/dist/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAChB,aAAa,EAIb,YAAY,EACZ,aAAa,EACb,cAAc,EACf,MAAM,YAAY,CAAC;AAEpB,KAAK,WAAW,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;AAE3E,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG;IAC9C,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC,CAAC;AAoBF,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB,GAAG,aAAa,CAOhB;AAuCD,wBAAgB,0BAA0B,IAAI,gBAAgB,CAc7D;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,WAAW,GAAG,gBAAgB,CAuHjG"}
|
package/dist/auth.js
CHANGED
|
@@ -1,30 +1,55 @@
|
|
|
1
1
|
import { QmuseCloudError } from './errors.js';
|
|
2
|
-
|
|
2
|
+
const DEFAULT_REFRESH_BEFORE_MS = 5 * 60 * 1000;
|
|
3
|
+
function validUser(value) {
|
|
3
4
|
if (!value || typeof value !== 'object')
|
|
4
5
|
return false;
|
|
5
6
|
const item = value;
|
|
6
|
-
return typeof item.
|
|
7
|
-
typeof item.
|
|
8
|
-
|
|
9
|
-
!!item.user && typeof item.user === 'object' && typeof item.user.id === 'string' && item.user.id.length > 0;
|
|
7
|
+
return typeof item.id === 'string' && item.id.length > 0 &&
|
|
8
|
+
typeof item.username === 'string' && item.username.length > 0 &&
|
|
9
|
+
(item.role === 'user' || item.role === 'admin');
|
|
10
10
|
}
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
typeof shadow.secret === 'string' && shadow.secret.length > 0 &&
|
|
19
|
-
typeof shadow.expire === 'string' && shadow.expire.length > 0;
|
|
11
|
+
export function toCloudAuthUser(value) {
|
|
12
|
+
const id = value?.$id?.trim();
|
|
13
|
+
const username = /^([a-z0-9_]{3,32})@qmuse[.]cn$/.exec(value?.email?.trim().toLowerCase() ?? '')?.[1];
|
|
14
|
+
if (!id || !username) {
|
|
15
|
+
throw new QmuseCloudError('AppBase returned an invalid current user', 'AUTH_USER_INVALID');
|
|
16
|
+
}
|
|
17
|
+
return { id, username, role: value.labels?.includes('admin') ? 'admin' : 'user' };
|
|
20
18
|
}
|
|
21
|
-
function
|
|
19
|
+
function userResult(response) {
|
|
20
|
+
const user = response.data?.data;
|
|
21
|
+
if (!validUser(user)) {
|
|
22
|
+
throw new QmuseCloudError('Cloud API returned an invalid user', 'AUTH_USER_INVALID');
|
|
23
|
+
}
|
|
24
|
+
return user;
|
|
25
|
+
}
|
|
26
|
+
function loginResult(response) {
|
|
22
27
|
const value = response.data?.data;
|
|
23
|
-
if (!
|
|
28
|
+
if (!value || typeof value !== 'object' || !validUser(value.user)) {
|
|
29
|
+
throw new QmuseCloudError('Cloud API returned an invalid authentication response', 'AUTH_RESPONSE_INVALID');
|
|
30
|
+
}
|
|
31
|
+
const token = value.sessionToken;
|
|
32
|
+
if (!token || typeof token !== 'object' || token.userId !== value.user.id ||
|
|
33
|
+
typeof token.secret !== 'string' || token.secret.length === 0 ||
|
|
34
|
+
typeof token.expire !== 'string' || token.expire.length === 0) {
|
|
24
35
|
throw new QmuseCloudError('Cloud API returned an invalid authentication response', 'AUTH_RESPONSE_INVALID');
|
|
25
36
|
}
|
|
26
37
|
return value;
|
|
27
38
|
}
|
|
39
|
+
function expiration(value) {
|
|
40
|
+
const parsed = Date.parse(value);
|
|
41
|
+
if (!Number.isFinite(parsed)) {
|
|
42
|
+
throw new QmuseCloudError('AppBase returned an invalid Session expiry', 'AUTH_SESSION_INVALID');
|
|
43
|
+
}
|
|
44
|
+
return parsed;
|
|
45
|
+
}
|
|
46
|
+
function isMissingSession(error) {
|
|
47
|
+
if (!error || typeof error !== 'object')
|
|
48
|
+
return false;
|
|
49
|
+
const value = error;
|
|
50
|
+
return [value.code, value.status, value.statusCode]
|
|
51
|
+
.some(code => Number(code) === 401 || Number(code) === 404);
|
|
52
|
+
}
|
|
28
53
|
export function createUnavailableCloudAuth() {
|
|
29
54
|
const unavailable = async () => {
|
|
30
55
|
throw new QmuseCloudError('Cloud Auth is not configured', 'AUTH_UNAVAILABLE');
|
|
@@ -32,74 +57,85 @@ export function createUnavailableCloudAuth() {
|
|
|
32
57
|
return {
|
|
33
58
|
register: unavailable,
|
|
34
59
|
login: unavailable,
|
|
35
|
-
|
|
36
|
-
|
|
60
|
+
refreshSession: unavailable,
|
|
61
|
+
restoreSession: async () => null,
|
|
37
62
|
logout: async () => undefined,
|
|
63
|
+
logoutAll: async () => undefined,
|
|
38
64
|
getCurrentUser: async () => null,
|
|
39
|
-
|
|
65
|
+
prepareRequest: async () => undefined,
|
|
40
66
|
};
|
|
41
67
|
}
|
|
42
68
|
export function createCloudAuth(options, request) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
69
|
+
const account = options?.account;
|
|
70
|
+
if (!account || typeof account.createSession !== 'function' ||
|
|
71
|
+
typeof account.getCurrentUser !== 'function' || typeof account.getCurrentSession !== 'function' ||
|
|
72
|
+
typeof account.refreshCurrentSession !== 'function' || typeof account.deleteCurrentSession !== 'function' ||
|
|
73
|
+
typeof account.deleteAllSessions !== 'function') {
|
|
74
|
+
throw new TypeError('Cloud Auth requires an AppBase Account capability');
|
|
46
75
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
throw new TypeError('
|
|
76
|
+
const refreshBeforeMs = options.refreshBeforeMs ?? DEFAULT_REFRESH_BEFORE_MS;
|
|
77
|
+
if (!Number.isFinite(refreshBeforeMs) || refreshBeforeMs < 0) {
|
|
78
|
+
throw new TypeError('refreshBeforeMs must be a non-negative finite number');
|
|
50
79
|
}
|
|
51
|
-
|
|
52
|
-
let
|
|
80
|
+
const now = options.now ?? Date.now;
|
|
81
|
+
let initialized = false;
|
|
82
|
+
let user = null;
|
|
83
|
+
let expiresAt = 0;
|
|
53
84
|
let refreshFlight = null;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
loaded = true;
|
|
60
|
-
if (stored && !credentials)
|
|
61
|
-
await options.credentialStore.clear();
|
|
62
|
-
return credentials;
|
|
85
|
+
let restoreFlight = null;
|
|
86
|
+
const clear = () => {
|
|
87
|
+
initialized = true;
|
|
88
|
+
user = null;
|
|
89
|
+
expiresAt = 0;
|
|
63
90
|
};
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
91
|
+
const readCurrent = async () => {
|
|
92
|
+
const [currentUser, currentSession] = await Promise.all([
|
|
93
|
+
account.getCurrentUser(),
|
|
94
|
+
account.getCurrentSession(),
|
|
95
|
+
]);
|
|
96
|
+
if (!validUser(currentUser)) {
|
|
97
|
+
throw new QmuseCloudError('AppBase returned an invalid current user', 'AUTH_USER_INVALID');
|
|
98
|
+
}
|
|
99
|
+
expiresAt = expiration(currentSession.expire);
|
|
100
|
+
user = currentUser;
|
|
101
|
+
initialized = true;
|
|
102
|
+
return currentUser;
|
|
68
103
|
};
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
104
|
+
const restoreSession = async () => {
|
|
105
|
+
if (restoreFlight)
|
|
106
|
+
return restoreFlight;
|
|
107
|
+
restoreFlight = (async () => {
|
|
108
|
+
try {
|
|
109
|
+
return await readCurrent();
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (!isMissingSession(error))
|
|
113
|
+
throw error;
|
|
114
|
+
clear();
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
})();
|
|
74
118
|
try {
|
|
75
|
-
await
|
|
76
|
-
credentials = result.credentials;
|
|
77
|
-
loaded = true;
|
|
78
|
-
return result.credentials.user;
|
|
119
|
+
return await restoreFlight;
|
|
79
120
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
throw error;
|
|
121
|
+
finally {
|
|
122
|
+
restoreFlight = null;
|
|
83
123
|
}
|
|
84
124
|
};
|
|
85
|
-
const
|
|
86
|
-
await options.shadowSession.deleteCurrentSession().catch(() => undefined);
|
|
87
|
-
return establish(sessionResult(await request({ method: 'POST', path, body })));
|
|
88
|
-
};
|
|
89
|
-
const refresh = async () => {
|
|
125
|
+
const refreshSession = async () => {
|
|
90
126
|
if (refreshFlight)
|
|
91
127
|
return refreshFlight;
|
|
92
128
|
refreshFlight = (async () => {
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return
|
|
129
|
+
const session = await account.refreshCurrentSession();
|
|
130
|
+
expiresAt = expiration(session.expire);
|
|
131
|
+
const currentUser = await account.getCurrentUser();
|
|
132
|
+
if (!validUser(currentUser)) {
|
|
133
|
+
clear();
|
|
134
|
+
throw new QmuseCloudError('AppBase returned an invalid current user', 'AUTH_USER_INVALID');
|
|
135
|
+
}
|
|
136
|
+
user = currentUser;
|
|
137
|
+
initialized = true;
|
|
138
|
+
return currentUser;
|
|
103
139
|
})();
|
|
104
140
|
try {
|
|
105
141
|
return await refreshFlight;
|
|
@@ -109,48 +145,65 @@ export function createCloudAuth(options, request) {
|
|
|
109
145
|
}
|
|
110
146
|
};
|
|
111
147
|
return {
|
|
112
|
-
register
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
async
|
|
116
|
-
|
|
117
|
-
return null;
|
|
148
|
+
async register(input) {
|
|
149
|
+
return userResult(await request({ method: 'POST', path: '/api/auth/register', body: input }));
|
|
150
|
+
},
|
|
151
|
+
async login(input) {
|
|
152
|
+
const result = loginResult(await request({ method: 'POST', path: '/api/auth/login', body: input }));
|
|
118
153
|
try {
|
|
119
|
-
|
|
154
|
+
await account.deleteCurrentSession().catch(error => {
|
|
155
|
+
if (!isMissingSession(error))
|
|
156
|
+
throw error;
|
|
157
|
+
});
|
|
158
|
+
const session = await account.createSession({
|
|
159
|
+
userId: result.sessionToken.userId,
|
|
160
|
+
secret: result.sessionToken.secret,
|
|
161
|
+
});
|
|
162
|
+
expiresAt = expiration(session.expire);
|
|
163
|
+
const currentUser = await account.getCurrentUser();
|
|
164
|
+
if (!validUser(currentUser)) {
|
|
165
|
+
throw new QmuseCloudError('AppBase returned an invalid current user', 'AUTH_USER_INVALID');
|
|
166
|
+
}
|
|
167
|
+
user = currentUser;
|
|
168
|
+
initialized = true;
|
|
169
|
+
return currentUser;
|
|
120
170
|
}
|
|
121
|
-
catch
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
171
|
+
catch {
|
|
172
|
+
clear();
|
|
173
|
+
throw new QmuseCloudError('Could not establish the AppBase Session', 'AUTH_SESSION_EXCHANGE_FAILED');
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
result.sessionToken.secret = '';
|
|
125
177
|
}
|
|
126
178
|
},
|
|
179
|
+
refreshSession,
|
|
180
|
+
restoreSession,
|
|
127
181
|
async logout() {
|
|
128
|
-
const current = await load();
|
|
129
182
|
try {
|
|
130
|
-
|
|
131
|
-
await request({ method: 'POST', path: '/api/auth/logout' }, current.accessToken);
|
|
132
|
-
}
|
|
183
|
+
await account.deleteCurrentSession();
|
|
133
184
|
}
|
|
134
185
|
finally {
|
|
135
|
-
|
|
136
|
-
await clear();
|
|
186
|
+
clear();
|
|
137
187
|
}
|
|
138
188
|
},
|
|
139
|
-
async
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return null;
|
|
143
|
-
const response = await request({ method: 'GET', path: '/api/auth/me' }, current.accessToken);
|
|
144
|
-
const user = response.data?.data;
|
|
145
|
-
if (!user || typeof user !== 'object' || typeof user.id !== 'string') {
|
|
146
|
-
throw new QmuseCloudError('Cloud API returned an invalid current user', 'AUTH_USER_INVALID');
|
|
189
|
+
async logoutAll() {
|
|
190
|
+
try {
|
|
191
|
+
await account.deleteAllSessions();
|
|
147
192
|
}
|
|
148
|
-
|
|
149
|
-
|
|
193
|
+
finally {
|
|
194
|
+
clear();
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
async getCurrentUser() {
|
|
198
|
+
if (!initialized)
|
|
199
|
+
return restoreSession();
|
|
150
200
|
return user;
|
|
151
201
|
},
|
|
152
|
-
async
|
|
153
|
-
|
|
202
|
+
async prepareRequest() {
|
|
203
|
+
if (!initialized)
|
|
204
|
+
await restoreSession();
|
|
205
|
+
if (user && expiresAt - now() <= refreshBeforeMs)
|
|
206
|
+
await refreshSession();
|
|
154
207
|
},
|
|
155
208
|
};
|
|
156
209
|
}
|