@prisme.ai/sdk 1.0.0 → 1.0.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 +3 -1
- package/dist/_virtual/_tslib.js +17 -8
- package/dist/lib/ImportProcessing.d.ts +19 -0
- package/dist/lib/WorkspacesEndpoint.d.ts +10 -0
- package/dist/lib/api.d.ts +88 -29
- package/dist/lib/endpoints/users.d.ts +13 -0
- package/dist/lib/endpoints/workspaces.d.ts +16 -0
- package/dist/lib/endpoints/workspacesVersions.d.ts +2 -2
- package/dist/lib/events.d.ts +5 -1
- package/dist/lib/fetch.d.ts +2 -0
- package/dist/lib/fetcher.d.ts +11 -3
- package/dist/lib/interpolate.d.ts +2 -0
- package/dist/lib/interpolate.test.d.ts +1 -0
- package/dist/lib/interpolateVars.d.ts +2 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/sdk/lib/ImportProcessing.js +17 -0
- package/dist/sdk/lib/WorkspacesEndpoint.js +19 -0
- package/dist/sdk/lib/api.js +248 -62
- package/dist/sdk/lib/endpoints/users.js +94 -0
- package/dist/sdk/lib/endpoints/workspaces.js +121 -0
- package/dist/sdk/lib/endpoints/workspacesVersions.js +4 -4
- package/dist/sdk/lib/events.js +51 -13
- package/dist/sdk/lib/fetch.js +12 -0
- package/dist/sdk/lib/fetcher.js +98 -42
- package/dist/sdk/lib/interpolate.js +26 -0
- package/dist/sdk/lib/interpolateVars.js +26 -0
- package/dist/sdk/lib/utils.js +48 -1
- package/lib/ImportProcessing.ts +22 -0
- package/lib/api.test.ts +90 -60
- package/lib/api.ts +318 -79
- package/lib/endpoints/users.ts +58 -0
- package/lib/endpoints/workspaces.ts +103 -0
- package/lib/endpoints/workspacesVersions.ts +13 -9
- package/lib/events.test.ts +2 -2
- package/lib/events.ts +60 -14
- package/lib/fetcher.ts +77 -12
- package/lib/utils.ts +39 -0
- package/package.json +3 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Api } from '../api';
|
|
2
|
+
import { dataURItoBlob, isDataURL } from '../utils';
|
|
3
|
+
|
|
4
|
+
export class UsersEndpoint {
|
|
5
|
+
private id: string;
|
|
6
|
+
private api: Api;
|
|
7
|
+
|
|
8
|
+
constructor(id: string, api: Api) {
|
|
9
|
+
this.id = id;
|
|
10
|
+
this.api = api;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async update(
|
|
14
|
+
data: Partial<Prismeai.User>
|
|
15
|
+
): Promise<PrismeaiAPI.PatchUser.Responses.$200> {
|
|
16
|
+
if (isDataURL(data.photo)) {
|
|
17
|
+
await this.updatePhoto(data.photo);
|
|
18
|
+
delete data.photo;
|
|
19
|
+
}
|
|
20
|
+
return await this.api.patch('/user', data);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async updatePhoto(
|
|
24
|
+
photo: string
|
|
25
|
+
): Promise<PrismeaiAPI.PostUserPhoto.Responses.$200> {
|
|
26
|
+
if (!isDataURL(photo)) {
|
|
27
|
+
throw new Error('Photo must be a dataurl file');
|
|
28
|
+
}
|
|
29
|
+
const formData = new FormData();
|
|
30
|
+
formData.append('photo', ...dataURItoBlob(photo));
|
|
31
|
+
|
|
32
|
+
return await this.api.post<PrismeaiAPI.PostUserPhoto.Responses.$200>(
|
|
33
|
+
`/user/photo`,
|
|
34
|
+
formData
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async setMeta(
|
|
39
|
+
k: string,
|
|
40
|
+
v: any
|
|
41
|
+
): Promise<PrismeaiAPI.SetMeta.Responses.$200> {
|
|
42
|
+
return await this.api.post(`/user/meta`, {
|
|
43
|
+
[k]: v,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
async deleteMeta(k: string): Promise<PrismeaiAPI.DeleteMeta.Responses.$200> {
|
|
47
|
+
return await this.api.delete(`/user/meta/${k}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async sendDeleteValidation() {
|
|
51
|
+
return await this.api.delete(`/user`);
|
|
52
|
+
}
|
|
53
|
+
async delete(token?: string) {
|
|
54
|
+
return await this.api.delete(`/users/${this.id}?token=${token}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default UsersEndpoint;
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { Api } from '../api';
|
|
2
|
+
import { Fetched } from '../fetcher';
|
|
3
|
+
import {
|
|
4
|
+
ImportProcessing,
|
|
5
|
+
ImportProcessingError,
|
|
6
|
+
ImportSuccess,
|
|
7
|
+
} from '../ImportProcessing';
|
|
8
|
+
import { dataURItoBlob, removedUndefinedProperties } from '../utils';
|
|
2
9
|
import WorkspacesVersionsEndpoint from './workspacesVersions';
|
|
3
10
|
|
|
4
11
|
export class WorkspacesEndpoint {
|
|
@@ -13,6 +20,102 @@ export class WorkspacesEndpoint {
|
|
|
13
20
|
get versions() {
|
|
14
21
|
return new WorkspacesVersionsEndpoint(this.id, this.api);
|
|
15
22
|
}
|
|
23
|
+
|
|
24
|
+
async update(
|
|
25
|
+
workspace: Prismeai.DSULPatch
|
|
26
|
+
): Promise<Fetched<PrismeaiAPI.UpdateWorkspace.Responses.$200> | null> {
|
|
27
|
+
return await this.api.patch(
|
|
28
|
+
`/workspaces/${this.id}`,
|
|
29
|
+
await this.api.replaceAllImagesData(workspace, this.id)
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async delete(): Promise<PrismeaiAPI.DeleteWorkspace.Responses.$200> {
|
|
34
|
+
return await this.api.delete(`/workspaces/${this.id}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async uploadFiles(
|
|
38
|
+
files: string | string[],
|
|
39
|
+
opts?: {
|
|
40
|
+
expiresAfter?: number;
|
|
41
|
+
public?: boolean;
|
|
42
|
+
shareToken?: boolean;
|
|
43
|
+
}
|
|
44
|
+
) {
|
|
45
|
+
const formData = new FormData();
|
|
46
|
+
(Array.isArray(files) ? files : [files]).forEach((file) => {
|
|
47
|
+
try {
|
|
48
|
+
formData.append('file', ...dataURItoBlob(file));
|
|
49
|
+
} catch {}
|
|
50
|
+
});
|
|
51
|
+
if (opts?.expiresAfter) {
|
|
52
|
+
formData.append('expiresAfter', `${opts?.expiresAfter}`);
|
|
53
|
+
}
|
|
54
|
+
if (typeof opts?.public === 'boolean') {
|
|
55
|
+
formData.append('public', `${opts?.public}`);
|
|
56
|
+
}
|
|
57
|
+
if (typeof opts?.shareToken === 'boolean') {
|
|
58
|
+
formData.append('shareToken', `${opts?.shareToken}`);
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
return await this.api.post<PrismeaiAPI.UploadFile.Responses.$200>(
|
|
62
|
+
`/workspaces/${this.id}/files`,
|
|
63
|
+
formData
|
|
64
|
+
);
|
|
65
|
+
} catch (e) {
|
|
66
|
+
console.error(e);
|
|
67
|
+
}
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async listAppInstances(): Promise<
|
|
72
|
+
Fetched<PrismeaiAPI.ListAppInstances.Responses.$200>
|
|
73
|
+
> {
|
|
74
|
+
return await this.api.get(`/workspaces/${this.id}/apps`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async getUsage({
|
|
78
|
+
afterDate,
|
|
79
|
+
beforeDate,
|
|
80
|
+
details,
|
|
81
|
+
}: {
|
|
82
|
+
afterDate?: PrismeaiAPI.WorkspaceUsage.Parameters.AfterDate;
|
|
83
|
+
beforeDate?: PrismeaiAPI.WorkspaceUsage.Parameters.BeforeDate;
|
|
84
|
+
details?: PrismeaiAPI.WorkspaceUsage.Parameters.Details;
|
|
85
|
+
} = {}): Promise<Fetched<PrismeaiAPI.WorkspaceUsage.Responses.$200>> {
|
|
86
|
+
const params = new URLSearchParams(
|
|
87
|
+
removedUndefinedProperties(
|
|
88
|
+
{
|
|
89
|
+
afterDate: `${afterDate || ''}`,
|
|
90
|
+
beforeDate: `${beforeDate || ''}`,
|
|
91
|
+
details: `${details || ''}`,
|
|
92
|
+
},
|
|
93
|
+
true
|
|
94
|
+
)
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
return this.api.get(`/workspaces/${this.id}/usage?${params.toString()}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async importArchive(archive: File): Promise<ImportSuccess> {
|
|
101
|
+
return new Promise((resolve) => {
|
|
102
|
+
const fileReader = new FileReader();
|
|
103
|
+
fileReader.addEventListener('load', async ({ target }) => {
|
|
104
|
+
const file = target?.result as string;
|
|
105
|
+
const formData = new FormData();
|
|
106
|
+
formData.append('archive', ...dataURItoBlob(file));
|
|
107
|
+
const result = await this.api.post<ImportProcessing | ImportSuccess>(
|
|
108
|
+
`/workspaces/${this.id}/import`,
|
|
109
|
+
formData
|
|
110
|
+
);
|
|
111
|
+
if ((result as ImportProcessing).processing) {
|
|
112
|
+
throw new ImportProcessingError(result as ImportProcessing);
|
|
113
|
+
}
|
|
114
|
+
resolve(result as ImportSuccess);
|
|
115
|
+
});
|
|
116
|
+
fileReader.readAsDataURL(archive);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
16
119
|
}
|
|
17
120
|
|
|
18
121
|
export default WorkspacesEndpoint;
|
|
@@ -10,24 +10,28 @@ export class WorkspacesVersionsEndpoint {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
create(version?: PrismeaiAPI.PublishWorkspaceVersion.RequestBody) {
|
|
13
|
-
this.api.post(`/workspaces/${this.workspaceId}/versions`, version);
|
|
13
|
+
return this.api.post(`/workspaces/${this.workspaceId}/versions`, version);
|
|
14
14
|
}
|
|
15
15
|
rollback(
|
|
16
|
-
versionId: PrismeaiAPI.
|
|
16
|
+
versionId: PrismeaiAPI.PullWorkspaceVersion.PathParameters['versionId'],
|
|
17
|
+
opts?: PrismeaiAPI.PullWorkspaceVersion.RequestBody
|
|
17
18
|
) {
|
|
18
|
-
this.api.post(
|
|
19
|
-
`/workspaces/${this.workspaceId}/versions/${versionId}/
|
|
19
|
+
return this.api.post(
|
|
20
|
+
`/workspaces/${this.workspaceId}/versions/${versionId}/pull`,
|
|
21
|
+
opts
|
|
20
22
|
);
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
async export(
|
|
25
|
+
async export(
|
|
26
|
+
version: PrismeaiAPI.ExportWorkspaceVersion.Parameters.VersionId = 'current'
|
|
27
|
+
) {
|
|
24
28
|
const res = await this.api.prepareRequest(
|
|
25
|
-
`/workspaces/${this.workspaceId}/versions/${version}/export`,
|
|
26
|
-
|
|
29
|
+
`/workspaces/${this.workspaceId}/versions/${version}/export`,
|
|
30
|
+
{
|
|
31
|
+
method: 'post',
|
|
27
32
|
}
|
|
28
|
-
)
|
|
33
|
+
);
|
|
29
34
|
return new Blob([await res.arrayBuffer()], { type: 'application/zip' });
|
|
30
|
-
|
|
31
35
|
}
|
|
32
36
|
}
|
|
33
37
|
|
package/lib/events.test.ts
CHANGED
|
@@ -21,7 +21,7 @@ it('should connect to Websocket', () => {
|
|
|
21
21
|
`https://api.eda.prisme.ai/workspaces/1/events`,
|
|
22
22
|
{
|
|
23
23
|
extraHeaders: {
|
|
24
|
-
|
|
24
|
+
Authorization: 'Bearer abcde',
|
|
25
25
|
},
|
|
26
26
|
withCredentials: true,
|
|
27
27
|
}
|
|
@@ -39,7 +39,7 @@ it('should connect to Websocket with apiKey', () => {
|
|
|
39
39
|
`https://api.eda.prisme.ai/workspaces/1/events`,
|
|
40
40
|
{
|
|
41
41
|
extraHeaders: {
|
|
42
|
-
|
|
42
|
+
Authorization: 'Bearer abcde',
|
|
43
43
|
'x-prismeai-api-key': 'fghij',
|
|
44
44
|
},
|
|
45
45
|
withCredentials: true,
|
package/lib/events.ts
CHANGED
|
@@ -20,46 +20,81 @@ export class Events {
|
|
|
20
20
|
private listenedUserTopics: Map<string, string[]>;
|
|
21
21
|
private listeners: Map<string, Function[]> = new Map();
|
|
22
22
|
private lastReceivedEventDate: Date;
|
|
23
|
+
private socketId?: string;
|
|
23
24
|
|
|
24
25
|
constructor({
|
|
25
26
|
workspaceId,
|
|
26
27
|
token,
|
|
28
|
+
legacyToken,
|
|
27
29
|
apiKey,
|
|
28
30
|
apiHost = 'https://api.eda.prisme.ai',
|
|
29
31
|
filters,
|
|
30
32
|
api,
|
|
33
|
+
transports,
|
|
31
34
|
}: {
|
|
32
35
|
workspaceId: string;
|
|
33
36
|
token: string;
|
|
37
|
+
legacyToken?: string;
|
|
34
38
|
apiKey?: string;
|
|
35
39
|
apiHost?: string;
|
|
36
40
|
filters?: Record<string, any>;
|
|
37
41
|
api: Api;
|
|
42
|
+
transports?: string[];
|
|
38
43
|
}) {
|
|
39
44
|
this.workspaceId = workspaceId;
|
|
40
45
|
const queryString = new URLSearchParams(filters || {}).toString();
|
|
41
46
|
const fullQueryString = queryString ? `?${queryString}` : '';
|
|
42
|
-
const extraHeaders: any =
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
const extraHeaders: any = token
|
|
48
|
+
? {
|
|
49
|
+
authorization: `Bearer ${token}`,
|
|
50
|
+
}
|
|
51
|
+
: { 'x-prismeai-token': legacyToken };
|
|
45
52
|
this.lastReceivedEventDate = new Date();
|
|
46
53
|
if (apiKey) {
|
|
47
54
|
extraHeaders['x-prismeai-api-key'] = apiKey;
|
|
48
55
|
}
|
|
56
|
+
this.filters = [filters || {}];
|
|
57
|
+
this.listenedUserTopics = new Map();
|
|
49
58
|
|
|
50
59
|
this.client = io(
|
|
51
60
|
`${apiHost}/workspaces/${workspaceId}/events${fullQueryString}`,
|
|
52
61
|
{
|
|
53
62
|
extraHeaders,
|
|
54
|
-
withCredentials:
|
|
63
|
+
withCredentials: !extraHeaders.authorization,
|
|
64
|
+
transports: transports || ['polling', 'websocket'],
|
|
65
|
+
auth: (cb) => {
|
|
66
|
+
cb({
|
|
67
|
+
// Browser websockets cannot send extraHeaders, so we use socketio-client auth instead
|
|
68
|
+
extraHeaders:
|
|
69
|
+
transports && transports[0] === 'websocket' ? extraHeaders : {},
|
|
70
|
+
|
|
71
|
+
filters: {
|
|
72
|
+
payloadQuery: this.filters,
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
reuseSocketId: this.socketId,
|
|
76
|
+
});
|
|
77
|
+
},
|
|
55
78
|
}
|
|
56
79
|
);
|
|
57
80
|
|
|
81
|
+
this.client.on('connect_error', (err) => {
|
|
82
|
+
console.error(`Failed websocket connection : `, err);
|
|
83
|
+
// revert to classic upgrade
|
|
84
|
+
this.client.io.opts.transports = ['polling', 'websocket'];
|
|
85
|
+
});
|
|
86
|
+
this.client.on('error', (err) => {
|
|
87
|
+
this.client.io.opts.transports = ['polling', 'websocket'];
|
|
88
|
+
});
|
|
89
|
+
|
|
58
90
|
const onConnect = () => {
|
|
59
|
-
//
|
|
60
|
-
this.
|
|
61
|
-
|
|
62
|
-
|
|
91
|
+
// First connection
|
|
92
|
+
if (!this.socketId) {
|
|
93
|
+
this.socketId = this.client.id;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Retrieve lost history on reconnection
|
|
63
98
|
setTimeout(async () => {
|
|
64
99
|
const events = await api.getEvents(workspaceId, {
|
|
65
100
|
...this.filters[this.filters.length - 1],
|
|
@@ -72,16 +107,23 @@ export class Events {
|
|
|
72
107
|
});
|
|
73
108
|
}, 2000);
|
|
74
109
|
};
|
|
110
|
+
this.client.on('connect', onConnect);
|
|
111
|
+
|
|
75
112
|
this.client.on('disconnect', () => {
|
|
76
113
|
if (!this.lastReceivedEventDate) {
|
|
77
114
|
this.lastReceivedEventDate = new Date();
|
|
78
115
|
}
|
|
116
|
+
// Make sure we reconnect with current filters & socketId
|
|
117
|
+
this.client.auth = {
|
|
118
|
+
...this.client.auth,
|
|
119
|
+
filters: {
|
|
120
|
+
payloadQuery: this.filters,
|
|
121
|
+
},
|
|
122
|
+
reuseSocketId: this.socketId,
|
|
123
|
+
};
|
|
79
124
|
this.client.off('connect', onConnect);
|
|
80
125
|
this.client.on('connect', onConnect);
|
|
81
126
|
});
|
|
82
|
-
|
|
83
|
-
this.filters = [filters || {}];
|
|
84
|
-
this.listenedUserTopics = new Map();
|
|
85
127
|
}
|
|
86
128
|
|
|
87
129
|
get socket() {
|
|
@@ -101,12 +143,16 @@ export class Events {
|
|
|
101
143
|
}
|
|
102
144
|
|
|
103
145
|
all(listener: (eventName: string, eventData: Prismeai.PrismeEvent) => void) {
|
|
104
|
-
|
|
146
|
+
const anyListener = (
|
|
147
|
+
eventName: string,
|
|
148
|
+
eventData: Prismeai.PrismeEvent
|
|
149
|
+
) => {
|
|
105
150
|
this.lastReceivedEventDate = new Date(eventData?.createdAt);
|
|
106
151
|
return listener(eventName, eventData);
|
|
107
|
-
}
|
|
152
|
+
};
|
|
153
|
+
this.client.onAny(anyListener);
|
|
108
154
|
|
|
109
|
-
return () => this.client.offAny(
|
|
155
|
+
return () => this.client.offAny(anyListener);
|
|
110
156
|
}
|
|
111
157
|
|
|
112
158
|
on(ev: string, listener: (eventData: Prismeai.PrismeEvent) => void) {
|
package/lib/fetcher.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import FormData from 'form-data';
|
|
1
2
|
import ApiError from './ApiError';
|
|
2
3
|
import HTTPError from './HTTPError';
|
|
4
|
+
import { wait } from './utils';
|
|
3
5
|
|
|
4
6
|
const headersAsObject = (headers: Headers) =>
|
|
5
7
|
Array.from(headers).reduce(
|
|
@@ -10,36 +12,61 @@ const headersAsObject = (headers: Headers) =>
|
|
|
10
12
|
{}
|
|
11
13
|
);
|
|
12
14
|
|
|
15
|
+
export type Fetched<T> = T & {
|
|
16
|
+
headers?: Record<string, any>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function isFormData(data: any): data is FormData {
|
|
20
|
+
return !!(data.append && typeof data.append === 'function');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const CSRF_TOKEN_HEADER = 'x-prismeai-csrf-token';
|
|
24
|
+
|
|
13
25
|
export class Fetcher {
|
|
14
26
|
public host: string;
|
|
15
27
|
public token: string | null = null;
|
|
28
|
+
public legacyToken: string | null = null;
|
|
29
|
+
public overwriteClientId?: string;
|
|
30
|
+
private clientIdHeader?: string;
|
|
16
31
|
protected _apiKey: string | null = null;
|
|
32
|
+
protected _csrfToken: string | null = null;
|
|
17
33
|
public language: string | undefined;
|
|
34
|
+
public lastReceivedHeaders?: Record<string, any>;
|
|
18
35
|
|
|
19
|
-
constructor(host: string) {
|
|
36
|
+
constructor(host: string, clientIdHeader?: string) {
|
|
20
37
|
this.host = host;
|
|
38
|
+
this.clientIdHeader = clientIdHeader;
|
|
21
39
|
}
|
|
22
40
|
|
|
23
41
|
set apiKey(apiKey: string) {
|
|
24
42
|
this._apiKey = apiKey;
|
|
25
43
|
}
|
|
26
44
|
|
|
27
|
-
prepareRequest(url: string,
|
|
28
|
-
options: RequestInit = {}) {
|
|
45
|
+
prepareRequest(url: string, options: RequestInit = {}) {
|
|
29
46
|
const headers = new Headers(options.headers || {});
|
|
30
47
|
|
|
31
|
-
if (this.token && !headers.has('
|
|
32
|
-
headers.append('
|
|
48
|
+
if (this.token && !headers.has('Authorization')) {
|
|
49
|
+
headers.append('Authorization', `Bearer ${this.token}`);
|
|
50
|
+
} else if (this.legacyToken && !headers.has('Authorization')) {
|
|
51
|
+
headers.append('x-prismeai-token', this.legacyToken);
|
|
33
52
|
}
|
|
34
53
|
|
|
35
54
|
if (this._apiKey && !headers.has('x-prismeai-apikey')) {
|
|
36
55
|
headers.append('x-prismeai-api-key', this._apiKey);
|
|
37
56
|
}
|
|
38
57
|
|
|
58
|
+
if (this._csrfToken && options.method && options.method !== 'GET') {
|
|
59
|
+
headers.append(CSRF_TOKEN_HEADER, this._csrfToken);
|
|
60
|
+
}
|
|
61
|
+
|
|
39
62
|
if (this.language) {
|
|
40
63
|
headers.append('accept-language', this.language);
|
|
41
64
|
}
|
|
42
65
|
|
|
66
|
+
if (options.body instanceof URLSearchParams) {
|
|
67
|
+
headers.set('Content-Type', 'application/x-www-form-urlencoded');
|
|
68
|
+
}
|
|
69
|
+
|
|
43
70
|
if (
|
|
44
71
|
(!options.body || !(options.body instanceof FormData)) &&
|
|
45
72
|
!headers.has('Content-Type')
|
|
@@ -47,9 +74,12 @@ export class Fetcher {
|
|
|
47
74
|
headers.append('Content-Type', 'application/json');
|
|
48
75
|
}
|
|
49
76
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
77
|
+
const fullUrl =
|
|
78
|
+
url.startsWith('http://') || url.startsWith('https://')
|
|
79
|
+
? url
|
|
80
|
+
: `${this.host}${url}`;
|
|
81
|
+
return global.fetch(fullUrl, {
|
|
82
|
+
credentials: headers.has('Authorization') ? 'omit' : 'include',
|
|
53
83
|
...options,
|
|
54
84
|
headers,
|
|
55
85
|
});
|
|
@@ -57,11 +87,42 @@ export class Fetcher {
|
|
|
57
87
|
|
|
58
88
|
protected async _fetch<T>(
|
|
59
89
|
url: string,
|
|
60
|
-
options: RequestInit = {}
|
|
90
|
+
options: RequestInit = {},
|
|
91
|
+
maxRetries: number = 3
|
|
61
92
|
): Promise<T> {
|
|
62
|
-
|
|
93
|
+
let res: Response;
|
|
94
|
+
try {
|
|
95
|
+
res = await this.prepareRequest(url, options);
|
|
96
|
+
} catch (e: any) {
|
|
97
|
+
if (maxRetries > 0 && e.message === 'Load failed') {
|
|
98
|
+
await wait(20);
|
|
99
|
+
return this._fetch(url, options, --maxRetries);
|
|
100
|
+
}
|
|
101
|
+
throw e;
|
|
102
|
+
}
|
|
103
|
+
if (options.redirect === 'manual' && res.status === 0) {
|
|
104
|
+
return { redirected: true } as T;
|
|
105
|
+
}
|
|
63
106
|
|
|
107
|
+
this.lastReceivedHeaders = headersAsObject(res.headers);
|
|
108
|
+
if (this.clientIdHeader && this.lastReceivedHeaders[this.clientIdHeader]) {
|
|
109
|
+
this.overwriteClientId = this.lastReceivedHeaders[this.clientIdHeader];
|
|
110
|
+
}
|
|
111
|
+
if (this.lastReceivedHeaders[CSRF_TOKEN_HEADER]) {
|
|
112
|
+
this._csrfToken = this.lastReceivedHeaders[CSRF_TOKEN_HEADER];
|
|
113
|
+
}
|
|
64
114
|
if (!res.ok) {
|
|
115
|
+
if (
|
|
116
|
+
res.statusText?.length &&
|
|
117
|
+
res.statusText.includes('ECONNRESET') &&
|
|
118
|
+
maxRetries > 0
|
|
119
|
+
) {
|
|
120
|
+
console.log(
|
|
121
|
+
`Retrying request towards ${url} as we received ECONNRESET error : ${res.statusText}`
|
|
122
|
+
);
|
|
123
|
+
await wait(20);
|
|
124
|
+
return this._fetch(url, options, --maxRetries);
|
|
125
|
+
}
|
|
65
126
|
let error;
|
|
66
127
|
try {
|
|
67
128
|
error = new ApiError(await res.json(), res.status);
|
|
@@ -102,10 +163,14 @@ export class Fetcher {
|
|
|
102
163
|
});
|
|
103
164
|
}
|
|
104
165
|
|
|
105
|
-
async post<T>(url: string, body?: Record<string, any
|
|
166
|
+
async post<T>(url: string, body?: Record<string, any>, opts?: RequestInit) {
|
|
106
167
|
return this._fetch<T>(url, {
|
|
107
168
|
method: 'POST',
|
|
108
|
-
body:
|
|
169
|
+
body:
|
|
170
|
+
body && !isFormData(body) && !(body instanceof URLSearchParams)
|
|
171
|
+
? JSON.stringify(body)
|
|
172
|
+
: (body as BodyInit),
|
|
173
|
+
...opts,
|
|
109
174
|
});
|
|
110
175
|
}
|
|
111
176
|
|
package/lib/utils.ts
CHANGED
|
@@ -10,3 +10,42 @@ export const removedUndefinedProperties = (
|
|
|
10
10
|
}
|
|
11
11
|
return newObject;
|
|
12
12
|
}, {});
|
|
13
|
+
|
|
14
|
+
export function dataURItoBlob(dataURI: string): [Blob, string] {
|
|
15
|
+
// convert base64/URLEncoded data component to raw binary data held in a string
|
|
16
|
+
let byteString;
|
|
17
|
+
if (dataURI.split(',')[0].indexOf('base64') >= 0)
|
|
18
|
+
byteString = atob(dataURI.split(',')[1].split(';')[0]);
|
|
19
|
+
else byteString = unescape(dataURI.split(',')[1]);
|
|
20
|
+
// separate out the mime component
|
|
21
|
+
const metadata = dataURI
|
|
22
|
+
.split(';')
|
|
23
|
+
.map((v) => v.split(/:/))
|
|
24
|
+
.filter((pair) => pair.length === 2);
|
|
25
|
+
const [, mimeString = ''] = metadata.find(([k, v]) => k === 'data') || [];
|
|
26
|
+
const [, ext] = mimeString.split(/\//);
|
|
27
|
+
const [, fileName = `file.${ext}`] =
|
|
28
|
+
metadata.find(([k, v]) => k === 'filename') || [];
|
|
29
|
+
|
|
30
|
+
const sanitizedFileName = encodeURIComponent(
|
|
31
|
+
decodeURIComponent(fileName).replace(/[;,\s]/g, '-')
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// write the bytes of the string to a typed array
|
|
35
|
+
let ia = new Uint8Array(byteString.length);
|
|
36
|
+
for (var i = 0; i < byteString.length; i++) {
|
|
37
|
+
ia[i] = byteString.charCodeAt(i);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return [new Blob([ia], { type: mimeString }), sanitizedFileName];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function isDataURL(file: any): file is string {
|
|
44
|
+
return !!(typeof file === 'string' && file.match(/^data\:/));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function wait(delay: number = 0) {
|
|
48
|
+
return new Promise((resolve) => {
|
|
49
|
+
setTimeout(resolve, delay);
|
|
50
|
+
});
|
|
51
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisme.ai/sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Communicate with Prisme.ai API",
|
|
5
5
|
"main": "dist/sdk/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@prisme.ai/types": "^1.0.9",
|
|
13
|
+
"form-data": "^4.0.0",
|
|
14
|
+
"pkce-challenge": "^3.1.0",
|
|
13
15
|
"qs": "^6.10.3",
|
|
14
16
|
"socket.io-client": "^4.4.1"
|
|
15
17
|
}
|