@jd-data-limited/easy-fm 4.1.0 → 4.1.2
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/package.json +5 -2
- package/src/bin/generateTypes.js +121 -215
- package/src/connection/database.d.ts +7 -7
- package/src/connection/database.js +34 -50
- package/src/connection/database.ts +43 -60
- package/src/connection/databaseBase.d.ts +4 -5
- package/src/connection/databaseBase.ts +3 -4
- package/src/records/field.d.ts +8 -16
- package/src/records/field.js +225 -124
- package/src/records/field.ts +86 -39
- package/src/records/layoutRecord.js +0 -1
- package/src/records/layoutRecord.ts +0 -1
- package/src/records/recordBase.d.ts +1 -1
- package/src/types.d.ts +2 -6
- package/src/types.js +0 -5
- package/src/types.ts +0 -5
package/package.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jd-data-limited/easy-fm",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.2",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"@types/follow-redirects": "^1.14.4",
|
|
6
6
|
"follow-redirects": "^1.15.5",
|
|
7
|
-
"moment": "^2.29.4"
|
|
7
|
+
"moment": "^2.29.4",
|
|
8
|
+
"node-fetch": "^3.3.2",
|
|
9
|
+
"node-fetch-cookies": "^2.1.1"
|
|
8
10
|
},
|
|
9
11
|
"type": "module",
|
|
10
12
|
"main": "src/index.js",
|
|
@@ -49,6 +51,7 @@
|
|
|
49
51
|
"eslint-plugin-promise": "^6.1.1",
|
|
50
52
|
"inquirer": "^9.2.13",
|
|
51
53
|
"ts-morph": "^21.0.1",
|
|
54
|
+
"typedoc": "^0.25.12",
|
|
52
55
|
"typescript": "^5.4.3",
|
|
53
56
|
"webpack": "^5.81.0",
|
|
54
57
|
"webpack-cli": "^5.0.2"
|
package/src/bin/generateTypes.js
CHANGED
|
@@ -1,53 +1,10 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/*
|
|
3
2
|
* Copyright (c) 2024. See LICENSE file for more information
|
|
4
3
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
10
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
11
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
12
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
13
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
14
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
15
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
16
|
-
});
|
|
17
|
-
};
|
|
18
|
-
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
19
|
-
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
20
|
-
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
21
|
-
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
22
|
-
function step(op) {
|
|
23
|
-
if (f) throw new TypeError("Generator is already executing.");
|
|
24
|
-
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
25
|
-
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
26
|
-
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
27
|
-
switch (op[0]) {
|
|
28
|
-
case 0: case 1: t = op; break;
|
|
29
|
-
case 4: _.label++; return { value: op[1], done: false };
|
|
30
|
-
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
31
|
-
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
32
|
-
default:
|
|
33
|
-
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
34
|
-
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
35
|
-
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
36
|
-
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
37
|
-
if (t[2]) _.ops.pop();
|
|
38
|
-
_.trys.pop(); continue;
|
|
39
|
-
}
|
|
40
|
-
op = body.call(thisArg, _);
|
|
41
|
-
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
42
|
-
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
-
exports.generateTypesCLI = void 0;
|
|
47
|
-
var apiResults_js_1 = require("../models/apiResults.js");
|
|
48
|
-
var inquirer_1 = require("inquirer");
|
|
49
|
-
var FMHost_js_1 = require("../connection/FMHost.js");
|
|
50
|
-
var fs = require("fs");
|
|
4
|
+
import { ApiFieldResultTypes } from "../models/apiResults.js";
|
|
5
|
+
import inquirer from 'inquirer';
|
|
6
|
+
import FMHost from "../connection/FMHost.js";
|
|
7
|
+
import * as fs from "fs";
|
|
51
8
|
function sub(str) {
|
|
52
9
|
return str
|
|
53
10
|
.replace(/[^a-zA-Z0-9_]/g, "") // Remove non-alphanumeric characters
|
|
@@ -55,42 +12,38 @@ function sub(str) {
|
|
|
55
12
|
}
|
|
56
13
|
function generateFieldType(field) {
|
|
57
14
|
switch (field.result) {
|
|
58
|
-
case
|
|
15
|
+
case ApiFieldResultTypes.TEXT:
|
|
59
16
|
return "Field<string>";
|
|
60
|
-
case
|
|
17
|
+
case ApiFieldResultTypes.CONTAINER:
|
|
61
18
|
return "Field<Container>";
|
|
62
|
-
case
|
|
19
|
+
case ApiFieldResultTypes.DATE:
|
|
63
20
|
return "Field<Date>";
|
|
64
|
-
case
|
|
21
|
+
case ApiFieldResultTypes.NUMBER:
|
|
65
22
|
return "Field<number>";
|
|
66
|
-
case
|
|
23
|
+
case ApiFieldResultTypes.TIME:
|
|
67
24
|
return "Field<Date>";
|
|
68
|
-
case
|
|
25
|
+
case ApiFieldResultTypes.TIMESTAMP:
|
|
69
26
|
return "Field<Date>";
|
|
70
27
|
}
|
|
71
28
|
}
|
|
72
29
|
function processFieldsChunk(fields) {
|
|
73
|
-
|
|
74
|
-
fields = fields.filter(
|
|
30
|
+
let existing_fields = [];
|
|
31
|
+
fields = fields.filter(field => {
|
|
75
32
|
if (existing_fields.includes(field.name))
|
|
76
33
|
return false;
|
|
77
34
|
existing_fields.push(field.name);
|
|
78
35
|
return true;
|
|
79
36
|
});
|
|
80
|
-
return fields.map(
|
|
37
|
+
return fields.map(field => {
|
|
81
38
|
return JSON.stringify(field.name) + ": " + generateFieldType(field);
|
|
82
39
|
});
|
|
83
40
|
}
|
|
84
41
|
function camelToSnakeCase(str) {
|
|
85
42
|
return str.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();
|
|
86
43
|
}
|
|
87
|
-
function safeStringInjection(str) {
|
|
88
|
-
|
|
89
|
-
for (
|
|
90
|
-
args[_i - 1] = arguments[_i];
|
|
91
|
-
}
|
|
92
|
-
var result = "";
|
|
93
|
-
for (var i = 0; i < str.length; i++) {
|
|
44
|
+
function safeStringInjection(str, ...args) {
|
|
45
|
+
let result = "";
|
|
46
|
+
for (let i = 0; i < str.length; i++) {
|
|
94
47
|
result += str[i];
|
|
95
48
|
if (i < args.length) {
|
|
96
49
|
result += JSON.stringify(args[i].toString());
|
|
@@ -98,158 +51,111 @@ function safeStringInjection(str) {
|
|
|
98
51
|
}
|
|
99
52
|
return result;
|
|
100
53
|
}
|
|
101
|
-
function generateTypesCLI() {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
type: "input",
|
|
115
|
-
name: "hostname",
|
|
116
|
-
message: "The IP address/domain of your FileMaker server (must start with either http:// or https://)",
|
|
117
|
-
validate: function (input) {
|
|
118
|
-
return input.startsWith("http://") || input.startsWith("https://") || "Please enter a valid IP address/domain starting with http:// or https://";
|
|
119
|
-
}
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
type: "number",
|
|
123
|
-
name: "timezoneOffset",
|
|
124
|
-
message: "Server timezone offset (minutes)",
|
|
125
|
-
default: 0 - (new Date()).getTimezoneOffset()
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
type: "confirm",
|
|
129
|
-
name: "verify",
|
|
130
|
-
default: true,
|
|
131
|
-
message: "Verify your server's identity",
|
|
132
|
-
when: function (previousAnswers) {
|
|
133
|
-
return previousAnswers.hostname.startsWith("https://");
|
|
134
|
-
}
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
type: "input",
|
|
138
|
-
name: "database",
|
|
139
|
-
message: "Database filename"
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
type: "input",
|
|
143
|
-
name: "username",
|
|
144
|
-
message: "Database username"
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
type: "password",
|
|
148
|
-
name: "password",
|
|
149
|
-
message: "Database password",
|
|
150
|
-
mask: "*"
|
|
151
|
-
}
|
|
152
|
-
])];
|
|
153
|
-
case 1:
|
|
154
|
-
data = _c.sent();
|
|
155
|
-
HOST = new FMHost_js_1.default(data.hostname, data.timezoneOffset, data.verify);
|
|
156
|
-
DATABASE = HOST.database({
|
|
157
|
-
database: data.database,
|
|
158
|
-
credentials: {
|
|
159
|
-
method: "filemaker",
|
|
160
|
-
username: data.username,
|
|
161
|
-
password: data.password
|
|
162
|
-
},
|
|
163
|
-
externalSources: []
|
|
164
|
-
});
|
|
165
|
-
return [4 /*yield*/, DATABASE.login()
|
|
166
|
-
// Create file write stream
|
|
167
|
-
];
|
|
168
|
-
case 2:
|
|
169
|
-
_c.sent();
|
|
170
|
-
stream = fs.createWriteStream("./types.ts");
|
|
171
|
-
return [4 /*yield*/, DATABASE.listLayouts()];
|
|
172
|
-
case 3:
|
|
173
|
-
layouts = _c.sent();
|
|
174
|
-
stream.write("import FMHost, {Container, FieldBase, LayoutInterface, Portal} from \"@jd-data-limited/easy-fm\";\n");
|
|
175
|
-
stream.write("// " + layouts.length + " layouts found\n\n");
|
|
176
|
-
layoutInterfaces = new Map();
|
|
177
|
-
_loop_1 = function (layout) {
|
|
178
|
-
var substituteName, metadata, fields, portals, portalsProcessed, interfaceName;
|
|
179
|
-
return __generator(this, function (_d) {
|
|
180
|
-
switch (_d.label) {
|
|
181
|
-
case 0:
|
|
182
|
-
substituteName = sub(layout.name);
|
|
183
|
-
if (!substituteName)
|
|
184
|
-
return [2 /*return*/, "continue"];
|
|
185
|
-
console.log(substituteName);
|
|
186
|
-
return [4 /*yield*/, layout.getLayoutMeta()];
|
|
187
|
-
case 1:
|
|
188
|
-
metadata = _d.sent();
|
|
189
|
-
fields = processFieldsChunk(metadata.fieldMetaData);
|
|
190
|
-
portals = metadata.portalMetaData;
|
|
191
|
-
portalsProcessed = Object.keys(portals).map(function (portalName) {
|
|
192
|
-
return "".concat(JSON.stringify(portalName), ": Portal<{").concat(processFieldsChunk(portals[portalName]), "}>");
|
|
193
|
-
});
|
|
194
|
-
interfaceName = substituteName + "LayoutInterface";
|
|
195
|
-
layoutInterfaces.set(layout.name, {
|
|
196
|
-
interfaceName: interfaceName,
|
|
197
|
-
interface: "export interface ".concat(interfaceName, " extends LayoutInterface {\n fields: {").concat(fields.join(",\n "), "},\n portals: {").concat(portalsProcessed.join(",\n "), "}\n }")
|
|
198
|
-
});
|
|
199
|
-
return [2 /*return*/];
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
};
|
|
203
|
-
_i = 0, layouts_1 = layouts;
|
|
204
|
-
_c.label = 4;
|
|
205
|
-
case 4:
|
|
206
|
-
if (!(_i < layouts_1.length)) return [3 /*break*/, 7];
|
|
207
|
-
layout = layouts_1[_i];
|
|
208
|
-
return [5 /*yield**/, _loop_1(layout)];
|
|
209
|
-
case 5:
|
|
210
|
-
_c.sent();
|
|
211
|
-
_c.label = 6;
|
|
212
|
-
case 6:
|
|
213
|
-
_i++;
|
|
214
|
-
return [3 /*break*/, 4];
|
|
215
|
-
case 7:
|
|
216
|
-
layoutInterfaceLinks = [];
|
|
217
|
-
for (_a = 0, layoutInterfaces_1 = layoutInterfaces; _a < layoutInterfaces_1.length; _a++) {
|
|
218
|
-
layout = layoutInterfaces_1[_a];
|
|
219
|
-
layoutInterfaceLinks.push([
|
|
220
|
-
"".concat(JSON.stringify(layout[0]), ": ").concat(layout[1].interfaceName),
|
|
221
|
-
"".concat(sub(camelToSnakeCase(layout[0]).toUpperCase().replace(" ", "_")), ": HOST.getLayout<").concat(layout[1].interfaceName, ">(").concat(JSON.stringify(layout[0]), ")")
|
|
222
|
-
]);
|
|
223
|
-
}
|
|
224
|
-
stream.write(safeStringInjection(templateObject_1 || (templateObject_1 = __makeTemplateObject(["const HOST = new FMHost(", ", ", ", ", ");"], ["const HOST = new FMHost(", ", ", ", ", ");"])), data.hostname, data.timezoneOffset, data.verify));
|
|
225
|
-
stream.write("HOST.database<{layouts:{".concat(layoutInterfaceLinks.map(function (i) { return i[0]; }).join(","), "}}>();"));
|
|
226
|
-
stream.write("\n\n");
|
|
227
|
-
stream.write("const LAYOUTS = {".concat(layoutInterfaceLinks.map(function (i) { return i[1]; }).join(","), "};"));
|
|
228
|
-
for (_b = 0, layoutInterfaces_2 = layoutInterfaces; _b < layoutInterfaces_2.length; _b++) {
|
|
229
|
-
layout = layoutInterfaces_2[_b];
|
|
230
|
-
stream.write("\n\n");
|
|
231
|
-
stream.write(layout[1].interface + ";");
|
|
232
|
-
}
|
|
233
|
-
// StructureKind
|
|
234
|
-
//
|
|
235
|
-
// const project = new Project({
|
|
236
|
-
// tsConfigFilePath: "../tsconfig.json"
|
|
237
|
-
// })
|
|
238
|
-
// project.resolveSourceFileDependencies()
|
|
239
|
-
return [4 /*yield*/, DATABASE.logout()];
|
|
240
|
-
case 8:
|
|
241
|
-
// StructureKind
|
|
242
|
-
//
|
|
243
|
-
// const project = new Project({
|
|
244
|
-
// tsConfigFilePath: "../tsconfig.json"
|
|
245
|
-
// })
|
|
246
|
-
// project.resolveSourceFileDependencies()
|
|
247
|
-
_c.sent();
|
|
248
|
-
console.log("DONE! Wrote database types to ./types.ts");
|
|
249
|
-
return [2 /*return*/];
|
|
54
|
+
export async function generateTypesCLI() {
|
|
55
|
+
process.stdout.write('\x1Bc');
|
|
56
|
+
console.log("For this tool to work, you are required to have access to an account on the target database.\n" +
|
|
57
|
+
"This account must have the fmrest extended privilege enabled.\n" +
|
|
58
|
+
"This tool can only export type interfaces for layouts that the given account has access to.");
|
|
59
|
+
console.log("\n");
|
|
60
|
+
const data = await inquirer.prompt([
|
|
61
|
+
{
|
|
62
|
+
type: "input",
|
|
63
|
+
name: "hostname",
|
|
64
|
+
message: "The IP address/domain of your FileMaker server (must start with either http:// or https://)",
|
|
65
|
+
validate(input) {
|
|
66
|
+
return input.startsWith("http://") || input.startsWith("https://") || "Please enter a valid IP address/domain starting with http:// or https://";
|
|
250
67
|
}
|
|
251
|
-
}
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
type: "number",
|
|
71
|
+
name: "timezoneOffset",
|
|
72
|
+
message: "Server timezone offset (minutes)",
|
|
73
|
+
default: 0 - (new Date()).getTimezoneOffset()
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
type: "confirm",
|
|
77
|
+
name: "verify",
|
|
78
|
+
default: true,
|
|
79
|
+
message: "Verify your server's identity",
|
|
80
|
+
when(previousAnswers) {
|
|
81
|
+
return previousAnswers.hostname.startsWith("https://");
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
type: "input",
|
|
86
|
+
name: "database",
|
|
87
|
+
message: "Database filename"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
type: "input",
|
|
91
|
+
name: "username",
|
|
92
|
+
message: "Database username"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
type: "password",
|
|
96
|
+
name: "password",
|
|
97
|
+
message: "Database password",
|
|
98
|
+
mask: "*"
|
|
99
|
+
}
|
|
100
|
+
]);
|
|
101
|
+
const HOST = new FMHost(data.hostname, data.timezoneOffset, data.verify);
|
|
102
|
+
const DATABASE = HOST.database({
|
|
103
|
+
database: data.database,
|
|
104
|
+
credentials: {
|
|
105
|
+
method: "filemaker",
|
|
106
|
+
username: data.username,
|
|
107
|
+
password: data.password
|
|
108
|
+
},
|
|
109
|
+
externalSources: []
|
|
252
110
|
});
|
|
111
|
+
await DATABASE.login();
|
|
112
|
+
// Create file write stream
|
|
113
|
+
const stream = fs.createWriteStream("./types.ts");
|
|
114
|
+
let layouts = await DATABASE.listLayouts();
|
|
115
|
+
stream.write(`import FMHost, {Container, FieldBase, LayoutInterface, Portal} from "@jd-data-limited/easy-fm";\n`);
|
|
116
|
+
stream.write("// " + layouts.length + " layouts found\n\n");
|
|
117
|
+
let layoutInterfaces = new Map();
|
|
118
|
+
for (let layout of layouts) {
|
|
119
|
+
let substituteName = sub(layout.name);
|
|
120
|
+
if (!substituteName)
|
|
121
|
+
continue;
|
|
122
|
+
console.log(substituteName);
|
|
123
|
+
let metadata = await layout.getLayoutMeta();
|
|
124
|
+
let fields = processFieldsChunk(metadata.fieldMetaData);
|
|
125
|
+
let portals = metadata.portalMetaData;
|
|
126
|
+
let portalsProcessed = Object.keys(portals).map(portalName => {
|
|
127
|
+
return `${JSON.stringify(portalName)}: Portal<{${processFieldsChunk(portals[portalName])}}>`;
|
|
128
|
+
});
|
|
129
|
+
let interfaceName = substituteName + "LayoutInterface";
|
|
130
|
+
layoutInterfaces.set(layout.name, {
|
|
131
|
+
interfaceName,
|
|
132
|
+
interface: `export interface ${interfaceName} extends LayoutInterface {
|
|
133
|
+
fields: {${fields.join(",\n ")}},
|
|
134
|
+
portals: {${portalsProcessed.join(",\n ")}}
|
|
135
|
+
}`
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
let layoutInterfaceLinks = [];
|
|
139
|
+
for (let layout of layoutInterfaces) {
|
|
140
|
+
layoutInterfaceLinks.push([
|
|
141
|
+
`${JSON.stringify(layout[0])}: ${layout[1].interfaceName}`,
|
|
142
|
+
`${sub(camelToSnakeCase(layout[0]).toUpperCase().replace(" ", "_"))}: HOST.getLayout<${layout[1].interfaceName}>(${JSON.stringify(layout[0])})`
|
|
143
|
+
]);
|
|
144
|
+
}
|
|
145
|
+
stream.write(safeStringInjection `const HOST = new FMHost(${data.hostname}, ${data.timezoneOffset}, ${data.verify});`);
|
|
146
|
+
stream.write(`HOST.database<{layouts:{${layoutInterfaceLinks.map(i => i[0]).join(",")}}}>();`);
|
|
147
|
+
stream.write("\n\n");
|
|
148
|
+
stream.write(`const LAYOUTS = {${layoutInterfaceLinks.map(i => i[1]).join(",")}};`);
|
|
149
|
+
for (let layout of layoutInterfaces) {
|
|
150
|
+
stream.write("\n\n");
|
|
151
|
+
stream.write(layout[1].interface + ";");
|
|
152
|
+
}
|
|
153
|
+
// StructureKind
|
|
154
|
+
//
|
|
155
|
+
// const project = new Project({
|
|
156
|
+
// tsConfigFilePath: "../tsconfig.json"
|
|
157
|
+
// })
|
|
158
|
+
// project.resolveSourceFileDependencies()
|
|
159
|
+
await DATABASE.logout();
|
|
160
|
+
console.log("DONE! Wrote database types to ./types.ts");
|
|
253
161
|
}
|
|
254
|
-
exports.generateTypesCLI = generateTypesCLI;
|
|
255
|
-
var templateObject_1;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
/// <reference types="node"
|
|
2
|
-
/// <reference types="node" resolution-mode="require"/>
|
|
1
|
+
/// <reference types="node" />
|
|
3
2
|
import { EventEmitter } from 'events';
|
|
4
3
|
import { type LayoutInterface } from '../layouts/layoutInterface.js';
|
|
5
4
|
import { Layout } from '../layouts/layout.js';
|
|
@@ -8,7 +7,7 @@ import { type HostBase } from './HostBase.js';
|
|
|
8
7
|
import { type DatabaseBase } from './databaseBase.js';
|
|
9
8
|
import { type ApiResults } from '../models/apiResults.js';
|
|
10
9
|
import { type DatabaseStructure } from '../databaseStructure.js';
|
|
11
|
-
import { type
|
|
10
|
+
import { type RequestInfo, type RequestInit, type Response } from 'node-fetch';
|
|
12
11
|
/**
|
|
13
12
|
* Represents a database connection.
|
|
14
13
|
* @template T - The structure of the database.
|
|
@@ -38,7 +37,7 @@ export declare class Database<T extends DatabaseStructure> extends EventEmitter
|
|
|
38
37
|
* @throws {FMError} - Throws an FMError if login fails.
|
|
39
38
|
* @return {Promise<string>} - Returns a promise that resolves to the access token upon successful login.
|
|
40
39
|
*/
|
|
41
|
-
login(forceLogin?: boolean): Promise<string>;
|
|
40
|
+
login(forceLogin?: boolean): Promise<string | undefined>;
|
|
42
41
|
get token(): string;
|
|
43
42
|
/**
|
|
44
43
|
* Returns the endpoint URL for the database connection.
|
|
@@ -46,10 +45,12 @@ export declare class Database<T extends DatabaseStructure> extends EventEmitter
|
|
|
46
45
|
* @returns {string} The endpoint URL.
|
|
47
46
|
*/
|
|
48
47
|
get endpoint(): string;
|
|
49
|
-
_apiRequestRaw(url:
|
|
48
|
+
_apiRequestRaw(url: URL | RequestInfo, options?: RequestInit & {
|
|
50
49
|
headers?: Record<string, string>;
|
|
50
|
+
useCookieJar?: boolean;
|
|
51
51
|
}, autoRelogin?: boolean): Promise<Response>;
|
|
52
|
-
|
|
52
|
+
private _generateCookieHeader;
|
|
53
|
+
_apiRequestJSON<T = any>(url: URL | RequestInfo, options?: RequestInit & {
|
|
53
54
|
headers?: Record<string, string>;
|
|
54
55
|
}): Promise<ApiResults<T>>;
|
|
55
56
|
/**
|
|
@@ -62,5 +63,4 @@ export declare class Database<T extends DatabaseStructure> extends EventEmitter
|
|
|
62
63
|
layout<R extends keyof T['layouts']>(name: R): Layout<T['layouts'][R]>;
|
|
63
64
|
layout<R extends LayoutInterface>(name: string): Layout<R>;
|
|
64
65
|
script(name: string, parameter?: string): Script;
|
|
65
|
-
streamURL(url: string): Promise<IncomingMessage>;
|
|
66
66
|
}
|
|
@@ -5,7 +5,9 @@ import { EventEmitter } from 'events';
|
|
|
5
5
|
import { generateAuthorizationHeaders } from './generateAuthorizationHeaders.js';
|
|
6
6
|
import { FMError } from '../FMError.js';
|
|
7
7
|
import { Layout } from '../layouts/layout.js';
|
|
8
|
-
import
|
|
8
|
+
import fetch from 'node-fetch';
|
|
9
|
+
// @ts-expect-error
|
|
10
|
+
import fetchWithCookies, { CookieJar } from 'node-fetch-cookies';
|
|
9
11
|
/**
|
|
10
12
|
* Represents a database connection.
|
|
11
13
|
* @template T - The structure of the database.
|
|
@@ -14,7 +16,7 @@ export class Database extends EventEmitter {
|
|
|
14
16
|
_token = '';
|
|
15
17
|
host;
|
|
16
18
|
connection_details;
|
|
17
|
-
cookies =
|
|
19
|
+
cookies = new CookieJar();
|
|
18
20
|
name;
|
|
19
21
|
debug;
|
|
20
22
|
constructor(host, conn) {
|
|
@@ -55,7 +57,6 @@ export class Database extends EventEmitter {
|
|
|
55
57
|
}
|
|
56
58
|
});
|
|
57
59
|
await _fetch.json();
|
|
58
|
-
// console.log(data)
|
|
59
60
|
this._token = '';
|
|
60
61
|
}
|
|
61
62
|
/**
|
|
@@ -67,18 +68,16 @@ export class Database extends EventEmitter {
|
|
|
67
68
|
* @return {Promise<string>} - Returns a promise that resolves to the access token upon successful login.
|
|
68
69
|
*/
|
|
69
70
|
async login(forceLogin = false) {
|
|
70
|
-
|
|
71
|
+
if (this.token !== '' && !forceLogin)
|
|
72
|
+
return;
|
|
71
73
|
// Reset cookies
|
|
72
|
-
this.cookies =
|
|
74
|
+
this.cookies = new CookieJar();
|
|
73
75
|
await this.host.getMetadata();
|
|
74
|
-
if (this.token !== '' && !forceLogin)
|
|
75
|
-
throw new Error('Already logged in. Run logout() first');
|
|
76
76
|
if (this.connection_details.credentials.method === 'token') {
|
|
77
77
|
this._token = (this.connection_details.credentials).token;
|
|
78
78
|
return this.token;
|
|
79
79
|
}
|
|
80
80
|
else {
|
|
81
|
-
console.time();
|
|
82
81
|
const url = new URL(`${this.endpoint}/sessions`);
|
|
83
82
|
url.hostname = this.host.hostname;
|
|
84
83
|
const res = await fetch(url, {
|
|
@@ -91,7 +90,6 @@ export class Database extends EventEmitter {
|
|
|
91
90
|
})
|
|
92
91
|
})
|
|
93
92
|
});
|
|
94
|
-
console.time();
|
|
95
93
|
const _res = (await res.json());
|
|
96
94
|
if (res.status === 200) {
|
|
97
95
|
this._token = res.headers.get('x-fm-data-access-token') ?? '';
|
|
@@ -121,19 +119,38 @@ export class Database extends EventEmitter {
|
|
|
121
119
|
if (!options.headers)
|
|
122
120
|
options.headers = {};
|
|
123
121
|
options.headers.authorization = 'Bearer ' + this._token;
|
|
122
|
+
// options.redirect = "manual"
|
|
123
|
+
// options.headers.cookies = this._generateCookieHeader()
|
|
124
|
+
// console.log(options.headers.cookies)
|
|
124
125
|
// options.rejectUnauthorized = this.host.verify
|
|
125
|
-
const _fetch =
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
126
|
+
const _fetch = options.useCookieJar
|
|
127
|
+
? await fetchWithCookies(this.cookies, url, options)
|
|
128
|
+
: await fetch(url, options);
|
|
129
|
+
// if (_fetch.headers.get('set-cookie')) {
|
|
130
|
+
// console.log(_fetch.headers.get('set-cookie'))
|
|
131
|
+
// for (const cookie of (_fetch.headers.get("Set-Cookie") ?? "").split("; ")) {
|
|
132
|
+
// const cookieSplit = cookie.split('=')
|
|
133
|
+
// console.log("COOKIE SPLIT:", cookieSplit)
|
|
134
|
+
// if (cookieSplit[1]) this.cookies[cookieSplit[0]] = cookieSplit[1]
|
|
135
|
+
// }
|
|
136
|
+
// }
|
|
137
|
+
// console.log(this.cookies, _fetch.status)
|
|
138
|
+
// // console.log(this, this.cookies, url)
|
|
139
|
+
// console.trace()
|
|
132
140
|
if (_fetch.status === 401 && autoRelogin) {
|
|
141
|
+
console.trace();
|
|
133
142
|
await this.login(true);
|
|
134
143
|
return await this._apiRequestRaw(url, options, false);
|
|
135
144
|
}
|
|
136
|
-
|
|
145
|
+
else
|
|
146
|
+
return _fetch;
|
|
147
|
+
}
|
|
148
|
+
_generateCookieHeader() {
|
|
149
|
+
const out = [];
|
|
150
|
+
for (const key of Object.keys(this.cookies)) {
|
|
151
|
+
out.push(`${key}=${this.cookies[key]}`);
|
|
152
|
+
}
|
|
153
|
+
return out.join('; ');
|
|
137
154
|
}
|
|
138
155
|
async _apiRequestJSON(url, options = {}) {
|
|
139
156
|
if (!options.headers)
|
|
@@ -179,37 +196,4 @@ export class Database extends EventEmitter {
|
|
|
179
196
|
script(name, parameter = '') {
|
|
180
197
|
return ({ name, parameter });
|
|
181
198
|
}
|
|
182
|
-
async streamURL(url) {
|
|
183
|
-
return await new Promise((resolve, reject) => {
|
|
184
|
-
if (!url || typeof url !== 'string') {
|
|
185
|
-
throw new Error('URL is empty, or has invalid value');
|
|
186
|
-
}
|
|
187
|
-
const headers = {};
|
|
188
|
-
if (Object.keys(this.cookies).length !== 0) {
|
|
189
|
-
headers.Cookie = Object.keys(this.cookies)
|
|
190
|
-
.map(key => {
|
|
191
|
-
return key + '=' + this.cookies[key];
|
|
192
|
-
})
|
|
193
|
-
.join('; ');
|
|
194
|
-
}
|
|
195
|
-
const checkForCookies = (res) => {
|
|
196
|
-
// Check for the 'set-cookie' header. If it exists, remember it and strip it for better security.
|
|
197
|
-
if (res.headers['set-cookie']) {
|
|
198
|
-
for (const cookie of res.headers['set-cookie']) {
|
|
199
|
-
const cookieSplit = cookie.split('=');
|
|
200
|
-
this.cookies[cookieSplit[0]] = cookieSplit[1];
|
|
201
|
-
}
|
|
202
|
-
res.headers['set-cookie'] = undefined;
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
// Automatically switch between the http and https modules, based on which is needed
|
|
206
|
-
(url.startsWith('https') ? HTTP_REDIRECT.https : HTTP_REDIRECT.http).get(url, {
|
|
207
|
-
headers,
|
|
208
|
-
beforeRedirect: (options, res) => { checkForCookies(res); }
|
|
209
|
-
}, (res) => {
|
|
210
|
-
checkForCookies(res);
|
|
211
|
-
resolve(res);
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
199
|
}
|