@keetanetwork/anchor 0.0.3 → 0.0.6

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 (49) hide show
  1. package/client/index.d.ts +6 -0
  2. package/client/index.d.ts.map +1 -1
  3. package/client/index.js +7 -0
  4. package/client/index.js.map +1 -1
  5. package/lib/error.d.ts +14 -0
  6. package/lib/error.d.ts.map +1 -0
  7. package/lib/error.js +38 -0
  8. package/lib/error.js.map +1 -0
  9. package/lib/http-server.d.ts +59 -0
  10. package/lib/http-server.d.ts.map +1 -0
  11. package/lib/http-server.js +383 -0
  12. package/lib/http-server.js.map +1 -0
  13. package/lib/resolver.d.ts +159 -60
  14. package/lib/resolver.d.ts.map +1 -1
  15. package/lib/resolver.js +3519 -384
  16. package/lib/resolver.js.map +1 -1
  17. package/lib/utils/brand.d.ts +12 -0
  18. package/lib/utils/brand.d.ts.map +1 -0
  19. package/lib/utils/brand.js +2 -0
  20. package/lib/utils/brand.js.map +1 -0
  21. package/lib/utils/never.d.ts +4 -0
  22. package/lib/utils/never.d.ts.map +1 -1
  23. package/lib/utils/never.js.map +1 -1
  24. package/lib/utils/signing.d.ts +17 -0
  25. package/lib/utils/signing.d.ts.map +1 -0
  26. package/lib/utils/signing.js +73 -0
  27. package/lib/utils/signing.js.map +1 -0
  28. package/lib/utils/url.d.ts +2 -0
  29. package/lib/utils/url.d.ts.map +1 -0
  30. package/lib/utils/url.js +8 -0
  31. package/lib/utils/url.js.map +1 -0
  32. package/npm-shrinkwrap.json +529 -16210
  33. package/package.json +31 -40
  34. package/services/fx/client.d.ts +137 -0
  35. package/services/fx/client.d.ts.map +1 -0
  36. package/services/fx/client.js +519 -0
  37. package/services/fx/client.js.map +1 -0
  38. package/services/fx/common.d.ts +133 -0
  39. package/services/fx/common.d.ts.map +1 -0
  40. package/services/fx/common.js +45 -0
  41. package/services/fx/common.js.map +1 -0
  42. package/services/fx/server.d.ts +58 -0
  43. package/services/fx/server.d.ts.map +1 -0
  44. package/services/fx/server.js +349 -0
  45. package/services/fx/server.js.map +1 -0
  46. package/services/kyc/client.d.ts +1 -1
  47. package/services/kyc/client.d.ts.map +1 -1
  48. package/services/kyc/client.js +7 -53
  49. package/services/kyc/client.js.map +1 -1
package/client/index.d.ts CHANGED
@@ -1,10 +1,16 @@
1
1
  import type { KeetaKYCAnchorClientConfig } from '../services/kyc/client.ts';
2
+ import type { KeetaFXAnchorClientConfig } from '../services/fx/client.ts';
2
3
  import KeetaKYCAnchorClient from '../services/kyc/client.js';
4
+ import KeetaFXAnchorClient from '../services/fx/client.js';
3
5
  import * as lib from '../lib/index.js';
4
6
  import * as KeetaNet from '@keetanetwork/keetanet-client';
5
7
  export declare namespace KYC {
6
8
  type ClientConfig = KeetaKYCAnchorClientConfig;
7
9
  const Client: typeof KeetaKYCAnchorClient;
8
10
  }
11
+ export declare namespace FX {
12
+ type ClientConfig = KeetaFXAnchorClientConfig;
13
+ const Client: typeof KeetaFXAnchorClient;
14
+ }
9
15
  export { lib, KeetaNet };
10
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,0BAA0B,EAC1B,MAAM,2BAA2B,CAAC;AACnC,OAAO,oBAAoB,MAAM,2BAA2B,CAAC;AAC7D,OAAO,KAAK,GAAG,MAAM,iBAAiB,CAAC;AACvC,OAAO,KAAK,QAAQ,MAAM,+BAA+B,CAAC;AAI1D,yBAAiB,GAAG,CAAC;IACpB,KAAY,YAAY,GAAG,0BAA0B,CAAC;IAC/C,MAAM,MAAM,EAAE,OAAO,oBAA2C,CAAC;CACxE;AAED,OAAO,EACN,GAAG,EACH,QAAQ,EACR,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,0BAA0B,EAC1B,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EACX,yBAAyB,EACzB,MAAM,0BAA0B,CAAC;AAClC,OAAO,oBAAoB,MAAM,2BAA2B,CAAC;AAC7D,OAAO,mBAAmB,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,GAAG,MAAM,iBAAiB,CAAC;AACvC,OAAO,KAAK,QAAQ,MAAM,+BAA+B,CAAC;AAI1D,yBAAiB,GAAG,CAAC;IACpB,KAAY,YAAY,GAAG,0BAA0B,CAAC;IAC/C,MAAM,MAAM,EAAE,OAAO,oBAA2C,CAAC;CACxE;AAGD,yBAAiB,EAAE,CAAC;IACnB,KAAY,YAAY,GAAG,yBAAyB,CAAC;IAC9C,MAAM,MAAM,EAAE,OAAO,mBAAyC,CAAC;CACtE;AAED,OAAO,EACN,GAAG,EACH,QAAQ,EACR,CAAC"}
package/client/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import KeetaKYCAnchorClient from '../services/kyc/client.js';
2
+ import KeetaFXAnchorClient from '../services/fx/client.js';
2
3
  import * as lib from '../lib/index.js';
3
4
  import * as KeetaNet from '@keetanetwork/keetanet-client';
4
5
  // TODO: Determine how we want to export the client
@@ -7,5 +8,11 @@ export var KYC;
7
8
  (function (KYC) {
8
9
  KYC.Client = KeetaKYCAnchorClient;
9
10
  })(KYC || (KYC = {}));
11
+ // TODO: Determine how we want to export the client
12
+ // eslint-disable-next-line @typescript-eslint/no-namespace
13
+ export var FX;
14
+ (function (FX) {
15
+ FX.Client = KeetaFXAnchorClient;
16
+ })(FX || (FX = {}));
10
17
  export { lib, KeetaNet };
