@keetanetwork/anchor 0.0.29 → 0.0.30

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.
Files changed (50) hide show
  1. package/lib/http-server-shared.d.ts +14 -0
  2. package/lib/http-server-shared.d.ts.map +1 -0
  3. package/lib/http-server-shared.js +108 -0
  4. package/lib/http-server-shared.js.map +1 -0
  5. package/lib/http-server.d.ts +62 -0
  6. package/lib/http-server.d.ts.map +1 -0
  7. package/lib/http-server.js +431 -0
  8. package/lib/http-server.js.map +1 -0
  9. package/lib/log/common.d.ts +35 -0
  10. package/lib/log/common.d.ts.map +1 -0
  11. package/lib/log/common.js +19 -0
  12. package/lib/log/common.js.map +1 -0
  13. package/lib/queue/common.d.ts +23 -0
  14. package/lib/queue/common.d.ts.map +1 -0
  15. package/lib/queue/common.js +47 -0
  16. package/lib/queue/common.js.map +1 -0
  17. package/lib/queue/drivers/queue_file.d.ts +17 -0
  18. package/lib/queue/drivers/queue_file.d.ts.map +1 -0
  19. package/lib/queue/drivers/queue_file.js +100 -0
  20. package/lib/queue/drivers/queue_file.js.map +1 -0
  21. package/lib/queue/drivers/queue_sqlite3.d.ts +28 -0
  22. package/lib/queue/drivers/queue_sqlite3.d.ts.map +1 -0
  23. package/lib/queue/drivers/queue_sqlite3.js +379 -0
  24. package/lib/queue/drivers/queue_sqlite3.js.map +1 -0
  25. package/lib/queue/index.d.ts +341 -0
  26. package/lib/queue/index.d.ts.map +1 -0
  27. package/lib/queue/index.js +940 -0
  28. package/lib/queue/index.js.map +1 -0
  29. package/lib/queue/internal.d.ts +8 -0
  30. package/lib/queue/internal.d.ts.map +1 -0
  31. package/lib/queue/internal.js +28 -0
  32. package/lib/queue/internal.js.map +1 -0
  33. package/lib/queue/pipeline.d.ts +149 -0
  34. package/lib/queue/pipeline.d.ts.map +1 -0
  35. package/lib/queue/pipeline.js +296 -0
  36. package/lib/queue/pipeline.js.map +1 -0
  37. package/lib/utils/asleep.d.ts +2 -0
  38. package/lib/utils/asleep.d.ts.map +1 -0
  39. package/lib/utils/asleep.js +3 -0
  40. package/lib/utils/asleep.js.map +1 -0
  41. package/lib/utils/defer.d.ts +3 -0
  42. package/lib/utils/defer.d.ts.map +1 -0
  43. package/lib/utils/defer.js +3 -0
  44. package/lib/utils/defer.js.map +1 -0
  45. package/npm-shrinkwrap.json +37 -1375
  46. package/package.json +1 -1
  47. package/services/asset-movement/common.d.ts +26 -5
  48. package/services/asset-movement/common.d.ts.map +1 -1
  49. package/services/asset-movement/common.js +136 -3
  50. package/services/asset-movement/common.js.map +1 -1
