@prisme.ai/sdk 1.0.1 → 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/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(
@@ -14,6 +16,12 @@ export type Fetched<T> = T & {
14
16
  headers?: Record<string, any>;
15
17
  };
16
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
+
17
25
  export class Fetcher {
18
26
  public host: string;
19
27
  public token: string | null = null;
@@ -21,6 +29,7 @@ export class Fetcher {
21
29
  public overwriteClientId?: string;
22
30
  private clientIdHeader?: string;
23
31
  protected _apiKey: string | null = null;
32
+ protected _csrfToken: string | null = null;
24
33
  public language: string | undefined;
25
34
  public lastReceivedHeaders?: Record<string, any>;
26
35
 
@@ -46,6 +55,10 @@ export class Fetcher {
46
55
  headers.append('x-prismeai-api-key', this._apiKey);
47
56
  }
48
57
 
58
+ if (this._csrfToken && options.method && options.method !== 'GET') {
59
+ headers.append(CSRF_TOKEN_HEADER, this._csrfToken);
60
+ }
61
+
49
62
  if (this.language) {
50
63
  headers.append('accept-language', this.language);
51
64
  }
@@ -66,7 +79,7 @@ export class Fetcher {
66
79
  ? url
67
80
  : `${this.host}${url}`;
68
81
  return global.fetch(fullUrl, {
69
- credentials: 'include',
82
+ credentials: headers.has('Authorization') ? 'omit' : 'include',
70
83
  ...options,
71
84
  headers,
72
85
  });
@@ -74,9 +87,19 @@ export class Fetcher {
74
87
 
75
88
  protected async _fetch<T>(
76
89
  url: string,
77
- options: RequestInit = {}
90
+ options: RequestInit = {},
91
+ maxRetries: number = 3
78
92
  ): Promise<T> {
79
- const res = await this.prepareRequest(url, options);
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
+ }
80
103
  if (options.redirect === 'manual' && res.status === 0) {
81
104
  return { redirected: true } as T;
82
105
  }
@@ -85,7 +108,21 @@ export class Fetcher {
85
108
  if (this.clientIdHeader && this.lastReceivedHeaders[this.clientIdHeader]) {
86
109
  this.overwriteClientId = this.lastReceivedHeaders[this.clientIdHeader];
87
110
  }
111
+ if (this.lastReceivedHeaders[CSRF_TOKEN_HEADER]) {
112
+ this._csrfToken = this.lastReceivedHeaders[CSRF_TOKEN_HEADER];
113
+ }
88
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
+ }
89
126
  let error;
90
127
  try {
91
128
  error = new ApiError(await res.json(), res.status);
@@ -130,11 +167,9 @@ export class Fetcher {
130
167
  return this._fetch<T>(url, {
131
168
  method: 'POST',
132
169
  body:
133
- body &&
134
- !(body instanceof FormData) &&
135
- !(body instanceof URLSearchParams)
170
+ body && !isFormData(body) && !(body instanceof URLSearchParams)
136
171
  ? JSON.stringify(body)
137
- : body,
172
+ : (body as BodyInit),
138
173
  ...opts,
139
174
  });
140
175
  }
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.1",
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,7 @@
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
12
  "@prisme.ai/types": "^1.0.9",
13
+ "form-data": "^4.0.0",
13
14
  "pkce-challenge": "^3.1.0",
14
15
  "qs": "^6.10.3",
15
16
  "socket.io-client": "^4.4.1"