11
18
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAGA,OAAO,oBAAoB,MAAM,2BAA2B,CAAC;AAC7D,OAAO,KAAK,GAAG,MAAM,iBAAiB,CAAC;AACvC,OAAO,KAAK,QAAQ,MAAM,+BAA+B,CAAC;AAE1D,mDAAmD;AACnD,2DAA2D;AAC3D,MAAM,KAAW,GAAG,CAGnB;AAHD,WAAiB,GAAG;IAEN,UAAM,GAAgC,oBAAoB,CAAC;AACzE,CAAC,EAHgB,GAAG,KAAH,GAAG,QAGnB;AAED,OAAO,EACN,GAAG,EACH,QAAQ,EACR,CAAC","sourcesContent":["import type {\n\tKeetaKYCAnchorClientConfig\n} from '../services/kyc/client.ts';\nimport KeetaKYCAnchorClient from '../services/kyc/client.js';\nimport * as lib from '../lib/index.js';\nimport * as KeetaNet from '@keetanetwork/keetanet-client';\n\n// TODO: Determine how we want to export the client\n// eslint-disable-next-line @typescript-eslint/no-namespace\nexport namespace KYC {\n\texport type ClientConfig = KeetaKYCAnchorClientConfig;\n\texport const Client: typeof KeetaKYCAnchorClient = KeetaKYCAnchorClient;\n}\n\nexport {\n\tlib,\n\tKeetaNet\n};\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAMA,OAAO,oBAAoB,MAAM,2BAA2B,CAAC;AAC7D,OAAO,mBAAmB,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,GAAG,MAAM,iBAAiB,CAAC;AACvC,OAAO,KAAK,QAAQ,MAAM,+BAA+B,CAAC;AAE1D,mDAAmD;AACnD,2DAA2D;AAC3D,MAAM,KAAW,GAAG,CAGnB;AAHD,WAAiB,GAAG;IAEN,UAAM,GAAgC,oBAAoB,CAAC;AACzE,CAAC,EAHgB,GAAG,KAAH,GAAG,QAGnB;AACD,mDAAmD;AACnD,2DAA2D;AAC3D,MAAM,KAAW,EAAE,CAGlB;AAHD,WAAiB,EAAE;IAEL,SAAM,GAA+B,mBAAmB,CAAC;AACvE,CAAC,EAHgB,EAAE,KAAF,EAAE,QAGlB;AAED,OAAO,EACN,GAAG,EACH,QAAQ,EACR,CAAC","sourcesContent":["import type {\n\tKeetaKYCAnchorClientConfig\n} from '../services/kyc/client.ts';\nimport type {\n\tKeetaFXAnchorClientConfig\n} from '../services/fx/client.ts';\nimport KeetaKYCAnchorClient from '../services/kyc/client.js';\nimport KeetaFXAnchorClient from '../services/fx/client.js';\nimport * as lib from '../lib/index.js';\nimport * as KeetaNet from '@keetanetwork/keetanet-client';\n\n// TODO: Determine how we want to export the client\n// eslint-disable-next-line @typescript-eslint/no-namespace\nexport namespace KYC {\n\texport type ClientConfig = KeetaKYCAnchorClientConfig;\n\texport const Client: typeof KeetaKYCAnchorClient = KeetaKYCAnchorClient;\n}\n// TODO: Determine how we want to export the client\n// eslint-disable-next-line @typescript-eslint/no-namespace\nexport namespace FX {\n\texport type ClientConfig = KeetaFXAnchorClientConfig;\n\texport const Client: typeof KeetaFXAnchorClient = KeetaFXAnchorClient;\n}\n\nexport {\n\tlib,\n\tKeetaNet\n};\n"]}
package/lib/error.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ export declare class KeetaAnchorUserError extends Error {
2
+ readonly name: string;
3
+ protected statusCode: number;
4
+ protected keetaAnchorUserErrorObjectTypeID: string;
5
+ private static readonly keetaAnchorUserErrorObjectTypeID;
6
+ static isInstance(input: unknown): input is KeetaAnchorUserError;
7
+ constructor(message: string);
8
+ asErrorResponse(contentType: 'text/plain' | 'application/json'): {
9
+ error: string;
10
+ statusCode: number;
11
+ contentType: string;
12
+ };
13
+ }
14
+ //# sourceMappingURL=error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../src/lib/error.ts"],"names":[],"mappings":"AAAA,qBAAa,oBAAqB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,UAAU,SAAO;IAC3B,SAAS,CAAC,gCAAgC,EAAG,MAAM,CAAC;IACpD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gCAAgC,CAA0C;IAElG,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,oBAAoB;gBAgBpD,OAAO,EAAE,MAAM;IAU3B,eAAe,CAAC,WAAW,EAAE,YAAY,GAAG,kBAAkB,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE;CAY3H"}
package/lib/error.js ADDED
@@ -0,0 +1,38 @@
1
+ export class KeetaAnchorUserError extends Error {
2
+ name;
3
+ statusCode = 400;
4
+ keetaAnchorUserErrorObjectTypeID;
5
+ static keetaAnchorUserErrorObjectTypeID = 'a1e64819-14b6-45ac-a1ec-b9c0bdd51e7b';
6
+ static isInstance(input) {
7
+ if (typeof input !== 'object' || input === null) {
8
+ return (false);
9
+ }
10
+ if (!('keetaAnchorUserErrorObjectTypeID' in input)) {
11
+ return (false);
12
+ }
13
+ if (input.keetaAnchorUserErrorObjectTypeID !== KeetaAnchorUserError.keetaAnchorUserErrorObjectTypeID) {
14
+ return (false);
15
+ }
16
+ return (true);
17
+ }
18
+ constructor(message) {
19
+ super(message);
20
+ this.name = 'KeetaAnchorUserError';
21
+ Object.defineProperty(this, 'keetaAnchorUserErrorObjectTypeID', {
22
+ value: KeetaAnchorUserError.keetaAnchorUserErrorObjectTypeID,
23
+ enumerable: false
24
+ });
25
+ }
26
+ asErrorResponse(contentType) {
27
+ let message = this.message;
28
+ if (contentType === 'application/json') {
29
+ message = JSON.stringify({ ok: false, error: this.message });
30
+ }
31
+ return ({
32
+ error: message,
33
+ statusCode: this.statusCode,
34
+ contentType: contentType
35
+ });
36
+ }
37
+ }
38
+ //# sourceMappingURL=error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error.js","sourceRoot":"","sources":["../../src/lib/error.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IACrC,IAAI,CAAS;IACZ,UAAU,GAAG,GAAG,CAAC;IACjB,gCAAgC,CAAU;IAC5C,MAAM,CAAU,gCAAgC,GAAG,sCAAsC,CAAC;IAElG,MAAM,CAAC,UAAU,CAAC,KAAc;QAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACjD,OAAM,CAAC,KAAK,CAAC,CAAC;QACf,CAAC;QAED,IAAI,CAAC,CAAC,kCAAkC,IAAI,KAAK,CAAC,EAAE,CAAC;YACpD,OAAM,CAAC,KAAK,CAAC,CAAC;QACf,CAAC;QAED,IAAI,KAAK,CAAC,gCAAgC,KAAK,oBAAoB,CAAC,gCAAgC,EAAE,CAAC;YACtG,OAAM,CAAC,KAAK,CAAC,CAAC;QACf,CAAC;QAED,OAAM,CAAC,IAAI,CAAC,CAAC;IACd,CAAC;IAED,YAAY,OAAe;QAC1B,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;QAEnC,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,kCAAkC,EAAE;YAC/D,KAAK,EAAE,oBAAoB,CAAC,gCAAgC;YAC5D,UAAU,EAAE,KAAK;SACjB,CAAC,CAAC;IACJ,CAAC;IAED,eAAe,CAAC,WAA8C;QAC7D,IAAI,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC3B,IAAI,WAAW,KAAK,kBAAkB,EAAE,CAAC;YACxC,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9D,CAAC;QAED,OAAM,CAAC;YACN,KAAK,EAAE,OAAO;YACd,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,WAAW,EAAE,WAAW;SACxB,CAAC,CAAC;IACJ,CAAC","sourcesContent":["export class KeetaAnchorUserError extends Error {\n\treadonly name: string;\n\tprotected statusCode = 400;\n\tprotected keetaAnchorUserErrorObjectTypeID!: string;\n\tprivate static readonly keetaAnchorUserErrorObjectTypeID = 'a1e64819-14b6-45ac-a1ec-b9c0bdd51e7b';\n\n\tstatic isInstance(input: unknown): input is KeetaAnchorUserError {\n\t\tif (typeof input !== 'object' || input === null) {\n\t\t\treturn(false);\n\t\t}\n\n\t\tif (!('keetaAnchorUserErrorObjectTypeID' in input)) {\n\t\t\treturn(false);\n\t\t}\n\n\t\tif (input.keetaAnchorUserErrorObjectTypeID !== KeetaAnchorUserError.keetaAnchorUserErrorObjectTypeID) {\n\t\t\treturn(false);\n\t\t}\n\n\t\treturn(true);\n\t}\n\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'KeetaAnchorUserError';\n\n\t\tObject.defineProperty(this, 'keetaAnchorUserErrorObjectTypeID', {\n\t\t\tvalue: KeetaAnchorUserError.keetaAnchorUserErrorObjectTypeID,\n\t\t\tenumerable: false\n\t\t});\n\t}\n\n\tasErrorResponse(contentType: 'text/plain' | 'application/json'): { error: string; statusCode: number; contentType: string } {\n\t\tlet message = this.message;\n\t\tif (contentType === 'application/json') {\n\t\t\tmessage = JSON.stringify({ ok: false, error: this.message });\n\t\t}\n\n\t\treturn({\n\t\t\terror: message,\n\t\t\tstatusCode: this.statusCode,\n\t\t\tcontentType: contentType\n\t\t});\n\t}\n}\n"]}
@@ -0,0 +1,59 @@
1
+ import type { JSONSerializable } from './utils/json.js';
2
+ import type { Logger } from './log/index.js';
3
+ export declare const AssertHTTPErrorData: (input: unknown) => {
4
+ error: string;
5
+ statusCode?: number;
6
+ contentType?: string;
7
+ };
8
+ export type Routes = {
9
+ [route: string]: (urlParams: Map<string, string>, postData: JSONSerializable | undefined) => Promise<{
10
+ output: string;
11
+ statusCode?: number;
12
+ contentType?: string;
13
+ headers?: {
14
+ [headerName: string]: string;
15
+ };
16
+ }>;
17
+ };
18
+ export interface KeetaAnchorHTTPServerConfig {
19
+ /**
20
+ * The port for the HTTP server to listen on (default is an ephemeral port).
21
+ */
22
+ port?: number;
23
+ /**
24
+ * Enable debug logging
25
+ */
26
+ logger?: Logger;
27
+ }
28
+ export declare abstract class KeetaNetAnchorHTTPServer<ConfigType extends KeetaAnchorHTTPServerConfig = KeetaAnchorHTTPServerConfig> implements Required<KeetaAnchorHTTPServerConfig> {
29
+ #private;
30
+ readonly port: NonNullable<KeetaAnchorHTTPServerConfig['port']>;
31
+ readonly logger: NonNullable<KeetaAnchorHTTPServerConfig['logger']>;
32
+ constructor(config: ConfigType);
33
+ protected abstract initRoutes(config: ConfigType): Promise<Routes>;
34
+ private static routeMatch;
35
+ private static routeFind;
36
+ private static addCORS;
37
+ private main;
38
+ /**
39
+ * Start the HTTP server and wait for it to be fully initialized.
40
+ */
41
+ start(): Promise<void>;
42
+ /**
43
+ * Wait for the server to terminate. This will only resolve once the
44
+ * server has been stopped.
45
+ */
46
+ wait(): Promise<void>;
47
+ /**
48
+ * Stop the HTTP server and wait for it to be fully terminated.
49
+ */
50
+ stop(): Promise<void>;
51
+ /**
52
+ * Get the URL of the server, which can be used to make requests to
53
+ * it. This will use "localhost" as the hostname and the port that
54
+ * the server is listening on.
55
+ */
56
+ get url(): string;
57
+ [Symbol.asyncDispose](): Promise<void>;
58
+ }
59
+ //# 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":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAI7C,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,KAAK,OAAO,CAAC;QAAE,MAAM,EAAE,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;CAClN,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;gBAKxD,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;IA6KlB;;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;;;;OAIG;IACH,IAAI,GAAG,IAAI,MAAM,CAMhB;IAED,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC;CAGtC"}
@@ -0,0 +1,383 @@
1
+ import * as __typia_transform__assertGuard from "typia/lib/internal/_assertGuard.js";
2
+ import * as http from 'http';
3
+ import { KeetaAnchorUserError } from './error.js';
4
+ import { Log } from './log/index.js';
5
+ import { createAssert } from 'typia';
6
+ 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, {
7
+ method: "createAssert",
8
+ path: _path + ".error",
9
+ expected: "string",
10
+ value: input.error
11
+ }, _errorFactory)) && (undefined === input.statusCode || "number" === typeof input.statusCode || __typia_transform__assertGuard._assertGuard(_exceptionable, {
12
+ method: "createAssert",
13
+ path: _path + ".statusCode",
14
+ expected: "(number | undefined)",
15
+ value: input.statusCode
16
+ }, _errorFactory)) && (undefined === input.contentType || "string" === typeof input.contentType || __typia_transform__assertGuard._assertGuard(_exceptionable, {
17
+ method: "createAssert",
18
+ path: _path + ".contentType",
19
+ expected: "(string | undefined)",
20
+ value: input.contentType
21
+ }, _errorFactory)); const __is = input => "object" === typeof input && null !== input && _io0(input); let _errorFactory; return (input, errorFactory) => {
22
+ if (false === __is(input)) {
23
+ _errorFactory = errorFactory;
24
+ ((input, _path, _exceptionable = true) => ("object" === typeof input && null !== input || __typia_transform__assertGuard._assertGuard(true, {
25
+ method: "createAssert",
26
+ path: _path + "",
27
+ expected: "__type",
28
+ value: input
29
+ }, _errorFactory)) && _ao0(input, _path + "", true) || __typia_transform__assertGuard._assertGuard(true, {
30
+ method: "createAssert",
31
+ path: _path + "",
32
+ expected: "__type",
33
+ value: input
34
+ }, _errorFactory))(input, "$input", true);
35
+ }
36
+ return input;
37
+ }; })();
38
+ /**
39
+ * The maximum size of a request (128KiB)
40
+ */
41
+ const MAX_REQUEST_SIZE = 1024 * 128;
42
+ ;
43
+ export class KeetaNetAnchorHTTPServer {
44
+ port;
45
+ logger;
46
+ #serverPromise;
47
+ #server;
48
+ #config;
49
+ constructor(config) {
50
+ this.#config = { ...config };
51
+ this.port = config.port ?? 0;
52
+ this.logger = config.logger ?? new Log();
53
+ }
54
+ static routeMatch(requestURL, routeURL) {
55
+ const requestURLPaths = requestURL.pathname.split('/');
56
+ const routeURLPaths = routeURL.pathname.split('/');
57
+ if (requestURLPaths.length !== routeURLPaths.length) {
58
+ return ({ match: false });
59
+ }
60
+ const params = new Map();
61
+ for (let partIndex = 0; partIndex < requestURLPaths.length; partIndex++) {
62
+ const requestPath = requestURLPaths[partIndex];
63
+ const routePath = routeURLPaths[partIndex];
64
+ if (routePath === undefined || requestPath === undefined) {
65
+ return ({ match: false });
66
+ }
67
+ if (routePath.startsWith(':')) {
68
+ params.set(routePath.slice(1), requestPath);
69
+ }
70
+ else if (requestPath !== routePath) {
71
+ return ({ match: false });
72
+ }
73
+ }
74
+ return ({ match: true, params: params });
75
+ }
76
+ static routeFind(method, requestURL, routes) {
77
+ for (const routeKey in routes) {
78
+ const route = routes[routeKey];
79
+ if (route === undefined) {
80
+ continue;
81
+ }
82
+ const [routeMethod, ...routePathParts] = routeKey.split(' ');
83
+ const routePath = `/${routePathParts.join(' ')}`.replace(/^\/+/, '/');
84
+ if (method !== routeMethod) {
85
+ continue;
86
+ }
87
+ const routeURL = new URL(routePath, 'http://localhost');
88
+ const matchResult = this.routeMatch(requestURL, routeURL);
89
+ if (matchResult.match) {
90
+ return ({
91
+ route: route,
92
+ params: matchResult.params
93
+ });
94
+ }
95
+ }
96
+ return (null);
97
+ }
98
+ static addCORS(routes) {
99
+ const newRoutes = {};
100
+ const validMethods = new Set(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD']);
101
+ const methodsByPath = {};
102
+ for (const routeKey in routes) {
103
+ const methodAndPath = routeKey.split(' ');
104
+ const method = methodAndPath[0];
105
+ const path = methodAndPath.slice(1).join(' ');
106
+ if (method === undefined || path === undefined) {
107
+ continue;
108
+ }
109
+ if (!validMethods.has(method)) {
110
+ continue;
111
+ }
112
+ if (!(path in methodsByPath)) {
113
+ methodsByPath[path] = new Set();
114
+ }
115
+ if (methodsByPath[path] === undefined) {
116
+ throw (new Error(`internal error: methodsByPath missing path for ${path}`));
117
+ }
118
+ methodsByPath[path].add(method);
119
+ }
120
+ const seenPaths = new Set();
121
+ for (const routeKey in routes) {
122
+ const methodAndPath = routeKey.split(' ');
123
+ const method = methodAndPath[0];
124
+ const path = methodAndPath.slice(1).join(' ');
125
+ const routeHandler = routes[routeKey];
126
+ if (routeHandler === undefined) {
127
+ throw (new Error(`internal error: routeHandler missing for routeKey ${routeKey}`));
128
+ }
129
+ if (method !== 'ERROR') {
130
+ if (method === undefined || path === undefined) {
131
+ newRoutes[routeKey] = routeHandler;
132
+ continue;
133
+ }
134
+ if (!validMethods.has(method)) {
135
+ newRoutes[routeKey] = routeHandler;
136
+ continue;
137
+ }
138
+ }
139
+ const validMethodsForPath = methodsByPath[path];
140
+ let validMethodsForPathParts = [];
141
+ if (validMethodsForPath !== undefined) {
142
+ validMethodsForPath.add('OPTIONS');
143
+ validMethodsForPathParts = Array.from(validMethodsForPath);
144
+ }
145
+ else {
146
+ validMethodsForPathParts = [...Array.from(validMethods), 'OPTIONS'];
147
+ }
148
+ newRoutes[routeKey] = async function (...args) {
149
+ const retval = await routeHandler(...args);
150
+ /* Add CORS headers to the response for the original route handler */
151
+ if (retval.contentType === 'application/json' || retval.contentType === undefined) {
152
+ if (!('headers' in retval) || retval.headers === undefined) {
153
+ retval.headers = {};
154
+ }
155
+ retval.headers['Access-Control-Allow-Origin'] = '*';
156
+ }
157
+ return (retval);
158
+ };
159
+ if (!seenPaths.has(path) && path !== '' && path !== undefined) {
160
+ const corsRouteKey = `OPTIONS ${path}`;
161
+ newRoutes[corsRouteKey] = async function () {
162
+ return ({
163
+ output: '',
164
+ statusCode: 204,
165
+ contentType: 'text/plain',
166
+ headers: {
167
+ 'Access-Control-Allow-Origin': '*',
168
+ 'Access-Control-Allow-Methods': validMethodsForPathParts.join(', '),
169
+ 'Access-Control-Allow-Headers': 'Content-Type',
170
+ 'Access-Control-Max-Age': '86400'
171
+ }
172
+ });
173
+ };
174
+ seenPaths.add(path);
175
+ }
176
+ }
177
+ return (newRoutes);
178
+ }
179
+ async main(onSetPort) {
180
+ this.logger?.debug('KeetaAnchorHTTP.Server', 'Starting HTTP server...');
181
+ const port = this.port;
182
+ const routes = KeetaNetAnchorHTTPServer.addCORS({
183
+ ERROR: async function (_ignore_params, postData) {
184
+ const errorInfo = AssertHTTPErrorData(postData);
185
+ const retval = {
186
+ output: errorInfo.error,
187
+ statusCode: errorInfo.statusCode ?? 400,
188
+ contentType: errorInfo.contentType ?? 'text/plain'
189
+ };
190
+ return (retval);
191
+ },
192
+ ...(await this.initRoutes(this.#config))
193
+ });
194
+ const server = new http.Server(async (request, response) => {
195
+ const url = new URL(request.url ?? '/', `http://${request.headers.host ?? 'localhost'}`);
196
+ const method = request.method ?? 'GET';
197
+ /*
198
+ * Lookup the route based on the request
199
+ */
200
+ const requestedRouteAndParams = KeetaNetAnchorHTTPServer.routeFind(method, url, routes);
201
+ if (requestedRouteAndParams === null) {
202
+ response.statusCode = 404;
203
+ response.setHeader('Content-Type', 'text/plain');
204
+ response.write('Not Found');
205
+ response.end();
206
+ return;
207
+ }
208
+ /*
209
+ * Extract the route handler and the parameters from
210
+ * the request
211
+ */
212
+ const { route, params } = requestedRouteAndParams;
213
+ /**
214
+ * Attempt to run the route, catch any errors
215
+ */
216
+ let result = undefined;
217
+ let generatedResult = false;
218
+ try {
219
+ /**
220
+ * If POST'ing, read and parse the POST data
221
+ */
222
+ let postData;
223
+ if (request.method === 'POST') {
224
+ const data = await request.map(function (chunk) {
225
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
226
+ return (Buffer.from(chunk));
227
+ }).reduce(function (prev, curr) {
228
+ if (prev.length > MAX_REQUEST_SIZE) {
229
+ throw (new Error('Request too large'));
230
+ }
231
+ if (!Buffer.isBuffer(curr)) {
232
+ throw (new Error(`internal error: Current item is not a buffer -- ${typeof curr}`));
233
+ }
234
+ return (Buffer.concat([prev, curr]));
235
+ }, Buffer.from(''));
236
+ if (request.headers['content-type'] === 'application/json') {
237
+ try {
238
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
239
+ postData = JSON.parse(data.toString('utf-8'));
240
+ }
241
+ catch {
242
+ throw (new Error('Invalid JSON data'));
243
+ }
244
+ }
245
+ else {
246
+ throw (new KeetaAnchorUserError('Unsupported content type'));
247
+ }
248
+ /**
249
+ * Call the route handler
250
+ */
251
+ result = await route(params, postData);
252
+ }
253
+ else {
254
+ result = await route(params, undefined);
255
+ }
256
+ generatedResult = true;
257
+ }
258
+ catch (err) {
259
+ /**
260
+ * If an error occurs, log it and return an error page
261
+ */
262
+ this.logger?.error('KeetaAnchorHTTP.Server', err);
263
+ /**
264
+ * If it is a user error, provide a user-friendly error page
265
+ */
266
+ const errorHandlerRoute = routes['ERROR'];
267
+ if (errorHandlerRoute !== undefined) {
268
+ if (KeetaAnchorUserError.isInstance(err)) {
269
+ result = await errorHandlerRoute(new Map(), err.asErrorResponse('application/json'));
270
+ generatedResult = true;
271
+ }
272
+ else {
273
+ result = await errorHandlerRoute(new Map(), {
274
+ error: JSON.stringify({ ok: false, error: 'Internal Server Error' }),
275
+ statusCode: 500,
276
+ contentType: 'application/json'
277
+ });
278
+ generatedResult = true;
279
+ }
280
+ }
281
+ if (!generatedResult) {
282
+ /**
283
+ * Otherwise provide a generic error page
284
+ */
285
+ response.statusCode = 500;
286
+ response.setHeader('Content-Type', 'text/plain');
287
+ response.write('Internal Server Error');
288
+ response.end();
289
+ return;
290
+ }
291
+ }
292
+ if (result === undefined) {
293
+ throw (new Error('internal error: No result'));
294
+ }
295
+ /**
296
+ * Write the response to the client
297
+ */
298
+ response.statusCode = result.statusCode ?? 200;
299
+ for (const headerKey in result.headers ?? {}) {
300
+ const headerValue = result.headers?.[headerKey];
301
+ if (headerValue !== undefined) {
302
+ response.setHeader(headerKey, headerValue);
303
+ }
304
+ }
305
+ response.setHeader('Content-Type', result.contentType ?? 'application/json');
306
+ response.write(result.output);
307
+ response.end();
308
+ });
309
+ this.#server = server;
310
+ /**
311
+ * Create a promise to wait for the server to close
312
+ */
313
+ const waiter = new Promise((resolve) => {
314
+ server.on('close', () => {
315
+ this.logger?.debug('KeetaAnchorHTTP.Server', 'Server closed');
316
+ resolve();
317
+ });
318
+ });
319
+ /**
320
+ * Start listening on the port
321
+ */
322
+ server.listen(port, () => {
323
+ const address = server.address();
324
+ if (address !== null && typeof address === 'object') {
325
+ // @ts-ignore
326
+ this.port = address.port;
327
+ onSetPort?.(this.port);
328
+ }
329
+ this.logger?.debug('KeetaAnchorHTTP.Server', 'Listening on port:', this.port);
330
+ });
331
+ /**
332
+ * Wait for the server to close
333
+ */
334
+ await waiter;
335
+ }
336
+ /**
337
+ * Start the HTTP server and wait for it to be fully initialized.
338
+ */
339
+ async start() {
340
+ /*
341
+ * Start the server and wait for it to be initialized before returning
342
+ */
343
+ await new Promise((resolve, reject) => {
344
+ this.#serverPromise = this.main(function () {
345
+ resolve();
346
+ }).catch(function (error) {
347
+ // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
348
+ reject(error);
349
+ });
350
+ });
351
+ }
352
+ /**
353
+ * Wait for the server to terminate. This will only resolve once the
354
+ * server has been stopped.
355
+ */
356
+ async wait() {
357
+ await this.#serverPromise;
358
+ }
359
+ /**
360
+ * Stop the HTTP server and wait for it to be fully terminated.
361
+ */
362
+ async stop() {
363
+ this.#server?.close();
364
+ // @ts-ignore
365
+ this.#server = undefined;
366
+ await this.wait();
367
+ }
368
+ /**
369
+ * Get the URL of the server, which can be used to make requests to
370
+ * it. This will use "localhost" as the hostname and the port that
371
+ * the server is listening on.
372
+ */
373
+ get url() {
374
+ if (this.port === 0 || this.#server === undefined) {
375
+ throw (new Error('Server not started'));
376
+ }
377
+ return (`http://localhost:${this.port}`);
378
+ }
379
+ [Symbol.asyncDispose]() {
380
+ return (this.stop());
381
+ }
382
+ }
383
+ //# sourceMappingURL=http-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-server.js","sourceRoot":"","sources":["../../src/lib/http-server.ts"],"names":[],"mappings":"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","sourcesContent":["import * as http from 'http';\nimport {\n\tKeetaAnchorUserError\n} from './error.js';\nimport type { JSONSerializable } from './utils/json.js';\nimport type { Logger } from './log/index.js';\nimport { Log } from './log/index.js';\nimport { createAssert } from 'typia';\n\nexport const AssertHTTPErrorData: (input: unknown) => { error: string; statusCode?: number; contentType?: string; } = createAssert<{ error: string; statusCode?: number; contentType?: string; }>();\n\n/**\n * The maximum size of a request (128KiB)\n */\nconst MAX_REQUEST_SIZE = 1024 * 128;\n\nexport type Routes = {\n\t[route: string]: (urlParams: Map<string, string>, postData: JSONSerializable | undefined) => Promise<{ output: string; statusCode?: number; contentType?: string; headers?: { [headerName: string]: string; }; }>;\n};\n\nexport interface KeetaAnchorHTTPServerConfig {\n\t/**\n\t * The port for the HTTP server to listen on (default is an ephemeral port).\n\t */\n\tport?: number;\n\n\t/**\n\t * Enable debug logging\n\t */\n\tlogger?: Logger;\n};\n\nexport abstract class KeetaNetAnchorHTTPServer<ConfigType extends KeetaAnchorHTTPServerConfig = KeetaAnchorHTTPServerConfig> implements Required<KeetaAnchorHTTPServerConfig> {\n\treadonly port: NonNullable<KeetaAnchorHTTPServerConfig['port']>;\n\treadonly logger: NonNullable<KeetaAnchorHTTPServerConfig['logger']>;\n\t#serverPromise?: Promise<void>;\n\t#server?: http.Server;\n\treadonly #config: ConfigType;\n\n\tconstructor(config: ConfigType) {\n\t\tthis.#config = { ...config };\n\t\tthis.port = config.port ?? 0;\n\t\tthis.logger = config.logger ?? new Log();\n\t}\n\n\tprotected abstract initRoutes(config: ConfigType): Promise<Routes>;\n\n\tprivate static routeMatch(requestURL: URL, routeURL: URL): ({ match: true; params: Map<string, string> } | { match: false }) {\n\t\tconst requestURLPaths = requestURL.pathname.split('/');\n\t\tconst routeURLPaths = routeURL.pathname.split('/');\n\n\t\tif (requestURLPaths.length !== routeURLPaths.length) {\n\t\t\treturn({ match: false });\n\t\t}\n\n\t\tconst params = new Map<string, string>();\n\t\tfor (let partIndex = 0; partIndex < requestURLPaths.length; partIndex++) {\n\t\t\tconst requestPath = requestURLPaths[partIndex];\n\t\t\tconst routePath = routeURLPaths[partIndex];\n\n\t\t\tif (routePath === undefined || requestPath === undefined) {\n\t\t\t\treturn({ match: false });\n\t\t\t}\n\n\t\t\tif (routePath.startsWith(':')) {\n\t\t\t\tparams.set(routePath.slice(1), requestPath);\n\t\t\t} else if (requestPath !== routePath) {\n\t\t\t\treturn({ match: false });\n\t\t\t}\n\t\t}\n\n\t\treturn({ match: true, params: params });\n\t}\n\n\tprivate static routeFind(method: string, requestURL: URL, routes: Routes): { route: Routes[keyof Routes]; params: Map<string, string> } | null {\n\t\tfor (const routeKey in routes) {\n\t\t\tconst route = routes[routeKey];\n\t\t\tif (route === undefined) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst [routeMethod, ...routePathParts] = routeKey.split(' ');\n\t\t\tconst routePath = `/${routePathParts.join(' ')}`.replace(/^\\/+/, '/');\n\n\t\t\tif (method !== routeMethod) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst routeURL = new URL(routePath, 'http://localhost');\n\t\t\tconst matchResult = this.routeMatch(requestURL, routeURL);\n\t\t\tif (matchResult.match) {\n\t\t\t\treturn({\n\t\t\t\t\troute: route,\n\t\t\t\t\tparams: matchResult.params\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn(null);\n\t}\n\n\tprivate static addCORS(routes: Routes): Routes {\n\t\tconst newRoutes: Routes = {};\n\n\t\tconst validMethods = new Set(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD']);\n\n\t\tconst methodsByPath: { [key: string]: Set<string>; } = {};\n\t\tfor (const routeKey in routes) {\n\t\t\tconst methodAndPath = routeKey.split(' ');\n\t\t\tconst method = methodAndPath[0];\n\t\t\tconst path = methodAndPath.slice(1).join(' ');\n\n\t\t\tif (method === undefined || path === undefined) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!validMethods.has(method)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!(path in methodsByPath)) {\n\t\t\t\tmethodsByPath[path] = new Set<string>();\n\t\t\t}\n\n\t\t\tif (methodsByPath[path] === undefined) {\n\t\t\t\tthrow(new Error(`internal error: methodsByPath missing path for ${path}`));\n\t\t\t}\n\n\t\t\tmethodsByPath[path].add(method);\n\t\t}\n\n\t\tconst seenPaths = new Set<string>();\n\t\tfor (const routeKey in routes) {\n\t\t\tconst methodAndPath = routeKey.split(' ');\n\t\t\tconst method = methodAndPath[0];\n\t\t\tconst path = methodAndPath.slice(1).join(' ');\n\n\t\t\tconst routeHandler = routes[routeKey];\n\t\t\tif (routeHandler === undefined) {\n\t\t\t\tthrow(new Error(`internal error: routeHandler missing for routeKey ${routeKey}`));\n\t\t\t}\n\n\t\t\tif (method !== 'ERROR') {\n\t\t\t\tif (method === undefined || path === undefined) {\n\t\t\t\t\tnewRoutes[routeKey] = routeHandler;\n\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (!validMethods.has(method)) {\n\t\t\t\t\tnewRoutes[routeKey] = routeHandler;\n\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst validMethodsForPath = methodsByPath[path];\n\n\t\t\tlet validMethodsForPathParts: string[] = [];\n\t\t\tif (validMethodsForPath !== undefined) {\n\t\t\t\tvalidMethodsForPath.add('OPTIONS');\n\t\t\t\tvalidMethodsForPathParts = Array.from(validMethodsForPath);\n\t\t\t} else {\n\t\t\t\tvalidMethodsForPathParts = [...Array.from(validMethods), 'OPTIONS'];\n\t\t\t}\n\n\t\t\tnewRoutes[routeKey] = async function(...args: Parameters<typeof routes[keyof typeof routes]>) {\n\t\t\t\tconst retval = await routeHandler(...args);\n\n\t\t\t\t/* Add CORS headers to the response for the original route handler */\n\t\t\t\tif (retval.contentType === 'application/json' || retval.contentType === undefined) {\n\t\t\t\t\tif (!('headers' in retval) || retval.headers === undefined) {\n\t\t\t\t\t\tretval.headers = {};\n\t\t\t\t\t}\n\t\t\t\t\tretval.headers['Access-Control-Allow-Origin'] = '*';\n\t\t\t\t}\n\n\t\t\t\treturn(retval);\n\t\t\t};\n\n\t\t\tif (!seenPaths.has(path) && path !== '' && path !== undefined) {\n\t\t\t\tconst corsRouteKey = `OPTIONS ${path}`;\n\n\t\t\t\tnewRoutes[corsRouteKey] = async function() {\n\t\t\t\t\treturn({\n\t\t\t\t\t\toutput: '',\n\t\t\t\t\t\tstatusCode: 204,\n\t\t\t\t\t\tcontentType: 'text/plain',\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'Access-Control-Allow-Origin': '*',\n\t\t\t\t\t\t\t'Access-Control-Allow-Methods': validMethodsForPathParts.join(', '),\n\t\t\t\t\t\t\t'Access-Control-Allow-Headers': 'Content-Type',\n\t\t\t\t\t\t\t'Access-Control-Max-Age': '86400'\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t};\n\t\t\t\tseenPaths.add(path);\n\t\t\t}\n\t\t}\n\n\t\treturn(newRoutes);\n\t}\n\n\tprivate async main(onSetPort?: (port: number) => void): Promise<void> {\n\t\tthis.logger?.debug('KeetaAnchorHTTP.Server', 'Starting HTTP server...');\n\n\t\tconst port = this.port;\n\n\t\tconst routes = KeetaNetAnchorHTTPServer.addCORS({\n\t\t\tERROR: async function(_ignore_params, postData) {\n\t\t\t\tconst errorInfo = AssertHTTPErrorData(postData);\n\n\t\t\t\tconst retval = {\n\t\t\t\t\toutput: errorInfo.error,\n\t\t\t\t\tstatusCode: errorInfo.statusCode ?? 400,\n\t\t\t\t\tcontentType: errorInfo.contentType ?? 'text/plain'\n\t\t\t\t};\n\n\t\t\t\treturn(retval);\n\t\t\t},\n\t\t\t...(await this.initRoutes(this.#config))\n\t\t});\n\n\t\tconst server = new http.Server(async (request, response) => {\n\t\t\tconst url = new URL(request.url ?? '/', `http://${request.headers.host ?? 'localhost'}`);\n\t\t\tconst method = request.method ?? 'GET';\n\n\t\t\t/*\n\t\t\t * Lookup the route based on the request\n\t\t\t */\n\t\t\tconst requestedRouteAndParams = KeetaNetAnchorHTTPServer.routeFind(method, url, routes);\n\t\t\tif (requestedRouteAndParams === null) {\n\t\t\t\tresponse.statusCode = 404;\n\t\t\t\tresponse.setHeader('Content-Type', 'text/plain');\n\t\t\t\tresponse.write('Not Found');\n\t\t\t\tresponse.end();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * Extract the route handler and the parameters from\n\t\t\t * the request\n\t\t\t */\n\t\t\tconst { route, params } = requestedRouteAndParams;\n\n\t\t\t/**\n\t\t\t * Attempt to run the route, catch any errors\n\t\t\t */\n\t\t\tlet result: Awaited<ReturnType<typeof route>> | undefined = undefined;\n\t\t\tlet generatedResult = false;\n\t\t\ttry {\n\t\t\t\t/**\n\t\t\t\t * If POST'ing, read and parse the POST data\n\t\t\t\t */\n\t\t\t\tlet postData: JSONSerializable | undefined;\n\t\t\t\tif (request.method === 'POST') {\n\t\t\t\t\tconst data = await request.map(function(chunk) {\n\t\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n\t\t\t\t\t\treturn(Buffer.from(chunk));\n\t\t\t\t\t}).reduce(function(prev, curr) {\n\t\t\t\t\t\tif (prev.length > MAX_REQUEST_SIZE) {\n\t\t\t\t\t\t\tthrow(new Error('Request too large'));\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!Buffer.isBuffer(curr)) {\n\t\t\t\t\t\t\tthrow(new Error(`internal error: Current item is not a buffer -- ${typeof curr}`));\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn(Buffer.concat([prev, curr]));\n\t\t\t\t\t}, Buffer.from(''));\n\n\t\t\t\t\tif (request.headers['content-type'] === 'application/json') {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\t\t\t\t\t\tpostData = JSON.parse(data.toString('utf-8'));\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\tthrow(new Error('Invalid JSON data'));\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthrow(new KeetaAnchorUserError('Unsupported content type'));\n\t\t\t\t\t}\n\t\t\t\t\t/**\n\t\t\t\t\t * Call the route handler\n\t\t\t\t\t */\n\t\t\t\t\tresult = await route(params, postData);\n\t\t\t\t} else {\n\t\t\t\t\tresult = await route(params, undefined);\n\t\t\t\t}\n\n\t\t\t\tgeneratedResult = true;\n\t\t\t} catch (err) {\n\t\t\t\t/**\n\t\t\t\t * If an error occurs, log it and return an error page\n\t\t\t\t */\n\t\t\t\tthis.logger?.error('KeetaAnchorHTTP.Server', err);\n\n\t\t\t\t/**\n\t\t\t\t * If it is a user error, provide a user-friendly error page\n\t\t\t\t */\n\t\t\t\tconst errorHandlerRoute = routes['ERROR'];\n\t\t\t\tif (errorHandlerRoute !== undefined) {\n\t\t\t\t\tif (KeetaAnchorUserError.isInstance(err)) {\n\t\t\t\t\t\tresult = await errorHandlerRoute(new Map(), err.asErrorResponse('application/json'));\n\t\t\t\t\t\tgeneratedResult = true;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult = await errorHandlerRoute(new Map(), {\n\t\t\t\t\t\t\terror: JSON.stringify({ ok: false, error: 'Internal Server Error' }),\n\t\t\t\t\t\t\tstatusCode: 500,\n\t\t\t\t\t\t\tcontentType: 'application/json'\n\t\t\t\t\t\t});\n\t\t\t\t\t\tgeneratedResult = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!generatedResult) {\n\t\t\t\t\t/**\n\t\t\t\t\t * Otherwise provide a generic error page\n\t\t\t\t\t */\n\t\t\t\t\tresponse.statusCode = 500;\n\t\t\t\t\tresponse.setHeader('Content-Type', 'text/plain');\n\t\t\t\t\tresponse.write('Internal Server Error');\n\t\t\t\t\tresponse.end();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (result === undefined) {\n\t\t\t\tthrow(new Error('internal error: No result'));\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Write the response to the client\n\t\t\t */\n\t\t\tresponse.statusCode = result.statusCode ?? 200;\n\n\t\t\tfor (const headerKey in result.headers ?? {}) {\n\t\t\t\tconst headerValue = result.headers?.[headerKey];\n\t\t\t\tif (headerValue !== undefined) {\n\t\t\t\t\tresponse.setHeader(headerKey, headerValue);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresponse.setHeader('Content-Type', result.contentType ?? 'application/json');\n\t\t\tresponse.write(result.output);\n\t\t\tresponse.end();\n\t\t});\n\t\tthis.#server = server;\n\n\t\t/**\n\t\t * Create a promise to wait for the server to close\n\t\t */\n\t\tconst waiter = new Promise<void>((resolve) => {\n\t\t\tserver.on('close', () => {\n\t\t\t\tthis.logger?.debug('KeetaAnchorHTTP.Server', 'Server closed');\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\n\t\t/**\n\t\t * Start listening on the port\n\t\t */\n\t\tserver.listen(port, () => {\n\t\t\tconst address = server.address();\n\t\t\tif (address !== null && typeof address === 'object') {\n\t\t\t\t// @ts-ignore\n\t\t\t\tthis.port = address.port;\n\t\t\t\tonSetPort?.(this.port);\n\t\t\t}\n\t\t\tthis.logger?.debug('KeetaAnchorHTTP.Server', 'Listening on port:', this.port);\n\t\t});\n\n\t\t/**\n\t\t * Wait for the server to close\n\t\t */\n\t\tawait waiter;\n\t}\n\n\t/**\n\t * Start the HTTP server and wait for it to be fully initialized.\n\t */\n\tasync start(): Promise<void> {\n\t\t/*\n\t\t * Start the server and wait for it to be initialized before returning\n\t\t */\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tthis.#serverPromise = this.main(function() {\n\t\t\t\tresolve();\n\t\t\t}).catch(function(error: unknown) {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors\n\t\t\t\treject(error);\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Wait for the server to terminate. This will only resolve once the\n\t * server has been stopped.\n\t */\n\tasync wait(): Promise<void> {\n\t\tawait this.#serverPromise;\n\t}\n\n\t/**\n\t * Stop the HTTP server and wait for it to be fully terminated.\n\t */\n\tasync stop(): Promise<void> {\n\t\tthis.#server?.close();\n\t\t// @ts-ignore\n\t\tthis.#server = undefined;\n\t\tawait this.wait();\n\t}\n\n\t/**\n\t * Get the URL of the server, which can be used to make requests to\n\t * it. This will use \"localhost\" as the hostname and the port that\n\t * the server is listening on.\n\t */\n\tget url(): string {\n\t\tif (this.port === 0 || this.#server === undefined) {\n\t\t\tthrow(new Error('Server not started'));\n\t\t}\n\n\t\treturn(`http://localhost:${this.port}`);\n\t}\n\n\t[Symbol.asyncDispose](): Promise<void> {\n\t\treturn(this.stop());\n\t}\n}\n"]}