@jd-data-limited/easy-fm 0.0.11 → 1.0.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.js +156 -90
- package/index.ts +217 -96
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -20,79 +20,17 @@ import path from "path";
|
|
|
20
20
|
// @ts-ignore
|
|
21
21
|
import { fileURLToPath } from "url";
|
|
22
22
|
import * as https from "https";
|
|
23
|
-
// import * as btoa from "btoa";
|
|
24
23
|
// @ts-ignore
|
|
25
24
|
const errs = JSON.parse(fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), 'fmErrors.json')).toString());
|
|
26
|
-
|
|
27
|
-
constructor() {
|
|
28
|
-
|
|
29
|
-
|
|
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;
|
|
30
31
|
}
|
|
31
|
-
|
|
32
|
-
return
|
|
33
|
-
if (this.token)
|
|
34
|
-
throw new Error("Already logged in. Run logout() first");
|
|
35
|
-
this.username = username;
|
|
36
|
-
this.password = password;
|
|
37
|
-
this.hostname = hostname;
|
|
38
|
-
this.database = database;
|
|
39
|
-
this.rejectUnauthroized = rejectUnauthorized;
|
|
40
|
-
return yield this.createSession();
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
logout() {
|
|
44
|
-
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
|
45
|
-
if (!this.token)
|
|
46
|
-
reject(new Error("Not logged in"));
|
|
47
|
-
let _fetch = yield fetch(`${this.endpoint}/sessions/${this.token}`, {
|
|
48
|
-
method: "DELETE",
|
|
49
|
-
headers: {
|
|
50
|
-
"content-type": "application/json"
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
let data = yield _fetch.json();
|
|
54
|
-
// console.log(data)
|
|
55
|
-
this.token = null;
|
|
56
|
-
this.database = null;
|
|
57
|
-
this.hostname = null;
|
|
58
|
-
resolve();
|
|
59
|
-
}));
|
|
60
|
-
}
|
|
61
|
-
importSession(hostname, database, token, rejectUnauthorized = true) {
|
|
62
|
-
this.token = token;
|
|
63
|
-
this.database = database;
|
|
64
|
-
this.hostname = hostname;
|
|
65
|
-
this.rejectUnauthroized = rejectUnauthorized;
|
|
66
|
-
}
|
|
67
|
-
createSession() {
|
|
68
|
-
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
|
69
|
-
fetch(`${this.endpoint}/sessions`, {
|
|
70
|
-
hostname: this.hostname,
|
|
71
|
-
port: 443,
|
|
72
|
-
method: "POST",
|
|
73
|
-
headers: {
|
|
74
|
-
"Content-Type": "application/json",
|
|
75
|
-
"Authorization": "Basic " + Buffer.from(this.username + ":" + this.password).toString("base64")
|
|
76
|
-
}
|
|
77
|
-
}).then(res => {
|
|
78
|
-
console.log(res);
|
|
79
|
-
return res.json();
|
|
80
|
-
})
|
|
81
|
-
.then(res => {
|
|
82
|
-
let _res = res;
|
|
83
|
-
if (_res.messages[0].code === "0") {
|
|
84
|
-
this.token = _res.response.token;
|
|
85
|
-
console.log(_res);
|
|
86
|
-
resolve(this.token);
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
reject(new FMError(_res.messages[0].code, _res.status, res));
|
|
90
|
-
}
|
|
91
|
-
})
|
|
92
|
-
.catch(e => {
|
|
93
|
-
reject(e);
|
|
94
|
-
});
|
|
95
|
-
}));
|
|
32
|
+
get token() {
|
|
33
|
+
return this.host.token;
|
|
96
34
|
}
|
|
97
35
|
getLayout(name) {
|
|
98
36
|
return new layout(this, name);
|
|
@@ -102,8 +40,8 @@ export default class FileMakerConnection {
|
|
|
102
40
|
if (!options.headers)
|
|
103
41
|
options.headers = {};
|
|
104
42
|
options.headers["content-type"] = options.headers["content-type"] ? options.headers["content-type"] : "application/json";
|
|
105
|
-
options.headers["authorization"] = "Bearer " + this.token;
|
|
106
|
-
options.rejectUnauthorized = this.rejectUnauthroized;
|
|
43
|
+
options.headers["authorization"] = "Bearer " + this.host.token;
|
|
44
|
+
options.rejectUnauthorized = this.host.rejectUnauthroized;
|
|
107
45
|
let _fetch = yield fetch(url, options);
|
|
108
46
|
let data = yield _fetch.json();
|
|
109
47
|
return data;
|
|
@@ -132,14 +70,141 @@ export default class FileMakerConnection {
|
|
|
132
70
|
script(name, parameter) {
|
|
133
71
|
return { name, parameter };
|
|
134
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
|
+
}
|
|
135
200
|
}
|
|
136
201
|
class layout {
|
|
137
|
-
constructor(
|
|
138
|
-
this.
|
|
202
|
+
constructor(database, name) {
|
|
203
|
+
this.database = database;
|
|
139
204
|
this.name = name;
|
|
140
205
|
}
|
|
141
206
|
get endpoint() {
|
|
142
|
-
return `${this.
|
|
207
|
+
return `${this.database.endpoint}/layouts/${this.name}`;
|
|
143
208
|
}
|
|
144
209
|
getScript(script) {
|
|
145
210
|
return new script(this, script);
|
|
@@ -147,9 +212,9 @@ class layout {
|
|
|
147
212
|
createRecord() {
|
|
148
213
|
return new Promise((resolve, reject) => {
|
|
149
214
|
// Get the layout's metadata
|
|
150
|
-
this.
|
|
215
|
+
this.getLayoutMeta().then(layout => {
|
|
151
216
|
let fields = {};
|
|
152
|
-
for (let _field of
|
|
217
|
+
for (let _field of this.metadata.fieldMetaData) {
|
|
153
218
|
fields[_field.name] = "";
|
|
154
219
|
}
|
|
155
220
|
resolve(new record(this, -1, 0, fields));
|
|
@@ -167,7 +232,7 @@ class layout {
|
|
|
167
232
|
let url = `${this.endpoint}/script/${encodeURI(script.name)}`;
|
|
168
233
|
if (script.parameter)
|
|
169
234
|
url += "?" + encodeURI(script.parameter);
|
|
170
|
-
this.
|
|
235
|
+
this.database.apiRequest(url, {
|
|
171
236
|
port: 443,
|
|
172
237
|
method: "GET"
|
|
173
238
|
})
|
|
@@ -187,7 +252,7 @@ class layout {
|
|
|
187
252
|
}
|
|
188
253
|
getLayoutMeta() {
|
|
189
254
|
return new Promise((resolve, reject) => {
|
|
190
|
-
this.
|
|
255
|
+
this.database.apiRequest(this.endpoint).then(res => {
|
|
191
256
|
this.metadata = res.response;
|
|
192
257
|
resolve(this);
|
|
193
258
|
})
|
|
@@ -239,7 +304,7 @@ class record extends EventEmitter {
|
|
|
239
304
|
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
|
240
305
|
if (!this.layout.metadata)
|
|
241
306
|
yield this.layout.getLayoutMeta();
|
|
242
|
-
this.layout.
|
|
307
|
+
this.layout.database.apiRequest(this.endpoint, {
|
|
243
308
|
port: 443,
|
|
244
309
|
method: "GET"
|
|
245
310
|
})
|
|
@@ -262,24 +327,24 @@ class record extends EventEmitter {
|
|
|
262
327
|
});
|
|
263
328
|
}));
|
|
264
329
|
}
|
|
265
|
-
commit(extraBody = {
|
|
330
|
+
commit(extraBody = {
|
|
331
|
+
fieldData: undefined
|
|
332
|
+
}) {
|
|
266
333
|
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
|
267
334
|
let data = this.toObject();
|
|
268
335
|
delete data.recordId;
|
|
269
336
|
delete data.modId;
|
|
270
337
|
if (this.recordId === -1) {
|
|
271
338
|
// This is a new record
|
|
272
|
-
|
|
339
|
+
extraBody.fieldData = data.fieldData;
|
|
340
|
+
this.layout.database.apiRequest(`${this.layout.endpoint}/records`, {
|
|
273
341
|
port: 443,
|
|
274
342
|
method: "POST",
|
|
275
|
-
body: JSON.stringify(
|
|
276
|
-
fieldData: data.fieldData
|
|
277
|
-
})
|
|
343
|
+
body: JSON.stringify(extraBody)
|
|
278
344
|
})
|
|
279
345
|
.then(res => {
|
|
280
346
|
if (res.messages[0].code === "0") {
|
|
281
347
|
this.recordId = parseInt(res.response.recordId);
|
|
282
|
-
console.log(this.recordId);
|
|
283
348
|
this.modId = parseInt(res.response.modId);
|
|
284
349
|
resolve(this);
|
|
285
350
|
}
|
|
@@ -294,7 +359,7 @@ class record extends EventEmitter {
|
|
|
294
359
|
}
|
|
295
360
|
for (let item of Object.keys(extraBody))
|
|
296
361
|
data[item] = extraBody[item];
|
|
297
|
-
this.layout.
|
|
362
|
+
this.layout.database.apiRequest(this.endpoint, {
|
|
298
363
|
port: 443,
|
|
299
364
|
method: "PATCH",
|
|
300
365
|
body: JSON.stringify(data)
|
|
@@ -374,7 +439,7 @@ class field {
|
|
|
374
439
|
form.append("upload", new File([buffer], filename, { type: mime }));
|
|
375
440
|
let _fetch = yield fetch(`${this.record.endpoint}/containers/${this.id}/1`, {
|
|
376
441
|
method: "POST",
|
|
377
|
-
headers: { "Authorization": "Bearer " + this.record.layout.
|
|
442
|
+
headers: { "Authorization": "Bearer " + this.record.layout.database.token },
|
|
378
443
|
body: form
|
|
379
444
|
}).then(res => res.json())
|
|
380
445
|
.then(data => {
|
|
@@ -416,7 +481,7 @@ class portalItem extends record {
|
|
|
416
481
|
attachPortal(portal) {
|
|
417
482
|
this.portal = portal;
|
|
418
483
|
}
|
|
419
|
-
commit(extraBody = {}) {
|
|
484
|
+
commit(extraBody = { fieldData: undefined }) {
|
|
420
485
|
return this.portal.record.commit(extraBody);
|
|
421
486
|
}
|
|
422
487
|
toObject(fieldFilter) {
|
|
@@ -466,7 +531,7 @@ class find {
|
|
|
466
531
|
find() {
|
|
467
532
|
return new Promise((resolve, reject) => {
|
|
468
533
|
// console.log(this.#toObject())
|
|
469
|
-
this.layout.
|
|
534
|
+
this.layout.database.apiRequest(`${this.layout.endpoint}/_find`, {
|
|
470
535
|
port: 443,
|
|
471
536
|
method: "POST",
|
|
472
537
|
body: JSON.stringify(this.toObject())
|
|
@@ -498,6 +563,7 @@ export class FMError extends Error {
|
|
|
498
563
|
super(errs.find(err => err.e === code).d || "Unknown error");
|
|
499
564
|
this.httpStatus = httpStatus;
|
|
500
565
|
this.res = res;
|
|
566
|
+
this.messages = res.messages;
|
|
501
567
|
this.code = typeof code === "string" ? parseInt(code) : code;
|
|
502
568
|
Error.captureStackTrace(this, FMError);
|
|
503
569
|
}
|
package/index.ts
CHANGED
|
@@ -25,6 +25,47 @@ import { fileURLToPath } from "url"
|
|
|
25
25
|
import * as https from "https";
|
|
26
26
|
// import * as btoa from "btoa";
|
|
27
27
|
|
|
28
|
+
interface connectionOptions {
|
|
29
|
+
hostname: string | undefined,
|
|
30
|
+
database: databaseOptions
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface hostConnectionOptions {
|
|
34
|
+
hostname: string
|
|
35
|
+
database: loginOptionsOAuth | loginOptionsFileMaker | loginOptionsClaris | loginOptionsToken
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface databaseOptions {
|
|
39
|
+
method: any
|
|
40
|
+
database: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface loginOptionsToken extends databaseOptions {
|
|
44
|
+
method: "token"
|
|
45
|
+
token: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface loginOptionsOAuth extends databaseOptions {
|
|
49
|
+
method: "oauth"
|
|
50
|
+
oauth: {
|
|
51
|
+
requestId: string,
|
|
52
|
+
requestIdentifier: string
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface loginOptionsFileMaker extends databaseOptions {
|
|
57
|
+
method: "filemaker"
|
|
58
|
+
username: string,
|
|
59
|
+
password: string,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface loginOptionsClaris extends databaseOptions {
|
|
63
|
+
method: "claris"
|
|
64
|
+
claris: {
|
|
65
|
+
fmid: string
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
28
69
|
// @ts-ignore
|
|
29
70
|
const errs = JSON.parse(fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), 'fmErrors.json')).toString())
|
|
30
71
|
|
|
@@ -39,86 +80,23 @@ interface recordObject {
|
|
|
39
80
|
fieldData: any
|
|
40
81
|
}
|
|
41
82
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
public
|
|
45
|
-
public token: any;
|
|
46
|
-
private username: any;
|
|
47
|
-
private password: any;
|
|
48
|
-
private rejectUnauthroized: boolean;
|
|
49
|
-
|
|
50
|
-
constructor() {}
|
|
51
|
-
|
|
52
|
-
get endpoint(): string {
|
|
53
|
-
return `https://${this.hostname}/fmi/data/v2/databases/${encodeURI(this.database)}`
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async login(hostname, database, username, password, rejectUnauthorized = true) {
|
|
57
|
-
if (this.token) throw new Error("Already logged in. Run logout() first")
|
|
58
|
-
|
|
59
|
-
this.username = username
|
|
60
|
-
this.password = password
|
|
61
|
-
this.hostname = hostname
|
|
62
|
-
this.database = database
|
|
63
|
-
this.rejectUnauthroized = rejectUnauthorized
|
|
64
|
-
|
|
65
|
-
return await this.createSession()
|
|
66
|
-
}
|
|
83
|
+
class database {
|
|
84
|
+
external: boolean;
|
|
85
|
+
public name: string;
|
|
67
86
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (!this.token) reject(new Error("Not logged in"))
|
|
87
|
+
protected host: FileMakerConnection;
|
|
88
|
+
protected props: databaseOptions;
|
|
71
89
|
|
|
72
|
-
|
|
73
|
-
method: "DELETE",
|
|
74
|
-
headers: {
|
|
75
|
-
"content-type": "application/json"
|
|
76
|
-
}
|
|
77
|
-
})
|
|
78
|
-
let data = await _fetch.json()
|
|
79
|
-
// console.log(data)
|
|
80
|
-
this.token = null
|
|
81
|
-
this.database = null
|
|
82
|
-
this.hostname = null
|
|
83
|
-
resolve()
|
|
84
|
-
})
|
|
85
|
-
}
|
|
90
|
+
public hostname: string;
|
|
86
91
|
|
|
87
|
-
|
|
88
|
-
this.
|
|
89
|
-
this.
|
|
90
|
-
this.
|
|
91
|
-
this.rejectUnauthroized = rejectUnauthorized
|
|
92
|
+
constructor(props: databaseOptions, host: FileMakerConnection = null) {
|
|
93
|
+
this.props = props
|
|
94
|
+
this.name = this.props.database
|
|
95
|
+
if (host) this.host = host
|
|
92
96
|
}
|
|
93
97
|
|
|
94
|
-
|
|
95
|
-
return
|
|
96
|
-
fetch(`${this.endpoint}/sessions`, {
|
|
97
|
-
hostname: this.hostname,
|
|
98
|
-
port: 443,
|
|
99
|
-
method: "POST",
|
|
100
|
-
headers: {
|
|
101
|
-
"Content-Type": "application/json",
|
|
102
|
-
"Authorization": "Basic " + Buffer.from(this.username + ":" + this.password).toString("base64")
|
|
103
|
-
}
|
|
104
|
-
}).then(res => {
|
|
105
|
-
console.log(res)
|
|
106
|
-
return res.json()
|
|
107
|
-
})
|
|
108
|
-
.then(res => {
|
|
109
|
-
let _res = res as any
|
|
110
|
-
if (_res.messages[0].code === "0") {
|
|
111
|
-
this.token = _res.response.token
|
|
112
|
-
console.log(_res)
|
|
113
|
-
resolve(this.token)
|
|
114
|
-
} else {
|
|
115
|
-
reject(new FMError(_res.messages[0].code, _res.status, res))
|
|
116
|
-
}
|
|
117
|
-
})
|
|
118
|
-
.catch(e => {
|
|
119
|
-
reject(e)
|
|
120
|
-
})
|
|
121
|
-
})
|
|
98
|
+
get token() {
|
|
99
|
+
return this.host.token
|
|
122
100
|
}
|
|
123
101
|
|
|
124
102
|
getLayout(name): layout {
|
|
@@ -128,8 +106,8 @@ export default class FileMakerConnection {
|
|
|
128
106
|
async apiRequest(url: string | Request, options: any = {}): Promise<any> {
|
|
129
107
|
if (!options.headers) options.headers = {}
|
|
130
108
|
options.headers["content-type"] = options.headers["content-type"] ? options.headers["content-type"] : "application/json"
|
|
131
|
-
options.headers["authorization"] = "Bearer " + this.token
|
|
132
|
-
options.rejectUnauthorized = this.rejectUnauthroized
|
|
109
|
+
options.headers["authorization"] = "Bearer " + this.host.token
|
|
110
|
+
options.rejectUnauthorized = this.host.rejectUnauthroized
|
|
133
111
|
|
|
134
112
|
let _fetch = await fetch(url, options)
|
|
135
113
|
let data = await _fetch.json()
|
|
@@ -159,19 +137,159 @@ export default class FileMakerConnection {
|
|
|
159
137
|
script(name, parameter): Script {
|
|
160
138
|
return ({name, parameter} as Script)
|
|
161
139
|
}
|
|
140
|
+
|
|
141
|
+
get endpoint(): string {
|
|
142
|
+
return `https://${this.hostname || this.host.hostname}/fmi/data/v2/databases/${encodeURI(this.name)}`
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
get externalLoginInfo() {
|
|
146
|
+
// Returns an object that can be used when using this database object as an external source
|
|
147
|
+
let out = {
|
|
148
|
+
database: this.name,
|
|
149
|
+
username: undefined,
|
|
150
|
+
password: undefined,
|
|
151
|
+
oauthRequestId: undefined,
|
|
152
|
+
oauthIdentifier: undefined
|
|
153
|
+
}
|
|
154
|
+
switch (this.props.method) {
|
|
155
|
+
case "filemaker":
|
|
156
|
+
out.username = (<loginOptionsFileMaker>this.props).username
|
|
157
|
+
out.password = (<loginOptionsFileMaker>this.props).password
|
|
158
|
+
case "token":
|
|
159
|
+
throw "Token logins cannot be used for connecting to external sources. Please either use no login method, or open a second connection."
|
|
160
|
+
case "oauth":
|
|
161
|
+
out.oauthRequestId = (<loginOptionsOAuth>this.props).oauth.requestId
|
|
162
|
+
out.oauthIdentifier = (<loginOptionsOAuth>this.props).oauth.requestIdentifier
|
|
163
|
+
|
|
164
|
+
case "claris":
|
|
165
|
+
throw "Claris logins cannot be used for connecting to external sources. Please either use no login method, or open a second connection."
|
|
166
|
+
}
|
|
167
|
+
return out
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export default class FileMakerConnection extends database{
|
|
172
|
+
private _token: any;
|
|
173
|
+
private username: any;
|
|
174
|
+
private password: any;
|
|
175
|
+
public readonly rejectUnauthroized: boolean;
|
|
176
|
+
externalSources: database[];
|
|
177
|
+
|
|
178
|
+
constructor(conn: hostConnectionOptions, externalDataSources:databaseOptions[] = [], rejectUnauthorized = true) {
|
|
179
|
+
super(conn.database);
|
|
180
|
+
this.host = this
|
|
181
|
+
this.hostname = conn.hostname
|
|
182
|
+
this.externalSources = externalDataSources.map(i => {let e = new database(i, this); e.external = true; return e;})
|
|
183
|
+
this.rejectUnauthroized = rejectUnauthorized
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
logout(): Promise<void> {
|
|
187
|
+
return new Promise(async (resolve, reject) => {
|
|
188
|
+
if (!this.token) reject(new Error("Not logged in"))
|
|
189
|
+
|
|
190
|
+
let _fetch = await fetch(`${this.endpoint}/sessions/${this.token}`, {
|
|
191
|
+
method: "DELETE",
|
|
192
|
+
headers: {
|
|
193
|
+
"content-type": "application/json"
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
let data = await _fetch.json()
|
|
197
|
+
// console.log(data)
|
|
198
|
+
this._token = null
|
|
199
|
+
this.name = null
|
|
200
|
+
this.hostname = null
|
|
201
|
+
resolve()
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
login() {
|
|
206
|
+
return new Promise<string>((resolve, reject) => {
|
|
207
|
+
if (this.token) throw new Error("Already logged in. Run logout() first")
|
|
208
|
+
|
|
209
|
+
if (this.props.method === "filemaker") {
|
|
210
|
+
this.username = (<loginOptionsFileMaker>this.props).username
|
|
211
|
+
this.name = this.props.database
|
|
212
|
+
|
|
213
|
+
fetch(`${this.endpoint}/sessions`, {
|
|
214
|
+
hostname: this.hostname,
|
|
215
|
+
port: 443,
|
|
216
|
+
method: "POST",
|
|
217
|
+
headers: {
|
|
218
|
+
"Content-Type": "application/json",
|
|
219
|
+
"Authorization": "Basic " + Buffer.from(this.username + ":" + (<loginOptionsFileMaker>this.props).password).toString("base64")
|
|
220
|
+
}
|
|
221
|
+
}).then(async res => {
|
|
222
|
+
if (res.status === 200) {
|
|
223
|
+
this._token = res.headers.get('x-fm-data-access-token')
|
|
224
|
+
resolve(this._token)
|
|
225
|
+
} else {
|
|
226
|
+
let _res = <any>(await res.json())
|
|
227
|
+
reject(new FMError(_res.messages[0].code, _res.status, res))
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
.catch(e => {
|
|
231
|
+
reject(e)
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
else if (this.props.method === "token") {
|
|
235
|
+
this._token = (<loginOptionsToken>this.props).token
|
|
236
|
+
this.name = this.props.database
|
|
237
|
+
resolve(this.token)
|
|
238
|
+
}
|
|
239
|
+
else if (this.props.method === "oauth") {
|
|
240
|
+
this.name = this.props.database
|
|
241
|
+
fetch(`${this.endpoint}/sessions`, {
|
|
242
|
+
method: "POST",
|
|
243
|
+
headers: {
|
|
244
|
+
"Content-Type": "application/json",
|
|
245
|
+
"X-FM-Data-OAuth-RequestId": (<loginOptionsOAuth>this.props).oauth.requestId,
|
|
246
|
+
"X-FM-Data-OAuth-Identifier": (<loginOptionsOAuth>this.props).oauth.requestIdentifier
|
|
247
|
+
},
|
|
248
|
+
body: "{}"
|
|
249
|
+
})
|
|
250
|
+
.then(res => res.json())
|
|
251
|
+
.then(res => {
|
|
252
|
+
let _res = <any>res
|
|
253
|
+
this._token = _res.headers["x-fm-data-access-token"]
|
|
254
|
+
resolve(this.token)
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
else if (this.props.method === "claris") {
|
|
258
|
+
this.name = this.props.database
|
|
259
|
+
fetch(`${this.endpoint}/sessions`, {
|
|
260
|
+
method: "POST",
|
|
261
|
+
headers: {
|
|
262
|
+
"Content-Type": "application/json",
|
|
263
|
+
"Authorization": (<loginOptionsClaris>this.props).claris.fmid,
|
|
264
|
+
},
|
|
265
|
+
body: "{}"
|
|
266
|
+
})
|
|
267
|
+
.then(res => res.json())
|
|
268
|
+
.then(res => {
|
|
269
|
+
let _res = <any>res
|
|
270
|
+
this._token = _res.headers["x-fm-data-access-token"]
|
|
271
|
+
resolve(this.token)
|
|
272
|
+
})
|
|
273
|
+
}
|
|
274
|
+
})
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
get token() {
|
|
278
|
+
return this._token
|
|
279
|
+
}
|
|
162
280
|
}
|
|
163
281
|
|
|
164
282
|
class layout {
|
|
165
|
-
readonly
|
|
283
|
+
readonly database: database;
|
|
166
284
|
protected name: string;
|
|
167
285
|
metadata: any;
|
|
168
|
-
constructor(
|
|
169
|
-
this.
|
|
286
|
+
constructor(database: database, name: string) {
|
|
287
|
+
this.database = database
|
|
170
288
|
this.name = name
|
|
171
289
|
}
|
|
172
290
|
|
|
173
291
|
get endpoint() {
|
|
174
|
-
return `${this.
|
|
292
|
+
return `${this.database.endpoint}/layouts/${this.name}`
|
|
175
293
|
}
|
|
176
294
|
|
|
177
295
|
getScript(script) {
|
|
@@ -181,9 +299,9 @@ class layout {
|
|
|
181
299
|
createRecord(): Promise<record | Error | void> {
|
|
182
300
|
return new Promise((resolve, reject) => {
|
|
183
301
|
// Get the layout's metadata
|
|
184
|
-
this.
|
|
302
|
+
this.getLayoutMeta().then(layout => {
|
|
185
303
|
let fields = {}
|
|
186
|
-
for (let _field of
|
|
304
|
+
for (let _field of this.metadata.fieldMetaData) {
|
|
187
305
|
fields[_field.name] = ""
|
|
188
306
|
}
|
|
189
307
|
resolve(new record(this, -1, 0, fields))
|
|
@@ -203,7 +321,7 @@ class layout {
|
|
|
203
321
|
return new Promise(async (resolve, reject) => {
|
|
204
322
|
let url = `${this.endpoint}/script/${encodeURI(script.name)}`
|
|
205
323
|
if (script.parameter) url += "?" + encodeURI(script.parameter)
|
|
206
|
-
this.
|
|
324
|
+
this.database.apiRequest(url, {
|
|
207
325
|
port: 443,
|
|
208
326
|
method: "GET"
|
|
209
327
|
})
|
|
@@ -223,7 +341,7 @@ class layout {
|
|
|
223
341
|
|
|
224
342
|
public getLayoutMeta(): Promise<layout | FMError> {
|
|
225
343
|
return new Promise((resolve, reject) => {
|
|
226
|
-
this.
|
|
344
|
+
this.database.apiRequest(this.endpoint).then(res => {
|
|
227
345
|
this.metadata = res.response
|
|
228
346
|
resolve(this)
|
|
229
347
|
})
|
|
@@ -285,7 +403,7 @@ class record extends EventEmitter {
|
|
|
285
403
|
return new Promise(async (resolve, reject) => {
|
|
286
404
|
if (!this.layout.metadata) await this.layout.getLayoutMeta()
|
|
287
405
|
|
|
288
|
-
this.layout.
|
|
406
|
+
this.layout.database.apiRequest(this.endpoint, {
|
|
289
407
|
port: 443,
|
|
290
408
|
method: "GET"
|
|
291
409
|
})
|
|
@@ -307,7 +425,9 @@ class record extends EventEmitter {
|
|
|
307
425
|
})
|
|
308
426
|
}
|
|
309
427
|
|
|
310
|
-
commit(extraBody = {
|
|
428
|
+
commit(extraBody = {
|
|
429
|
+
fieldData: undefined
|
|
430
|
+
}): Promise<record | FMError> {
|
|
311
431
|
return new Promise(async (resolve, reject) => {
|
|
312
432
|
let data = this.toObject()
|
|
313
433
|
delete data.recordId
|
|
@@ -315,17 +435,16 @@ class record extends EventEmitter {
|
|
|
315
435
|
|
|
316
436
|
if (this.recordId === -1) {
|
|
317
437
|
// This is a new record
|
|
318
|
-
|
|
438
|
+
extraBody.fieldData = data.fieldData
|
|
439
|
+
|
|
440
|
+
this.layout.database.apiRequest(`${this.layout.endpoint}/records`, {
|
|
319
441
|
port: 443,
|
|
320
442
|
method: "POST",
|
|
321
|
-
body: JSON.stringify(
|
|
322
|
-
fieldData: data.fieldData
|
|
323
|
-
})
|
|
443
|
+
body: JSON.stringify(extraBody)
|
|
324
444
|
})
|
|
325
445
|
.then(res => {
|
|
326
446
|
if (res.messages[0].code === "0") {
|
|
327
447
|
this.recordId = parseInt(res.response.recordId)
|
|
328
|
-
console.log(this.recordId)
|
|
329
448
|
this.modId = parseInt(res.response.modId)
|
|
330
449
|
resolve(this)
|
|
331
450
|
} else {
|
|
@@ -340,7 +459,7 @@ class record extends EventEmitter {
|
|
|
340
459
|
}
|
|
341
460
|
|
|
342
461
|
for (let item of Object.keys(extraBody)) data[item] = extraBody[item]
|
|
343
|
-
this.layout.
|
|
462
|
+
this.layout.database.apiRequest(this.endpoint, {
|
|
344
463
|
port: 443,
|
|
345
464
|
method: "PATCH",
|
|
346
465
|
body: JSON.stringify(data)
|
|
@@ -432,7 +551,7 @@ class field {
|
|
|
432
551
|
|
|
433
552
|
let _fetch = await fetch(`${this.record.endpoint}/containers/${this.id}/1`, {
|
|
434
553
|
method: "POST",
|
|
435
|
-
headers: {"Authorization": "Bearer " + this.record.layout.
|
|
554
|
+
headers: {"Authorization": "Bearer " + this.record.layout.database.token},
|
|
436
555
|
body: form
|
|
437
556
|
}).then(res => res.json())
|
|
438
557
|
.then(data => {
|
|
@@ -482,7 +601,7 @@ class portalItem extends record {
|
|
|
482
601
|
this.portal = portal
|
|
483
602
|
}
|
|
484
603
|
|
|
485
|
-
commit(extraBody = {}) {
|
|
604
|
+
commit(extraBody = {fieldData: undefined}) {
|
|
486
605
|
return this.portal.record.commit(extraBody)
|
|
487
606
|
}
|
|
488
607
|
|
|
@@ -541,7 +660,7 @@ class find {
|
|
|
541
660
|
find() {
|
|
542
661
|
return new Promise((resolve, reject) => {
|
|
543
662
|
// console.log(this.#toObject())
|
|
544
|
-
this.layout.
|
|
663
|
+
this.layout.database.apiRequest(`${this.layout.endpoint}/_find`, {
|
|
545
664
|
port: 443,
|
|
546
665
|
method: "POST",
|
|
547
666
|
body: JSON.stringify(this.toObject())
|
|
@@ -570,12 +689,14 @@ export class FMError extends Error {
|
|
|
570
689
|
readonly res: any
|
|
571
690
|
readonly code: number
|
|
572
691
|
readonly message: string
|
|
692
|
+
readonly messages: string
|
|
573
693
|
|
|
574
694
|
constructor(code, httpStatus, res) {
|
|
575
695
|
if (typeof code === "string") code = parseInt(code)
|
|
576
696
|
super(errs.find(err => err.e === code).d || "Unknown error");
|
|
577
697
|
this.httpStatus = httpStatus
|
|
578
698
|
this.res = res
|
|
699
|
+
this.messages = res.messages
|
|
579
700
|
this.code = typeof code === "string" ? parseInt(code) : code
|
|
580
701
|
Error.captureStackTrace(this, FMError)
|
|
581
702
|
}
|