@jd-data-limited/easy-fm 4.1.0 → 4.1.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.
@@ -7,13 +7,14 @@ import {generateAuthorizationHeaders} from './generateAuthorizationHeaders.js'
7
7
  import {FMError} from '../FMError.js'
8
8
  import {type LayoutInterface} from '../layouts/layoutInterface.js'
9
9
  import {Layout} from '../layouts/layout.js'
10
- import HTTP_REDIRECT from 'follow-redirects'
11
10
  import {type databaseOptionsBase, type databaseOptionsWithExternalSources, type Script} from '../types.js'
12
11
  import {type HostBase} from './HostBase.js'
13
12
  import {type DatabaseBase} from './databaseBase.js'
14
13
  import {type ApiLayout, type ApiResults} from '../models/apiResults.js'
15
14
  import {type DatabaseStructure} from '../databaseStructure.js'
16
- import {type IncomingMessage} from 'http'
15
+ import fetch, {type HeadersInit, type RequestInfo, type RequestInit, type Response} from 'node-fetch'
16
+ // @ts-expect-error
17
+ import fetchWithCookies, {CookieJar} from 'node-fetch-cookies'
17
18
 
18
19
  /**
19
20
  * Represents a database connection.
@@ -23,7 +24,7 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
23
24
  private _token: string = ''
24
25
  readonly host: HostBase
25
26
  private readonly connection_details: databaseOptionsWithExternalSources
26
- private cookies: Record<string, string> = {}
27
+ private cookies = new CookieJar()
27
28
  readonly name: string
28
29
  readonly debug: boolean
29
30
 
@@ -66,7 +67,6 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
66
67
  }
67
68
  })
68
69
  await _fetch.json()
69
- // console.log(data)
70
70
  this._token = ''
71
71
  }
72
72
 
@@ -79,18 +79,17 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
79
79
  * @return {Promise<string>} - Returns a promise that resolves to the access token upon successful login.
80
80
  */
81
81
  async login (forceLogin = false) {
82
- // console.trace()
82
+ if (this.token !== '' && !forceLogin) return
83
+
83
84
  // Reset cookies
84
- this.cookies = {}
85
+ this.cookies = new CookieJar()
85
86
 
86
87
  await this.host.getMetadata()
87
- if (this.token !== '' && !forceLogin) throw new Error('Already logged in. Run logout() first')
88
88
 
89
89
  if (this.connection_details.credentials.method === 'token') {
90
90
  this._token = (this.connection_details.credentials).token
91
91
  return this.token
92
92
  } else {
93
- console.time()
94
93
  const url = new URL(`${this.endpoint}/sessions`)
95
94
  url.hostname = this.host.hostname
96
95
  const res = await fetch(url, {
@@ -103,8 +102,7 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
103
102
  })
104
103
  })
105
104
  })
106
- console.time()
107
- const _res = (await res.json())
105
+ const _res = (await res.json()) as any
108
106
  if (res.status === 200) {
109
107
  this._token = res.headers.get('x-fm-data-access-token') ?? ''
110
108
  return this._token
@@ -131,30 +129,53 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
131
129
  return `${this.host.hostname}/fmi/data/v2/databases/${this.name}`
132
130
  }
133
131
 
