@ossy/sdk 3.5.0 → 3.12.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/README.md +2 -2
- package/build/public.index.d.ts +15 -7
- package/build/public.index.js +1 -1
- package/build/types/Actions.d.ts +3 -1
- package/build/types/platform-action.d.ts +0 -4
- package/build/types/platform-action.spec.d.ts +1 -0
- package/build/types/resolve-http-action-url.d.ts +10 -1
- package/build/types/resolve-http-action-url.spec.d.ts +1 -0
- package/build/types/sdk.d.ts +3 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ import { SDK } from '@ossy/sdk'
|
|
|
15
15
|
|
|
16
16
|
const sdk = SDK.of({
|
|
17
17
|
workspaceId: 'your-workspace-id',
|
|
18
|
-
apiUrl: '
|
|
18
|
+
apiUrl: '/api/v0', // optional, this is the default (same origin)
|
|
19
19
|
})
|
|
20
20
|
|
|
21
21
|
// List resources in a folder
|
|
@@ -30,7 +30,7 @@ const resource = await sdk.resources.get({ id: 'resource-id' })
|
|
|
30
30
|
| Option | Description |
|
|
31
31
|
|--------|-------------|
|
|
32
32
|
| `workspaceId` | Your workspace identifier (required for most operations) |
|
|
33
|
-
| `apiUrl` | API base URL (default: `https://
|
|
33
|
+
| `apiUrl` | API base URL (default: `/api/v0`, same origin). CLI / Node clients without a page origin should use `https://ossy.se/api/v0`. |
|
|
34
34
|
| `authorization` | Bearer token for authenticated requests |
|
|
35
35
|
|
|
36
36
|
## API overview
|
package/build/public.index.d.ts
CHANGED
|
@@ -5,17 +5,22 @@ export { ActionAccess, ActionRoute } from '@ossy/types';
|
|
|
5
5
|
type ActionRef = Action | string;
|
|
6
6
|
/** Normalize dot aliases to slash ids (`@ossy.booking.actions.create` → `@ossy/booking/actions/create`). */
|
|
7
7
|
declare function resolveActionId(action: ActionRef): string;
|
|
8
|
-
/**
|
|
9
|
-
* Resolve POST /actions URL from an SDK `apiUrl`.
|
|
10
|
-
* Actions are served at the app server root, not under `/api/v0` or the `/@ossy` proxy prefix.
|
|
11
|
-
*/
|
|
12
8
|
declare function resolveActionsUrl(apiUrl: string): string;
|
|
13
9
|
/** Resolve GET /events SSE URL from an SDK `apiUrl`. */
|
|
14
10
|
declare function resolveEventsUrl(apiUrl: string): string;
|
|
15
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Whether `sdk.invoke` can send `payload` through a manifest HTTP action route.
|
|
14
|
+
*
|
|
15
|
+
* GET/HEAD adapters only forward `route.query` keys. Extra payload fields (e.g.
|
|
16
|
+
* `{ email, code }` on verify-sign-in while the adapter only accepts `token`)
|
|
17
|
+
* must use `POST /actions` instead — otherwise the adapter drops them and auth
|
|
18
|
+
* fails silently from the caller's perspective (#751).
|
|
19
|
+
*/
|
|
20
|
+
declare function canUseHttpActionRoute(route: ActionRoute, payload?: Record<string, unknown>): boolean;
|
|
16
21
|
/**
|
|
17
22
|
* Resolve a manifest HTTP action route to a fetch URL.
|
|
18
|
-
* Supports same-origin `/@ossy` proxy
|
|
23
|
+
* Supports same-origin `/api/v0` (and the legacy `/@ossy` proxy) plus absolute `/api/v0` bases.
|
|
19
24
|
*/
|
|
20
25
|
declare function resolveHttpActionUrl(apiUrl: string, route: ActionRoute, payload?: Record<string, unknown>): string;
|
|
21
26
|
|
|
@@ -24,6 +29,7 @@ declare function isLocalStorageUploadUrl(url: string): boolean;
|
|
|
24
29
|
declare class SDK {
|
|
25
30
|
workspaceId?: string;
|
|
26
31
|
authorization?: string;
|
|
32
|
+
/** Same-origin REST prefix. `POST /actions` is resolved separately (not under this path). */
|
|
27
33
|
baseUrl: string;
|
|
28
34
|
actionRoutes: Record<string, ActionRoute>;
|
|
29
35
|
static of(config: SDKConfig): SDK;
|
|
@@ -195,7 +201,9 @@ declare const AuthSignIn: PlatformAction<{
|
|
|
195
201
|
email: string;
|
|
196
202
|
}>;
|
|
197
203
|
declare const AuthVerifySignIn: PlatformAction<{
|
|
198
|
-
token
|
|
204
|
+
token?: string;
|
|
205
|
+
email?: string;
|
|
206
|
+
code?: string;
|
|
199
207
|
}>;
|
|
200
208
|
declare const AuthVerifyInvitation: PlatformAction<{
|
|
201
209
|
workspaceId: string;
|
|
@@ -210,5 +218,5 @@ declare const AuthSignOff: PlatformAction;
|
|
|
210
218
|
/** @deprecated Use `PlatformAction`. */
|
|
211
219
|
type RestAction<TPayload = Record<string, unknown>> = PlatformAction<TPayload>;
|
|
212
220
|
|
|
213
|
-
export { ApiTokenCreate, ApiTokenGetAll, ApiTokenInvalidate, AuthGetAuthenticatedUser, AuthGetAuthenticatedUserHistory, AuthGetUser, AuthSignIn, AuthSignOff, AuthSignUp, AuthVerifyInvitation, AuthVerifySignIn, ResourceUpdateAccess, ResourcesCreate, ResourcesCreateDirectory, ResourcesGet, ResourcesList, ResourcesMove, ResourcesRemove, ResourcesRename, ResourcesSearch, ResourcesUpdateContent, ResourcesUpload, ResourcesUploadNamedVersion, SDK, UserCurrentGet, UserCurrentGetHistory, UserCurrentUpdate, WorkspacesAddMember, WorkspacesCreate, WorkspacesCreateApiToken, WorkspacesDisableService, WorkspacesEnableService, WorkspacesGet, WorkspacesGetApiTokens, WorkspacesGetCurrent, WorkspacesGetSchemas, WorkspacesGetUsers, WorkspacesImportSchemas, WorkspacesInviteUser, WorkspacesList, WorkspacesRemoveMember, isLocalStorageUploadUrl, resolveActionId, resolveActionsUrl, resolveEventsUrl, resolveHttpActionUrl };
|
|
221
|
+
export { ApiTokenCreate, ApiTokenGetAll, ApiTokenInvalidate, AuthGetAuthenticatedUser, AuthGetAuthenticatedUserHistory, AuthGetUser, AuthSignIn, AuthSignOff, AuthSignUp, AuthVerifyInvitation, AuthVerifySignIn, ResourceUpdateAccess, ResourcesCreate, ResourcesCreateDirectory, ResourcesGet, ResourcesList, ResourcesMove, ResourcesRemove, ResourcesRename, ResourcesSearch, ResourcesUpdateContent, ResourcesUpload, ResourcesUploadNamedVersion, SDK, UserCurrentGet, UserCurrentGetHistory, UserCurrentUpdate, WorkspacesAddMember, WorkspacesCreate, WorkspacesCreateApiToken, WorkspacesDisableService, WorkspacesEnableService, WorkspacesGet, WorkspacesGetApiTokens, WorkspacesGetCurrent, WorkspacesGetSchemas, WorkspacesGetUsers, WorkspacesImportSchemas, WorkspacesInviteUser, WorkspacesList, WorkspacesRemoveMember, canUseHttpActionRoute, isLocalStorageUploadUrl, resolveActionId, resolveActionsUrl, resolveEventsUrl, resolveHttpActionUrl };
|
|
214
222
|
export type { ActionRef, PlatformAction, PushMessage, RestAction };
|
package/build/public.index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const s={VisualContentDescriptors:"@ossy/tasks/visual-content-descriptors",ResizeCommonWeb:"@ossy/tasks/resize-common-web"},e={Queued:"queued",InProgress:"in progress",Success:"success",Failed:"failed"},t={id:"@ossy/users/actions/get-api-tokens"},o={id:"@ossy/users/actions/create-api-token"},i={id:"@ossy/users/actions/invalidate-api-token"},n={id:"@ossy/workspaces/actions/list"},a={id:"@ossy/workspaces/actions/get"},r={id:"@ossy/workspaces/actions/get"},c={id:"@ossy/workspaces/actions/create"},u={id:"@ossy/workspaces/actions/import-schemas"},d={id:"@ossy/workspaces/actions/get-schemas"},h={id:"@ossy/workspaces/actions/create-api-token"},p={id:"@ossy/workspaces/actions/get-api-tokens"},l={id:"@ossy/workspaces/actions/invite-user"},y={id:"@ossy/workspaces/actions/enable-service"},k={id:"@ossy/workspaces/actions/disable-service"},g={id:"@ossy/workspaces/actions/get-users"},v={id:"@ossy/users/actions/join-workspace"},w={id:"@ossy/workspaces/actions/remove-member"},
|
|
1
|
+
const s={VisualContentDescriptors:"@ossy/tasks/visual-content-descriptors",ResizeCommonWeb:"@ossy/tasks/resize-common-web"},e={Queued:"queued",InProgress:"in progress",Success:"success",Failed:"failed"},t={id:"@ossy/users/actions/get-api-tokens"},o={id:"@ossy/users/actions/create-api-token"},i={id:"@ossy/users/actions/invalidate-api-token"},n={id:"@ossy/workspaces/actions/list"},a={id:"@ossy/workspaces/actions/get"},r={id:"@ossy/workspaces/actions/get"},c={id:"@ossy/workspaces/actions/create"},u={id:"@ossy/workspaces/actions/import-schemas"},d={id:"@ossy/workspaces/actions/get-schemas"},h={id:"@ossy/workspaces/actions/create-api-token"},p={id:"@ossy/workspaces/actions/get-api-tokens"},l={id:"@ossy/workspaces/actions/invite-user"},y={id:"@ossy/workspaces/actions/enable-service"},k={id:"@ossy/workspaces/actions/disable-service"},g={id:"@ossy/workspaces/actions/get-users"},v={id:"@ossy/users/actions/join-workspace"},w={id:"@ossy/workspaces/actions/remove-member"},f={id:"@ossy/authentication/actions/get-current-user"},m={id:"@ossy/users/actions/get-current-user-history"},b={id:"@ossy/users/actions/update-details"},z={id:"@ossy/resources/actions/create",payload:{type:"directory"}},j={id:"@ossy/resources/actions/create"},$={id:"@ossy/resources/actions/create"},I={id:"@ossy/resources/actions/upload-named-version"},O={id:"@ossy/resources/actions/get"},T={id:"@ossy/resources/actions/list"},R={id:"@ossy/resources/actions/search"},S={id:"@ossy/resources/actions/update-access"},U={id:"@ossy/resources/actions/delete"},E={id:"@ossy/resources/actions/update-content"},P={id:"@ossy/resources/actions/update-location"},C={id:"@ossy/resources/actions/update-name"},A={id:"@ossy/authentication/actions/sign-up"},G={id:"@ossy/authentication/actions/request-sign-in"},H={id:"@ossy/authentication/actions/verify-sign-in"},W={id:"@ossy/workspaces/actions/accept-invitation"},q={id:"@ossy/authentication/actions/get-current-user"},D={id:"@ossy/users/actions/get-current-user-history"},N={id:"@ossy/authentication/actions/get-current-user"},J={id:"@ossy/authentication/actions/sign-out"};function L(s){const e="string"==typeof s?s:s.id;return e.includes(".")?e.replace(/\./g,"/"):e}function V(s){return!s||"/@ossy"===s||s.endsWith("/@ossy")||"/api/v0"===s}function x(s){const e=(null!=s?s:"").replace(/\/$/,"");if(V(e))return"/actions";const t=e.replace(/\/api\/v0\/?$/,"").replace(/\/$/,"");return t?`${t}/actions`:"/actions"}function F(s){const e=(null!=s?s:"").replace(/\/$/,"");if(V(e))return"/events";const t=e.replace(/\/api\/v0\/?$/,"").replace(/\/$/,"");return t?`${t}/events`:"/events"}function M(s,e){var t,o;const i=null!==(t=s.method)&&void 0!==t?t:"GET";if("GET"!==i&&"HEAD"!==i)return!0;const n=new Set(null!==(o=s.query)&&void 0!==o?o:[]);return Object.entries(null!=e?e:{}).every(([s,e])=>null==e||""===e||n.has(s))}function Q(s,e,t){var o,i;const n=e.path.startsWith("/")?e.path:`/${e.path}`,a=(null!=s?s:"").replace(/\/$/,"");let r;r=!a||"/@ossy"===a||a.endsWith("/@ossy")?`/@ossy${n.replace(/^\/api\/v0/,"")}`:a.includes("/api/v0")?`${a.replace(/\/api\/v0\/?$/,"")}${n}`:`${a}${n}`;const c=null!==(o=e.method)&&void 0!==o?o:"GET";if("GET"===c||"HEAD"===c){const s=new URLSearchParams;for(const o of null!==(i=e.query)&&void 0!==i?i:[]){const e=null==t?void 0:t[o];null!=e&&""!==e&&s.set(o,String(e))}const o=s.toString();o&&(r+=`${r.includes("?")?"&":"?"}${o}`)}return r}function B(s){if(!s||"string"!=typeof s)return!1;try{const e=new URL(s,"http://localhost");return"/local-storage"===e.pathname||e.pathname.endsWith("/local-storage")}catch(s){return!1}}class K{static of(s){return new K(s)}constructor(s){this.baseUrl="/api/v0",this.actionRoutes={},this.invoke=(s,e)=>this.invokePlatformAction(L(s),e),this.invokePlatformAction=(s,e)=>{const t=this.actionRoutes[s];if(t&&M(t,e))return this.invokeHttpRoute(t,e);const o={method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({action:s,payload:null!=e?e:{}})};this.workspaceId&&(o.headers=Object.assign(Object.assign({},o.headers),{workspaceId:this.workspaceId})),this.authorization&&(o.headers=Object.assign(Object.assign({},o.headers),{Authorization:this.authorization}));const i=x(this.baseUrl);return fetch(i,o).then(this.handleResponse)},this.invokeHttpRoute=(s,e)=>{var t;const o=null!==(t=s.method)&&void 0!==t?t:"GET",i=Q(this.baseUrl,s,e),n={method:o,credentials:"include",headers:{}};return this.workspaceId&&(n.headers=Object.assign(Object.assign({},n.headers),{workspaceId:this.workspaceId})),this.authorization&&(n.headers=Object.assign(Object.assign({},n.headers),{Authorization:this.authorization})),"GET"!==o&&"HEAD"!==o&&(n.headers=Object.assign(Object.assign({},n.headers),{"Content-Type":"application/json"}),n.body=JSON.stringify(null!=e?e:{})),fetch(i,n).then(this.handleResponse)},this.handleResponse=s=>{const e=s.headers.get("Content-Type")||"",t=s.status;return 400===t?s.json().then(s=>Promise.reject(s.error)):[200,204].includes(t)?e.includes("application/json")?s.json():s:Promise.reject(s)},this.subscribePush=s=>{if("undefined"==typeof EventSource)return()=>{};const e=F(this.baseUrl),t=new EventSource(e,{withCredentials:!0});return t.onmessage=e=>{try{const t=JSON.parse(e.data);s.onMessage(t)}catch(s){}},t.onerror=e=>{var t;null===(t=s.onError)||void 0===t||t.call(s,e)},()=>{t.close()}},this.updateConfig(s)}updateConfig(s){var e;this.baseUrl=s.apiUrl||this.baseUrl,this.workspaceId=s.workspaceId||this.workspaceId,this.authorization=s.authorization||this.authorization,this.actionRoutes=null!==(e=s.actionRoutes)&&void 0!==e?e:this.actionRoutes}get resources(){return{upload:({location:s="/",file:e})=>{const t={type:e.type,location:s,name:e.name,size:e.size};return this.invoke($,t).then(s=>{const t=s.content.uploadUrl,o={};e.type&&(o["Content-Type"]=e.type);const i={method:"PUT",headers:o,body:e};return B(t)&&(i.credentials="include",this.workspaceId&&(o.workspaceId=this.workspaceId),this.authorization&&(o.Authorization=this.authorization)),fetch(t,i).then(()=>s)})},list:s=>{const e=new URLSearchParams(Object.assign({},s)).toString();return this.invoke(T,{search:e})},uploadNamedVersion:({id:s,name:e,file:t})=>{const o={id:s,namedVersion:e,name:e,size:t.size,type:t.type};return this.invoke(I,o).then(s=>{const e=s.content.uploadUrl,o={};t.type&&(o["Content-Type"]=t.type);const i={method:"PUT",headers:o,body:t};return B(e)&&(i.credentials="include",this.workspaceId&&(o.workspaceId=this.workspaceId),this.authorization&&(o.Authorization=this.authorization)),fetch(e,i).then(()=>s)})}}}}export{o as ApiTokenCreate,t as ApiTokenGetAll,i as ApiTokenInvalidate,q as AuthGetAuthenticatedUser,D as AuthGetAuthenticatedUserHistory,N as AuthGetUser,G as AuthSignIn,J as AuthSignOff,A as AuthSignUp,W as AuthVerifyInvitation,H as AuthVerifySignIn,S as ResourceUpdateAccess,j as ResourcesCreate,z as ResourcesCreateDirectory,O as ResourcesGet,T as ResourcesList,P as ResourcesMove,U as ResourcesRemove,C as ResourcesRename,R as ResourcesSearch,E as ResourcesUpdateContent,$ as ResourcesUpload,I as ResourcesUploadNamedVersion,K as SDK,e as TaskStatus,s as Tasks,f as UserCurrentGet,m as UserCurrentGetHistory,b as UserCurrentUpdate,v as WorkspacesAddMember,c as WorkspacesCreate,h as WorkspacesCreateApiToken,k as WorkspacesDisableService,y as WorkspacesEnableService,r as WorkspacesGet,p as WorkspacesGetApiTokens,a as WorkspacesGetCurrent,d as WorkspacesGetSchemas,g as WorkspacesGetUsers,u as WorkspacesImportSchemas,l as WorkspacesInviteUser,n as WorkspacesList,w as WorkspacesRemoveMember,M as canUseHttpActionRoute,B as isLocalStorageUploadUrl,L as resolveActionId,x as resolveActionsUrl,F as resolveEventsUrl,Q as resolveHttpActionUrl};
|
package/build/types/Actions.d.ts
CHANGED
|
@@ -116,7 +116,9 @@ export declare const AuthSignIn: PlatformAction<{
|
|
|
116
116
|
email: string;
|
|
117
117
|
}>;
|
|
118
118
|
export declare const AuthVerifySignIn: PlatformAction<{
|
|
119
|
-
token
|
|
119
|
+
token?: string;
|
|
120
|
+
email?: string;
|
|
121
|
+
code?: string;
|
|
120
122
|
}>;
|
|
121
123
|
export declare const AuthVerifyInvitation: PlatformAction<{
|
|
122
124
|
workspaceId: string;
|
|
@@ -3,10 +3,6 @@ export type ActionRef = Action | string;
|
|
|
3
3
|
export declare function isActionRef(value: unknown): value is ActionRef;
|
|
4
4
|
/** Normalize dot aliases to slash ids (`@ossy.booking.actions.create` → `@ossy/booking/actions/create`). */
|
|
5
5
|
export declare function resolveActionId(action: ActionRef): string;
|
|
6
|
-
/**
|
|
7
|
-
* Resolve POST /actions URL from an SDK `apiUrl`.
|
|
8
|
-
* Actions are served at the app server root, not under `/api/v0` or the `/@ossy` proxy prefix.
|
|
9
|
-
*/
|
|
10
6
|
export declare function resolveActionsUrl(apiUrl: string): string;
|
|
11
7
|
/** Resolve GET /events SSE URL from an SDK `apiUrl`. */
|
|
12
8
|
export declare function resolveEventsUrl(apiUrl: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import type { ActionRoute } from '@ossy/types';
|
|
2
|
+
/**
|
|
3
|
+
* Whether `sdk.invoke` can send `payload` through a manifest HTTP action route.
|
|
4
|
+
*
|
|
5
|
+
* GET/HEAD adapters only forward `route.query` keys. Extra payload fields (e.g.
|
|
6
|
+
* `{ email, code }` on verify-sign-in while the adapter only accepts `token`)
|
|
7
|
+
* must use `POST /actions` instead — otherwise the adapter drops them and auth
|
|
8
|
+
* fails silently from the caller's perspective (#751).
|
|
9
|
+
*/
|
|
10
|
+
export declare function canUseHttpActionRoute(route: ActionRoute, payload?: Record<string, unknown>): boolean;
|
|
2
11
|
/**
|
|
3
12
|
* Resolve a manifest HTTP action route to a fetch URL.
|
|
4
|
-
* Supports same-origin `/@ossy` proxy
|
|
13
|
+
* Supports same-origin `/api/v0` (and the legacy `/@ossy` proxy) plus absolute `/api/v0` bases.
|
|
5
14
|
*/
|
|
6
15
|
export declare function resolveHttpActionUrl(apiUrl: string, route: ActionRoute, payload?: Record<string, unknown>): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/build/types/sdk.d.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { SDKConfig } from './config';
|
|
2
2
|
import { resolveActionId, resolveActionsUrl, resolveEventsUrl, type ActionRef } from './platform-action';
|
|
3
|
-
import { resolveHttpActionUrl } from './resolve-http-action-url';
|
|
3
|
+
import { canUseHttpActionRoute, resolveHttpActionUrl } from './resolve-http-action-url';
|
|
4
4
|
import type { ActionRoute } from '@ossy/types';
|
|
5
|
-
export { resolveActionId, resolveActionsUrl, resolveEventsUrl, resolveHttpActionUrl, type ActionRef };
|
|
5
|
+
export { resolveActionId, resolveActionsUrl, resolveEventsUrl, canUseHttpActionRoute, resolveHttpActionUrl, type ActionRef, };
|
|
6
6
|
export type { ActionRoute };
|
|
7
7
|
/** Detect filesystem-backend upload URLs; S3 URLs must not get session headers. */
|
|
8
8
|
export declare function isLocalStorageUploadUrl(url: string): boolean;
|
|
9
9
|
export declare class SDK {
|
|
10
10
|
workspaceId?: string;
|
|
11
11
|
authorization?: string;
|
|
12
|
+
/** Same-origin REST prefix. `POST /actions` is resolved separately (not under this path). */
|
|
12
13
|
baseUrl: string;
|
|
13
14
|
actionRoutes: Record<string, ActionRoute>;
|
|
14
15
|
static of(config: SDKConfig): SDK;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/sdk",
|
|
3
3
|
"description": "Software Development Kit for interacting with our services",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.12.0",
|
|
5
5
|
"url": "git://github.com/ossy-se/packages/sdk",
|
|
6
6
|
"source": "src/public.index.ts",
|
|
7
7
|
"main": "build/public.index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"scripts": {
|
|
27
27
|
"start": "",
|
|
28
28
|
"build": "rollup -c rollup.config.js",
|
|
29
|
-
"test": ""
|
|
29
|
+
"test": "node --test --experimental-strip-types src/*.spec.js"
|
|
30
30
|
},
|
|
31
31
|
"publishConfig": {
|
|
32
32
|
"access": "public",
|
|
@@ -36,5 +36,5 @@
|
|
|
36
36
|
"/build",
|
|
37
37
|
"README.md"
|
|
38
38
|
],
|
|
39
|
-
"gitHead": "
|
|
39
|
+
"gitHead": "c2650803219ad1831559b512848358d9550ffad2"
|
|
40
40
|
}
|