@ossy/sdk 3.11.1 → 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 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: 'https://api.ossy.se/api/v0', // optional, this is the default
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://api.ossy.se/api/v0`) |
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
@@ -5,10 +5,6 @@ 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;
@@ -24,7 +20,7 @@ declare function resolveEventsUrl(apiUrl: string): string;
24
20
  declare function canUseHttpActionRoute(route: ActionRoute, payload?: Record<string, unknown>): boolean;
25
21
  /**
26
22
  * Resolve a manifest HTTP action route to a fetch URL.
27
- * Supports same-origin `/@ossy` proxy and absolute `/api/v0` bases.
23
+ * Supports same-origin `/api/v0` (and the legacy `/@ossy` proxy) plus absolute `/api/v0` bases.
28
24
  */
29
25
  declare function resolveHttpActionUrl(apiUrl: string, route: ActionRoute, payload?: Record<string, unknown>): string;
30
26
 
@@ -33,6 +29,7 @@ declare function isLocalStorageUploadUrl(url: string): boolean;
33
29
  declare class SDK {
34
30
  workspaceId?: string;
35
31
  authorization?: string;
32
+ /** Same-origin REST prefix. `POST /actions` is resolved separately (not under this path). */
36
33
  baseUrl: string;
37
34
  actionRoutes: Record<string, ActionRoute>;
38
35
  static of(config: SDKConfig): SDK;
@@ -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"},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"},W={id:"@ossy/authentication/actions/verify-sign-in"},H={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){const e=(null!=s?s:"").replace(/\/$/,"");if(!e||"/@ossy"===e||e.endsWith("/@ossy"))return"/actions";const t=e.replace(/\/api\/v0\/?$/,"").replace(/\/$/,"");return t?`${t}/actions`:"/actions"}function x(s){const e=(null!=s?s:"").replace(/\/$/,"");if(!e||"/@ossy"===e||e.endsWith("/@ossy"))return"/events";const t=e.replace(/\/api\/v0\/?$/,"").replace(/\/$/,"");return t?`${t}/events`:"/events"}function F(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 M(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 Q(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 B{static of(s){return new B(s)}constructor(s){this.baseUrl="https://api.ossy.se/api/v0",this.actionRoutes={},this.invoke=(s,e)=>this.invokePlatformAction(L(s),e),this.invokePlatformAction=(s,e)=>{const t=this.actionRoutes[s];if(t&&F(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=V(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=M(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=x(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 Q(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 Q(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,H as AuthVerifyInvitation,W 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,B 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,F as canUseHttpActionRoute,Q as isLocalStorageUploadUrl,L as resolveActionId,V as resolveActionsUrl,x as resolveEventsUrl,M as resolveHttpActionUrl};
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};
@@ -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 {};
@@ -10,6 +10,6 @@ import type { ActionRoute } from '@ossy/types';
10
10
  export declare function canUseHttpActionRoute(route: ActionRoute, payload?: Record<string, unknown>): boolean;
11
11
  /**
12
12
  * Resolve a manifest HTTP action route to a fetch URL.
13
- * Supports same-origin `/@ossy` proxy and absolute `/api/v0` bases.
13
+ * Supports same-origin `/api/v0` (and the legacy `/@ossy` proxy) plus absolute `/api/v0` bases.
14
14
  */
15
15
  export declare function resolveHttpActionUrl(apiUrl: string, route: ActionRoute, payload?: Record<string, unknown>): string;
@@ -9,6 +9,7 @@ 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.11.1",
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",
@@ -36,5 +36,5 @@
36
36
  "/build",
37
37
  "README.md"
38
38
  ],
39
- "gitHead": "f279de3535b6bc76631cbc6b943dae8f92a1565f"
39
+ "gitHead": "c2650803219ad1831559b512848358d9550ffad2"
40
40
  }