@jd-data-limited/easy-fm 1.0.0-alpha1 → 1.0.1-alpha1

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/index.js CHANGED
@@ -1,574 +1,582 @@
1
- /*
2
- * Copyright (c) 2022. See LICENSE file for more information
3
- */
4
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
5
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
6
- return new (P || (P = Promise))(function (resolve, reject) {
7
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
8
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
9
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
10
- step((generator = generator.apply(thisArg, _arguments || [])).next());
11
- });
12
- };
13
- // const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
14
- import fetch, { File, FormData } from 'node-fetch';
15
- import { EventEmitter } from "events";
16
- // @ts-ignore
17
- import fs from "fs";
18
- // @ts-ignore
19
- import path from "path";
20
- // @ts-ignore
21
- import { fileURLToPath } from "url";
22
- import * as https from "https";
23
- // @ts-ignore
24
- const errs = JSON.parse(fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), 'fmErrors.json')).toString());
25
- class database {
26
- constructor(props, host = null) {
27
- this.props = props;
28
- this.name = this.props.database;
29
- if (host)
30
- this.host = host;
31
- }
32
- get token() {
33
- return this.host.token;
34
- }
35
- getLayout(name) {
36
- return new layout(this, name);
37
- }
38
- apiRequest(url, options = {}) {
39
- return __awaiter(this, void 0, void 0, function* () {
40
- if (!options.headers)
41
- options.headers = {};
42
- options.headers["content-type"] = options.headers["content-type"] ? options.headers["content-type"] : "application/json";
43
- options.headers["authorization"] = "Bearer " + this.host.token;
44
- options.rejectUnauthorized = this.host.rejectUnauthroized;
45
- let _fetch = yield fetch(url, options);
46
- let data = yield _fetch.json();
47
- return data;
48
- });
49
- }
50
- setGlobals(globalFields) {
51
- // console.log({globalFields})
52
- return new Promise((resolve, reject) => {
53
- this.apiRequest(`${this.endpoint}/globals`, {
54
- method: "PATCH",
55
- body: JSON.stringify({ globalFields })
56
- }).then(res => {
57
- // console.log(res)
58
- if (res.messages[0].code === "0") {
59
- resolve();
60
- }
61
- else {
62
- reject(new FMError(res.messages[0].code, res.status, res));
63
- }
64
- })
65
- .catch(e => {
66
- reject(e);
67
- });
68
- });
69
- }
70
- script(name, parameter) {
71
- return { name, parameter };
72
- }
73
- get endpoint() {
74
- return `https://${this.hostname || this.host.hostname}/fmi/data/v2/databases/${encodeURI(this.name)}`;
75
- }
76
- get externalLoginInfo() {
77
- // Returns an object that can be used when using this database object as an external source
78
- let out = {
79
- database: this.name,
80
- username: undefined,
81
- password: undefined,
82
- oauthRequestId: undefined,
83
- oauthIdentifier: undefined
84
- };
85
- switch (this.props.method) {
86
- case "filemaker":
87
- out.username = this.props.username;
88
- out.password = this.props.password;
89
- case "token":
90
- throw "Token logins cannot be used for connecting to external sources. Please either use no login method, or open a second connection.";
91
- case "oauth":
92
- out.oauthRequestId = this.props.oauth.requestId;
93
- out.oauthIdentifier = this.props.oauth.requestIdentifier;
94
- case "claris":
95
- throw "Claris logins cannot be used for connecting to external sources. Please either use no login method, or open a second connection.";
96
- }
97
- return out;
98
- }
99
- }
100
- export default class FileMakerConnection extends database {
101
- constructor(conn, externalDataSources = [], rejectUnauthorized = true) {
102
- super(conn.database);
103
- this.host = this;
104
- this.hostname = conn.hostname;
105
- this.externalSources = externalDataSources.map(i => { let e = new database(i, this); e.external = true; return e; });
106
- this.rejectUnauthroized = rejectUnauthorized;
107
- }
108
- logout() {
109
- return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
110
- if (!this.token)
111
- reject(new Error("Not logged in"));
112
- let _fetch = yield fetch(`${this.endpoint}/sessions/${this.token}`, {
113
- method: "DELETE",
114
- headers: {
115
- "content-type": "application/json"
116
- }
117
- });
118
- let data = yield _fetch.json();
119
- // console.log(data)
120
- this._token = null;
121
- this.name = null;
122
- this.hostname = null;
123
- resolve();
124
- }));
125
- }
126
- login() {
127
- return new Promise((resolve, reject) => {
128
- if (this.token)
129
- throw new Error("Already logged in. Run logout() first");
130
- if (this.props.method === "filemaker") {
131
- this.username = this.props.username;
132
- this.name = this.props.database;
133
- fetch(`${this.endpoint}/sessions`, {
134
- hostname: this.hostname,
135
- port: 443,
136
- method: "POST",
137
- headers: {
138
- "Content-Type": "application/json",
139
- "Authorization": "Basic " + Buffer.from(this.username + ":" + this.props.password).toString("base64")
140
- }
141
- }).then((res) => __awaiter(this, void 0, void 0, function* () {
142
- if (res.status === 200) {
143
- this._token = res.headers.get('x-fm-data-access-token');
144
- resolve(this._token);
145
- }
146
- else {
147
- let _res = (yield res.json());
148
- reject(new FMError(_res.messages[0].code, _res.status, res));
149
- }
150
- }))
151
- .catch(e => {
152
- reject(e);
153
- });
154
- }
155
- else if (this.props.method === "token") {
156
- this._token = this.props.token;
157
- this.name = this.props.database;
158
- resolve(this.token);
159
- }
160
- else if (this.props.method === "oauth") {
161
- this.name = this.props.database;
162
- fetch(`${this.endpoint}/sessions`, {
163
- method: "POST",
164
- headers: {
165
- "Content-Type": "application/json",
166
- "X-FM-Data-OAuth-RequestId": this.props.oauth.requestId,
167
- "X-FM-Data-OAuth-Identifier": this.props.oauth.requestIdentifier
168
- },
169
- body: "{}"
170
- })
171
- .then(res => res.json())
172
- .then(res => {
173
- let _res = res;
174
- this._token = _res.headers["x-fm-data-access-token"];
175
- resolve(this.token);
176
- });
177
- }
178
- else if (this.props.method === "claris") {
179
- this.name = this.props.database;
180
- fetch(`${this.endpoint}/sessions`, {
181
- method: "POST",
182
- headers: {
183
- "Content-Type": "application/json",
184
- "Authorization": this.props.claris.fmid,
185
- },
186
- body: "{}"
187
- })
188
- .then(res => res.json())
189
- .then(res => {
190
- let _res = res;
191
- this._token = _res.headers["x-fm-data-access-token"];
192
- resolve(this.token);
193
- });
194
- }
195
- });
196
- }
197
- get token() {
198
- return this._token;
199
- }
200
- }
201
- class layout {
202
- constructor(database, name) {
203
- this.database = database;
204
- this.name = name;
205
- }
206
- get endpoint() {
207
- return `${this.database.endpoint}/layouts/${this.name}`;
208
- }
209
- getScript(script) {
210
- return new script(this, script);
211
- }
212
- createRecord() {
213
- return new Promise((resolve, reject) => {
214
- // Get the layout's metadata
215
- this.getLayoutMeta().then(layout => {
216
- let fields = {};
217
- for (let _field of this.metadata.fieldMetaData) {
218
- fields[_field.name] = "";
219
- }
220
- resolve(new record(this, -1, 0, fields));
221
- });
222
- });
223
- }
224
- getRecord(recordId) {
225
- return new record(this, recordId);
226
- }
227
- newFind() {
228
- return new find(this);
229
- }
230
- runScript(script) {
231
- return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
232
- let url = `${this.endpoint}/script/${encodeURI(script.name)}`;
233
- if (script.parameter)
234
- url += "?" + encodeURI(script.parameter);
235
- this.database.apiRequest(url, {
236
- port: 443,
237
- method: "GET"
238
- })
239
- .then(res => {
240
- if (res.messages[0].code === "0") {
241
- resolve(res.response);
242
- }
243
- else {
244
- // console.log(res)
245
- reject(new FMError(res.messages[0].code, res.status, res));
246
- }
247
- })
248
- .catch(e => {
249
- reject(e);
250
- });
251
- }));
252
- }
253
- getLayoutMeta() {
254
- return new Promise((resolve, reject) => {
255
- this.database.apiRequest(this.endpoint).then(res => {
256
- this.metadata = res.response;
257
- resolve(this);
258
- })
259
- .catch(e => reject(e));
260
- });
261
- }
262
- }
263
- class record extends EventEmitter {
264
- constructor(layout, recordId, modId = recordId, fieldData = {}, portalData = null) {
265
- super();
266
- this.layout = layout;
267
- this.recordId = recordId;
268
- this.modId = modId;
269
- this.fields = this.processFieldData(fieldData);
270
- this.portals = [];
271
- if (portalData) {
272
- this.processPortalData(portalData);
273
- }
274
- this.on("saved", () => {
275
- for (let field of this.fields)
276
- field.edited = false;
277
- });
278
- }
279
- get endpoint() {
280
- return `${this.layout.endpoint}/records/${this.recordId}`;
281
- }
282
- get edited() {
283
- return !!this.fields.find(i => i.edited);
284
- }
285
- processPortalData(portalData) {
286
- for (let item of Object.keys(portalData)) {
287
- this.portals.push(new portal(this, item, portalData[item].map(item => {
288
- let fieldData = item;
289
- delete fieldData.recordId;
290
- delete fieldData.modId;
291
- return new portalItem(this, item.recordId, item.modId, fieldData);
292
- })));
293
- }
294
- }
295
- processFieldData(fieldData) {
296
- return Object.keys(fieldData).map(item => {
297
- return new field(this, item, fieldData[item]);
298
- });
299
- }
300
- get() {
301
- if (this.recordId === -1) {
302
- throw "Cannot get this record until a commit() is done.";
303
- }
304
- return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
305
- if (!this.layout.metadata)
306
- yield this.layout.getLayoutMeta();
307
- this.layout.database.apiRequest(this.endpoint, {
308
- port: 443,
309
- method: "GET"
310
- })
311
- .then(res => {
312
- if (res.messages[0].code === "0") {
313
- // console.log(res, res.response.data)
314
- this.modId = res.response.data[0].modId;
315
- this.fields = this.processFieldData(res.response.data[0].fieldData);
316
- this.portalData = [];
317
- if (res.response.data[0].portalData)
318
- this.processPortalData(res.response.data[0].portalData);
319
- resolve(this);
320
- }
321
- else {
322
- reject(new FMError(res.messages[0].code, res.status, res));
323
- }
324
- })
325
- .catch(e => {
326
- reject(e);
327
- });
328
- }));
329
- }
330
- commit(extraBody = {
331
- fieldData: undefined
332
- }) {
333
- return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
334
- let data = this.toObject();
335
- delete data.recordId;
336
- delete data.modId;
337
- if (this.recordId === -1) {
338
- // This is a new record
339
- extraBody.fieldData = data.fieldData;
340
- this.layout.database.apiRequest(`${this.layout.endpoint}/records`, {
341
- port: 443,
342
- method: "POST",
343
- body: JSON.stringify(extraBody)
344
- })
345
- .then(res => {
346
- if (res.messages[0].code === "0") {
347
- this.recordId = parseInt(res.response.recordId);
348
- this.modId = parseInt(res.response.modId);
349
- resolve(this);
350
- }
351
- else {
352
- reject(new FMError(res.messages[0].code, res.status, res));
353
- }
354
- })
355
- .catch(e => {
356
- reject(e);
357
- });
358
- return;
359
- }
360
- for (let item of Object.keys(extraBody))
361
- data[item] = extraBody[item];
362
- this.layout.database.apiRequest(this.endpoint, {
363
- port: 443,
364
- method: "PATCH",
365
- body: JSON.stringify(data)
366
- })
367
- .then(res => {
368
- if (res.messages[0].code === "0") {
369
- this.modId = res.response.modId;
370
- this.emit("saved");
371
- resolve(this);
372
- }
373
- else {
374
- reject(new FMError(res.messages[0].code, res.status, res));
375
- }
376
- })
377
- .catch(e => {
378
- reject(e);
379
- });
380
- }));
381
- }
382
- getField(field) {
383
- return this.fields.find(_field => _field.id === field);
384
- }
385
- getPortal(portal) {
386
- return this.portals.find(p => p.name === portal);
387
- }
388
- toObject(filter = (a) => a.edited, portalFilter = (a) => a.records.find(record => record.edited), portalRowFilter = (a) => a.edited, portalFieldFilter = (a) => a.edited) {
389
- let fields_processed = {};
390
- for (let field of this.fields.filter(field => filter(field))) {
391
- fields_processed[field.id] = field.value;
392
- }
393
- let obj = {
394
- "recordId": this.recordId,
395
- "modId": this.modId,
396
- "fieldData": fields_processed
397
- };
398
- // Check if there's been any edited portal information
399
- let portals = this.portals.filter(a => portalFilter(a));
400
- if (portals) {
401
- obj["portalData"] = {};
402
- for (let portal of portals) {
403
- obj["portalData"][portal.name] = portal.records.filter(a => portalRowFilter(a)).map(record => {
404
- return record.toObject(portalFieldFilter);
405
- });
406
- }
407
- }
408
- return obj;
409
- }
410
- }
411
- class field {
412
- constructor(record, id, contents) {
413
- this.record = record;
414
- this.id = id;
415
- this.value = contents;
416
- this.edited = false;
417
- }
418
- set(content) {
419
- if (this.dataType === "container")
420
- throw "Cannot set container value using set(). Use upload() instead.";
421
- this.value = content;
422
- this.edited = true;
423
- }
424
- get dataType() {
425
- if (!this.record.layout.metadata)
426
- return "unknown";
427
- if (this.record instanceof portalItem) {
428
- return this.record.layout.metadata.portalMetaData.find(i => i.name === this.id).result || "unknown";
429
- }
430
- else {
431
- return this.record.layout.metadata.fieldMetaData.find(i => i.name === this.id).result || "unknown";
432
- }
433
- }
434
- upload(buffer, filename, mime) {
435
- if (this.dataType !== "container")
436
- throw "Cannot upload a file to the field; " + this.id + " (not a container field)";
437
- return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
438
- let form = new FormData();
439
- form.append("upload", new File([buffer], filename, { type: mime }));
440
- let _fetch = yield fetch(`${this.record.endpoint}/containers/${this.id}/1`, {
441
- method: "POST",
442
- headers: { "Authorization": "Bearer " + this.record.layout.database.token },
443
- body: form
444
- }).then(res => res.json())
445
- .then(data => {
446
- let _res = data;
447
- if (_res.messages[0].code === "0") {
448
- resolve();
449
- }
450
- else {
451
- reject(new FMError(_res.messages[0].code, _res.status, data));
452
- }
453
- })
454
- .catch(e => { reject(e); });
455
- }));
456
- }
457
- download() {
458
- return new Promise((resolve, reject) => {
459
- https.get(this.value.toString(), (res) => {
460
- resolve(res);
461
- });
462
- });
463
- }
464
- }
465
- class portal {
466
- constructor(record, name, records = []) {
467
- this.record = record;
468
- this.name = name;
469
- this.records = records;
470
- for (let item of this.records)
471
- item.attachPortal(this);
472
- }
473
- }
474
- class portalItem extends record {
475
- constructor(record, recordId, modId = recordId, fieldData = {}) {
476
- super(record.layout, recordId, modId, fieldData);
477
- record.on("saved", () => {
478
- this.emit("saved");
479
- });
480
- }
481
- attachPortal(portal) {
482
- this.portal = portal;
483
- }
484
- commit(extraBody = { fieldData: undefined }) {
485
- return this.portal.record.commit(extraBody);
486
- }
487
- toObject(fieldFilter) {
488
- super.toObject();
489
- let res = {
490
- recordId: this.recordId, modId: this.modId
491
- };
492
- for (let field of this.fields.filter(a => fieldFilter(a)))
493
- res[field.id] = field.value;
494
- // console.log(res)
495
- return res;
496
- }
497
- }
498
- class find {
499
- constructor(layout) {
500
- this.layout = layout;
501
- this.queries = [];
502
- this.scripts = {
503
- "script": null,
504
- "script.prerequeset": null,
505
- "script.presort": null // Runs before the sort
506
- };
507
- this.sort = [];
508
- }
509
- toObject() {
510
- let out = { query: this.queries, sort: undefined };
511
- if (this.sort.length !== 0)
512
- out.sort = this.sort;
513
- for (let item of Object.keys(this.scripts)) {
514
- if (this.scripts[item]) {
515
- out[item] = this.scripts[item].name;
516
- if (this.scripts[item].parameter)
517
- out[item + ".param"] = this.scripts[item].parameter;
518
- }
519
- }
520
- return out;
521
- }
522
- addRequests(...requests) {
523
- for (let item of requests)
524
- this.queries.push(item);
525
- return this;
526
- }
527
- addSort(fieldName, sortOrder) {
528
- this.sort.push({ fieldName, sortOrder });
529
- return this;
530
- }
531
- find() {
532
- return new Promise((resolve, reject) => {
533
- // console.log(this.#toObject())
534
- this.layout.database.apiRequest(`${this.layout.endpoint}/_find`, {
535
- port: 443,
536
- method: "POST",
537
- body: JSON.stringify(this.toObject())
538
- }).then((res) => __awaiter(this, void 0, void 0, function* () {
539
- // // console.log(res)
540
- if (res.messages[0].code === "0") {
541
- // console.log("RESOLVING")
542
- if (!this.layout.metadata)
543
- yield this.layout.getLayoutMeta();
544
- let data = res.response.data.map(item => {
545
- return new record(this.layout, item.recordId, item.modId, item.fieldData, item.portalData);
546
- });
547
- resolve(data);
548
- }
549
- else {
550
- reject(new FMError(res.messages[0].code, res.status, res));
551
- }
552
- }))
553
- .catch(e => {
554
- reject(e);
555
- });
556
- });
557
- }
558
- }
559
- export class FMError extends Error {
560
- constructor(code, httpStatus, res) {
561
- if (typeof code === "string")
562
- code = parseInt(code);
563
- super(errs.find(err => err.e === code).d || "Unknown error");
564
- this.httpStatus = httpStatus;
565
- this.res = res;
566
- this.messages = res.messages;
567
- this.code = typeof code === "string" ? parseInt(code) : code;
568
- Error.captureStackTrace(this, FMError);
569
- }
570
- }
571
- //
572
- // module.exports = {
573
- // default: {FileMakerConnection}
574
- // }
1
+ /*
2
+ * Copyright (c) 2022. See LICENSE file for more information
3
+ */
4
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
5
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
6
+ return new (P || (P = Promise))(function (resolve, reject) {
7
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
8
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
9
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
10
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
11
+ });
12
+ };
13
+ // const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
14
+ import fetch, { File, FormData } from 'node-fetch';
15
+ import { EventEmitter } from "events";
16
+ // @ts-ignore
17
+ import fs from "fs";
18
+ // @ts-ignore
19
+ import path from "path";
20
+ // @ts-ignore
21
+ import { fileURLToPath } from "url";
22
+ import * as https from "https";
23
+ // @ts-ignore
24
+ const errs = JSON.parse(fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), 'fmErrors.json')).toString());
25
+ class database {
26
+ constructor(props, host = null) {
27
+ this.props = props;
28
+ this.name = this.props.database;
29
+ if (host)
30
+ this.host = host;
31
+ }
32
+ get token() {
33
+ return this.host.token;
34
+ }
35
+ getLayout(name) {
36
+ return new layout(this, name);
37
+ }
38
+ apiRequest(url, options = {}) {
39
+ return __awaiter(this, void 0, void 0, function* () {
40
+ if (!options.headers)
41
+ options.headers = {};
42
+ options.headers["content-type"] = options.headers["content-type"] ? options.headers["content-type"] : "application/json";
43
+ options.headers["authorization"] = "Bearer " + this.host.token;
44
+ options.rejectUnauthorized = this.host.rejectUnauthroized;
45
+ let _fetch = yield fetch(url, options);
46
+ let data = yield _fetch.json();
47
+ return data;
48
+ });
49
+ }
50
+ setGlobals(globalFields) {
51
+ // console.log({globalFields})
52
+ return new Promise((resolve, reject) => {
53
+ this.apiRequest(`${this.endpoint}/globals`, {
54
+ method: "PATCH",
55
+ body: JSON.stringify({ globalFields })
56
+ }).then(res => {
57
+ // console.log(res)
58
+ if (res.messages[0].code === "0") {
59
+ resolve();
60
+ }
61
+ else {
62
+ reject(new FMError(res.messages[0].code, res.status, res));
63
+ }
64
+ })
65
+ .catch(e => {
66
+ reject(e);
67
+ });
68
+ });
69
+ }
70
+ script(name, parameter) {
71
+ return { name, parameter };
72
+ }
73
+ get endpoint() {
74
+ return `https://${this.hostname || this.host.hostname}/fmi/data/v2/databases/${encodeURI(this.name)}`;
75
+ }
76
+ get externalLoginInfo() {
77
+ // Returns an object that can be used when using this database object as an external source
78
+ let out = {
79
+ database: this.name,
80
+ username: undefined,
81
+ password: undefined,
82
+ oauthRequestId: undefined,
83
+ oauthIdentifier: undefined
84
+ };
85
+ switch (this.props.method) {
86
+ case "filemaker":
87
+ out.username = this.props.username;
88
+ out.password = this.props.password;
89
+ case "token":
90
+ throw "Token logins cannot be used for connecting to external sources. Please either use no login method, or open a second connection.";
91
+ case "oauth":
92
+ out.oauthRequestId = this.props.oauth.requestId;
93
+ out.oauthIdentifier = this.props.oauth.requestIdentifier;
94
+ case "claris":
95
+ throw "Claris logins cannot be used for connecting to external sources. Please either use no login method, or open a second connection.";
96
+ }
97
+ return out;
98
+ }
99
+ }
100
+ export default class FileMakerConnection extends database {
101
+ constructor(conn, externalDataSources = [], rejectUnauthorized = true) {
102
+ super(conn.database);
103
+ this.host = this;
104
+ this.hostname = conn.hostname;
105
+ this.externalSources = externalDataSources.map(i => { let e = new database(i, this); e.external = true; return e; });
106
+ this.rejectUnauthroized = rejectUnauthorized;
107
+ }
108
+ logout() {
109
+ return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
110
+ if (!this.token)
111
+ reject(new Error("Not logged in"));
112
+ let _fetch = yield fetch(`${this.endpoint}/sessions/${this.token}`, {
113
+ method: "DELETE",
114
+ headers: {
115
+ "content-type": "application/json"
116
+ }
117
+ });
118
+ let data = yield _fetch.json();
119
+ // console.log(data)
120
+ this._token = null;
121
+ this.name = null;
122
+ this.hostname = null;
123
+ resolve();
124
+ }));
125
+ }
126
+ login() {
127
+ return new Promise((resolve, reject) => {
128
+ if (this.token)
129
+ throw new Error("Already logged in. Run logout() first");
130
+ if (this.props.method === "filemaker") {
131
+ this.username = this.props.username;
132
+ this.name = this.props.database;
133
+ fetch(`${this.endpoint}/sessions`, {
134
+ hostname: this.hostname,
135
+ port: 443,
136
+ method: "POST",
137
+ headers: {
138
+ "Content-Type": "application/json",
139
+ "Authorization": "Basic " + Buffer.from(this.username + ":" + this.props.password).toString("base64")
140
+ }
141
+ }).then((res) => __awaiter(this, void 0, void 0, function* () {
142
+ if (res.status === 200) {
143
+ this._token = res.headers.get('x-fm-data-access-token');
144
+ resolve(this._token);
145
+ }
146
+ else {
147
+ let _res = (yield res.json());
148
+ reject(new FMError(_res.messages[0].code, _res.status, res));
149
+ }
150
+ }))
151
+ .catch(e => {
152
+ reject(e);
153
+ });
154
+ }
155
+ else if (this.props.method === "token") {
156
+ this._token = this.props.token;
157
+ this.name = this.props.database;
158
+ resolve(this.token);
159
+ }
160
+ else if (this.props.method === "oauth") {
161
+ this.name = this.props.database;
162
+ fetch(`${this.endpoint}/sessions`, {
163
+ method: "POST",
164
+ headers: {
165
+ "Content-Type": "application/json",
166
+ "X-FM-Data-OAuth-RequestId": this.props.oauth.requestId,
167
+ "X-FM-Data-OAuth-Identifier": this.props.oauth.requestIdentifier
168
+ },
169
+ body: "{}"
170
+ })
171
+ .then(res => res.json())
172
+ .then(res => {
173
+ let _res = res;
174
+ this._token = _res.headers["x-fm-data-access-token"];
175
+ resolve(this.token);
176
+ });
177
+ }
178
+ else if (this.props.method === "claris") {
179
+ this.name = this.props.database;
180
+ fetch(`${this.endpoint}/sessions`, {
181
+ method: "POST",
182
+ headers: {
183
+ "Content-Type": "application/json",
184
+ "Authorization": this.props.claris.fmid,
185
+ },
186
+ body: "{}"
187
+ })
188
+ .then(res => res.json())
189
+ .then(res => {
190
+ let _res = res;
191
+ this._token = _res.headers["x-fm-data-access-token"];
192
+ resolve(this.token);
193
+ });
194
+ }
195
+ });
196
+ }
197
+ get token() {
198
+ return this._token;
199
+ }
200
+ }
201
+ class layout {
202
+ constructor(database, name) {
203
+ this.database = database;
204
+ this.name = name;
205
+ }
206
+ get endpoint() {
207
+ return `${this.database.endpoint}/layouts/${this.name}`;
208
+ }
209
+ getScript(script) {
210
+ return new script(this, script);
211
+ }
212
+ createRecord() {
213
+ return new Promise((resolve, reject) => {
214
+ // Get the layout's metadata
215
+ this.getLayoutMeta().then(layout => {
216
+ let fields = {};
217
+ for (let _field of this.metadata.fieldMetaData) {
218
+ fields[_field.name] = "";
219
+ }
220
+ resolve(new record(this, -1, 0, fields));
221
+ });
222
+ });
223
+ }
224
+ getRecord(recordId) {
225
+ return new record(this, recordId);
226
+ }
227
+ newFind() {
228
+ return new find(this);
229
+ }
230
+ runScript(script) {
231
+ return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
232
+ let url = `${this.endpoint}/script/${encodeURI(script.name)}`;
233
+ if (script.parameter)
234
+ url += "?" + encodeURI(script.parameter);
235
+ this.database.apiRequest(url, {
236
+ port: 443,
237
+ method: "GET"
238
+ })
239
+ .then(res => {
240
+ if (res.messages[0].code === "0") {
241
+ resolve(res.response);
242
+ }
243
+ else {
244
+ // console.log(res)
245
+ reject(new FMError(res.messages[0].code, res.status, res));
246
+ }
247
+ })
248
+ .catch(e => {
249
+ reject(e);
250
+ });
251
+ }));
252
+ }
253
+ getLayoutMeta() {
254
+ return new Promise((resolve, reject) => {
255
+ this.database.apiRequest(this.endpoint).then(res => {
256
+ this.metadata = res.response;
257
+ resolve(this);
258
+ })
259
+ .catch(e => reject(e));
260
+ });
261
+ }
262
+ }
263
+ class record extends EventEmitter {
264
+ constructor(layout, recordId, modId = recordId, fieldData = {}, portalData = null) {
265
+ super();
266
+ this.layout = layout;
267
+ this.recordId = recordId;
268
+ this.modId = modId;
269
+ this.fields = this.processFieldData(fieldData);
270
+ this.portals = [];
271
+ if (portalData) {
272
+ this.processPortalData(portalData);
273
+ }
274
+ this.on("saved", () => {
275
+ for (let field of this.fields)
276
+ field.edited = false;
277
+ });
278
+ }
279
+ get endpoint() {
280
+ return `${this.layout.endpoint}/records/${this.recordId}`;
281
+ }
282
+ get edited() {
283
+ return !!this.fields.find(i => i.edited);
284
+ }
285
+ processPortalData(portalData) {
286
+ for (let item of Object.keys(portalData)) {
287
+ this.portals.push(new portal(this, item, portalData[item].map(item => {
288
+ let fieldData = item;
289
+ delete fieldData.recordId;
290
+ delete fieldData.modId;
291
+ return new portalItem(this, item.recordId, item.modId, fieldData);
292
+ })));
293
+ }
294
+ }
295
+ processFieldData(fieldData) {
296
+ return Object.keys(fieldData).map(item => {
297
+ return new field(this, item, fieldData[item]);
298
+ });
299
+ }
300
+ get() {
301
+ if (this.recordId === -1) {
302
+ throw "Cannot get this record until a commit() is done.";
303
+ }
304
+ return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
305
+ if (!this.layout.metadata)
306
+ yield this.layout.getLayoutMeta();
307
+ this.layout.database.apiRequest(this.endpoint, {
308
+ port: 443,
309
+ method: "GET"
310
+ })
311
+ .then(res => {
312
+ if (res.messages[0].code === "0") {
313
+ // console.log(res, res.response.data)
314
+ this.modId = res.response.data[0].modId;
315
+ this.fields = this.processFieldData(res.response.data[0].fieldData);
316
+ this.portalData = [];
317
+ if (res.response.data[0].portalData)
318
+ this.processPortalData(res.response.data[0].portalData);
319
+ resolve(this);
320
+ }
321
+ else {
322
+ reject(new FMError(res.messages[0].code, res.status, res));
323
+ }
324
+ })
325
+ .catch(e => {
326
+ reject(e);
327
+ });
328
+ }));
329
+ }
330
+ commit(extraBody = {
331
+ fieldData: undefined
332
+ }) {
333
+ return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
334
+ let data = this.toObject();
335
+ delete data.recordId;
336
+ delete data.modId;
337
+ if (this.recordId === -1) {
338
+ // This is a new record
339
+ extraBody.fieldData = data.fieldData;
340
+ this.layout.database.apiRequest(`${this.layout.endpoint}/records`, {
341
+ port: 443,
342
+ method: "POST",
343
+ body: JSON.stringify(extraBody)
344
+ })
345
+ .then(res => {
346
+ if (typeof res.response.scriptError !== "undefined" && res.response.scriptError !== '0') {
347
+ reject(new FMError(res.response.scriptError, res.status, res));
348
+ }
349
+ else if (res.messages[0].code === "0") {
350
+ console.log(res);
351
+ this.recordId = parseInt(res.response.recordId);
352
+ this.modId = parseInt(res.response.modId);
353
+ resolve(this);
354
+ }
355
+ else {
356
+ reject(new FMError(res.messages[0].code, res.status, res));
357
+ }
358
+ })
359
+ .catch(e => {
360
+ reject(e);
361
+ });
362
+ return;
363
+ }
364
+ for (let item of Object.keys(extraBody))
365
+ data[item] = extraBody[item];
366
+ this.layout.database.apiRequest(this.endpoint, {
367
+ port: 443,
368
+ method: "PATCH",
369
+ body: JSON.stringify(data)
370
+ })
371
+ .then(res => {
372
+ if (typeof res.response.scriptError !== "undefined" && res.response.scriptError !== '0') {
373
+ reject(new FMError(res.response.scriptError, res.status, res));
374
+ }
375
+ else if (res.messages[0].code === "0") {
376
+ console.log(res);
377
+ this.modId = res.response.modId;
378
+ this.emit("saved");
379
+ resolve(this);
380
+ }
381
+ else {
382
+ reject(new FMError(res.messages[0].code, res.status, res));
383
+ }
384
+ })
385
+ .catch(e => {
386
+ reject(e);
387
+ });
388
+ }));
389
+ }
390
+ getField(field) {
391
+ return this.fields.find(_field => _field.id === field);
392
+ }
393
+ getPortal(portal) {
394
+ return this.portals.find(p => p.name === portal);
395
+ }
396
+ toObject(filter = (a) => a.edited, portalFilter = (a) => a.records.find(record => record.edited), portalRowFilter = (a) => a.edited, portalFieldFilter = (a) => a.edited) {
397
+ let fields_processed = {};
398
+ for (let field of this.fields.filter(field => filter(field))) {
399
+ fields_processed[field.id] = field.value;
400
+ }
401
+ let obj = {
402
+ "recordId": this.recordId,
403
+ "modId": this.modId,
404
+ "fieldData": fields_processed
405
+ };
406
+ // Check if there's been any edited portal information
407
+ let portals = this.portals.filter(a => portalFilter(a));
408
+ if (portals) {
409
+ obj["portalData"] = {};
410
+ for (let portal of portals) {
411
+ obj["portalData"][portal.name] = portal.records.filter(a => portalRowFilter(a)).map(record => {
412
+ return record.toObject(portalFieldFilter);
413
+ });
414
+ }
415
+ }
416
+ return obj;
417
+ }
418
+ }
419
+ class field {
420
+ constructor(record, id, contents) {
421
+ this.record = record;
422
+ this.id = id;
423
+ this.value = contents;
424
+ this.edited = false;
425
+ }
426
+ set(content) {
427
+ if (this.dataType === "container")
428
+ throw "Cannot set container value using set(). Use upload() instead.";
429
+ this.value = content;
430
+ this.edited = true;
431
+ }
432
+ get dataType() {
433
+ if (!this.record.layout.metadata)
434
+ return "unknown";
435
+ if (this.record instanceof portalItem) {
436
+ return this.record.layout.metadata.portalMetaData.find(i => i.name === this.id).result || "unknown";
437
+ }
438
+ else {
439
+ return this.record.layout.metadata.fieldMetaData.find(i => i.name === this.id).result || "unknown";
440
+ }
441
+ }
442
+ upload(buffer, filename, mime) {
443
+ if (this.dataType !== "container")
444
+ throw "Cannot upload a file to the field; " + this.id + " (not a container field)";
445
+ return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
446
+ let form = new FormData();
447
+ form.append("upload", new File([buffer], filename, { type: mime }));
448
+ let _fetch = yield fetch(`${this.record.endpoint}/containers/${this.id}/1`, {
449
+ method: "POST",
450
+ headers: { "Authorization": "Bearer " + this.record.layout.database.token },
451
+ body: form
452
+ }).then(res => res.json())
453
+ .then(data => {
454
+ let _res = data;
455
+ if (_res.messages[0].code === "0") {
456
+ resolve();
457
+ }
458
+ else {
459
+ reject(new FMError(_res.messages[0].code, _res.status, data));
460
+ }
461
+ })
462
+ .catch(e => { reject(e); });
463
+ }));
464
+ }
465
+ download() {
466
+ return new Promise((resolve, reject) => {
467
+ https.get(this.value.toString(), (res) => {
468
+ resolve(res);
469
+ });
470
+ });
471
+ }
472
+ }
473
+ class portal {
474
+ constructor(record, name, records = []) {
475
+ this.record = record;
476
+ this.name = name;
477
+ this.records = records;
478
+ for (let item of this.records)
479
+ item.attachPortal(this);
480
+ }
481
+ }
482
+ class portalItem extends record {
483
+ constructor(record, recordId, modId = recordId, fieldData = {}) {
484
+ super(record.layout, recordId, modId, fieldData);
485
+ record.on("saved", () => {
486
+ this.emit("saved");
487
+ });
488
+ }
489
+ attachPortal(portal) {
490
+ this.portal = portal;
491
+ }
492
+ commit(extraBody = { fieldData: undefined }) {
493
+ return this.portal.record.commit(extraBody);
494
+ }
495
+ toObject(fieldFilter) {
496
+ super.toObject();
497
+ let res = {
498
+ recordId: this.recordId, modId: this.modId
499
+ };
500
+ for (let field of this.fields.filter(a => fieldFilter(a)))
501
+ res[field.id] = field.value;
502
+ // console.log(res)
503
+ return res;
504
+ }
505
+ }
506
+ class find {
507
+ constructor(layout) {
508
+ this.layout = layout;
509
+ this.queries = [];
510
+ this.scripts = {
511
+ "script": null,
512
+ "script.prerequeset": null,
513
+ "script.presort": null // Runs before the sort
514
+ };
515
+ this.sort = [];
516
+ }
517
+ toObject() {
518
+ let out = { query: this.queries, sort: undefined };
519
+ if (this.sort.length !== 0)
520
+ out.sort = this.sort;
521
+ for (let item of Object.keys(this.scripts)) {
522
+ if (this.scripts[item]) {
523
+ out[item] = this.scripts[item].name;
524
+ if (this.scripts[item].parameter)
525
+ out[item + ".param"] = this.scripts[item].parameter;
526
+ }
527
+ }
528
+ return out;
529
+ }
530
+ addRequests(...requests) {
531
+ for (let item of requests)
532
+ this.queries.push(item);
533
+ return this;
534
+ }
535
+ addSort(fieldName, sortOrder) {
536
+ this.sort.push({ fieldName, sortOrder });
537
+ return this;
538
+ }
539
+ find() {
540
+ return new Promise((resolve, reject) => {
541
+ // console.log(this.#toObject())
542
+ this.layout.database.apiRequest(`${this.layout.endpoint}/_find`, {
543
+ port: 443,
544
+ method: "POST",
545
+ body: JSON.stringify(this.toObject())
546
+ }).then((res) => __awaiter(this, void 0, void 0, function* () {
547
+ // // console.log(res)
548
+ if (res.messages[0].code === "0") {
549
+ // console.log("RESOLVING")
550
+ if (!this.layout.metadata)
551
+ yield this.layout.getLayoutMeta();
552
+ let data = res.response.data.map(item => {
553
+ return new record(this.layout, item.recordId, item.modId, item.fieldData, item.portalData);
554
+ });
555
+ resolve(data);
556
+ }
557
+ else {
558
+ reject(new FMError(res.messages[0].code, res.status, res));
559
+ }
560
+ }))
561
+ .catch(e => {
562
+ reject(e);
563
+ });
564
+ });
565
+ }
566
+ }
567
+ export class FMError extends Error {
568
+ constructor(code, httpStatus, res) {
569
+ if (typeof code === "string")
570
+ code = parseInt(code);
571
+ super(errs.find(err => err.e === code).d || "Unknown error");
572
+ this.httpStatus = httpStatus;
573
+ this.res = res;
574
+ this.messages = res.messages;
575
+ this.code = typeof code === "string" ? parseInt(code) : code;
576
+ Error.captureStackTrace(this, FMError);
577
+ }
578
+ }
579
+ //
580
+ // module.exports = {
581
+ // default: {FileMakerConnection}
582
+ // }