134
- async _apiRequestRaw (url: string | Request, options: RequestInit & { headers?: Record<string, string> } = {}, autoRelogin = true): Promise<Response> {
132
+ async _apiRequestRaw (url: URL | RequestInfo, options: RequestInit & {
133
+ headers?: Record<string, string>
134
+ useCookieJar?: boolean
135
+ } = {}, autoRelogin = true): Promise<Response> {
135
136
  if (this.debug) console.log(`EASYFM DEBUG: ${JSON.stringify(options)} ${url instanceof Request ? url.url : url}`)
136
137
  if (this.token === '') await this.login(true)
137
138
 
138
139
  if (!options.headers) options.headers = {}
139
140
  options.headers.authorization = 'Bearer ' + this._token
141
+ // options.redirect = "manual"
142
+ // options.headers.cookies = this._generateCookieHeader()
143
+ // console.log(options.headers.cookies)
140
144
  // options.rejectUnauthorized = this.host.verify
141
145
 
142
- const _fetch = await fetch(url, options)
143
- if (_fetch.headers.get('set-cookie')) {
144
- for (const cookie of _fetch.headers.get('set-cookie') ?? []) {
145
- const cookieSplit = cookie.split('=')
146
- this.cookies[cookieSplit[0]] = cookieSplit[1]
147
- }
148
- }
149
-
146
+ const _fetch: Response =
147
+ options.useCookieJar
148
+ ? await fetchWithCookies(this.cookies, url, options)
149
+ : await fetch(url, options)
150
+ // if (_fetch.headers.get('set-cookie')) {
151
+ // console.log(_fetch.headers.get('set-cookie'))
152
+ // for (const cookie of (_fetch.headers.get("Set-Cookie") ?? "").split("; ")) {
153
+ // const cookieSplit = cookie.split('=')
154
+ // console.log("COOKIE SPLIT:", cookieSplit)
155
+ // if (cookieSplit[1]) this.cookies[cookieSplit[0]] = cookieSplit[1]
156
+ // }
157
+ // }
158
+ // console.log(this.cookies, _fetch.status)
159
+ // // console.log(this, this.cookies, url)
160
+ // console.trace()
150
161
  if (_fetch.status === 401 && autoRelogin) {
162
+ console.trace()
151
163
  await this.login(true)
152
164
  return await this._apiRequestRaw(url, options, false)
165
+ } else return _fetch
166
+ }
167
+
168
+ private _generateCookieHeader () {
169
+ const out: string[] = []
170
+ for (const key of Object.keys(this.cookies)) {
171
+ out.push(`${key}=${this.cookies[key]}`)
153
172
  }
154
- return _fetch
173
+ return out.join('; ')
155
174
  }
156
175
 
157
- async _apiRequestJSON<T = any>(url: string | Request, options: RequestInit & { headers?: Record<string, string> } = {}): Promise<ApiResults<T>> {
176
+ async _apiRequestJSON<T = any>(url: URL | RequestInfo, options: RequestInit & {
177
+ headers?: Record<string, string>
178
+ } = {}): Promise<ApiResults<T>> {
158
179
  if (!options.headers) options.headers = {}
159
180
  options.headers['content-type'] = options.headers['content-type'] ? options.headers['content-type'] : 'application/json'
160
181
  const _fetch = await this._apiRequestRaw(url, options)
@@ -200,44 +221,6 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
200
221
  }
201
222
 
202
223
  script (name: string, parameter = ''): Script {
203
- return ({ name, parameter } satisfies Script)
204
- }
205
-
206
- async streamURL (url: string): Promise<IncomingMessage> {
207
- return await new Promise((resolve, reject) => {
208
- if (!url || typeof url !== 'string') {
209
- throw new Error('URL is empty, or has invalid value')
210
- }
211
-
212
- const headers: Record<string, string> = {}
213
- if (Object.keys(this.cookies).length !== 0) {
214
- headers.Cookie = Object.keys(this.cookies)
215
- .map(key => {
216
- return key + '=' + this.cookies[key]
217
- })
218
- .join('; ')
219
- }
220
-
221
- const checkForCookies = (res: HTTP_REDIRECT.ResponseDetails) => {
222
- // Check for the 'set-cookie' header. If it exists, remember it and strip it for better security.
223
- if (res.headers['set-cookie']) {
224
- for (const cookie of res.headers['set-cookie']) {
225
- const cookieSplit = cookie.split('=')
226
- this.cookies[cookieSplit[0]] = cookieSplit[1]
227
- }
228
- res.headers['set-cookie'] = undefined
229
- }
230
- }
231
-
232
- // Automatically switch between the http and https modules, based on which is needed
233
- (url.startsWith('https') ? HTTP_REDIRECT.https : HTTP_REDIRECT.http).get(url, {
234
- headers,
235
- beforeRedirect: (options: any, res: HTTP_REDIRECT.ResponseDetails) => { checkForCookies(res) }
236
- }, (res) => {
237
- checkForCookies(res)
238
-
239
- resolve(res)
240
- })
241
- })
224
+ return ({name, parameter} satisfies Script)
242
225
  }
243
226
  }
@@ -1,17 +1,16 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
1
  import { type HostBase } from './HostBase.js';
3
2
  import { type ApiResults } from '../models/apiResults.js';
4
- import { type IncomingMessage } from 'http';
3
+ import { type RequestInfo, type RequestInit, type Response } from 'node-fetch';
5
4
  export interface DatabaseBase {
6
5
  host: HostBase;
7
6
  readonly name: string;
8
7
  endpoint: string;
9
8
  token: string;
10
- _apiRequestJSON: <T = unknown>(url: string | Request, options?: RequestInit & {
9
+ _apiRequestJSON: <T = unknown>(url: URL | RequestInfo, options?: RequestInit & {
11
10
  headers?: Record<string, string>;
12
11
  } | undefined, autoRelogin?: boolean) => Promise<ApiResults<T>>;
13
- _apiRequestRaw: (url: string | Request, options?: RequestInit & {
12
+ _apiRequestRaw: (url: URL | RequestInfo, options?: RequestInit & {
14
13
  headers?: Record<string, string>;
14
+ useCookieJar?: boolean;
15
15
  } | undefined) => Promise<Response>;
16
- streamURL: (url: string) => Promise<IncomingMessage>;
17
16
  }
@@ -4,7 +4,7 @@
4
4
 
5
5
  import {type HostBase} from './HostBase.js'
6
6
  import {type ApiResults} from '../models/apiResults.js'
7
- import {type IncomingMessage} from 'http'
7
+ import {type RequestInfo, type RequestInit, type Response} from 'node-fetch'
8
8
 
9
9
  export interface DatabaseBase {
10
10
  host: HostBase
@@ -14,7 +14,6 @@ export interface DatabaseBase {
14
14
 
15
15
  // layouts: DatabaseStructure["layouts"]
16
16
 
17
- _apiRequestJSON: <T = unknown>(url: string | Request, options?: RequestInit & { headers?: Record<string, string> } | undefined, autoRelogin?: boolean) => Promise<ApiResults<T>>
18
- _apiRequestRaw: (url: string | Request, options?: RequestInit & { headers?: Record<string, string> } | undefined) => Promise<Response>
19
- streamURL: (url: string) => Promise<IncomingMessage>
17
+ _apiRequestJSON: <T = unknown>(url: URL | RequestInfo, options?: RequestInit & { headers?: Record<string, string> } | undefined, autoRelogin?: boolean) => Promise<ApiResults<T>>
18
+ _apiRequestRaw: (url: URL | RequestInfo, options?: RequestInit & { headers?: Record<string, string>, useCookieJar?: boolean } | undefined) => Promise<Response>
20
19
  }
@@ -1,7 +1,5 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
- /// <reference types="node" resolution-mode="require"/>
3
- import type * as http from 'http';
4
- import { type ContainerBufferResult, DownloadModes, RecordTypes } from '../types.js';
1
+ /// <reference types="node" />
2
+ import { RecordTypes } from '../types.js';
5
3
  import { type ApiFieldMetadata } from '../models/apiResults.js';
6
4
  import { type LayoutBase } from '../layouts/layoutBase.js';
7
5
  import { type Moment } from 'moment';
@@ -21,10 +19,12 @@ export interface Parentable {
21
19
  * @template T - The type of the field value.
22
20
  */
23
21
  export declare class Field<T extends FieldValue> {
22
+ #private;
24
23
  parent: Parentable;
25
24
  id: string;
26
25
  protected _value: T | null;
27
26
  edited: boolean;
27
+ static firstContainerDownload: Promise<any> | null;
28
28
  constructor(record: Parentable, id: string, contents: T);
29
29
  /**
30
30
  * Sets the value of the field.
@@ -40,22 +40,14 @@ export declare class Field<T extends FieldValue> {
40
40
  /**
41
41
  * Uploads a file to the container field.
42
42
  *
43
- * @param {Buffer} buffer - The file content as a buffer.
44
- * @param {string} filename - The filename of the file.
45
- * @param {string} mime - The MIME type of the file.
43
+ * @param {Buffer} file - The file content as a buffer.
46
44
  *
47
45
  * @throws {Error} - Cannot upload a file to the field if it's not a container field.
48
46
  * @throws {Error} - Upload failed with HTTP error.
49
47
  *
50
48
  * @returns {Promise<void>} - A promise that resolves when the file is successfully uploaded.
51
49
  */
52
- upload(buffer: Buffer, filename: string, mime: string): Promise<void>;
53
- /**
54
- * Downloads a resource from the server using the specified download mode.
55
- *
56
- * @param {DownloadModes} mode - The mode to use for downloading the resource.
57
- * @return {Promise<http.IncomingMessage>} - A promise that resolves with the incoming message representing the downloaded resource.
58
- */
59
- download(mode: DownloadModes.Stream): Promise<http.IncomingMessage>;
60
- download(mode: DownloadModes.Buffer): Promise<ContainerBufferResult>;
50
+ upload(file: File): Promise<void>;
51
+ stream(): Promise<NodeJS.ReadableStream>;
52
+ arrayBuffer(): Promise<ArrayBuffer>;
61
53
  }
@@ -1,60 +1,100 @@
1
1
  /*
2
2
  * Copyright (c) 2023-2024. See LICENSE file for more information
3
3
  */
4
- import { DownloadModes, RecordTypes } from '../types.js';
4
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
5
+ var useValue = arguments.length > 2;
6
+ for (var i = 0; i < initializers.length; i++) {
7
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
8
+ }
9
+ return useValue ? value : void 0;
10
+ };
11
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
12
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
13
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
14
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
15
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
16
+ var _, done = false;
17
+ for (var i = decorators.length - 1; i >= 0; i--) {
18
+ var context = {};
19
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
20
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
21
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
22
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
23
+ if (kind === "accessor") {
24
+ if (result === void 0) continue;
25
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
26
+ if (_ = accept(result.get)) descriptor.get = _;
27
+ if (_ = accept(result.set)) descriptor.set = _;
28
+ if (_ = accept(result.init)) initializers.unshift(_);
29
+ }
30
+ else if (_ = accept(result)) {
31
+ if (kind === "field") initializers.unshift(_);
32
+ else descriptor[key] = _;
33
+ }
34
+ }
35
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
36
+ done = true;
37
+ };
38
+ var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) {
39
+ if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
40
+ return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
41
+ };
42
+ import { RecordTypes } from '../types.js';
5
43
  import { ApiFieldDisplayTypes, ApiFieldResultTypes, ApiFieldTypes } from '../models/apiResults.js';
44
+ import { FMError } from '../FMError.js';
6
45
  /**
7
46
  * A class representing a field in a record.
8
47
  *
9
48
  * @template T - The type of the field value.
10
49
  */
11
- export class Field {
12
- parent;
13
- id;
14
- _value;
15
- edited;
16
- constructor(record, id, contents) {
17
- this.parent = record;
18
- this.id = id;
19
- this._value = contents;
20
- this.edited = false;
21
- }
22
- /**
23
- * Sets the value of the field.
24
- *
25
- * @param {T | null} content - The new content value to be set. Can be either the type T or null.
26
- * @throws {Error} Cannot set container value using set(). Use upload() instead, if the result is a 'container'.
27
- */
28
- set(content) {
29
- if (this.metadata.result === 'container')
30
- throw new Error('Cannot set container value using set(). Use upload() instead.');
31
- else
32
- this._value = content;
33
- this.edited = true;
34
- }
35
- get metadata() {
36
- if (!this.parent.layout.metadata) {
37
- // Default to a regular text field
38
- return {
39
- name: this.id.toString(),
40
- type: ApiFieldTypes.NORMAL,
41
- displayType: ApiFieldDisplayTypes.EDIT_TEXT,
42
- result: ApiFieldResultTypes.TEXT,
43
- global: false,
44
- autoEnter: true,
45
- fourDigitYear: false,
46
- maxRepeat: 1,
47
- maxCharacters: 0,
48
- notEmpty: false,
49
- numeric: false,
50
- timeOfDay: false,
51
- repetitions: 1,
52
- valueList: ''
53
- };
50
+ let Field = (() => {
51
+ let _instanceExtraInitializers = [];
52
+ let _private_streamAsync_decorators;
53
+ let _private_streamAsync_descriptor;
54
+ let _arrayBuffer_decorators;
55
+ return class Field {
56
+ static {
57
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
58
+ _private_streamAsync_decorators = [containerDownloadFunction];
59
+ _arrayBuffer_decorators = [containerDownloadFunction];
60
+ __esDecorate(this, _private_streamAsync_descriptor = { value: __setFunctionName(async function () {
61
+ const req = await this.parent.layout.database._apiRequestRaw(this.string, { useCookieJar: true });
62
+ if (!req.ok || !req.body) {
63
+ throw new Error(`HTTP Error: ${req.status} (${req.statusText})`);
64
+ }
65
+ return req.body;
66
+ }, "#streamAsync") }, _private_streamAsync_decorators, { kind: "method", name: "#streamAsync", static: false, private: true, access: { has: obj => #streamAsync in obj, get: obj => obj.#streamAsync }, metadata: _metadata }, null, _instanceExtraInitializers);
67
+ __esDecorate(this, null, _arrayBuffer_decorators, { kind: "method", name: "arrayBuffer", static: false, private: false, access: { has: obj => "arrayBuffer" in obj, get: obj => obj.arrayBuffer }, metadata: _metadata }, null, _instanceExtraInitializers);
68
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
69
+ }
70
+ parent = __runInitializers(this, _instanceExtraInitializers);
71
+ id;
72
+ _value;
73
+ edited;
74
+ static firstContainerDownload = null;
75
+ constructor(record, id, contents) {
76
+ this.parent = record;
77
+ this.id = id;
78
+ this._value = contents;
79
+ this.edited = false;
54
80
  }
55
- if (this.parent.type === RecordTypes.PORTAL && this.parent.portal) {
56
- return this.parent.layout.metadata.portalMetaData[this.parent.portal.name || 'portal not attached'].find(i => i.name === this.id) ??
57
- {
81
+ /**
82
+ * Sets the value of the field.
83
+ *
84
+ * @param {T | null} content - The new content value to be set. Can be either the type T or null.
85
+ * @throws {Error} Cannot set container value using set(). Use upload() instead, if the result is a 'container'.
86
+ */
87
+ set(content) {
88
+ if (this.metadata.result === 'container')
89
+ throw new Error('Cannot set container value using set(). Use upload() instead.');
90
+ else
91
+ this._value = content;
92
+ this.edited = true;
93
+ }
94
+ get metadata() {
95
+ if (!this.parent.layout.metadata) {
96
+ // Default to a regular text field
97
+ return {
58
98
  name: this.id.toString(),
59
99
  type: ApiFieldTypes.NORMAL,
60
100
  displayType: ApiFieldDisplayTypes.EDIT_TEXT,
@@ -70,90 +110,151 @@ export class Field {
70
110
  repetitions: 1,
71
111
  valueList: ''
72
112
  };
113
+ }
114
+ if (this.parent.type === RecordTypes.PORTAL && this.parent.portal) {
115
+ return this.parent.layout.metadata.portalMetaData[this.parent.portal.name || 'portal not attached'].find(i => i.name === this.id) ??
116
+ {
117
+ name: this.id.toString(),
118
+ type: ApiFieldTypes.NORMAL,
119
+ displayType: ApiFieldDisplayTypes.EDIT_TEXT,
120
+ result: ApiFieldResultTypes.TEXT,
121
+ global: false,
122
+ autoEnter: true,
123
+ fourDigitYear: false,
124
+ maxRepeat: 1,
125
+ maxCharacters: 0,
126
+ notEmpty: false,
127
+ numeric: false,
128
+ timeOfDay: false,
129
+ repetitions: 1,
130
+ valueList: ''
131
+ };
132
+ }
133
+ else {
134
+ return this.parent.layout.metadata.fieldMetaData.find(i => i.name === this.id) ?? {
135
+ name: this.id.toString(),
136
+ type: ApiFieldTypes.NORMAL,
137
+ displayType: ApiFieldDisplayTypes.EDIT_TEXT,
138
+ result: ApiFieldResultTypes.TEXT,
139
+ global: false,
140
+ autoEnter: true,
141
+ fourDigitYear: false,
142
+ maxRepeat: 1,
143
+ maxCharacters: 0,
144
+ notEmpty: false,
145
+ numeric: false,
146
+ timeOfDay: false,
147
+ repetitions: 1,
148
+ valueList: ''
149
+ };
150
+ }
73
151
  }
74
- else {
75
- return this.parent.layout.metadata.fieldMetaData.find(i => i.name === this.id) ?? {
76
- name: this.id.toString(),
77
- type: ApiFieldTypes.NORMAL,
78
- displayType: ApiFieldDisplayTypes.EDIT_TEXT,
79
- result: ApiFieldResultTypes.TEXT,
80
- global: false,
81
- autoEnter: true,
82
- fourDigitYear: false,
83
- maxRepeat: 1,
84
- maxCharacters: 0,
85
- notEmpty: false,
86
- numeric: false,
87
- timeOfDay: false,
88
- repetitions: 1,
89
- valueList: ''
90
- };
91
- }
92
- }
93
- get value() {
94
- // if (this.metadata.result === "container") throw "Use await field.stream() to get the contents of a container field, instead of field.value"
95
- return this._value;
96
- }
97
- set value(value) {
98
- this._value = value;
99
- }
100
- get string() {
101
- if (typeof this._value === 'string') {
152
+ get value() {
153
+ // if (this.metadata.result === "container") throw "Use await field.stream() to get the contents of a container field, instead of field.value"
102
154
  return this._value;
103
155
  }
104
- throw new Error('Field value is not a string');
105
- }
106
- /**
107
- * Uploads a file to the container field.
108
- *
109
- * @param {Buffer} buffer - The file content as a buffer.
110
- * @param {string} filename - The filename of the file.
111
- * @param {string} mime - The MIME type of the file.
112
- *
113
- * @throws {Error} - Cannot upload a file to the field if it's not a container field.
114
- * @throws {Error} - Upload failed with HTTP error.
115
- *
116
- * @returns {Promise<void>} - A promise that resolves when the file is successfully uploaded.
117
- */
118
- async upload(buffer, filename, mime) {
119
- if (this.metadata.result !== 'container') {
120
- throw new Error('Cannot upload a file to the field; ' + this.id + ' (not a container field)');
156
+ set value(value) {
157
+ this._value = value;
121
158
  }
122
- const form = new FormData();
123
- form.append('upload', new File([buffer], filename, { type: mime }));
124
- const res = await this.parent.layout.database._apiRequestRaw(`${this.parent.endpoint}/containers/${this.id}/1`, {
125
- method: 'POST',
126
- headers: {
127
- Authorization: 'Bearer ' + this.parent.layout.database.token,
128
- 'Content-Type': 'multipart/form-data'
129
- },
130
- body: form
131
- });
132
- if (!res.ok)
133
- throw new Error(`Upload failed with HTTP error: ${res.status} (${res.statusText})`);
134
- }
135
- async download(mode = DownloadModes.Stream) {
136
- if (this.metadata.result !== ApiFieldResultTypes.CONTAINER)
137
- throw new Error('Cannot perform download() on a non-container field');
138
- const stream = await this.parent.layout.database.streamURL(this.string);
139
- if (mode === DownloadModes.Stream) {
140
- return stream;
159
+ get string() {
160
+ if (typeof this._value === 'string') {
161
+ return this._value;
162
+ }
163
+ throw new Error('Field value is not a string');
141
164
  }
142
- const body = [];
143
- return await new Promise((resolve, reject) => {
144
- stream.on('data', (chunk) => {
145
- body.push(chunk);
165
+ /**
166
+ * Uploads a file to the container field.
167
+ *
168
+ * @param {Buffer} file - The file content as a buffer.
169
+ *
170
+ * @throws {Error} - Cannot upload a file to the field if it's not a container field.
171
+ * @throws {Error} - Upload failed with HTTP error.
172
+ *
173
+ * @returns {Promise<void>} - A promise that resolves when the file is successfully uploaded.
174
+ */
175
+ async upload(file) {
176
+ if (this.metadata.result !== 'container') {
177
+ throw new Error('Cannot upload a file to the field; ' + this.id + ' (not a container field)');
178
+ }
179
+ const form = new FormData();
180
+ form.append('upload', file);
181
+ const res = await this.parent.layout.database._apiRequestRaw(`${this.parent.endpoint}/containers/${this.id}/1`, {
182
+ method: 'POST',
183
+ headers: {
184
+ // Authorization: 'Bearer ' + this.parent.layout.database.token,
185
+ // 'Content-Type': 'multipart/form-data'
186
+ },
187
+ body: form
146
188
  });
147
- stream.on('error', (e) => {
148
- reject(e);
149
- });
150
- stream.on('end', () => {
151
- resolve({
152
- buffer: Buffer.concat(body),
153
- mime: stream.headers['content-type'],
154
- request: stream
189
+ if (!res.ok) {
190
+ throw new Error(`Upload failed with HTTP error: ${res.status} (${res.statusText})`);
191
+ }
192
+ const data = await res.json();
193
+ if (data.messages[0].code === '0')
194
+ return;
195
+ else {
196
+ throw new FMError(data.messages[0].code, res.status, res);
197
+ }
198
+ }
199
+ get #streamAsync() { return _private_streamAsync_descriptor.value; }
200
+ async stream() {
201
+ return await this.#streamAsync();
202
+ }
203
+ async arrayBuffer() {
204
+ const req = await this.parent.layout.database._apiRequestRaw(this.string, { useCookieJar: true });
205
+ if (!req.ok)
206
+ throw new Error(`HTTP Error: ${req.status} (${req.statusText})`);
207
+ return await req.arrayBuffer();
208
+ }
209
+ };
210
+ })();
211
+ export { Field };
212
+ function containerDownloadFunction(originalMethod, context) {
213
+ async function replacementMethod(...args) {
214
+ return await ContainerDownloadExecutor.processTask(async () => await originalMethod.call(this, ...args));
215
+ }
216
+ return replacementMethod;
217
+ }
218
+ class ContainerDownloadExecutorClass {
219
+ hasDownloadedFirstContainer = 0;
220
+ waitingFuncs = [];
221
+ async processTask(task) {
222
+ const isFirstTask = this.hasDownloadedFirstContainer === 0;
223
+ if (isFirstTask) {
224
+ this.hasDownloadedFirstContainer = 1;
225
+ let res;
226
+ try {
227
+ res = await task();
228
+ }
229
+ catch (e) {
230
+ this.hasDownloadedFirstContainer = 0;
231
+ const nextTask = this.waitingFuncs.splice(0, 1)[0];
232
+ if (nextTask) {
233
+ this.processTask(nextTask.func)
234
+ .then(res => nextTask.done(res))
235
+ .catch(e => nextTask.err(e));
236
+ }
237
+ throw e;
238
+ }
239
+ this.hasDownloadedFirstContainer = 2;
240
+ for (const task of this.waitingFuncs) {
241
+ task.func()
242
+ .then((res) => task.done(res))
243
+ .catch((e) => task.err(e));
244
+ }
245
+ return res;
246
+ }
247
+ else if (this.hasDownloadedFirstContainer === 1) {
248
+ return await new Promise((resolve, reject) => {
249
+ this.waitingFuncs.push({
250
+ func: task,
251
+ done: resolve,
252
+ err: reject
155
253
  });
156
254
  });
157
- });
255
+ }
256
+ else
257
+ return await task();
158
258
  }
159
259
  }
260
+ const ContainerDownloadExecutor = new ContainerDownloadExecutorClass();