@jd-data-limited/easy-fm 1.3.0 → 2.0.0

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/src/index.ts CHANGED
@@ -18,6 +18,8 @@ import fetch, {
18
18
  import {EventEmitter} from "events";
19
19
  // @ts-ignore
20
20
  import * as moment from "moment"
21
+ import * as http from "http";
22
+ import * as https from "https";
21
23
 
22
24
  // import * as btoa from "btoa";
23
25
  interface databaseOptionsBase {
@@ -58,7 +60,7 @@ interface loginOptionsClaris {
58
60
  }
59
61
 
60
62
  interface limitPortalsInterface {
61
- portal: portal,
63
+ portal: Portal,
62
64
  offset: number,
63
65
  limit: number
64
66
  }
@@ -80,6 +82,11 @@ interface extraBodyOptions {
80
82
  }
81
83
  }
82
84
 
85
+ export enum DOWNLOAD_MODES {
86
+ Stream,
87
+ Buffer
88
+ }
89
+
83
90
  // @ts-ignore
84
91
  const errs = [
85
92
  {
@@ -1141,6 +1148,11 @@ interface FMHostMetadata {
1141
1148
  }
1142
1149
  }
1143
1150
 
1151
+ export interface ContainerBufferResult {
1152
+ buffer: Buffer,
1153
+ mime: string,
1154
+ request: http.IncomingMessage
1155
+ }
1144
1156
  export default class FMHost {
1145
1157
  readonly hostname: string
1146
1158
  readonly timezoneOffset: number
@@ -1169,7 +1181,8 @@ export default class FMHost {
1169
1181
 
1170
1182
  if (data.messages[0].code === "0") {
1171
1183
  return data.response.databases
1172
- } else {
1184
+ }
1185
+ else {
1173
1186
  // @ts-ignore
1174
1187
  throw new FMError(data.messages[0].code, data.status, data)
1175
1188
  }
@@ -1191,7 +1204,8 @@ export default class FMHost {
1191
1204
  if (data.messages[0].code === "0") {
1192
1205
  this.metadata = data.response
1193
1206
  return data.response
1194
- } else {
1207
+ }
1208
+ else {
1195
1209
  // @ts-ignore
1196
1210
  throw new FMError(data.messages[0].code, data.status, data)
1197
1211
  }
@@ -1202,6 +1216,7 @@ export class Database extends EventEmitter {
1202
1216
  private _token: any;
1203
1217
  readonly host: FMHost;
1204
1218
  private connection_details: databaseOptionsWithExternalSources
1219
+ private cookies: { [key: string]: string } = {}
1205
1220
  readonly name: string;
1206
1221
 
1207
1222
  constructor(host: FMHost, conn: databaseOptionsWithExternalSources) {
@@ -1219,7 +1234,8 @@ export class Database extends EventEmitter {
1219
1234
  username: _data.username,
1220
1235
  password: _data.password
1221
1236
  }
1222
- } else {
1237
+ }
1238
+ else {
1223
1239
  throw "Not yet supported login method"
1224
1240
  }
1225
1241
  }
@@ -1254,7 +1270,8 @@ export class Database extends EventEmitter {
1254
1270
  if (this.connection_details.credentials.method === "token") {
1255
1271
  this._token = (<loginOptionsToken>this.connection_details.credentials).token
1256
1272
  resolve(this.token)
1257
- } else {
1273
+ }
1274
+ else {
1258
1275
  fetch(`${this.endpoint}/sessions`, {
1259
1276
  hostname: this.host.hostname,
1260
1277
  port: 443,
@@ -1271,7 +1288,8 @@ export class Database extends EventEmitter {
1271
1288
  if (res.status === 200) {
1272
1289
  this._token = res.headers.get('x-fm-data-access-token')
1273
1290
  resolve(this._token)
1274
- } else {
1291
+ }
1292
+ else {
1275
1293
  reject(new FMError(_res.messages[0].code, _res.status, res))
1276
1294
  }
1277
1295
  })
@@ -1297,6 +1315,12 @@ export class Database extends EventEmitter {
1297
1315
  options.rejectUnauthorized = this.host.verify
1298
1316
 
1299
1317
  let _fetch = await fetch(url, options)
1318
+ if (_fetch.headers.get('set-cookie')) {
1319
+ for (let cookie of _fetch.headers.get('set-cookie')) {
1320
+ let cookie_split = cookie.split("=")
1321
+ this.cookies[cookie_split[0]] = cookie_split[1]
1322
+ }
1323
+ }
1300
1324
  let data = await _fetch.json() as fileMakerResponse
1301
1325
  // console.log(data.messages[0])
1302
1326
  if (data.messages[0].code == 952 && autoRelogin) {
@@ -1307,8 +1331,8 @@ export class Database extends EventEmitter {
1307
1331
  return (data as any)
1308
1332
  }
1309
1333
 
1310
- getLayout(name): layout {
1311
- return new layout(this, name)
1334
+ getLayout(name): Layout {
1335
+ return new Layout(this, name)
1312
1336
  }
1313
1337
 
1314
1338
  setGlobals(globalFields): Promise<void> {
@@ -1321,7 +1345,8 @@ export class Database extends EventEmitter {
1321
1345
  // console.log(res)
1322
1346
  if (res.messages[0].code === "0") {
1323
1347
  resolve()
1324
- } else {
1348
+ }
1349
+ else {
1325
1350
  reject(
1326
1351
  (res.messages[0].code, res.status, res))
1327
1352
  }
@@ -1339,11 +1364,49 @@ export class Database extends EventEmitter {
1339
1364
  _tokenExpired() {
1340
1365
  this.emit("token_expired")
1341
1366
  }
1367
+
1368
+ streamContainer(field, url): Promise<http.IncomingMessage> {
1369
+ return new Promise((resolve, reject) => {
1370
+ if (field.metadata.result !== "container") {
1371
+ reject("Cannot stream the field " + field.id + " as it is not a container")
1372
+ return
1373
+ }
1374
+ if (!url || typeof url !== "string") {
1375
+ reject("Container is empty, or has invalid value")
1376
+ return
1377
+ }
1378
+
1379
+ let headers = {}
1380
+ if (Object.keys(this.cookies).length !== 0) {
1381
+ headers["Cookie"] = Object.keys(this.cookies)
1382
+ .map(key => {
1383
+ return key + "=" + this.cookies[key]
1384
+ })
1385
+ .join("; ")
1386
+ }
1387
+
1388
+ // Automatically switch between the http and https modules, based on which is needed
1389
+ (url.startsWith("https") ? https : http).get(url, {
1390
+ headers
1391
+ }, (res) => {
1392
+ // Check for the 'set-cookie' header. If it exists, remember it and strip it for better security.
1393
+ if (res.headers['set-cookie']) {
1394
+ for (let cookie of res.headers['set-cookie']) {
1395
+ let cookie_split = cookie.split("=")
1396
+ this.cookies[cookie_split[0]] = cookie_split[1]
1397
+ }
1398
+ res.headers['set-cookie'] = null
1399
+ }
1400
+
1401
+ resolve(res)
1402
+ })
1403
+ })
1404
+ }
1342
1405
  }
1343
1406
 
1344
- class layout {
1407
+ export class Layout {
1345
1408
  readonly database: Database;
1346
- readonly records = new layoutRecordManager(this)
1409
+ readonly records = new LayourRecordManager(this)
1347
1410
  protected name: string;
1348
1411
  metadata: any;
1349
1412
 
@@ -1373,7 +1436,7 @@ class layout {
1373
1436
  /**
1374
1437
  * @deprecated use layout.records.find() instead
1375
1438
  */
1376
- newFind(): find {
1439
+ newFind(): Find {
1377
1440
  return this.records.find()
1378
1441
  }
1379
1442
 
@@ -1388,7 +1451,8 @@ class layout {
1388
1451
  .then(res => {
1389
1452
  if (res.messages[0].code === "0") {
1390
1453
  resolve(res.response)
1391
- } else {
1454
+ }
1455
+ else {
1392
1456
  // console.log(res)
1393
1457
  reject(new FMError(res.messages[0].code, res.status, res))
1394
1458
  }
@@ -1399,7 +1463,7 @@ class layout {
1399
1463
  })
1400
1464
  }
1401
1465
 
1402
- public getLayoutMeta(): Promise<layout | FMError> {
1466
+ public getLayoutMeta(): Promise<Layout | FMError> {
1403
1467
  return new Promise((resolve, reject) => {
1404
1468
  if (this.metadata) {
1405
1469
  resolve(this.metadata)
@@ -1415,10 +1479,10 @@ class layout {
1415
1479
  }
1416
1480
  }
1417
1481
 
1418
- class layoutRecordManager {
1419
- readonly layout: layout
1482
+ class LayourRecordManager {
1483
+ readonly layout: Layout
1420
1484
 
1421
- constructor(layout: layout) {
1485
+ constructor(layout: Layout) {
1422
1486
  this.layout = layout
1423
1487
  }
1424
1488
 
@@ -1454,21 +1518,21 @@ class layoutRecordManager {
1454
1518
  })
1455
1519
  }
1456
1520
 
1457
- range() {
1458
- return new recordGetRange(this.layout)
1521
+ range(start = 0, limit = 100) {
1522
+ return new RecordGetRange(this.layout, start, limit)
1459
1523
  }
1460
1524
 
1461
- find(): find {
1462
- return new find(this.layout)
1525
+ find(): Find {
1526
+ return new Find(this.layout)
1463
1527
  }
1464
1528
  }
1465
1529
 
1466
- class RecordBase extends EventEmitter {
1467
- readonly layout: layout;
1530
+ export class RecordBase extends EventEmitter {
1531
+ readonly layout: Layout;
1468
1532
  public recordId: number;
1469
1533
  modId: number;
1470
- fields: field[];
1471
- portals: portal[] = [];
1534
+ fields: Field[];
1535
+ portals: Portal[] = [];
1472
1536
  protected portalData: any[];
1473
1537
 
1474
1538
  constructor(layout, recordId, modId = recordId) {
@@ -1488,7 +1552,7 @@ class RecordBase extends EventEmitter {
1488
1552
 
1489
1553
  protected processPortalData(portalData): void {
1490
1554
  for (let item of Object.keys(portalData)) {
1491
- let _portal = new portal(this, item)
1555
+ let _portal = new Portal(this, item)
1492
1556
  _portal.records = portalData[item].map(item => {
1493
1557
  let fieldData = item;
1494
1558
  delete fieldData.recordId;
@@ -1499,9 +1563,9 @@ class RecordBase extends EventEmitter {
1499
1563
  }
1500
1564
  }
1501
1565
 
1502
- processFieldData(fieldData): field[] {
1566
+ processFieldData(fieldData): Field[] {
1503
1567
  return Object.keys(fieldData).map(item => {
1504
- let _field = new field(this, item, fieldData[item])
1568
+ let _field = new Field(this, item, fieldData[item])
1505
1569
  if (!!fieldData[item]) {
1506
1570
  if (_field.metadata.result === "timeStamp") {
1507
1571
  // @ts-ignore
@@ -1511,14 +1575,16 @@ class RecordBase extends EventEmitter {
1511
1575
  _field.set(date.toDate())
1512
1576
  _field.edited = false
1513
1577
 
1514
- } else if (_field.metadata.result === "time") {
1578
+ }
1579
+ else if (_field.metadata.result === "time") {
1515
1580
  // @ts-ignore
1516
1581
  let date = moment.default(fieldData[item], this.layout.database.host.metadata.productInfo.timeFormat)
1517
1582
  .utcOffset(this.layout.database.host.timezoneOffset, true)
1518
1583
  .local()
1519
1584
  _field.set(date.toDate())
1520
1585
  _field.edited = false
1521
- } else if (_field.metadata.result === "date") {
1586
+ }
1587
+ else if (_field.metadata.result === "date") {
1522
1588
  // @ts-ignore
1523
1589
  let date = moment.default(fieldData[item], this.layout.database.host.metadata.productInfo.dateFormat)
1524
1590
  .utcOffset(this.layout.database.host.timezoneOffset, true)
@@ -1550,7 +1616,8 @@ class RecordBase extends EventEmitter {
1550
1616
  this.portalData = []
1551
1617
  if (res.response.data[0].portalData) this.processPortalData(res.response.data[0].portalData)
1552
1618
  resolve(this)
1553
- } else {
1619
+ }
1620
+ else {
1554
1621
  reject(new FMError(res.messages[0].code, res.status, res))
1555
1622
  }
1556
1623
  })
@@ -1580,11 +1647,13 @@ class RecordBase extends EventEmitter {
1580
1647
  .then(res => {
1581
1648
  if (typeof res.response.scriptError !== "undefined" && res.response.scriptError !== '0') {
1582
1649
  reject(new FMError(res.response.scriptError, res.status, res))
1583
- } else if (res.messages[0].code === "0") {
1650
+ }
1651
+ else if (res.messages[0].code === "0") {
1584
1652
  this.recordId = parseInt(res.response.recordId)
1585
1653
  this.modId = parseInt(res.response.modId)
1586
1654
  resolve(this)
1587
- } else {
1655
+ }
1656
+ else {
1588
1657
  reject(new FMError(res.messages[0].code, res.status, res))
1589
1658
  }
1590
1659
  })
@@ -1604,11 +1673,13 @@ class RecordBase extends EventEmitter {
1604
1673
  .then(res => {
1605
1674
  if (typeof res.response.scriptError !== "undefined" && res.response.scriptError !== '0') {
1606
1675
  reject(new FMError(res.response.scriptError, res.status, res))
1607
- } else if (res.messages[0].code === "0") {
1676
+ }
1677
+ else if (res.messages[0].code === "0") {
1608
1678
  this.modId = res.response.modId
1609
1679
  this._onSave()
1610
1680
  resolve(this)
1611
- } else {
1681
+ }
1682
+ else {
1612
1683
  reject(new FMError(res.messages[0].code, res.status, res))
1613
1684
  }
1614
1685
  })
@@ -1617,7 +1688,7 @@ class RecordBase extends EventEmitter {
1617
1688
  })
1618
1689
  })
1619
1690
  }
1620
-
1691
+
1621
1692
  _onSave() {
1622
1693
  this.emit("saved")
1623
1694
  for (let field of this.fields) field.edited = false
@@ -1640,10 +1711,12 @@ class RecordBase extends EventEmitter {
1640
1711
  .then(res => {
1641
1712
  if (typeof res.response.scriptError !== "undefined" && res.response.scriptError !== '0') {
1642
1713
  reject(new FMError(res.response.scriptError, res.status, res))
1643
- } else if (res.messages[0].code === "0") {
1714
+ }
1715
+ else if (res.messages[0].code === "0") {
1644
1716
  this.emit("deleted")
1645
1717
  resolve()
1646
- } else {
1718
+ }
1719
+ else {
1647
1720
  reject(new FMError(res.messages[0].code, res.status, res))
1648
1721
  }
1649
1722
  })
@@ -1662,13 +1735,15 @@ class RecordBase extends EventEmitter {
1662
1735
  .then(res => {
1663
1736
  if (typeof res.response.scriptError !== "undefined" && res.response.scriptError !== '0') {
1664
1737
  reject(new FMError(res.response.scriptError, res.status, res))
1665
- } else if (res.messages[0].code === "0") {
1738
+ }
1739
+ else if (res.messages[0].code === "0") {
1666
1740
  let data = this.toObject((a) => true, (a) => true, (a) => false, (a) => false)
1667
1741
  let _res = new LayoutRecord(this.layout, res.response.recordId, res.response.modId, data.fieldData, data.portalData)
1668
1742
 
1669
1743
  this.emit("duplicated")
1670
1744
  resolve(_res)
1671
- } else {
1745
+ }
1746
+ else {
1672
1747
  reject(new FMError(res.messages[0].code, res.status, res))
1673
1748
  }
1674
1749
  })
@@ -1726,8 +1801,9 @@ class RecordBase extends EventEmitter {
1726
1801
  return obj
1727
1802
  }
1728
1803
  }
1729
- class LayoutRecord extends RecordBase {
1730
- portals: portal[];
1804
+
1805
+ export class LayoutRecord extends RecordBase {
1806
+ portals: Portal[];
1731
1807
 
1732
1808
  constructor(layout, recordId, modId = recordId, fieldData = {}, portalData = null) {
1733
1809
  super(layout, recordId, modId);
@@ -1739,10 +1815,10 @@ class LayoutRecord extends RecordBase {
1739
1815
  }
1740
1816
  }
1741
1817
 
1742
- class field {
1818
+ export class Field {
1743
1819
  record: RecordBase;
1744
1820
  id: number;
1745
- private _value: string | number | Date;
1821
+ protected _value: string | number | Date;
1746
1822
  edited: boolean;
1747
1823
 
1748
1824
  constructor(record, id, contents) {
@@ -1803,7 +1879,8 @@ class field {
1803
1879
  repetitionStart: 1,
1804
1880
  repetitionEnd: 1
1805
1881
  } as fieldMetaData
1806
- } else {
1882
+ }
1883
+ else {
1807
1884
  return this.record.layout.metadata.fieldMetaData.find(i => i.name === this.id) as fieldMetaData || {
1808
1885
  name: this.id.toString(),
1809
1886
  type: 'normal',
@@ -1824,6 +1901,7 @@ class field {
1824
1901
  }
1825
1902
 
1826
1903
  get value() {
1904
+ // if (this.metadata.result === "container") throw "Use await field.stream() to get the contents of a container field, instead of field.value"
1827
1905
  return this._value
1828
1906
  }
1829
1907
 
@@ -1842,7 +1920,8 @@ class field {
1842
1920
  let _res = data as any
1843
1921
  if (_res.messages[0].code === "0") {
1844
1922
  resolve()
1845
- } else {
1923
+ }
1924
+ else {
1846
1925
  reject(new FMError(_res.messages[0].code, _res.status, data))
1847
1926
  }
1848
1927
  })
@@ -1852,15 +1931,40 @@ class field {
1852
1931
  })
1853
1932
  }
1854
1933
 
1855
- download() {
1934
+ download(mode: DOWNLOAD_MODES.Stream): Promise<http.IncomingMessage>
1935
+ download(mode: DOWNLOAD_MODES.Buffer): Promise<ContainerBufferResult>
1936
+ download(mode: DOWNLOAD_MODES = DOWNLOAD_MODES.Stream): Promise<http.IncomingMessage | ContainerBufferResult> {
1856
1937
  return new Promise((resolve, reject) => {
1857
- fetch(this.value.toString())
1858
- .then(res => resolve(res))
1938
+ this.record.layout.database.streamContainer(this, this._value)
1939
+ .then(stream => {
1940
+ if (mode === DOWNLOAD_MODES.Stream) {
1941
+ resolve(stream)
1942
+ return
1943
+ }
1944
+
1945
+ let body = []
1946
+ stream.on("data", chunk => {
1947
+ body.push(chunk)
1948
+ })
1949
+ stream.on("error", (e) => {
1950
+ reject(e)
1951
+ })
1952
+ stream.on("end", () => {
1953
+ resolve({
1954
+ buffer: Buffer.concat(body),
1955
+ mime: stream.headers["content-type"],
1956
+ request: stream
1957
+ } as ContainerBufferResult)
1958
+ })
1959
+ })
1960
+ .catch(e => {
1961
+ reject(e)
1962
+ })
1859
1963
  })
1860
1964
  }
1861
1965
  }
1862
1966
 
1863
- class portal {
1967
+ export class Portal {
1864
1968
  readonly record: RecordBase;
1865
1969
  readonly name: string;
1866
1970
  public records: PortalRecord[];
@@ -1871,8 +1975,8 @@ class portal {
1871
1975
  }
1872
1976
  }
1873
1977
 
1874
- class PortalRecord extends RecordBase {
1875
- readonly portal: portal;
1978
+ export class PortalRecord extends RecordBase {
1979
+ readonly portal: Portal;
1876
1980
 
1877
1981
  constructor(record, portal, recordId, modId = recordId, fieldData = {}) {
1878
1982
  super(record.layout, recordId, modId);
@@ -1901,8 +2005,8 @@ class PortalRecord extends RecordBase {
1901
2005
  }
1902
2006
  }
1903
2007
 
1904
- class recordGetOperation {
1905
- protected layout: layout
2008
+ export class RecordGetOperation {
2009
+ protected layout: Layout
1906
2010
  protected limit: number = 100
1907
2011
  protected scripts: object
1908
2012
  protected sort: object[]
@@ -1923,7 +2027,7 @@ class recordGetOperation {
1923
2027
  addToPortalLimit() will adjust the results of the get request so that if a layout has multiple portals in it,
1924
2028
  only data from the specified ones will be read. This may help reduce load on your FileMaker API
1925
2029
  */
1926
- addToPortalLimit(portal: portal, offset = 0, limit = 100) {
2030
+ addToPortalLimit(portal: Portal, offset = 0, limit = 100) {
1927
2031
  if (offset < 0) throw "Portal offset cannot be less than 0"
1928
2032
  this.limitPortals.push({portal, offset, limit})
1929
2033
  return this
@@ -1947,9 +2051,11 @@ class recordGetOperation {
1947
2051
  }
1948
2052
  }
1949
2053
 
1950
- class recordGetRange extends recordGetOperation {
1951
- constructor(layout) {
2054
+ export class RecordGetRange extends RecordGetOperation {
2055
+ constructor(layout, start = 0, limit = 100) {
1952
2056
  super(layout)
2057
+ this.setOffset(start)
2058
+ this.setLimit(limit)
1953
2059
  }
1954
2060
 
1955
2061
  private generateQueryParams(extraBody: extraBodyOptions = {}) {
@@ -1983,7 +2089,14 @@ class recordGetRange extends recordGetOperation {
1983
2089
  return "?" + params.join("&")
1984
2090
  }
1985
2091
 
2092
+ /**
2093
+ * @deprecated in favour of .fetch()
2094
+ */
1986
2095
  run(extraBody: extraBodyOptions = {}): Promise<RecordBase[]> {
2096
+ return this.fetch(extraBody)
2097
+ }
2098
+
2099
+ fetch(extraBody: extraBodyOptions = {}): Promise<RecordBase[]> {
1987
2100
  return new Promise((resolve, reject) => {
1988
2101
  // console.log(this.#toObject())
1989
2102
  this.layout.getLayoutMeta().then(() => {
@@ -2000,7 +2113,8 @@ class recordGetRange extends recordGetOperation {
2000
2113
  return new LayoutRecord(this.layout, item.recordId, item.modId, item.fieldData, item.portalData)
2001
2114
  })
2002
2115
  resolve(data)
2003
- } else {
2116
+ }
2117
+ else {
2004
2118
  reject(new FMError(res.messages[0].code, res.status, res))
2005
2119
  }
2006
2120
  })
@@ -2011,7 +2125,7 @@ class recordGetRange extends recordGetOperation {
2011
2125
  }
2012
2126
  }
2013
2127
 
2014
- class find extends recordGetOperation {
2128
+ export class Find extends RecordGetOperation {
2015
2129
  protected queries: object[]
2016
2130
 
2017
2131
  constructor(layout) {
@@ -2048,11 +2162,14 @@ class find extends recordGetOperation {
2048
2162
  return this
2049
2163
  }
2050
2164
 
2051
- find() {
2052
- return this.run()
2165
+ /**
2166
+ * @deprecated in favour of .fetch()
2167
+ */
2168
+ run() {
2169
+ return this.fetch()
2053
2170
  }
2054
2171
 
2055
- run(): Promise<RecordBase[]> {
2172
+ fetch(): Promise<RecordBase[]> {
2056
2173
  return new Promise((resolve, reject) => {
2057
2174
  // console.log(this.#toObject())
2058
2175
  this.layout.getLayoutMeta()
@@ -2072,7 +2189,8 @@ class find extends recordGetOperation {
2072
2189
  return new LayoutRecord(this.layout, item.recordId, item.modId, item.fieldData, item.portalData)
2073
2190
  })
2074
2191
  resolve(data)
2075
- } else {
2192
+ }
2193
+ else {
2076
2194
  reject(new FMError(res.messages[0].code, res.status, res))
2077
2195
  }
2078
2196
  })