@@ -0,0 +1,14 @@
1
+ import type { Account } from "@keetanetwork/keetanet-client/lib/account.js";
2
+ export interface HTTPSignedField {
3
+ nonce: string;
4
+ timestamp: string;
5
+ signature: string;
6
+ }
7
+ export declare const assertHTTPSignedField: (input: unknown) => HTTPSignedField;
8
+ export interface HTTPSignedFieldURLParameters {
9
+ signedField: HTTPSignedField | null;
10
+ account: Account | null;
11
+ }
12
+ export declare function addSignatureToURL(input: URL | string, data: HTTPSignedFieldURLParameters): URL;
13
+ export declare function parseSignatureFromURL(input: URL | string): HTTPSignedFieldURLParameters;
14
+ //# sourceMappingURL=http-server-shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-server-shared.d.ts","sourceRoot":"","sources":["../../src/lib/http-server-shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,8CAA8C,CAAC;AAK5E,MAAM,WAAW,eAAe;IAC/B,KAAK,EAAE,MAAM,CAAC;IAEd,SAAS,EAAE,MAAM,CAAC;IAElB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,qBAAqB,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,eAAuD,CAAC;AAEhH,MAAM,WAAW,4BAA4B;IAC5C,WAAW,EAAE,eAAe,GAAG,IAAI,CAAC;IACpC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;CACxB;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,GAAG,GAAG,MAAM,EAAE,IAAI,EAAE,4BAA4B,GAAG,GAAG,CA0B9F;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,GAAG,GAAG,MAAM,GAAG,4BAA4B,CAoCvF"}
@@ -0,0 +1,108 @@
1
+ import * as __typia_transform__assertGuard from "typia/lib/internal/_assertGuard.js";
2
+ import * as __typia_transform__accessExpressionAsString from "typia/lib/internal/_accessExpressionAsString.js";
3
+ import { KeetaAnchorUserError } from "./error.js";
4
+ import { KeetaNet } from "../client/index.js";
5
+ import { createAssertEquals } from "typia";
6
+ export const assertHTTPSignedField = (() => { const _io0 = (input, _exceptionable = true) => "string" === typeof input.nonce && "string" === typeof input.timestamp && "string" === typeof input.signature && (3 === Object.keys(input).length || Object.keys(input).every(key => {
7
+ if (["nonce", "timestamp", "signature"].some(prop => key === prop))
8
+ return true;
9
+ const value = input[key];
10
+ if (undefined === value)
11
+ return true;
12
+ return false;
13
+ })); const _ao0 = (input, _path, _exceptionable = true) => ("string" === typeof input.nonce || __typia_transform__assertGuard._assertGuard(_exceptionable, {
14
+ method: "createAssertEquals",
15
+ path: _path + ".nonce",
16
+ expected: "string",
17
+ value: input.nonce
18
+ }, _errorFactory)) && ("string" === typeof input.timestamp || __typia_transform__assertGuard._assertGuard(_exceptionable, {
19
+ method: "createAssertEquals",
20
+ path: _path + ".timestamp",
21
+ expected: "string",
22
+ value: input.timestamp
23
+ }, _errorFactory)) && ("string" === typeof input.signature || __typia_transform__assertGuard._assertGuard(_exceptionable, {
24
+ method: "createAssertEquals",
25
+ path: _path + ".signature",
26
+ expected: "string",
27
+ value: input.signature
28
+ }, _errorFactory)) && (3 === Object.keys(input).length || (false === _exceptionable || Object.keys(input).every(key => {
29
+ if (["nonce", "timestamp", "signature"].some(prop => key === prop))
30
+ return true;
31
+ const value = input[key];
32
+ if (undefined === value)
33
+ return true;
34
+ return __typia_transform__assertGuard._assertGuard(_exceptionable, {
35
+ method: "createAssertEquals",
36
+ path: _path + __typia_transform__accessExpressionAsString._accessExpressionAsString(key),
37
+ expected: "undefined",
38
+ value: value
39
+ }, _errorFactory);
40
+ }))); const __is = (input, _exceptionable = true) => "object" === typeof input && null !== input && _io0(input, true); let _errorFactory; return (input, errorFactory) => {
41
+ if (false === __is(input)) {
42
+ _errorFactory = errorFactory;
43
+ ((input, _path, _exceptionable = true) => ("object" === typeof input && null !== input || __typia_transform__assertGuard._assertGuard(true, {
44
+ method: "createAssertEquals",
45
+ path: _path + "",
46
+ expected: "HTTPSignedField",
47
+ value: input
48
+ }, _errorFactory)) && _ao0(input, _path + "", true) || __typia_transform__assertGuard._assertGuard(true, {
49
+ method: "createAssertEquals",
50
+ path: _path + "",
51
+ expected: "HTTPSignedField",
52
+ value: input
53
+ }, _errorFactory))(input, "$input", true);
54
+ }
55
+ return input;
56
+ }; })();
57
+ export function addSignatureToURL(input, data) {
58
+ let url;
59
+ if (typeof input === 'string') {
60
+ url = new URL(input);
61
+ }
62
+ else {
63
+ url = new URL(input.toString());
64
+ }
65
+ for (const key of ['nonce', 'timestamp', 'signature']) {
66
+ const searchKey = `signed.${key}`;
67
+ if (url.searchParams.has(searchKey)) {
68
+ throw (new KeetaAnchorUserError(`URL already has signed field parameter: ${searchKey}`));
69
+ }
70
+ if (data.signedField) {
71
+ url.searchParams.set(`signed.${key}`, data.signedField[key]);
72
+ }
73
+ }
74
+ if (data.account) {
75
+ url.searchParams.set('account', data.account.publicKeyString.get());
76
+ }
77
+ return (url);
78
+ }
79
+ export function parseSignatureFromURL(input) {
80
+ let url;
81
+ if (typeof input === 'string') {
82
+ url = new URL(input);
83
+ }
84
+ else {
85
+ url = new URL(input.toString());
86
+ }
87
+ const signedField = (() => {
88
+ const nonce = url.searchParams.get('signed.nonce');
89
+ const timestamp = url.searchParams.get('signed.timestamp');
90
+ const signature = url.searchParams.get('signed.signature');
91
+ if (nonce === null && timestamp === null && signature === null) {
92
+ return (null);
93
+ }
94
+ if (!nonce || !timestamp || !signature) {
95
+ throw (new KeetaAnchorUserError('Incomplete signature fields in URL'));
96
+ }
97
+ return ({ nonce, timestamp, signature });
98
+ })();
99
+ const account = (() => {
100
+ const accountParam = url.searchParams.get('account');
101
+ if (accountParam === null) {
102
+ return (null);
103
+ }
104
+ return (KeetaNet.lib.Account.fromPublicKeyString(accountParam).assertAccount());
105
+ })();
106
+ return ({ signedField, account });
107
+ }
108
+ //# sourceMappingURL=http-server-shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-server-shared.js","sourceRoot":"","sources":["../../src/lib/http-server-shared.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Account } from \"@keetanetwork/keetanet-client/lib/account.js\";\nimport { KeetaAnchorUserError } from \"./error.js\";\nimport { KeetaNet } from \"../client/index.js\";\nimport { createAssertEquals } from \"typia\";\n\nexport interface HTTPSignedField {\n\tnonce: string;\n\t/* Date and time of the request in ISO 8601 format */\n\ttimestamp: string;\n\t/* Signature of the account public key and the nonce as an ASN.1 Sequence, Base64 DER */\n\tsignature: string;\n}\n\nexport const assertHTTPSignedField: (input: unknown) => HTTPSignedField = createAssertEquals<HTTPSignedField>();\n\nexport interface HTTPSignedFieldURLParameters {\n\tsignedField: HTTPSignedField | null;\n\taccount: Account | null;\n}\n\nexport function addSignatureToURL(input: URL | string, data: HTTPSignedFieldURLParameters): URL {\n\tlet url: URL;\n\n\tif (typeof input === 'string') {\n\t\turl = new URL(input);\n\t} else {\n\t\turl = new URL(input.toString());\n\t}\n\n\tfor (const key of [ 'nonce', 'timestamp', 'signature' ] as const) {\n\t\tconst searchKey = `signed.${key}`;\n\n\t\tif (url.searchParams.has(searchKey)) {\n\t\t\tthrow(new KeetaAnchorUserError(`URL already has signed field parameter: ${searchKey}`));\n\t\t}\n\n\t\tif (data.signedField) {\n\t\t\turl.searchParams.set(`signed.${key}`, data.signedField[key]);\n\t\t}\n\t}\n\n\tif (data.account) {\n\t\turl.searchParams.set('account', data.account.publicKeyString.get());\n\t}\n\n\treturn(url);\n}\n\nexport function parseSignatureFromURL(input: URL | string): HTTPSignedFieldURLParameters {\n\tlet url: URL;\n\n\tif (typeof input === 'string') {\n\t\turl = new URL(input);\n\t} else {\n\t\turl = new URL(input.toString());\n\t}\n\n\tconst signedField = ((): HTTPSignedField | null => {\n\t\tconst nonce = url.searchParams.get('signed.nonce');\n\t\tconst timestamp = url.searchParams.get('signed.timestamp');\n\t\tconst signature = url.searchParams.get('signed.signature');\n\n\t\tif (nonce === null && timestamp === null && signature === null) {\n\t\t\treturn(null);\n\t\t}\n\n\t\tif (!nonce || !timestamp || !signature) {\n\t\t\tthrow(new KeetaAnchorUserError('Incomplete signature fields in URL'));\n\t\t}\n\n\t\treturn({ nonce, timestamp, signature });\n\t})();\n\n\tconst account = ((): Account | null => {\n\t\tconst accountParam = url.searchParams.get('account');\n\t\tif (accountParam === null) {\n\t\t\treturn(null);\n\t\t}\n\n\t\treturn(KeetaNet.lib.Account.fromPublicKeyString(accountParam).assertAccount());\n\t})();\n\n\n\treturn({ signedField, account });\n}\n"]}
@@ -0,0 +1,62 @@
1
+ import * as http from 'http';
2
+ import type { JSONSerializable } from './utils/json.js';
3
+ import type { Logger } from './log/index.js';
4
+ export declare const AssertHTTPErrorData: (input: unknown) => {
5
+ error: string;
6
+ statusCode?: number;
7
+ contentType?: string;
8
+ };
9
+ export type Routes = {
10
+ [route: string]: (urlParams: Map<string, string>, postData: JSONSerializable | undefined, requestHeaders: http.IncomingHttpHeaders) => Promise<{
11
+ output: string | Buffer;
12
+ statusCode?: number;
13
+ contentType?: string;
14
+ headers?: {
15
+ [headerName: string]: string;
16
+ };
17
+ }>;
18
+ };
19
+ export interface KeetaAnchorHTTPServerConfig {
20
+ /**
21
+ * The port for the HTTP server to listen on (default is an ephemeral port).
22
+ */
23
+ port?: number;
24
+ /**
25
+ * Enable debug logging
26
+ */
27
+ logger?: Logger;
28
+ }
29
+ export declare abstract class KeetaNetAnchorHTTPServer<ConfigType extends KeetaAnchorHTTPServerConfig = KeetaAnchorHTTPServerConfig> implements Required<KeetaAnchorHTTPServerConfig> {
30
+ #private;
31
+ readonly port: NonNullable<KeetaAnchorHTTPServerConfig['port']>;
32
+ readonly logger: NonNullable<KeetaAnchorHTTPServerConfig['logger']>;
33
+ constructor(config: ConfigType);
34
+ protected abstract initRoutes(config: ConfigType): Promise<Routes>;
35
+ private static routeMatch;
36
+ private static routeFind;
37
+ private static addCORS;
38
+ private main;
39
+ /**
40
+ * Start the HTTP server and wait for it to be fully initialized.
41
+ */
42
+ start(): Promise<void>;
43
+ /**
44
+ * Wait for the server to terminate. This will only resolve once the
45
+ * server has been stopped.
46
+ */
47
+ wait(): Promise<void>;
48
+ /**
49
+ * Stop the HTTP server and wait for it to be fully terminated.
50
+ */
51
+ stop(): Promise<void>;
52
+ /**
53
+ * Get the URL of the server, which can be used to make requests to
54
+ * it. This will use "localhost" as the hostname and the port that
55
+ * the server is listening on by default but can be overridden by
56
+ * setting a custom URL.
57
+ */
58
+ get url(): string;
59
+ set url(value: string | URL | ((object: this) => string));
60
+ [Symbol.asyncDispose](): Promise<void>;
61
+ }
62
+ //# sourceMappingURL=http-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../../src/lib/http-server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAK7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,MAAM,EAAY,MAAM,gBAAgB,CAAC;AAKvD,eAAO,MAAM,mBAAmB,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAAkF,CAAC;AAOpM,MAAM,MAAM,MAAM,GAAG;IACpB,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,EAAE,gBAAgB,GAAG,SAAS,EAAE,cAAc,EAAE,IAAI,CAAC,mBAAmB,KAAK,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE;YAAE,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;SAAE,CAAC;KAAE,CAAC,CAAC;CACrQ,CAAC;AAEF,MAAM,WAAW,2BAA2B;IAC3C;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,8BAAsB,wBAAwB,CAAC,UAAU,SAAS,2BAA2B,GAAG,2BAA2B,CAAE,YAAW,QAAQ,CAAC,2BAA2B,CAAC;;IAC5K,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,2BAA2B,CAAC,MAAM,CAAC,CAAC,CAAC;IAChE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,2BAA2B,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAMxD,MAAM,EAAE,UAAU;IAM9B,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;IAElE,OAAO,CAAC,MAAM,CAAC,UAAU;IA2BzB,OAAO,CAAC,MAAM,CAAC,SAAS;IA2BxB,OAAO,CAAC,MAAM,CAAC,OAAO;YAsGR,IAAI;IAsMlB;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAc5B;;;OAGG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAO3B;;;;;OAKG;IACH,IAAI,GAAG,IAAI,MAAM,CAyBhB;IAED,IAAI,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,MAAM,EAAE,IAAI,KAAK,MAAM,CAAC,EAEvD;IAED,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC;CAGtC"}
@@ -0,0 +1,431 @@
1
+ import * as __typia_transform__assertGuard from "typia/lib/internal/_assertGuard.js";
2
+ import * as http from 'http';
3
+ import { KeetaAnchorError, KeetaAnchorUserError } from './error.js';
4
+ import { Log } from './log/index.js';
5
+ import { createAssert } from 'typia';
6
+ import { assertNever } from './utils/never.js';
7
+ export const AssertHTTPErrorData = (() => { const _io0 = input => "string" === typeof input.error && (undefined === input.statusCode || "number" === typeof input.statusCode) && (undefined === input.contentType || "string" === typeof input.contentType); const _ao0 = (input, _path, _exceptionable = true) => ("string" === typeof input.error || __typia_transform__assertGuard._assertGuard(_exceptionable, {
8
+ method: "createAssert",
9
+ path: _path + ".error",
10
+ expected: "string",
11
+ value: input.error
12
+ }, _errorFactory)) && (undefined === input.statusCode || "number" === typeof input.statusCode || __typia_transform__assertGuard._assertGuard(_exceptionable, {
13
+ method: "createAssert",
14
+ path: _path + ".statusCode",
15
+ expected: "(number | undefined)",
16
+ value: input.statusCode
17
+ }, _errorFactory)) && (undefined === input.contentType || "string" === typeof input.contentType || __typia_transform__assertGuard._assertGuard(_exceptionable, {
18
+ method: "createAssert",
19
+ path: _path + ".contentType",
20
+ expected: "(string | undefined)",
21
+ value: input.contentType
22
+ }, _errorFactory)); const __is = input => "object" === typeof input && null !== input && _io0(input); let _errorFactory; return (input, errorFactory) => {
23
+ if (false === __is(input)) {
24
+ _errorFactory = errorFactory;
25
+ ((input, _path, _exceptionable = true) => ("object" === typeof input && null !== input || __typia_transform__assertGuard._assertGuard(true, {
26
+ method: "createAssert",
27
+ path: _path + "",
28
+ expected: "__type",
29
+ value: input
30
+ }, _errorFactory)) && _ao0(input, _path + "", true) || __typia_transform__assertGuard._assertGuard(true, {
31
+ method: "createAssert",
32
+ path: _path + "",
33
+ expected: "__type",
34
+ value: input
35
+ }, _errorFactory))(input, "$input", true);
36
+ }
37
+ return input;
38
+ }; })();
39
+ /**
40
+ * The maximum size of a request (128KiB)
41
+ */
42
+ const MAX_REQUEST_SIZE = 1024 * 128;
43
+ ;
44
+ export class KeetaNetAnchorHTTPServer {
45
+ port;
46
+ logger;
47
+ #serverPromise;
48
+ #server;
49
+ #url;
50
+ #config;
51
+ constructor(config) {
52
+ this.#config = { ...config };
53
+ this.port = config.port ?? 0;
54
+ this.logger = config.logger ?? Log.Legacy('ANCHOR');
55
+ }
56
+ static routeMatch(requestURL, routeURL) {
57
+ const requestURLPaths = requestURL.pathname.split('/');
58
+ const routeURLPaths = routeURL.pathname.split('/');
59
+ if (requestURLPaths.length !== routeURLPaths.length) {
60
+ return ({ match: false });
61
+ }
62
+ const params = new Map();
63
+ for (let partIndex = 0; partIndex < requestURLPaths.length; partIndex++) {
64
+ const requestPath = requestURLPaths[partIndex];
65
+ const routePath = routeURLPaths[partIndex];
66
+ if (routePath === undefined || requestPath === undefined) {
67
+ return ({ match: false });
68
+ }
69
+ if (routePath.startsWith(':')) {
70
+ params.set(routePath.slice(1), requestPath);
71
+ }
72
+ else if (requestPath !== routePath) {
73
+ return ({ match: false });
74
+ }
75
+ }
76
+ return ({ match: true, params: params });
77
+ }
78
+ static routeFind(method, requestURL, routes) {
79
+ for (const routeKey in routes) {
80
+ const route = routes[routeKey];
81
+ if (route === undefined) {
82
+ continue;
83
+ }
84
+ const [routeMethod, ...routePathParts] = routeKey.split(' ');
85
+ const routePath = `/${routePathParts.join(' ')}`.replace(/^\/+/, '/');
86
+ if (method !== routeMethod) {
87
+ continue;
88
+ }
89
+ const routeURL = new URL(routePath, 'http://localhost');
90
+ const matchResult = this.routeMatch(requestURL, routeURL);
91
+ if (matchResult.match) {
92
+ return ({
93
+ route: route,
94
+ params: matchResult.params
95
+ });
96
+ }
97
+ }
98
+ return (null);
99
+ }
100
+ static addCORS(routes) {
101
+ const newRoutes = {};
102
+ const validMethods = new Set(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD']);
103
+ const methodsByPath = {};
104
+ for (const routeKey in routes) {
105
+ const methodAndPath = routeKey.split(' ');
106
+ const method = methodAndPath[0];
107
+ const path = methodAndPath.slice(1).join(' ');
108
+ if (method === undefined || path === undefined) {
109
+ continue;
110
+ }
111
+ if (!validMethods.has(method)) {
112
+ continue;
113
+ }
114
+ if (!(path in methodsByPath)) {
115
+ methodsByPath[path] = new Set();
116
+ }
117
+ if (methodsByPath[path] === undefined) {
118
+ throw (new Error(`internal error: methodsByPath missing path for ${path}`));
119
+ }
120
+ methodsByPath[path].add(method);
121
+ }
122
+ const seenPaths = new Set();
123
+ for (const routeKey in routes) {
124
+ const methodAndPath = routeKey.split(' ');
125
+ const method = methodAndPath[0];
126
+ const path = methodAndPath.slice(1).join(' ');
127
+ const routeHandler = routes[routeKey];
128
+ if (routeHandler === undefined) {
129
+ throw (new Error(`internal error: routeHandler missing for routeKey ${routeKey}`));
130
+ }
131
+ if (method !== 'ERROR') {
132
+ if (method === undefined || path === undefined) {
133
+ newRoutes[routeKey] = routeHandler;
134
+ continue;
135
+ }
136
+ if (!validMethods.has(method)) {
137
+ newRoutes[routeKey] = routeHandler;
138
+ continue;
139
+ }
140
+ }
141
+ const validMethodsForPath = methodsByPath[path];
142
+ let validMethodsForPathParts = [];
143
+ if (validMethodsForPath !== undefined) {
144
+ validMethodsForPath.add('OPTIONS');
145
+ validMethodsForPathParts = Array.from(validMethodsForPath);
146
+ }
147
+ else {
148
+ validMethodsForPathParts = [...Array.from(validMethods), 'OPTIONS'];
149
+ }
150
+ newRoutes[routeKey] = async function (...args) {
151
+ const retval = await routeHandler(...args);
152
+ /* Add CORS headers to the response for the original route handler */
153
+ if (retval.contentType === 'application/json' || retval.contentType === undefined) {
154
+ if (!('headers' in retval) || retval.headers === undefined) {
155
+ retval.headers = {};
156
+ }
157
+ retval.headers['Access-Control-Allow-Origin'] = '*';
158
+ }
159
+ return (retval);
160
+ };
161
+ if (!seenPaths.has(path) && path !== '' && path !== undefined) {
162
+ const corsRouteKey = `OPTIONS ${path}`;
163
+ newRoutes[corsRouteKey] = async function () {
164
+ return ({
165
+ output: '',
166
+ statusCode: 204,
167
+ contentType: 'text/plain',
168
+ headers: {
169
+ 'Access-Control-Allow-Origin': '*',
170
+ 'Access-Control-Allow-Methods': validMethodsForPathParts.join(', '),
171
+ 'Access-Control-Allow-Headers': 'Content-Type',
172
+ 'Access-Control-Max-Age': '86400'
173
+ }
174
+ });
175
+ };
176
+ seenPaths.add(path);
177
+ }
178
+ }
179
+ return (newRoutes);
180
+ }
181
+ async main(onSetPort) {
182
+ this.logger?.debug('KeetaAnchorHTTP.Server', 'Starting HTTP server...');
183
+ const port = this.port;
184
+ const routes = KeetaNetAnchorHTTPServer.addCORS({
185
+ ERROR: async function (_ignore_params, postData) {
186
+ const errorInfo = AssertHTTPErrorData(postData);
187
+ const retval = {
188
+ output: errorInfo.error,
189
+ statusCode: errorInfo.statusCode ?? 400,
190
+ contentType: errorInfo.contentType ?? 'text/plain'
191
+ };
192
+ return (retval);
193
+ },
194
+ ...(await this.initRoutes(this.#config))
195
+ });
196
+ const server = new http.Server(async (request, response) => {
197
+ const url = new URL(request.url ?? '/', `http://${request.headers.host ?? 'localhost'}`);
198
+ const method = request.method ?? 'GET';
199
+ /*
200
+ * Finalize the response by syncing the logger and ending
201
+ * the response.
202
+ */
203
+ const responseFinalize = async () => {
204
+ if ('sync' in this.logger && typeof this.logger.sync === 'function') {
205
+ try {
206
+ await this.logger.sync();
207
+ }
208
+ catch {
209
+ /* ignore errors */
210
+ }
211
+ }
212
+ response.end();
213
+ };
214
+ /*
215
+ * Lookup the route based on the request
216
+ */
217
+ const requestedRouteAndParams = KeetaNetAnchorHTTPServer.routeFind(method, url, routes);
218
+ if (requestedRouteAndParams === null) {
219
+ response.statusCode = 404;
220
+ response.setHeader('Content-Type', 'text/plain');
221
+ response.write('Not Found');
222
+ await responseFinalize();
223
+ return;
224
+ }
225
+ /*
226
+ * Extract the route handler and the parameters from
227
+ * the request
228
+ */
229
+ const { route, params } = requestedRouteAndParams;
230
+ /**
231
+ * Attempt to run the route, catch any errors
232
+ */
233
+ let result = undefined;
234
+ let generatedResult = false;
235
+ try {
236
+ /**
237
+ * If POST'ing, read and parse the POST data
238
+ */
239
+ let postData;
240
+ if (request.method === 'POST') {
241
+ const data = await request.map(function (chunk) {
242
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
243
+ return (Buffer.from(chunk));
244
+ }).reduce(function (prev, curr) {
245
+ if (prev.length > MAX_REQUEST_SIZE) {
246
+ throw (new Error('Request too large'));
247
+ }
248
+ if (!Buffer.isBuffer(curr)) {
249
+ throw (new Error(`internal error: Current item is not a buffer -- ${typeof curr}`));
250
+ }
251
+ return (Buffer.concat([prev, curr]));
252
+ }, Buffer.from(''));
253
+ if (request.headers['content-type'] === 'application/json') {
254
+ try {
255
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
256
+ postData = JSON.parse(data.toString('utf-8'));
257
+ }
258
+ catch {
259
+ throw (new Error('Invalid JSON data'));
260
+ }
261
+ }
262
+ else {
263
+ throw (new KeetaAnchorUserError('Unsupported content type'));
264
+ }
265
+ /**
266
+ * Call the route handler
267
+ */
268
+ result = await route(params, postData, request.headers);
269
+ }
270
+ else {
271
+ result = await route(params, undefined, request.headers);
272
+ }
273
+ generatedResult = true;
274
+ }
275
+ catch (err) {
276
+ let logLevel = 'error';
277
+ if (KeetaAnchorError.isInstance(err)) {
278
+ /*
279
+ * We're able to safely cast this here because the cast
280
+ * duplicates the logic.
281
+ */
282
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
283
+ logLevel = err.logLevel.toLowerCase();
284
+ }
285
+ /**
286
+ * If an error occurs, log it and return an error page
287
+ */
288
+ this.logger?.[logLevel]('KeetaAnchorHTTP.Server', err);
289
+ /**
290
+ * If it is a user error, provide a user-friendly error page
291
+ */
292
+ const errorHandlerRoute = routes['ERROR'];
293
+ if (errorHandlerRoute !== undefined) {
294
+ if (KeetaAnchorUserError.isInstance(err)) {
295
+ result = await errorHandlerRoute(new Map(), err.asErrorResponse('application/json'), request.headers);
296
+ }
297
+ else {
298
+ result = await errorHandlerRoute(new Map(), {
299
+ error: JSON.stringify({ ok: false, error: 'Internal Server Error' }),
300
+ statusCode: 500,
301
+ contentType: 'application/json'
302
+ }, request.headers);
303
+ }
304
+ generatedResult = true;
305
+ }
306
+ if (!generatedResult) {
307
+ /**
308
+ * Otherwise provide a generic error page
309
+ */
310
+ response.statusCode = 500;
311
+ response.setHeader('Content-Type', 'text/plain');
312
+ response.write('Internal Server Error');
313
+ await responseFinalize();
314
+ return;
315
+ }
316
+ }
317
+ if (result === undefined) {
318
+ throw (new Error('internal error: No result'));
319
+ }
320
+ /**
321
+ * Write the response to the client
322
+ */
323
+ response.statusCode = result.statusCode ?? 200;
324
+ for (const headerKey in result.headers ?? {}) {
325
+ const headerValue = result.headers?.[headerKey];
326
+ if (headerValue !== undefined) {
327
+ response.setHeader(headerKey, headerValue);
328
+ }
329
+ }
330
+ response.setHeader('Content-Type', result.contentType ?? 'application/json');
331
+ response.write(result.output);
332
+ await responseFinalize();
333
+ });
334
+ this.#server = server;
335
+ /**
336
+ * Create a promise to wait for the server to close
337
+ */
338
+ const waiter = new Promise((resolve) => {
339
+ server.on('close', () => {
340
+ this.logger?.debug('KeetaAnchorHTTP.Server', 'Server closed');
341
+ resolve();
342
+ });
343
+ });
344
+ /**
345
+ * Start listening on the port
346
+ */
347
+ server.listen(port, () => {
348
+ const address = server.address();
349
+ if (address !== null && typeof address === 'object') {
350
+ // @ts-ignore
351
+ this.port = address.port;
352
+ onSetPort?.(this.port);
353
+ }
354
+ this.logger?.debug('KeetaAnchorHTTP.Server', 'Listening on port:', this.port);
355
+ });
356
+ /**
357
+ * Wait for the server to close
358
+ */
359
+ await waiter;
360
+ }
361
+ /**
362
+ * Start the HTTP server and wait for it to be fully initialized.
363
+ */
364
+ async start() {
365
+ /*
366
+ * Start the server and wait for it to be initialized before returning
367
+ */
368
+ await new Promise((resolve, reject) => {
369
+ this.#serverPromise = this.main(function () {
370
+ resolve();
371
+ }).catch(function (error) {
372
+ // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
373
+ reject(error);
374
+ });
375
+ });
376
+ }
377
+ /**
378
+ * Wait for the server to terminate. This will only resolve once the
379
+ * server has been stopped.
380
+ */
381
+ async wait() {
382
+ await this.#serverPromise;
383
+ }
384
+ /**
385
+ * Stop the HTTP server and wait for it to be fully terminated.
386
+ */
387
+ async stop() {
388
+ this.#server?.close();
389
+ // @ts-ignore
390
+ this.#server = undefined;
391
+ await this.wait();
392
+ }
393
+ /**
394
+ * Get the URL of the server, which can be used to make requests to
395
+ * it. This will use "localhost" as the hostname and the port that
396
+ * the server is listening on by default but can be overridden by
397
+ * setting a custom URL.
398
+ */
399
+ get url() {
400
+ if (this.port === 0 || this.#server === undefined) {
401
+ throw (new Error('Server not started'));
402
+ }
403
+ if (this.#url !== undefined) {
404
+ let newURL;
405
+ if (typeof this.#url === 'string') {
406
+ newURL = this.#url;
407
+ }
408
+ else if (this.#url instanceof URL || ('port' in this.#url && 'hostname' in this.#url && 'toString' in this.#url)) {
409
+ newURL = this.#url.toString();
410
+ }
411
+ else if (typeof this.#url === 'function') {
412
+ newURL = this.#url(this);
413
+ }
414
+ else {
415
+ assertNever(this.#url);
416
+ }
417
+ const newURLObj = new URL(newURL);
418
+ newURLObj.pathname = '/';
419
+ newURLObj.search = '';
420
+ return (newURLObj.toString());
421
+ }
422
+ return (`http://localhost:${this.port}`);
423
+ }
424
+ set url(value) {
425
+ this.#url = value;
426
+ }
427
+ [Symbol.asyncDispose]() {
428
+ return (this.stop());
429
+ }
430
+ }
431
+ //# sourceMappingURL=http-server.js.map