@jd-data-limited/easy-fm 3.0.10 → 3.1.1
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/.idea/compiler.xml +7 -0
- package/.idea/discord.xml +7 -0
- package/dist/index.min.js +2 -0
- package/dist/index.min.js.LICENSE.txt +3 -0
- package/examples/test.js +7 -0
- package/img.png +0 -0
- package/package.json +2 -2
- package/src/FMError.d.ts +12 -0
- package/src/FMError.js +1028 -0
- package/src/FMError.ts +1035 -0
- package/src/connection/FMHost.d.ts +12 -0
- package/src/connection/FMHost.js +68 -0
- package/src/connection/FMHost.ts +75 -0
- package/src/connection/database.d.ts +28 -0
- package/src/connection/database.js +195 -0
- package/src/connection/database.ts +216 -0
- package/src/connection/generateAuthorizationHeaders.d.ts +2 -0
- package/src/connection/generateAuthorizationHeaders.js +23 -0
- package/src/connection/generateAuthorizationHeaders.ts +32 -0
- package/src/index.d.ts +19 -0
- package/src/index.js +19 -2076
- package/src/index.ts +38 -2325
- package/src/layouts/layout.d.ts +29 -0
- package/src/layouts/layout.js +84 -0
- package/src/layouts/layout.ts +92 -0
- package/src/layouts/layoutInterface.d.ts +13 -0
- package/src/layouts/layoutInterface.js +4 -0
- package/src/layouts/layoutInterface.ts +21 -0
- package/src/layouts/layoutRecordManager.d.ts +13 -0
- package/src/layouts/layoutRecordManager.js +50 -0
- package/src/layouts/layoutRecordManager.ts +59 -0
- package/src/records/field.d.ts +23 -0
- package/src/records/field.js +177 -0
- package/src/records/field.ts +189 -0
- package/src/records/getOperations/find.d.ts +15 -0
- package/src/records/getOperations/find.js +90 -0
- package/src/records/getOperations/find.ts +88 -0
- package/src/records/getOperations/recordGetOperation.d.ts +17 -0
- package/src/records/getOperations/recordGetOperation.js +43 -0
- package/src/records/getOperations/recordGetOperation.ts +54 -0
- package/src/records/getOperations/recordGetRange.d.ts +13 -0
- package/src/records/getOperations/recordGetRange.js +95 -0
- package/src/records/getOperations/recordGetRange.ts +84 -0
- package/src/records/layoutRecord.d.ts +16 -0
- package/src/records/layoutRecord.js +211 -0
- package/src/records/layoutRecord.ts +220 -0
- package/src/records/portal.d.ts +10 -0
- package/src/records/portal.js +19 -0
- package/src/records/portal.ts +28 -0
- package/src/records/portalRecord.d.ts +12 -0
- package/src/records/portalRecord.js +32 -0
- package/src/records/portalRecord.ts +43 -0
- package/src/records/recordBase.d.ts +21 -0
- package/src/records/recordBase.js +95 -0
- package/src/records/recordBase.ts +121 -0
- package/src/types.d.ts +119 -0
- package/src/types.js +8 -0
- package/src/types.ts +151 -0
- package/test.d.ts +1 -0
- package/test.js +4 -0
- package/tsconfig.json +4 -0
- package/TEMP/FILEMAKER.txt +0 -218
- package/TEMP/WEBAPP.txt +0 -92
- package/TEMP/config.yaml +0 -40
- package/TEMP/data.json +0 -19837
- package/temp2/1.19.2-forge-43.2.8.json +0 -347
- package/temp2/Quinn-1.19.2-forge-43.2.8.json +0 -347
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Database } from "./database";
|
|
2
|
+
import { databaseOptionsWithExternalSources, FMHostMetadata, loginOptionsClaris, loginOptionsFileMaker, loginOptionsOAuth } from "../types.js";
|
|
3
|
+
export default class FMHost {
|
|
4
|
+
readonly hostname: string;
|
|
5
|
+
readonly timezoneOffset: number;
|
|
6
|
+
readonly verify: boolean;
|
|
7
|
+
metadata: FMHostMetadata;
|
|
8
|
+
constructor(_hostname: string, timezoneOffset?: number, verify?: boolean);
|
|
9
|
+
listDatabases(credentials?: loginOptionsOAuth | loginOptionsFileMaker | loginOptionsClaris): Promise<any>;
|
|
10
|
+
database(data: databaseOptionsWithExternalSources): Database;
|
|
11
|
+
getMetadata(): Promise<any>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023. 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
|
+
import { generateAuthorizationHeaders } from "./generateAuthorizationHeaders";
|
|
14
|
+
import fetch from "node-fetch";
|
|
15
|
+
import { FMError } from "../FMError";
|
|
16
|
+
import { Database } from "./database";
|
|
17
|
+
export default class FMHost {
|
|
18
|
+
constructor(_hostname, timezoneOffset = 0 - (new Date()).getTimezoneOffset(), verify = true) {
|
|
19
|
+
if (!(/^https?:\/\//).test(_hostname))
|
|
20
|
+
throw "hostname MUST begin with either http:// or https://";
|
|
21
|
+
this.hostname = _hostname;
|
|
22
|
+
this.timezoneOffset = timezoneOffset;
|
|
23
|
+
this.verify = verify;
|
|
24
|
+
}
|
|
25
|
+
listDatabases(credentials) {
|
|
26
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
27
|
+
let headers = {};
|
|
28
|
+
if (credentials) {
|
|
29
|
+
headers = generateAuthorizationHeaders(credentials);
|
|
30
|
+
}
|
|
31
|
+
let _fetch = yield fetch(`${this.hostname}/fmi/data/v2/databases`, {
|
|
32
|
+
method: "GET",
|
|
33
|
+
headers
|
|
34
|
+
});
|
|
35
|
+
let data = yield _fetch.json();
|
|
36
|
+
// console.log(data.messages[0])
|
|
37
|
+
if (data.messages[0].code === "0") {
|
|
38
|
+
return data.response.databases;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// @ts-ignore
|
|
42
|
+
throw new FMError(data.messages[0].code, data.status, data);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
database(data) {
|
|
47
|
+
return new Database(this, data);
|
|
48
|
+
}
|
|
49
|
+
getMetadata() {
|
|
50
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
51
|
+
if (this.metadata)
|
|
52
|
+
return this.metadata;
|
|
53
|
+
let _fetch = yield fetch(`${this.hostname}/fmi/data/v2/productInfo`, {
|
|
54
|
+
method: "GET",
|
|
55
|
+
});
|
|
56
|
+
let data = yield _fetch.json();
|
|
57
|
+
// console.log(data.messages[0])
|
|
58
|
+
if (data.messages[0].code === "0") {
|
|
59
|
+
this.metadata = data.response;
|
|
60
|
+
return data.response;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// @ts-ignore
|
|
64
|
+
throw new FMError(data.messages[0].code, data.status, data);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023. See LICENSE file for more information
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {generateAuthorizationHeaders} from "./generateAuthorizationHeaders";
|
|
6
|
+
import fetch from "node-fetch";
|
|
7
|
+
import {FMError} from "../FMError";
|
|
8
|
+
import {Database} from "./database";
|
|
9
|
+
import {
|
|
10
|
+
databaseOptionsWithExternalSources,
|
|
11
|
+
fileMakerResponse,
|
|
12
|
+
FMHostMetadata,
|
|
13
|
+
loginOptionsClaris,
|
|
14
|
+
loginOptionsFileMaker,
|
|
15
|
+
loginOptionsOAuth
|
|
16
|
+
} from "../types.js";
|
|
17
|
+
|
|
18
|
+
export default class FMHost {
|
|
19
|
+
readonly hostname: string
|
|
20
|
+
readonly timezoneOffset: number
|
|
21
|
+
readonly verify: boolean
|
|
22
|
+
metadata: FMHostMetadata
|
|
23
|
+
|
|
24
|
+
constructor(_hostname: string, timezoneOffset = 0 - (new Date()).getTimezoneOffset(), verify = true) {
|
|
25
|
+
if (!(/^https?:\/\//).test(_hostname)) throw "hostname MUST begin with either http:// or https://"
|
|
26
|
+
this.hostname = _hostname
|
|
27
|
+
this.timezoneOffset = timezoneOffset
|
|
28
|
+
this.verify = verify
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async listDatabases(credentials?: loginOptionsOAuth | loginOptionsFileMaker | loginOptionsClaris) {
|
|
32
|
+
let headers = {}
|
|
33
|
+
if (credentials) {
|
|
34
|
+
headers = generateAuthorizationHeaders(credentials)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let _fetch = await fetch(`${this.hostname}/fmi/data/v2/databases`, {
|
|
38
|
+
method: "GET",
|
|
39
|
+
headers
|
|
40
|
+
})
|
|
41
|
+
let data = await _fetch.json() as fileMakerResponse
|
|
42
|
+
// console.log(data.messages[0])
|
|
43
|
+
|
|
44
|
+
if (data.messages[0].code === "0") {
|
|
45
|
+
return data.response.databases
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// @ts-ignore
|
|
49
|
+
throw new FMError(data.messages[0].code, data.status, data)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
database(data: databaseOptionsWithExternalSources) {
|
|
54
|
+
return new Database(this, data)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async getMetadata() {
|
|
58
|
+
if (this.metadata) return this.metadata
|
|
59
|
+
|
|
60
|
+
let _fetch = await fetch(`${this.hostname}/fmi/data/v2/productInfo`, {
|
|
61
|
+
method: "GET",
|
|
62
|
+
})
|
|
63
|
+
let data = await _fetch.json() as fileMakerResponse
|
|
64
|
+
// console.log(data.messages[0])
|
|
65
|
+
|
|
66
|
+
if (data.messages[0].code === "0") {
|
|
67
|
+
this.metadata = data.response
|
|
68
|
+
return data.response
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// @ts-ignore
|
|
72
|
+
throw new FMError(data.messages[0].code, data.status, data)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
import { EventEmitter } from "events";
|
|
4
|
+
import { Request } from "node-fetch";
|
|
5
|
+
import { LayoutInterface } from "../layouts/layoutInterface";
|
|
6
|
+
import { Layout } from "../layouts/layout";
|
|
7
|
+
import * as http from "http";
|
|
8
|
+
import { databaseOptionsWithExternalSources, Script } from "../types.js";
|
|
9
|
+
import FMHost from "./FMHost";
|
|
10
|
+
export declare class Database extends EventEmitter {
|
|
11
|
+
private _token;
|
|
12
|
+
readonly host: FMHost;
|
|
13
|
+
private connection_details;
|
|
14
|
+
private cookies;
|
|
15
|
+
readonly name: string;
|
|
16
|
+
constructor(host: FMHost, conn: databaseOptionsWithExternalSources);
|
|
17
|
+
private generateExternalSourceLogin;
|
|
18
|
+
logout(): Promise<void>;
|
|
19
|
+
login(): Promise<string>;
|
|
20
|
+
get token(): any;
|
|
21
|
+
get endpoint(): string;
|
|
22
|
+
apiRequest(url: string | Request, options?: any, autoRelogin?: boolean): Promise<any>;
|
|
23
|
+
getLayout<T extends LayoutInterface>(name: any): Layout<T>;
|
|
24
|
+
setGlobals(globalFields: any): Promise<void>;
|
|
25
|
+
script(name: any, parameter?: string): Script;
|
|
26
|
+
_tokenExpired(): void;
|
|
27
|
+
streamContainer(field: any, url: any): Promise<http.IncomingMessage>;
|
|
28
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023. 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
|
+
import { EventEmitter } from "events";
|
|
14
|
+
import fetch from "node-fetch";
|
|
15
|
+
import { generateAuthorizationHeaders } from "./generateAuthorizationHeaders";
|
|
16
|
+
import { FMError } from "../FMError";
|
|
17
|
+
import { Layout } from "../layouts/layout";
|
|
18
|
+
import * as http from "http";
|
|
19
|
+
import * as https from "https";
|
|
20
|
+
export class Database extends EventEmitter {
|
|
21
|
+
constructor(host, conn) {
|
|
22
|
+
super();
|
|
23
|
+
this.cookies = {};
|
|
24
|
+
this.host = host;
|
|
25
|
+
this.name = conn.database;
|
|
26
|
+
this.connection_details = conn;
|
|
27
|
+
}
|
|
28
|
+
generateExternalSourceLogin(data) {
|
|
29
|
+
if (data.credentials.method === "filemaker") {
|
|
30
|
+
let _data = data.credentials;
|
|
31
|
+
return {
|
|
32
|
+
database: data.database,
|
|
33
|
+
username: _data.username,
|
|
34
|
+
password: _data.password
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
throw "Not yet supported login method";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
logout() {
|
|
42
|
+
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
|
43
|
+
if (!this.token)
|
|
44
|
+
reject(new Error("Not logged in"));
|
|
45
|
+
let _fetch = yield fetch(`${this.endpoint}/sessions/${this.token}`, {
|
|
46
|
+
method: "DELETE",
|
|
47
|
+
headers: {
|
|
48
|
+
"content-type": "application/json"
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
let data = yield _fetch.json();
|
|
52
|
+
// console.log(data)
|
|
53
|
+
this._token = null;
|
|
54
|
+
resolve();
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
login() {
|
|
58
|
+
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
|
59
|
+
// Reset cookies
|
|
60
|
+
this.cookies = {};
|
|
61
|
+
try {
|
|
62
|
+
yield this.host.getMetadata();
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
reject(e);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (this.token)
|
|
69
|
+
throw new Error("Already logged in. Run logout() first");
|
|
70
|
+
if (this.connection_details.credentials.method === "token") {
|
|
71
|
+
this._token = this.connection_details.credentials.token;
|
|
72
|
+
resolve(this.token);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
fetch(`${this.endpoint}/sessions`, {
|
|
76
|
+
hostname: this.host.hostname,
|
|
77
|
+
port: 443,
|
|
78
|
+
method: "POST",
|
|
79
|
+
headers: generateAuthorizationHeaders(this.connection_details.credentials),
|
|
80
|
+
body: JSON.stringify({
|
|
81
|
+
fmDataSource: this.connection_details.externalSources.map(i => {
|
|
82
|
+
let _i = i;
|
|
83
|
+
return this.generateExternalSourceLogin(_i);
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
}).then((res) => __awaiter(this, void 0, void 0, function* () {
|
|
87
|
+
let _res = (yield res.json());
|
|
88
|
+
if (res.status === 200) {
|
|
89
|
+
this._token = res.headers.get('x-fm-data-access-token');
|
|
90
|
+
resolve(this._token);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
reject(new FMError(_res.messages[0].code, _res.status, res));
|
|
94
|
+
}
|
|
95
|
+
}))
|
|
96
|
+
.catch(e => {
|
|
97
|
+
reject(e);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
get token() {
|
|
103
|
+
return this._token;
|
|
104
|
+
}
|
|
105
|
+
get endpoint() {
|
|
106
|
+
return `${this.host.hostname}/fmi/data/v2/databases/${this.name}`;
|
|
107
|
+
}
|
|
108
|
+
apiRequest(url, options = {}, autoRelogin = true) {
|
|
109
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
110
|
+
if (!options.headers)
|
|
111
|
+
options.headers = {};
|
|
112
|
+
options.headers["content-type"] = options.headers["content-type"] ? options.headers["content-type"] : "application/json";
|
|
113
|
+
options.headers["authorization"] = "Bearer " + this._token;
|
|
114
|
+
options.rejectUnauthorized = this.host.verify;
|
|
115
|
+
let _fetch = yield fetch(url, options);
|
|
116
|
+
if (_fetch.headers.get('set-cookie')) {
|
|
117
|
+
for (let cookie of _fetch.headers.get('set-cookie')) {
|
|
118
|
+
let cookie_split = cookie.split("=");
|
|
119
|
+
this.cookies[cookie_split[0]] = cookie_split[1];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
let data = yield _fetch.json();
|
|
123
|
+
// console.log(data.messages[0])
|
|
124
|
+
if (data.messages[0].code == 952 && autoRelogin) {
|
|
125
|
+
this._token = null;
|
|
126
|
+
yield this.login();
|
|
127
|
+
return yield this.apiRequest(url, options, false);
|
|
128
|
+
}
|
|
129
|
+
return data;
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
getLayout(name) {
|
|
133
|
+
return new Layout(this, name);
|
|
134
|
+
}
|
|
135
|
+
setGlobals(globalFields) {
|
|
136
|
+
// console.log({globalFields})
|
|
137
|
+
return new Promise((resolve, reject) => {
|
|
138
|
+
this.apiRequest(`${this.endpoint}/globals`, {
|
|
139
|
+
method: "PATCH",
|
|
140
|
+
body: JSON.stringify({ globalFields })
|
|
141
|
+
}).then(res => {
|
|
142
|
+
// console.log(res)
|
|
143
|
+
if (res.messages[0].code === "0") {
|
|
144
|
+
resolve();
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
reject((res.messages[0].code, res.status, res));
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
.catch(e => {
|
|
151
|
+
reject(e);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
script(name, parameter = "") {
|
|
156
|
+
return { name, parameter };
|
|
157
|
+
}
|
|
158
|
+
_tokenExpired() {
|
|
159
|
+
this.emit("token_expired");
|
|
160
|
+
}
|
|
161
|
+
streamContainer(field, url) {
|
|
162
|
+
return new Promise((resolve, reject) => {
|
|
163
|
+
if (field.metadata.result !== "container") {
|
|
164
|
+
reject("Cannot stream the field " + field.id + " as it is not a container");
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (!url || typeof url !== "string") {
|
|
168
|
+
reject("Container is empty, or has invalid value");
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
let headers = {};
|
|
172
|
+
if (Object.keys(this.cookies).length !== 0) {
|
|
173
|
+
headers["Cookie"] = Object.keys(this.cookies)
|
|
174
|
+
.map(key => {
|
|
175
|
+
return key + "=" + this.cookies[key];
|
|
176
|
+
})
|
|
177
|
+
.join("; ");
|
|
178
|
+
}
|
|
179
|
+
// Automatically switch between the http and https modules, based on which is needed
|
|
180
|
+
(url.startsWith("https") ? https : http).get(url, {
|
|
181
|
+
headers
|
|
182
|
+
}, (res) => {
|
|
183
|
+
// Check for the 'set-cookie' header. If it exists, remember it and strip it for better security.
|
|
184
|
+
if (res.headers['set-cookie']) {
|
|
185
|
+
for (let cookie of res.headers['set-cookie']) {
|
|
186
|
+
let cookie_split = cookie.split("=");
|
|
187
|
+
this.cookies[cookie_split[0]] = cookie_split[1];
|
|
188
|
+
}
|
|
189
|
+
res.headers['set-cookie'] = null;
|
|
190
|
+
}
|
|
191
|
+
resolve(res);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023. See LICENSE file for more information
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {EventEmitter} from "events";
|
|
6
|
+
import fetch, {HeadersInit, Request} from "node-fetch";
|
|
7
|
+
import {generateAuthorizationHeaders} from "./generateAuthorizationHeaders";
|
|
8
|
+
import {FMError} from "../FMError";
|
|
9
|
+
import {LayoutInterface} from "../layouts/layoutInterface";
|
|
10
|
+
import {Layout} from "../layouts/layout";
|
|
11
|
+
import * as http from "http";
|
|
12
|
+
import * as https from "https";
|
|
13
|
+
import {
|
|
14
|
+
databaseOptionsBase,
|
|
15
|
+
databaseOptionsWithExternalSources,
|
|
16
|
+
fileMakerResponse,
|
|
17
|
+
loginOptionsFileMaker,
|
|
18
|
+
loginOptionsToken,
|
|
19
|
+
Script
|
|
20
|
+
} from "../types.js";
|
|
21
|
+
import FMHost from "./FMHost";
|
|
22
|
+
|
|
23
|
+
export class Database extends EventEmitter {
|
|
24
|
+
private _token: any;
|
|
25
|
+
readonly host: FMHost;
|
|
26
|
+
private connection_details: databaseOptionsWithExternalSources
|
|
27
|
+
private cookies: { [key: string]: string } = {}
|
|
28
|
+
readonly name: string;
|
|
29
|
+
|
|
30
|
+
constructor(host: FMHost, conn: databaseOptionsWithExternalSources) {
|
|
31
|
+
super()
|
|
32
|
+
this.host = host
|
|
33
|
+
this.name = conn.database
|
|
34
|
+
this.connection_details = conn
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private generateExternalSourceLogin(data: databaseOptionsBase) {
|
|
38
|
+
if (data.credentials.method === "filemaker") {
|
|
39
|
+
let _data = <loginOptionsFileMaker>data.credentials
|
|
40
|
+
return {
|
|
41
|
+
database: data.database,
|
|
42
|
+
username: _data.username,
|
|
43
|
+
password: _data.password
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
throw "Not yet supported login method"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
logout(): Promise<void> {
|
|
52
|
+
return new Promise(async (resolve, reject) => {
|
|
53
|
+
if (!this.token) reject(new Error("Not logged in"))
|
|
54
|
+
|
|
55
|
+
let _fetch = await fetch(`${this.endpoint}/sessions/${this.token}`, {
|
|
56
|
+
method: "DELETE",
|
|
57
|
+
headers: {
|
|
58
|
+
"content-type": "application/json"
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
let data = await _fetch.json()
|
|
62
|
+
// console.log(data)
|
|
63
|
+
this._token = null
|
|
64
|
+
resolve()
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
login() {
|
|
69
|
+
return new Promise<string>(async (resolve, reject) => {
|
|
70
|
+
// Reset cookies
|
|
71
|
+
this.cookies = {}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
await this.host.getMetadata()
|
|
75
|
+
} catch (e) {
|
|
76
|
+
reject(e)
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
if (this.token) throw new Error("Already logged in. Run logout() first")
|
|
80
|
+
|
|
81
|
+
if (this.connection_details.credentials.method === "token") {
|
|
82
|
+
this._token = (<loginOptionsToken>this.connection_details.credentials).token
|
|
83
|
+
resolve(this.token)
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
fetch(`${this.endpoint}/sessions`, {
|
|
87
|
+
hostname: this.host.hostname,
|
|
88
|
+
port: 443,
|
|
89
|
+
method: "POST",
|
|
90
|
+
headers: generateAuthorizationHeaders(this.connection_details.credentials) as unknown as HeadersInit,
|
|
91
|
+
body: JSON.stringify({
|
|
92
|
+
fmDataSource: this.connection_details.externalSources.map(i => {
|
|
93
|
+
let _i = <databaseOptionsBase>i
|
|
94
|
+
return this.generateExternalSourceLogin(_i)
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
}).then(async res => {
|
|
98
|
+
let _res = <any>(await res.json())
|
|
99
|
+
if (res.status === 200) {
|
|
100
|
+
this._token = res.headers.get('x-fm-data-access-token')
|
|
101
|
+
resolve(this._token)
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
reject(new FMError(_res.messages[0].code, _res.status, res))
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
.catch(e => {
|
|
108
|
+
reject(e)
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
get token() {
|
|
115
|
+
return this._token
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
get endpoint(): string {
|
|
119
|
+
return `${this.host.hostname}/fmi/data/v2/databases/${this.name}`
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async apiRequest(url: string | Request, options: any = {}, autoRelogin = true): Promise<any> {
|
|
123
|
+
if (!options.headers) options.headers = {}
|
|
124
|
+
options.headers["content-type"] = options.headers["content-type"] ? options.headers["content-type"] : "application/json"
|
|
125
|
+
options.headers["authorization"] = "Bearer " + this._token
|
|
126
|
+
options.rejectUnauthorized = this.host.verify
|
|
127
|
+
|
|
128
|
+
let _fetch = await fetch(url, options)
|
|
129
|
+
if (_fetch.headers.get('set-cookie')) {
|
|
130
|
+
for (let cookie of _fetch.headers.get('set-cookie')) {
|
|
131
|
+
let cookie_split = cookie.split("=")
|
|
132
|
+
this.cookies[cookie_split[0]] = cookie_split[1]
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
let data = await _fetch.json() as fileMakerResponse
|
|
136
|
+
// console.log(data.messages[0])
|
|
137
|
+
if (data.messages[0].code == 952 && autoRelogin) {
|
|
138
|
+
this._token = null
|
|
139
|
+
await this.login()
|
|
140
|
+
return await this.apiRequest(url, options, false)
|
|
141
|
+
}
|
|
142
|
+
return (data as any)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
getLayout<T extends LayoutInterface>(name): Layout<T> {
|
|
146
|
+
return new Layout(this, name)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
setGlobals(globalFields): Promise<void> {
|
|
150
|
+
// console.log({globalFields})
|
|
151
|
+
return new Promise((resolve, reject) => {
|
|
152
|
+
this.apiRequest(`${this.endpoint}/globals`, {
|
|
153
|
+
method: "PATCH",
|
|
154
|
+
body: JSON.stringify({globalFields})
|
|
155
|
+
}).then(res => {
|
|
156
|
+
// console.log(res)
|
|
157
|
+
if (res.messages[0].code === "0") {
|
|
158
|
+
resolve()
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
reject(
|
|
162
|
+
(res.messages[0].code, res.status, res))
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
.catch(e => {
|
|
166
|
+
reject(e)
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
script(name, parameter = ""): Script {
|
|
172
|
+
return ({name, parameter} as Script)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
_tokenExpired() {
|
|
176
|
+
this.emit("token_expired")
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
streamContainer(field, url): Promise<http.IncomingMessage> {
|
|
180
|
+
return new Promise((resolve, reject) => {
|
|
181
|
+
if (field.metadata.result !== "container") {
|
|
182
|
+
reject("Cannot stream the field " + field.id + " as it is not a container")
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
if (!url || typeof url !== "string") {
|
|
186
|
+
reject("Container is empty, or has invalid value")
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let headers = {}
|
|
191
|
+
if (Object.keys(this.cookies).length !== 0) {
|
|
192
|
+
headers["Cookie"] = Object.keys(this.cookies)
|
|
193
|
+
.map(key => {
|
|
194
|
+
return key + "=" + this.cookies[key]
|
|
195
|
+
})
|
|
196
|
+
.join("; ")
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Automatically switch between the http and https modules, based on which is needed
|
|
200
|
+
(url.startsWith("https") ? https : http).get(url, {
|
|
201
|
+
headers
|
|
202
|
+
}, (res) => {
|
|
203
|
+
// Check for the 'set-cookie' header. If it exists, remember it and strip it for better security.
|
|
204
|
+
if (res.headers['set-cookie']) {
|
|
205
|
+
for (let cookie of res.headers['set-cookie']) {
|
|
206
|
+
let cookie_split = cookie.split("=")
|
|
207
|
+
this.cookies[cookie_split[0]] = cookie_split[1]
|
|
208
|
+
}
|
|
209
|
+
res.headers['set-cookie'] = null
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
resolve(res)
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { AuthorizationHeaders, AuthorizationHeadersOAuth, loginOptionsClaris, loginOptionsFileMaker, loginOptionsOAuth } from "../types.js";
|
|
2
|
+
export declare function generateAuthorizationHeaders(credentials: loginOptionsOAuth | loginOptionsFileMaker | loginOptionsClaris): AuthorizationHeaders | AuthorizationHeadersOAuth;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023. See LICENSE file for more information
|
|
3
|
+
*/
|
|
4
|
+
export function generateAuthorizationHeaders(credentials) {
|
|
5
|
+
switch (credentials.method) {
|
|
6
|
+
case "filemaker":
|
|
7
|
+
return {
|
|
8
|
+
"Content-Type": "application/json",
|
|
9
|
+
"Authorization": "Basic " + Buffer.from(credentials.username + ":" + credentials.password).toString("base64")
|
|
10
|
+
};
|
|
11
|
+
case "claris":
|
|
12
|
+
return {
|
|
13
|
+
"Content-Type": "application/json",
|
|
14
|
+
"Authorization": credentials.claris.fmid,
|
|
15
|
+
};
|
|
16
|
+
case "oauth":
|
|
17
|
+
return {
|
|
18
|
+
"Content-Type": "application/json",
|
|
19
|
+
"X-FM-Data-OAuth-RequestId": credentials.oauth.requestId,
|
|
20
|
+
"X-FM-Data-OAuth-Identifier": credentials.oauth.requestIdentifier
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023. See LICENSE file for more information
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
AuthorizationHeaders,
|
|
7
|
+
AuthorizationHeadersOAuth,
|
|
8
|
+
loginOptionsClaris,
|
|
9
|
+
loginOptionsFileMaker,
|
|
10
|
+
loginOptionsOAuth
|
|
11
|
+
} from "../types.js";
|
|
12
|
+
|
|
13
|
+
export function generateAuthorizationHeaders(credentials: loginOptionsOAuth | loginOptionsFileMaker | loginOptionsClaris): AuthorizationHeaders | AuthorizationHeadersOAuth {
|
|
14
|
+
switch (credentials.method) {
|
|
15
|
+
case "filemaker":
|
|
16
|
+
return {
|
|
17
|
+
"Content-Type": "application/json",
|
|
18
|
+
"Authorization": "Basic " + Buffer.from((<loginOptionsFileMaker>credentials).username + ":" + (<loginOptionsFileMaker>credentials).password).toString("base64")
|
|
19
|
+
} as AuthorizationHeaders
|
|
20
|
+
case "claris":
|
|
21
|
+
return {
|
|
22
|
+
"Content-Type": "application/json",
|
|
23
|
+
"Authorization": (<loginOptionsClaris>credentials).claris.fmid,
|
|
24
|
+
} as AuthorizationHeaders
|
|
25
|
+
case "oauth":
|
|
26
|
+
return {
|
|
27
|
+
"Content-Type": "application/json",
|
|
28
|
+
"X-FM-Data-OAuth-RequestId": (<loginOptionsOAuth>credentials).oauth.requestId,
|
|
29
|
+
"X-FM-Data-OAuth-Identifier": (<loginOptionsOAuth>credentials).oauth.requestIdentifier
|
|
30
|
+
} as AuthorizationHeadersOAuth
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import FMHost from "./connection/FMHost.js";
|
|
2
|
+
import { FMError } from "./FMError.js";
|
|
3
|
+
import * as TYPES from "./types.js";
|
|
4
|
+
import { Database } from "./connection/database";
|
|
5
|
+
import { Layout } from "./layouts/layout";
|
|
6
|
+
import { RecordBase } from "./records/recordBase";
|
|
7
|
+
import { LayoutRecord } from "./records/layoutRecord";
|
|
8
|
+
import { LayoutInterface } from "./layouts/layoutInterface";
|
|
9
|
+
import { RecordFieldsMap } from "./layouts/layoutInterface";
|
|
10
|
+
import { PortalInterface } from "./layouts/layoutInterface";
|
|
11
|
+
import { PortalRecord } from "./records/portalRecord";
|
|
12
|
+
import { Portal } from "./records/portal";
|
|
13
|
+
import { Field } from "./records/field";
|
|
14
|
+
import { Find } from "./records/getOperations/find";
|
|
15
|
+
import { RecordGetOperation } from "./records/getOperations/recordGetOperation";
|
|
16
|
+
import { RecordGetRange } from "./records/getOperations/recordGetRange";
|
|
17
|
+
import { LayoutRecordManager } from "./layouts/layoutRecordManager";
|
|
18
|
+
export default FMHost;
|
|
19
|
+
export { FMError, TYPES, Database, Layout, RecordBase, LayoutRecord, LayoutInterface, RecordFieldsMap, PortalInterface, PortalRecord, Portal, Field, Find, RecordGetOperation, RecordGetRange, LayoutRecordManager };
|