@jd-data-limited/easy-fm 1.1.6-alpha1 → 1.2.0-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.ts CHANGED
@@ -11,7 +11,7 @@ import fetch, {
11
11
  fileFrom,
12
12
  fileFromSync,
13
13
  FormData,
14
- Headers,
14
+ Headers, HeadersInit,
15
15
  Request,
16
16
  Response
17
17
  } from 'node-fetch';
@@ -23,31 +23,26 @@ import path from "path"
23
23
  // @ts-ignore
24
24
  import {fileURLToPath} from "url"
25
25
  import * as https from "https";
26
+ import * as moment from "moment"
26
27
 
27
28
  // import * as btoa from "btoa";
28
-
29
- interface connectionOptions {
30
- hostname: string | undefined,
31
- database: databaseOptions
32
- }
33
-
34
- interface hostConnectionOptions {
35
- hostname: string
36
- database: loginOptionsOAuth | loginOptionsFileMaker | loginOptionsClaris | loginOptionsToken,
37
- externalSources: (loginOptionsOAuth | loginOptionsFileMaker | loginOptionsClaris | loginOptionsToken)[]
29
+ interface databaseOptionsBase {
30
+ database: string
31
+ credentials: loginOptionsOAuth | loginOptionsFileMaker | loginOptionsClaris | loginOptionsToken,
38
32
  }
39
33
 
40
- interface databaseOptions {
41
- method: any
34
+ interface databaseOptionsWithExternalSources extends databaseOptionsBase {
42
35
  database: string
36
+ credentials: loginOptionsOAuth | loginOptionsFileMaker | loginOptionsClaris | loginOptionsToken,
37
+ externalSources: databaseOptionsBase[]
43
38
  }
44
39
 
45
- interface loginOptionsToken extends databaseOptions {
40
+ interface loginOptionsToken {
46
41
  method: "token"
47
42
  token: string
48
43
  }
49
44
 
50
- interface loginOptionsOAuth extends databaseOptions {
45
+ interface loginOptionsOAuth {
51
46
  method: "oauth"
52
47
  oauth: {
53
48
  requestId: string,
@@ -55,13 +50,13 @@ interface loginOptionsOAuth extends databaseOptions {
55
50
  }
56
51
  }
57
52
 
58
- interface loginOptionsFileMaker extends databaseOptions {
53
+ interface loginOptionsFileMaker {
59
54
  method: "filemaker"
60
55
  username: string,
61
56
  password: string,
62
57
  }
63
58
 
64
- interface loginOptionsClaris extends databaseOptions {
59
+ interface loginOptionsClaris {
65
60
  method: "claris"
66
61
  claris: {
67
62
  fmid: string
@@ -86,33 +81,121 @@ interface recordObject {
86
81
  recordId: number
87
82
  modId: number,
88
83
  fieldData: any,
89
- portalData: portalDataObject | undefined
84
+ portalData?: portalDataObject
85
+ }
86
+
87
+ interface fileMakerResponse {
88
+ response: any,
89
+ messages: any[]
90
90
  }
91
91
 
92
92
  interface portalDataObject {
93
93
  [key: string]: recordObject
94
94
  }
95
95
 
96
- export default class FileMakerConnection extends EventEmitter {
96
+ interface fieldMetaData {
97
+ name: string,
98
+ type: string,
99
+ displayType: string,
100
+ result: string,
101
+ global: boolean,
102
+ autoEnter: boolean,
103
+ fourDigitYear: boolean,
104
+ maxRepeat: number,
105
+ maxCharacters: number,
106
+ notEmpty: boolean,
107
+ numeric: boolean,
108
+ timeOfDay: false,
109
+ repetitionStart: number,
110
+ repetitionEnd: number
111
+ }
112
+
113
+ interface FMHostMetadata {
114
+ productInfo: {
115
+ buildDate: Date,
116
+ name: string,
117
+ version: string,
118
+ dateFormat: string,
119
+ timeFormat: string,
120
+ timeStampFormat: string
121
+ }
122
+ }
123
+
124
+ export default class FMHost {
125
+ readonly hostname: string
126
+ readonly timezoneOffset: number
127
+ readonly verify: boolean
128
+ metadata: FMHostMetadata
129
+
130
+ constructor(_hostname: string, timezoneOffset = 0 - (new Date()).getTimezoneOffset(), verify = true) {
131
+ if (!(/^https?:\/\//).test(_hostname)) throw "hostname MUST begin with either http:// or https://"
132
+ this.hostname = _hostname
133
+ this.timezoneOffset = timezoneOffset
134
+ this.verify = verify
135
+ }
136
+
137
+ async listDatabases(credentials?: loginOptionsOAuth | loginOptionsFileMaker | loginOptionsClaris) {
138
+ let headers = {}
139
+ if (credentials) {
140
+ headers = generateAuthorizationHeaders(credentials)
141
+ }
142
+
143
+ let _fetch = await fetch(`${this.hostname}/fmi/data/v2/databases`, {
144
+ method: "GET",
145
+ headers
146
+ })
147
+ let data = await _fetch.json() as fileMakerResponse
148
+ // console.log(data.messages[0])
149
+
150
+ if (data.messages[0].code === "0") {
151
+ return data.response.databases
152
+ } else {
153
+ // @ts-ignore
154
+ throw new FMError(data.messages[0].code, data.status, data)
155
+ }
156
+ }
157
+
158
+ database(data: databaseOptionsWithExternalSources) {
159
+ return new Database(this, data)
160
+ }
161
+
162
+ async getMetadata() {
163
+ if (this.metadata) return this.metadata
164
+
165
+ let _fetch = await fetch(`${this.hostname}/fmi/data/v2/productInfo`, {
166
+ method: "GET",
167
+ })
168
+ let data = await _fetch.json() as fileMakerResponse
169
+ // console.log(data.messages[0])
170
+
171
+ if (data.messages[0].code === "0") {
172
+ this.metadata = data.response
173
+ return data.response
174
+ } else {
175
+ // @ts-ignore
176
+ throw new FMError(data.messages[0].code, data.status, data)
177
+ }
178
+ }
179
+ }
180
+
181
+ export class Database extends EventEmitter {
97
182
  private _token: any;
98
- private hostname: string;
99
- public readonly rejectUnauthroized: boolean;
100
- private databaseConDetails: hostConnectionOptions
101
- public name: string;
183
+ readonly host: FMHost;
184
+ private connection_details: databaseOptionsWithExternalSources
185
+ readonly name: string;
102
186
 
103
- constructor(conn: hostConnectionOptions, rejectUnauthorized = true) {
187
+ constructor(host: FMHost, conn: databaseOptionsWithExternalSources) {
104
188
  super()
105
- this.hostname = conn.hostname
106
- this.name = conn.database.database
107
- this.databaseConDetails = conn
108
- this.rejectUnauthroized = rejectUnauthorized
189
+ this.host = host
190
+ this.name = conn.database
191
+ this.connection_details = conn
109
192
  }
110
193
 
111
- private generateExternalSourceLogin(data: databaseOptions | loginOptionsFileMaker) {
112
- if (data.method === "filemaker") {
113
- let _data = <loginOptionsFileMaker>data
194
+ private generateExternalSourceLogin(data: databaseOptionsBase) {
195
+ if (data.credentials.method === "filemaker") {
196
+ let _data = <loginOptionsFileMaker>data.credentials
114
197
  return {
115
- database: _data.database,
198
+ database: data.database,
116
199
  username: _data.username,
117
200
  password: _data.password
118
201
  }
@@ -134,81 +217,47 @@ export default class FileMakerConnection extends EventEmitter {
134
217
  let data = await _fetch.json()
135
218
  // console.log(data)
136
219
  this._token = null
137
- this.hostname = null
138
220
  resolve()
139
221
  })
140
222
  }
141
223
 
142
224
  login() {
143
- return new Promise<string>((resolve, reject) => {
225
+ return new Promise<string>(async (resolve, reject) => {
226
+ try {
227
+ await this.host.getMetadata()
228
+ } catch (e) {
229
+ reject(e)
230
+ return
231
+ }
144
232
  if (this.token) throw new Error("Already logged in. Run logout() first")
145
233
 
146
- if (this.databaseConDetails.database.method === "filemaker") {
234
+ if (this.connection_details.credentials.method === "token") {
235
+ this._token = (<loginOptionsToken>this.connection_details.credentials).token
236
+ resolve(this.token)
237
+ } else {
147
238
  fetch(`${this.endpoint}/sessions`, {
148
- hostname: this.hostname,
239
+ hostname: this.host.hostname,
149
240
  port: 443,
150
241
  method: "POST",
151
- headers: {
152
- "Content-Type": "application/json",
153
- "Authorization": "Basic " + Buffer.from((<loginOptionsFileMaker>this.databaseConDetails.database).username + ":" + (<loginOptionsFileMaker>this.databaseConDetails.database).password).toString("base64")
154
- },
242
+ headers: generateAuthorizationHeaders(this.connection_details.credentials) as unknown as HeadersInit,
155
243
  body: JSON.stringify({
156
- fmDataSource: this.databaseConDetails.externalSources.map(i => {
157
- let _i = <databaseOptions>i
244
+ fmDataSource: this.connection_details.externalSources.map(i => {
245
+ let _i = <databaseOptionsBase>i
158
246
  return this.generateExternalSourceLogin(_i)
159
247
  })
160
248
  })
161
249
  }).then(async res => {
250
+ let _res = <any>(await res.json())
162
251
  if (res.status === 200) {
163
252
  this._token = res.headers.get('x-fm-data-access-token')
164
253
  resolve(this._token)
165
254
  } else {
166
- let _res = <any>(await res.json())
167
255
  reject(new FMError(_res.messages[0].code, _res.status, res))
168
256
  }
169
257
  })
170
258
  .catch(e => {
171
259
  reject(e)
172
260
  })
173
- } else if (this.databaseConDetails.database.method === "token") {
174
- this._token = (<loginOptionsToken>this.databaseConDetails.database).token
175
- resolve(this.token)
176
- } else if (this.databaseConDetails.database.method === "oauth") {
177
- fetch(`${this.endpoint}/sessions`, {
178
- method: "POST",
179
- headers: {
180
- "Content-Type": "application/json",
181
- "X-FM-Data-OAuth-RequestId": (<loginOptionsOAuth>this.databaseConDetails.database).oauth.requestId,
182
- "X-FM-Data-OAuth-Identifier": (<loginOptionsOAuth>this.databaseConDetails.database).oauth.requestIdentifier
183
- },
184
- body: JSON.stringify({
185
- fmDataSource: this.databaseConDetails.externalSources.map(i => {
186
- let _i = <databaseOptions>i
187
- return this.generateExternalSourceLogin(_i)
188
- })
189
- })
190
- })
191
- .then(res => res.json())
192
- .then(res => {
193
- let _res = <any>res
194
- this._token = _res.headers["x-fm-data-access-token"]
195
- resolve(this.token)
196
- })
197
- } else if (this.databaseConDetails.database.method === "claris") {
198
- fetch(`${this.endpoint}/sessions`, {
199
- method: "POST",
200
- headers: {
201
- "Content-Type": "application/json",
202
- "Authorization": (<loginOptionsClaris>this.databaseConDetails.database).claris.fmid,
203
- },
204
- body: "{}"
205
- })
206
- .then(res => res.json())
207
- .then(res => {
208
- let _res = <any>res
209
- this._token = _res.headers["x-fm-data-access-token"]
210
- resolve(this.token)
211
- })
212
261
  }
213
262
  })
214
263
  }
@@ -218,22 +267,22 @@ export default class FileMakerConnection extends EventEmitter {
218
267
  }
219
268
 
220
269
  get endpoint(): string {
221
- return `https://${this.hostname}/fmi/data/v2/databases/${this.name}`
270
+ return `${this.host.hostname}/fmi/data/v2/databases/${this.name}`
222
271
  }
223
272
 
224
273
  async apiRequest(url: string | Request, options: any = {}, autoRelogin = true): Promise<any> {
225
274
  if (!options.headers) options.headers = {}
226
275
  options.headers["content-type"] = options.headers["content-type"] ? options.headers["content-type"] : "application/json"
227
276
  options.headers["authorization"] = "Bearer " + this._token
228
- options.rejectUnauthorized = this.rejectUnauthroized
277
+ options.rejectUnauthorized = this.host.verify
229
278
 
230
279
  let _fetch = await fetch(url, options)
231
- let data = await _fetch.json()
232
- console.log(data.messages[0])
280
+ let data = await _fetch.json() as fileMakerResponse
281
+ // console.log(data.messages[0])
233
282
  if (data.messages[0].code == 952 && autoRelogin) {
234
283
  this._token = null
235
284
  await this.login()
236
- return await this.apiRequest(url, options,false)
285
+ return await this.apiRequest(url, options, false)
237
286
  }
238
287
  return (data as any)
239
288
  }
@@ -272,11 +321,12 @@ export default class FileMakerConnection extends EventEmitter {
272
321
  }
273
322
 
274
323
  class layout {
275
- readonly database: FileMakerConnection;
324
+ readonly database: Database;
325
+ readonly records = new layoutRecordManager(this)
276
326
  protected name: string;
277
327
  metadata: any;
278
328
 
279
- constructor(database: FileMakerConnection, name: string) {
329
+ constructor(database: Database, name: string) {
280
330
  this.database = database
281
331
  this.name = name
282
332
  }
@@ -285,29 +335,25 @@ class layout {
285
335
  return `${this.database.endpoint}/layouts/${this.name}`
286
336
  }
287
337
 
288
- getScript(script) {
289
- return new script(this, script)
290
- }
291
-
338
+ /**
339
+ * @deprecated use layout.records.create() instead
340
+ */
292
341
  createRecord(): Promise<record | Error | void> {
293
- return new Promise((resolve, reject) => {
294
- // Get the layout's metadata
295
- this.getLayoutMeta().then(layout => {
296
- let fields = {}
297
- for (let _field of this.metadata.fieldMetaData) {
298
- fields[_field.name] = ""
299
- }
300
- resolve(new record(this, -1, 0, fields))
301
- }).catch(e => {reject(e)})
302
- })
342
+ return this.records.create()
303
343
  }
304
344
 
305
- getRecord(recordId): record {
306
- return new record(this, recordId)
345
+ /**
346
+ * @deprecated use layout.records.get() instead
347
+ */
348
+ getRecord(recordId): Promise<record> {
349
+ return this.records.get(recordId)
307
350
  }
308
351
 
352
+ /**
353
+ * @deprecated use layout.records.find() instead
354
+ */
309
355
  newFind(): find {
310
- return new find(this)
356
+ return this.records.find()
311
357
  }
312
358
 
313
359
  runScript(script): Promise<string | FMError | Error> {
@@ -334,6 +380,11 @@ class layout {
334
380
 
335
381
  public getLayoutMeta(): Promise<layout | FMError> {
336
382
  return new Promise((resolve, reject) => {
383
+ if (this.metadata) {
384
+ resolve(this.metadata)
385
+ return
386
+ }
387
+
337
388
  this.database.apiRequest(this.endpoint).then(res => {
338
389
  this.metadata = res.response
339
390
  resolve(this)
@@ -343,6 +394,54 @@ class layout {
343
394
  }
344
395
  }
345
396
 
397
+ class layoutRecordManager {
398
+ readonly layout: layout
399
+
400
+ constructor(layout: layout) {
401
+ this.layout = layout
402
+ }
403
+
404
+ create(): Promise<record | Error | void> {
405
+ return new Promise((resolve, reject) => {
406
+ // Get the layout's metadata
407
+ this.layout.getLayoutMeta().then(layout => {
408
+ let fields = {}
409
+ for (let _field of this.layout.metadata.fieldMetaData) {
410
+ fields[_field.name] = ""
411
+ }
412
+ resolve(new record(this, -1, 0, fields))
413
+ }).catch(e => {
414
+ reject(e)
415
+ })
416
+ })
417
+ }
418
+
419
+ get(recordId): Promise<record> {
420
+ return new Promise((resolve, reject) => {
421
+ let record
422
+ this.layout.getLayoutMeta()
423
+ .then(layout => {
424
+ record = new record(this.layout, recordId)
425
+ return record.get()
426
+ })
427
+ .then(() => {
428
+ resolve(record)
429
+ })
430
+ .catch(e => {
431
+ reject(e)
432
+ })
433
+ })
434
+ }
435
+
436
+ range() {
437
+ return new recordGetRange(this.layout)
438
+ }
439
+
440
+ find(): find {
441
+ return new find(this.layout)
442
+ }
443
+ }
444
+
346
445
  class record extends EventEmitter {
347
446
  readonly layout: layout;
348
447
  public recordId: number;
@@ -387,7 +486,33 @@ class record extends EventEmitter {
387
486
 
388
487
  processFieldData(fieldData): field[] {
389
488
  return Object.keys(fieldData).map(item => {
390
- return new field(this, item, fieldData[item])
489
+ let _field = new field(this, item, fieldData[item])
490
+ if (!!fieldData[item]) {
491
+ if (_field.metadata.result === "timeStamp") {
492
+ // @ts-ignore
493
+ let date = moment.default(fieldData[item], this.layout.database.host.metadata.productInfo.timeStampFormat)
494
+ .utcOffset(this.layout.database.host.timezoneOffset, true)
495
+ .local()
496
+ _field.set(date.toDate())
497
+ _field.edited = false
498
+
499
+ } else if (_field.metadata.result === "time") {
500
+ // @ts-ignore
501
+ let date = moment.default(fieldData[item], this.layout.database.host.metadata.productInfo.timeFormat)
502
+ .utcOffset(this.layout.database.host.timezoneOffset, true)
503
+ .local()
504
+ _field.set(date.toDate())
505
+ _field.edited = false
506
+ } else if (_field.metadata.result === "date") {
507
+ // @ts-ignore
508
+ let date = moment.default(fieldData[item], this.layout.database.host.metadata.productInfo.dateFormat)
509
+ .utcOffset(this.layout.database.host.timezoneOffset, true)
510
+ .local()
511
+ _field.set(date.toDate())
512
+ _field.edited = false
513
+ }
514
+ }
515
+ return _field
391
516
  })
392
517
  }
393
518
 
@@ -441,7 +566,6 @@ class record extends EventEmitter {
441
566
  if (typeof res.response.scriptError !== "undefined" && res.response.scriptError !== '0') {
442
567
  reject(new FMError(res.response.scriptError, res.status, res))
443
568
  } else if (res.messages[0].code === "0") {
444
- console.log(res)
445
569
  this.recordId = parseInt(res.response.recordId)
446
570
  this.modId = parseInt(res.response.modId)
447
571
  resolve(this)
@@ -456,17 +580,16 @@ class record extends EventEmitter {
456
580
  return
457
581
  }
458
582
 
459
- for (let item of Object.keys(extraBody)) data[item] = extraBody[item]
583
+ for (let item of Object.keys(data)) extraBody[item] = data[item]
460
584
  this.layout.database.apiRequest(this.endpoint, {
461
585
  port: 443,
462
586
  method: "PATCH",
463
- body: JSON.stringify(data)
587
+ body: JSON.stringify(extraBody)
464
588
  })
465
589
  .then(res => {
466
590
  if (typeof res.response.scriptError !== "undefined" && res.response.scriptError !== '0') {
467
591
  reject(new FMError(res.response.scriptError, res.status, res))
468
592
  } else if (res.messages[0].code === "0") {
469
- console.log(res)
470
593
  this.modId = res.response.modId
471
594
  this.emit("saved")
472
595
  resolve(this)
@@ -487,7 +610,7 @@ class record extends EventEmitter {
487
610
  getPortal(portal) {
488
611
  return this.portals.find(p => p.name === portal)
489
612
  }
490
-
613
+
491
614
  delete(): Promise<void> {
492
615
  return new Promise(async (resolve, reject) => {
493
616
  this.layout.database.apiRequest(this.endpoint, {
@@ -543,7 +666,26 @@ class record extends EventEmitter {
543
666
  ): recordObject {
544
667
  let fields_processed = {}
545
668
  for (let field of this.fields.filter(field => filter(field))) {
546
- fields_processed[field.id] = field.value
669
+ let value = field.value
670
+ if (value instanceof Date) {
671
+ // @ts-ignore
672
+ let _value = moment.default(value)
673
+ .utcOffset(this.layout.database.host.timezoneOffset)
674
+
675
+ // @ts-ignore
676
+
677
+ switch (field.metadata.result) {
678
+ case "time":
679
+ value = _value.format(this.layout.database.host.metadata.productInfo.timeFormat.replace("dd", "DD"))
680
+ break
681
+ case "date":
682
+ value = _value.format(this.layout.database.host.metadata.productInfo.dateFormat.replace("dd", "DD"))
683
+ break
684
+ default:
685
+ value = _value.format(this.layout.database.host.metadata.productInfo.timeStampFormat.replace("dd", "DD"))
686
+ }
687
+ }
688
+ fields_processed[field.id] = value
547
689
  }
548
690
  let obj = {
549
691
  "recordId": this.recordId,
@@ -568,33 +710,93 @@ class record extends EventEmitter {
568
710
  class field {
569
711
  record: record;
570
712
  id: number;
571
- value: string | number;
713
+ private _value: string | number | Date;
572
714
  edited: boolean;
573
715
 
574
716
  constructor(record, id, contents) {
575
717
  this.record = record
576
718
  this.id = id
577
- this.value = contents
719
+ this._value = contents
578
720
  this.edited = false
579
721
  }
580
722
 
581
- set(content) {
582
- if (this.dataType === "container") throw "Cannot set container value using set(). Use upload() instead."
583
- this.value = content
723
+ set(content: string | number | Date) {
724
+ if (this.metadata.result === "container") throw "Cannot set container value using set(). Use upload() instead."
725
+ if (
726
+ (this.metadata.result === "timeStamp" ||
727
+ this.metadata.result === "date" ||
728
+ this.metadata.result === "time")
729
+ && !(content instanceof Date) && !!content
730
+ ) {
731
+ throw "Value was not an instance of Date: " + content
732
+ }
733
+ this._value = content
584
734
  this.edited = true
585
735
  }
586
736
 
587
- get dataType() {
588
- if (!this.record.layout.metadata) return "unknown"
737
+ get metadata(): fieldMetaData {
738
+ if (!this.record.layout.metadata) {
739
+ // Default to a regular text field
740
+ return {
741
+ name: this.id.toString(),
742
+ type: 'normal',
743
+ displayType: 'editText',
744
+ result: 'text',
745
+ global: false,
746
+ autoEnter: true,
747
+ fourDigitYear: false,
748
+ maxRepeat: 1,
749
+ maxCharacters: 0,
750
+ notEmpty: false,
751
+ numeric: false,
752
+ timeOfDay: false,
753
+ repetitionStart: 1,
754
+ repetitionEnd: 1
755
+ } as fieldMetaData
756
+ }
589
757
  if (this.record instanceof portalItem) {
590
- return this.record.layout.metadata.portalMetaData.find(i => i.name === this.id).result || "unknown"
758
+ return this.record.layout.metadata.portalMetaData.find(i => i.name === this.id) as fieldMetaData || {
759
+ name: this.id.toString(),
760
+ type: 'normal',
761
+ displayType: 'editText',
762
+ result: 'text',
763
+ global: false,
764
+ autoEnter: true,
765
+ fourDigitYear: false,
766
+ maxRepeat: 1,
767
+ maxCharacters: 0,
768
+ notEmpty: false,
769
+ numeric: false,
770
+ timeOfDay: false,
771
+ repetitionStart: 1,
772
+ repetitionEnd: 1
773
+ } as fieldMetaData
591
774
  } else {
592
- return this.record.layout.metadata.fieldMetaData.find(i => i.name === this.id).result || "unknown"
775
+ return this.record.layout.metadata.fieldMetaData.find(i => i.name === this.id) as fieldMetaData || {
776
+ name: this.id.toString(),
777
+ type: 'normal',
778
+ displayType: 'editText',
779
+ result: 'text',
780
+ global: false,
781
+ autoEnter: true,
782
+ fourDigitYear: false,
783
+ maxRepeat: 1,
784
+ maxCharacters: 0,
785
+ notEmpty: false,
786
+ numeric: false,
787
+ timeOfDay: false,
788
+ repetitionStart: 1,
789
+ repetitionEnd: 1
790
+ } as fieldMetaData
593
791
  }
594
792
  }
595
793
 
794
+ get value() {
795
+ return this._value
796
+ }
797
+
596
798
  upload(buffer: Buffer, filename: string, mime: string): Promise<void | FMError> {
597
- if (this.dataType !== "container") throw "Cannot upload a file to the field; " + this.id + " (not a container field)"
799
+ if (this.metadata.result !== "container") throw "Cannot upload a file to the field; " + this.id + " (not a container field)"
598
800
  return new Promise(async (resolve, reject) => {
599
801
  let form = new FormData()
600
802
  form.append("upload", new File([buffer], filename, {type: mime}))
@@ -734,24 +936,27 @@ class recordGetRange extends recordGetOperation {
734
936
  return "?" + params.join("&")
735
937
  }
736
938
 
737
- run() {
939
+ run(): Promise<record[]> {
738
940
  return new Promise((resolve, reject) => {
739
941
  // console.log(this.#toObject())
740
- this.layout.database.apiRequest(`${this.layout.endpoint}/records${this.generateQueryParams()}`, {
741
- method: "GET"
742
- }).then(async res => {
743
- // // console.log(res)
744
- if (res.messages[0].code === "0") {
745
- // console.log("RESOLVING")
746
- if (!this.layout.metadata) await this.layout.getLayoutMeta()
747
- let data = res.response.data.map(item => {
748
- return new record(this.layout, item.recordId, item.modId, item.fieldData, item.portalData)
749
- })
750
- resolve(data)
751
- } else {
752
- reject(new FMError(res.messages[0].code, res.status, res))
753
- }
942
+ this.layout.getLayoutMeta().then(() => {
943
+ return this.layout.database.apiRequest(`${this.layout.endpoint}/records${this.generateQueryParams()}`, {
944
+ method: "GET"
945
+ })
754
946
  })
947
+ .then(async res => {
948
+ // // console.log(res)
949
+ if (res.messages[0].code === "0") {
950
+ // console.log("RESOLVING")
951
+ if (!this.layout.metadata) await this.layout.getLayoutMeta()
952
+ let data = res.response.data.map(item => {
953
+ return new record(this.layout, item.recordId, item.modId, item.fieldData, item.portalData)
954
+ })
955
+ resolve(data)
956
+ } else {
957
+ reject(new FMError(res.messages[0].code, res.status, res))
958
+ }
959
+ })
755
960
  .catch(e => {
756
961
  reject(e)
757
962
  })
@@ -800,26 +1005,30 @@ class find extends recordGetOperation {
800
1005
  return this.run()
801
1006
  }
802
1007
 
803
- run() {
1008
+ run(): Promise<record[]> {
804
1009
  return new Promise((resolve, reject) => {
805
1010
  // console.log(this.#toObject())
806
- this.layout.database.apiRequest(`${this.layout.endpoint}/_find`, {
807
- port: 443,
808
- method: "POST",
809
- body: JSON.stringify(this.toObject())
810
- }).then(async res => {
811
- // // console.log(res)
812
- if (res.messages[0].code === "0") {
813
- // console.log("RESOLVING")
814
- if (!this.layout.metadata) await this.layout.getLayoutMeta()
815
- let data = res.response.data.map(item => {
816
- return new record(this.layout, item.recordId, item.modId, item.fieldData, item.portalData)
1011
+ this.layout.getLayoutMeta()
1012
+ .then(() => {
1013
+ return this.layout.database.apiRequest(`${this.layout.endpoint}/_find`, {
1014
+ port: 443,
1015
+ method: "POST",
1016
+ body: JSON.stringify(this.toObject())
817
1017
  })
818
- resolve(data)
819
- } else {
820
- reject(new FMError(res.messages[0].code, res.status, res))
821
- }
822
- })
1018
+ })
1019
+ .then(async res => {
1020
+ // // console.log(res)
1021
+ if (res.messages[0].code === "0") {
1022
+ // console.log("RESOLVING")
1023
+ if (!this.layout.metadata) await this.layout.getLayoutMeta()
1024
+ let data = res.response.data.map(item => {
1025
+ return new record(this.layout, item.recordId, item.modId, item.fieldData, item.portalData)
1026
+ })
1027
+ resolve(data)
1028
+ } else {
1029
+ reject(new FMError(res.messages[0].code, res.status, res))
1030
+ }
1031
+ })
823
1032
  .catch(e => {
824
1033
  reject(e)
825
1034
  })
@@ -848,4 +1057,36 @@ export class FMError extends Error {
848
1057
  //
849
1058
  // module.exports = {
850
1059
  // default: {FileMakerConnection}
851
- // }
1060
+ // }
1061
+
1062
+ interface AuthorizationHeaders {
1063
+ "Content-Type": "application/json"
1064
+ Authorization: string
1065
+ }
1066
+
1067
+ interface AuthorizationHeadersOAuth {
1068
+ "Content-Type": "application/json"
1069
+ "X-FM-Data-OAuth-RequestId": string,
1070
+ "X-FM-Data-OAuth-Identifier": string
1071
+ }
1072
+
1073
+ function generateAuthorizationHeaders(credentials: loginOptionsOAuth | loginOptionsFileMaker | loginOptionsClaris): AuthorizationHeaders | AuthorizationHeadersOAuth {
1074
+ switch (credentials.method) {
1075
+ case "filemaker":
1076
+ return {
1077
+ "Content-Type": "application/json",
1078
+ "Authorization": "Basic " + Buffer.from((<loginOptionsFileMaker>credentials).username + ":" + (<loginOptionsFileMaker>credentials).password).toString("base64")
1079
+ } as AuthorizationHeaders
1080
+ case "claris":
1081
+ return {
1082
+ "Content-Type": "application/json",
1083
+ "Authorization": (<loginOptionsClaris>credentials).claris.fmid,
1084
+ } as AuthorizationHeaders
1085
+ case "oauth":
1086
+ return {
1087
+ "Content-Type": "application/json",
1088
+ "X-FM-Data-OAuth-RequestId": (<loginOptionsOAuth>credentials).oauth.requestId,
1089
+ "X-FM-Data-OAuth-Identifier": (<loginOptionsOAuth>credentials).oauth.requestIdentifier
1090
+ } as AuthorizationHeadersOAuth
1091
+ }
1092
+ }