@ibgib/space-gib 0.0.9 → 0.0.13
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/CHANGELOG.md +8 -0
- package/README.md +1 -0
- package/dist/client/bootstrap.mjs +33 -33
- package/dist/client/bootstrap.mjs.map +3 -3
- package/dist/client/chunk-ROJZUPPE.mjs +19 -0
- package/dist/client/chunk-ROJZUPPE.mjs.map +7 -0
- package/dist/client/chunk-WJHRARK6.mjs +2329 -0
- package/dist/client/chunk-WJHRARK6.mjs.map +7 -0
- package/dist/client/index.mjs +22 -1
- package/dist/client/index.mjs.map +4 -4
- package/dist/client/script.mjs +1 -1
- package/dist/server/server.mjs +19147 -18210
- package/dist/server/server.mjs.map +4 -4
- package/package.json +4 -4
- package/space-gib.localhost-1785337158142.log +75 -0
- package/src/client/AUTO-GENERATED-version.mts +1 -1
- package/src/client/README.md +1 -1
- package/src/client/api/space-gib-api-bridge.mts +8 -224
- package/src/client/ui/shell/space-gib-shell-service.mts +68 -18
- package/src/common/keystone-policies.mts +1 -1
- package/src/server/README.md +3 -2
- package/src/server/server.mts +13 -32
- package/src/server/test.json +0 -0
- package/dist/client/chunk-ACIGPCQW.mjs +0 -2407
- package/dist/client/chunk-ACIGPCQW.mjs.map +0 -7
- package/dist/client/chunk-QRMTJE6M.mjs +0 -42
- package/dist/client/chunk-QRMTJE6M.mjs.map +0 -7
- package/space-gib.localhost-1783713259760.log +0 -45
- package/src/client/components/identity-header/IMPLEMENTATION.md +0 -45
- package/src/client/components/identity-header/identity-header.css +0 -112
- package/src/client/components/identity-header/identity-header.html +0 -11
- package/src/client/components/identity-header/identity-header.mts +0 -439
- package/src/client/components/identity-manager/IMPLEMENTATION.md +0 -100
- package/src/client/components/identity-manager/identity-manager.css +0 -171
- package/src/client/components/identity-manager/identity-manager.html +0 -19
- package/src/client/components/identity-manager/identity-manager.mts +0 -639
- package/src/client/components/keystone-creator/keystone-creator.css +0 -65
- package/src/client/components/keystone-creator/keystone-creator.html +0 -41
- package/src/client/components/keystone-creator/keystone-creator.mts +0 -443
- package/src/client/components/keystone-details/keystone-details.css +0 -611
- package/src/client/components/keystone-details/keystone-details.html +0 -151
- package/src/client/components/keystone-details/keystone-details.mts +0 -1213
- package/src/client/components/keystone-scrubber/SCRUBBER_IMPLEMENTATION.md +0 -33
- package/src/client/components/keystone-scrubber/keystone-scrubber.css +0 -46
- package/src/client/components/keystone-scrubber/keystone-scrubber.html +0 -15
- package/src/client/components/keystone-scrubber/keystone-scrubber.mts +0 -356
- package/src/server/bootstrap-helper.mts +0 -141
- package/src/server/bootstrap-helper.respec.mts +0 -100
- package/src/server/metaspace-nodeindexedspace/metaspace-nodeindexedspace.mts +0 -85
- package/src/server/path-constants.mts +0 -101
- package/src/server/path-helper.mts +0 -101
- package/src/server/path-helper.respec.mts +0 -99
- package/src/server/serve-gib/CHANGELOG.md +0 -29
- package/src/server/serve-gib/README.md +0 -43
- package/src/server/serve-gib/constants.mts +0 -1
- package/src/server/serve-gib/handlers/api/debug/ws-echo.handler.mts +0 -105
- package/src/server/serve-gib/handlers/api/health.handler.mts +0 -23
- package/src/server/serve-gib/handlers/api/health.respec.mts +0 -51
- package/src/server/serve-gib/handlers/api/ibgib/ibgib-handler-types.mts +0 -49
- package/src/server/serve-gib/handlers/api/ibgib/ibgib.handler.mts +0 -176
- package/src/server/serve-gib/handlers/api/keystone/keystone-evolve.handler.mts +0 -150
- package/src/server/serve-gib/handlers/api/keystone/keystone-genesis.handler.mts +0 -146
- package/src/server/serve-gib/handlers/api/keystone/keystone-get.handler.mts +0 -198
- package/src/server/serve-gib/handlers/api/keystone/keystone-get.respec.mts +0 -107
- package/src/server/serve-gib/handlers/api/keystone/keystone-handler-types.mts +0 -29
- package/src/server/serve-gib/handlers/api/keystone/keystone-post.handler.mts +0 -55
- package/src/server/serve-gib/handlers/api/keystone/keystone-post.respec.mts +0 -130
- package/src/server/serve-gib/handlers/api/keystone/sso-config.handler.mts +0 -66
- package/src/server/serve-gib/handlers/api/keystone/sso-link.handler.mts +0 -140
- package/src/server/serve-gib/handlers/api/keystone/sso-login.handler.mts +0 -190
- package/src/server/serve-gib/handlers/error-handler.mts +0 -36
- package/src/server/serve-gib/handlers/handler-base.mts +0 -383
- package/src/server/serve-gib/handlers/static-handler.mts +0 -82
- package/src/server/serve-gib/handlers/ws/sync-upgrade-handler-base.mts +0 -233
- package/src/server/serve-gib/handlers/ws/sync-upgrade.handler.mts +0 -24
- package/src/server/serve-gib/handlers/ws/ws-helper.mts +0 -216
- package/src/server/serve-gib/handlers/ws/ws-types.mts +0 -53
- package/src/server/serve-gib/serve-gib-helpers.mts +0 -32
- package/src/server/serve-gib/serve-gib-v1.mts +0 -172
- package/src/server/serve-gib/serve-gib.respec.mts +0 -90
- package/src/server/serve-gib/types.mts +0 -102
- package/src/server/server-constants.mts +0 -2
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { join } from 'node:path';
|
|
2
|
-
import { existsSync, mkdirSync, rmSync } from 'node:fs';
|
|
3
|
-
|
|
4
|
-
import { respecfully, iReckon, ifWe } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
|
|
5
|
-
const sir = `[${import.meta.url}]`;
|
|
6
|
-
import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
|
|
7
|
-
import { KeystoneService_V1 } from '@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs';
|
|
8
|
-
import { createStandardPoolConfig } from '@ibgib/core-gib/dist/keystone/keystone-config-builder.mjs';
|
|
9
|
-
import { POOL_ID_DEFAULT } from '@ibgib/core-gib/dist/keystone/keystone-constants.mjs';
|
|
10
|
-
|
|
11
|
-
import { GLOBAL_LOG_A_LOT } from './server-constants.mjs';
|
|
12
|
-
import { getDomainRootPath, bootstrapDomainMetaspace } from './bootstrap-helper.mjs';
|
|
13
|
-
|
|
14
|
-
const logalot = GLOBAL_LOG_A_LOT;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Minimal mock to satisfy KeystoneService requirements during genesis.
|
|
19
|
-
*/
|
|
20
|
-
class MockMetaspace {
|
|
21
|
-
async getLocalUserSpace() { return new MockSpace(); }
|
|
22
|
-
async put() { }
|
|
23
|
-
async registerNewIbGib() { }
|
|
24
|
-
}
|
|
25
|
-
class MockSpace {
|
|
26
|
-
async witness() { return { data: { success: true } }; }
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
await respecfully(sir, 'bootstrap-helper', async () => {
|
|
30
|
-
const lc = `[bootstrap-helper.respec.mts][bootstrap-helper]`;
|
|
31
|
-
const testDataDir = './test-data';
|
|
32
|
-
console.log(`${lc} testDataDir: ${testDataDir} (I: c9f39853a57a5af97ab8ff7882c49826)`);
|
|
33
|
-
|
|
34
|
-
// Clean and recreate test-data directory
|
|
35
|
-
if (existsSync(testDataDir)) {
|
|
36
|
-
rmSync(testDataDir, { recursive: true, force: true });
|
|
37
|
-
}
|
|
38
|
-
mkdirSync(testDataDir, { recursive: true });
|
|
39
|
-
|
|
40
|
-
await ifWe(sir, 'calculate balanced domain root paths', async () => {
|
|
41
|
-
const tjpGib = 'abcde1234567890';
|
|
42
|
-
const root = getDomainRootPath(tjpGib, testDataDir);
|
|
43
|
-
|
|
44
|
-
// Should be: testDataDir/domains/ab/cd/abcde1234567890
|
|
45
|
-
iReckon(sir, root).includes(join('domains', 'ab', 'cd', tjpGib));
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
await ifWe(sir, 'bootstrap a domain metaspace and create the directory', async () => {
|
|
49
|
-
const keystoneAddr = 'keystone_alice^abcde1234567890';
|
|
50
|
-
const metaspace = await bootstrapDomainMetaspace(keystoneAddr, testDataDir);
|
|
51
|
-
|
|
52
|
-
iReckon(sir, metaspace).isGonnaBe(metaspace);
|
|
53
|
-
iReckon(sir, metaspace.initialized).isGonnaBe(true);
|
|
54
|
-
|
|
55
|
-
const expectedPath = getDomainRootPath('abcde1234567890', testDataDir);
|
|
56
|
-
iReckon(sir, existsSync(expectedPath)).isGonnaBe(true);
|
|
57
|
-
|
|
58
|
-
// The ibgib framework uses a specific subdirectory structure for the bootstrap
|
|
59
|
-
const bootstrapFile = join(expectedPath, 'ibgib', '000_zerospace', 'gib', 'bootstrap.json');
|
|
60
|
-
iReckon(sir, existsSync(bootstrapFile)).isGonnaBe(true);
|
|
61
|
-
|
|
62
|
-
// Also check that the default space was created
|
|
63
|
-
const defaultSpaceDir = join(expectedPath, 'ibgib', 'default');
|
|
64
|
-
iReckon(sir, existsSync(defaultSpaceDir)).isGonnaBe(true);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
await ifWe(sir, 'integrate with KeystoneService and bootstrap 5 unique domains', async () => {
|
|
68
|
-
const keystoneService = new KeystoneService_V1();
|
|
69
|
-
const mockMetaspace = new MockMetaspace() as any;
|
|
70
|
-
const mockSpace = new MockSpace() as any;
|
|
71
|
-
const config = createStandardPoolConfig({ id: POOL_ID_DEFAULT, salt: POOL_ID_DEFAULT });
|
|
72
|
-
|
|
73
|
-
const identities = ['Alice', 'Bob', 'Charlie', 'Dave', 'Eve'];
|
|
74
|
-
|
|
75
|
-
for (const name of identities) {
|
|
76
|
-
// 1. Generate an actual genesis keystone for this identity
|
|
77
|
-
const keystone = await keystoneService.genesis({
|
|
78
|
-
masterSecret: `SecretFor_${name}`,
|
|
79
|
-
configs: [config],
|
|
80
|
-
metaspace: mockMetaspace,
|
|
81
|
-
space: mockSpace,
|
|
82
|
-
});
|
|
83
|
-
const addr = getIbGibAddr({ ibGib: keystone });
|
|
84
|
-
const tjpGib = keystone.gib!;
|
|
85
|
-
|
|
86
|
-
if (logalot) { console.log(`[test] Bootstrapping domain for ${name}: ${addr} (I: 7ba45828f2719ca578a1f078ff16e126)`); }
|
|
87
|
-
|
|
88
|
-
// 2. Drive the bootstrap logic
|
|
89
|
-
const metaspace = await bootstrapDomainMetaspace(addr, testDataDir);
|
|
90
|
-
iReckon(sir, metaspace.initialized).isGonnaBe(true);
|
|
91
|
-
|
|
92
|
-
// 3. Verify the unique path
|
|
93
|
-
const expectedPath = getDomainRootPath(tjpGib, testDataDir);
|
|
94
|
-
iReckon(sir, existsSync(expectedPath)).isGonnaBe(true);
|
|
95
|
-
|
|
96
|
-
const bootstrapFile = join(expectedPath, 'ibgib', '000_zerospace', 'gib', 'bootstrap.json');
|
|
97
|
-
iReckon(sir, existsSync(bootstrapFile)).isGonnaBe(true);
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
});
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module node-metaspace driven by a node+fs storage substrate
|
|
3
|
-
*
|
|
4
|
-
* a metaspace is a space of spaces. it adds/removes spaces and does
|
|
5
|
-
* inter-spatial composition related things. it's similar to what people think
|
|
6
|
-
* of in terms of a node in a p2p network.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
10
|
-
import { IbGibSpaceAny } from '@ibgib/core-gib/dist/witness/space/space-base-v1.mjs';
|
|
11
|
-
import { IbGibCacheService } from '@ibgib/core-gib/dist/common/cache/cache-types.mjs';
|
|
12
|
-
import { MetaspaceBase } from '@ibgib/core-gib/dist/witness/space/metaspace/metaspace-base.mjs';
|
|
13
|
-
import { MetaspaceFactory } from '@ibgib/core-gib/dist/witness/space/metaspace/metaspace-types.mjs';
|
|
14
|
-
|
|
15
|
-
import { GLOBAL_LOG_A_LOT, GLOBAL_TIMER_NAME } from '../server-constants.mjs';
|
|
16
|
-
|
|
17
|
-
const logalot = GLOBAL_LOG_A_LOT;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
*
|
|
21
|
-
*/
|
|
22
|
-
export class Metaspace_Nodeindexedspace extends MetaspaceBase {
|
|
23
|
-
|
|
24
|
-
// we won't get an object back, only a DTO ibGib essentially
|
|
25
|
-
protected lc: string = `[${Metaspace_Nodeindexedspace.name}]`;
|
|
26
|
-
|
|
27
|
-
get zeroSpace(): IbGibSpaceAny {
|
|
28
|
-
const lc = `[${this.lc}][get zeroSpace]`;
|
|
29
|
-
if (this.metaspaceFactory.fnZeroSpaceFactory) {
|
|
30
|
-
return this.metaspaceFactory.fnZeroSpaceFactory();
|
|
31
|
-
} else {
|
|
32
|
-
throw new Error(`${lc} (UNEXPECTED) this.metaspaceFactory.fnZeroSpaceFactory falsy. not initialized? (E: 9e6c6954dacd868b18c9f34a71e63523)`);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
constructor(
|
|
37
|
-
// public modalController: ModalController,
|
|
38
|
-
// public alertController: AlertController,
|
|
39
|
-
protected cacheSvc: IbGibCacheService | undefined,
|
|
40
|
-
// private latestCacheSvc: IonicStorageLatestIbgibCacheService,
|
|
41
|
-
) {
|
|
42
|
-
super(cacheSvc);
|
|
43
|
-
const lc = `${this.lc}[ctor]`;
|
|
44
|
-
if (logalot) {
|
|
45
|
-
console.log(`${lc}${GLOBAL_TIMER_NAME}`);
|
|
46
|
-
console.timeLog(GLOBAL_TIMER_NAME);
|
|
47
|
-
console.log(`${lc} created. (I: 5690dc2ebf774df3a442bd463dee7455)`);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
protected async initializeMetaspaceFactory({ metaspaceFactory }: {
|
|
53
|
-
metaspaceFactory: MetaspaceFactory,
|
|
54
|
-
}): Promise<void> {
|
|
55
|
-
const lc = `[${this.initializeMetaspaceFactory.name}]`;
|
|
56
|
-
try {
|
|
57
|
-
if (logalot) { console.log(`${lc} starting... (I: 5a29670439ac47634dcdfe25225c8223)`); }
|
|
58
|
-
if (!metaspaceFactory) { throw new Error(`(UNEXPECTED) metaspaceFactory falsy? space-gib server initialization should have guaranteed this to be truthy (E: f305356c2978cf0ee8062fd818940826)`); }
|
|
59
|
-
if (!metaspaceFactory.fnDefaultLocalSpaceFactory) { throw new Error(`(UNEXPECTED) !metaspaceFactory.fnDefaultLocalSpaceFactory? space-gib server initialization should have guaranteed this to be truthy (E: 884281e5251bd65528eb7b3f48737826)`); }
|
|
60
|
-
if (!metaspaceFactory.fnDtoToSpace) { throw new Error(`(UNEXPECTED) !metaspaceFactory.fnDtoToSpace? space-gib server initialization should have guaranteed this to be truthy (E: 885b7828606f36f9183bb9e687e25826)`); }
|
|
61
|
-
if (!metaspaceFactory.fnZeroSpaceFactory) { throw new Error(`(UNEXPECTED) !metaspaceFactory.fnZeroSpaceFactory? space-gib server initialization should have guaranteed this to be truthy (E: 336cf6655758bad4aaeea8ce08d8c826)`); }
|
|
62
|
-
|
|
63
|
-
await super.initializeMetaspaceFactory({ metaspaceFactory });
|
|
64
|
-
} catch (error) {
|
|
65
|
-
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
66
|
-
throw error;
|
|
67
|
-
} finally {
|
|
68
|
-
if (logalot) { console.log(`${lc} complete.`); }
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// async initializeZeroSpace(): Promise<void> {
|
|
73
|
-
// const lc = `[${this.initializeZeroSpace.name}]`;
|
|
74
|
-
// try {
|
|
75
|
-
// if (logalot) { console.log(`${lc} starting... (I: a3364e9601c70c8163e67868e5d67723)`); }
|
|
76
|
-
|
|
77
|
-
// } catch (error) {
|
|
78
|
-
// console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
79
|
-
// throw error;
|
|
80
|
-
// } finally {
|
|
81
|
-
// if (logalot) { console.log(`${lc} complete.`); }
|
|
82
|
-
// }
|
|
83
|
-
// }
|
|
84
|
-
|
|
85
|
-
}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/** Maximum allowed path length to prevent DoS. */
|
|
2
|
-
export const MAX_PATH_LENGTH = 2048;
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Blacklisted patterns for any incoming path.
|
|
6
|
-
* We iterate through these for a quick security check.
|
|
7
|
-
*/
|
|
8
|
-
export const FORBIDDEN_PATTERNS = [
|
|
9
|
-
/\.\./, // No directory traversal
|
|
10
|
-
/\/\//, // No redundant slashes
|
|
11
|
-
/\0/, // No null bytes
|
|
12
|
-
/(^|\/)\./, // No hidden/dot-files (e.g. .env, .git)
|
|
13
|
-
];
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Regex for valid API paths.
|
|
17
|
-
*
|
|
18
|
-
* IMPORTANT: See IMPLEMENTATION.md for overall path schema documentation.
|
|
19
|
-
*
|
|
20
|
-
* Paths are URL encoded. The `ib` and `gib` portions of addresses are captured explicitly
|
|
21
|
-
* by splitting on `%5E` or `^`.
|
|
22
|
-
*/
|
|
23
|
-
export const API_PATH_REGEXES = {
|
|
24
|
-
/**
|
|
25
|
-
* /api/health
|
|
26
|
-
*/
|
|
27
|
-
HEALTH: /^\/api\/health\/?$/,
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* /api/ibgib/graph/:domainAddr/:ibgibAddr
|
|
31
|
-
*/
|
|
32
|
-
IBGIB_GRAPH: /^\/api\/ibgib\/graph\/([^\/]+?)(?:%5E|\^)([^\/]+)\/([^\/]+?)(?:%5E|\^)([^\/]+)\/?$/,
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* /api/ibgib/:domainAddr/:ibgibAddr
|
|
36
|
-
*/
|
|
37
|
-
IBGIB_ADDR: /^\/api\/ibgib\/([^\/]+?)(?:%5E|\^)([^\/]+)\/([^\/]+?)(?:%5E|\^)([^\/]+)\/?$/,
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* /api/keystone
|
|
41
|
-
*/
|
|
42
|
-
KEYSTONE: /^\/api\/keystone\/?$/,
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* /api/keystone/:domainAddr
|
|
46
|
-
*/
|
|
47
|
-
KEYSTONE_ADDR: /^\/api\/keystone\/([^\/]+?)(?:%5E|\^)([^\/]+)\/?$/,
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* /api/ibgib
|
|
51
|
-
*/
|
|
52
|
-
IBGIB_BASE: /^\/api\/ibgib\/?$/,
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* /api/keystone/evolve/:domainAddr
|
|
56
|
-
*/
|
|
57
|
-
KEYSTONE_EVOLVE: /^\/api\/keystone\/evolve\/([^\/]+?)(?:%5E|\^)([^\/]+)\/?$/,
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* /api/keystone/genesis
|
|
61
|
-
*
|
|
62
|
-
* Accepts a single genesis-frame keystone ibgib and creates a new
|
|
63
|
-
* domain-isolated metaspace for it. No `:domainAddr` in the URL because
|
|
64
|
-
* the server derives the domain from the incoming keystone ibgib itself.
|
|
65
|
-
*
|
|
66
|
-
* Parallel to /api/keystone/evolve.
|
|
67
|
-
*/
|
|
68
|
-
KEYSTONE_GENESIS: /^\/api\/keystone\/genesis\/?$/,
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* /api/debug/ws-echo
|
|
72
|
-
*
|
|
73
|
-
* Minimal WebSocket echo endpoint for validating WS plumbing (Traefik
|
|
74
|
-
* passthrough, server upgrade handling, client connectivity) before
|
|
75
|
-
* integrating real sync logic.
|
|
76
|
-
*
|
|
77
|
-
* ## intent
|
|
78
|
-
*
|
|
79
|
-
* NOT for production use. Remove or gate behind a flag once WS sync is
|
|
80
|
-
* confirmed working end-to-end.
|
|
81
|
-
*/
|
|
82
|
-
DEBUG_WS_ECHO: /^\/api\/debug\/ws-echo\/?$/,
|
|
83
|
-
/**
|
|
84
|
-
* /api/sync/ws/:domainAddr
|
|
85
|
-
*/
|
|
86
|
-
SYNC_WS: /^\/api\/sync\/ws\/([^\/]+?)(?:%5E|\^)([^\/]+)\/?$/,
|
|
87
|
-
/**
|
|
88
|
-
* /api/identity/sso/link
|
|
89
|
-
*/
|
|
90
|
-
IDENTITY_SSO_LINK: /^\/api\/identity\/sso\/link\/?$/,
|
|
91
|
-
/**
|
|
92
|
-
* /api/identity/sso/login
|
|
93
|
-
*/
|
|
94
|
-
IDENTITY_SSO_LOGIN: /^\/api\/identity\/sso\/login\/?$/,
|
|
95
|
-
/**
|
|
96
|
-
* /api/identity/sso/config
|
|
97
|
-
*/
|
|
98
|
-
IDENTITY_SSO_CONFIG: /^\/api\/identity\/sso\/config\/?$/,
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
export const VALID_STATIC_PATH_REGEX = /^\/[a-zA-Z0-9\-_\.\/]*$/;
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
2
|
-
import { validateIbGibAddr } from '@ibgib/ts-gib/dist/V1/validate-helper.mjs';
|
|
3
|
-
|
|
4
|
-
import { GLOBAL_LOG_A_LOT } from './server-constants.mjs';
|
|
5
|
-
import { FORBIDDEN_PATTERNS, MAX_PATH_LENGTH, VALID_STATIC_PATH_REGEX } from './path-constants.mjs';
|
|
6
|
-
|
|
7
|
-
const logalot = GLOBAL_LOG_A_LOT;
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* little helper function just to DRY out code.
|
|
11
|
-
*/
|
|
12
|
-
export function runtimeErrorValidationStrings(lc: string, error: any): string[] {
|
|
13
|
-
const emsg = `${lc} ${extractErrorMsg(error)}`;
|
|
14
|
-
console.error(emsg);
|
|
15
|
-
return [`runtime error: ${emsg}`];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Validates that a path is safe from common attacks and under max length.
|
|
20
|
-
*/
|
|
21
|
-
export function validateRawPath(rawPath: string): string[] {
|
|
22
|
-
const lc = `[${validateRawPath.name}]`;
|
|
23
|
-
try {
|
|
24
|
-
if (logalot) { console.log(`${lc} starting... (I: 80bd6f63e6c83b8e943b90383e044826)`); }
|
|
25
|
-
|
|
26
|
-
const errors: string[] = [];
|
|
27
|
-
|
|
28
|
-
if (!rawPath || rawPath.length > MAX_PATH_LENGTH) {
|
|
29
|
-
errors.push(`path too long. MAX_PATH_LENGTH: ${MAX_PATH_LENGTH}, incoming path length: ${rawPath.length}`);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
for (const pattern of FORBIDDEN_PATTERNS) {
|
|
33
|
-
if (pattern.test(rawPath)) {
|
|
34
|
-
errors.push(`invalid path. contains forbidden pattern.source: ${pattern.source} (E: 56a228fb9473a086b83d0088b5af1826)`);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return errors;
|
|
39
|
-
} catch (error) {
|
|
40
|
-
return runtimeErrorValidationStrings(lc, error);
|
|
41
|
-
} finally {
|
|
42
|
-
if (logalot) { console.log(`${lc} complete.`); }
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* simple boolean wrapper for {@link validateRawPath}
|
|
48
|
-
*/
|
|
49
|
-
export function isSafePath(path: string): boolean {
|
|
50
|
-
const errors = validateRawPath(path);
|
|
51
|
-
return errors.length === 0;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function validateStaticPath(path: string): string[] {
|
|
55
|
-
const lc = `[${validateStaticPath.name}]`;
|
|
56
|
-
try {
|
|
57
|
-
if (logalot) { console.log(`${lc} starting... (I: 4f9cf8ffd528c7dd88324529528eeb26)`); }
|
|
58
|
-
|
|
59
|
-
const errors: string[] = [];
|
|
60
|
-
|
|
61
|
-
if (!VALID_STATIC_PATH_REGEX.test(path)) {
|
|
62
|
-
errors.push(`static path fails to match VALID_STATIC_PATH_REGEX of ${VALID_STATIC_PATH_REGEX} (E: dfdf38a0439d4d09186e6fa81ab66826) `);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return errors;
|
|
66
|
-
} catch (error) {
|
|
67
|
-
return runtimeErrorValidationStrings(lc, error);
|
|
68
|
-
} finally {
|
|
69
|
-
if (logalot) { console.log(`${lc} complete.`); }
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Validates a static file path.
|
|
75
|
-
*/
|
|
76
|
-
export function isValidStaticPath(path: string): boolean {
|
|
77
|
-
if (!isSafePath(path)) { return false; }
|
|
78
|
-
const errors = validateStaticPath(path);
|
|
79
|
-
return errors.length === 0;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Decodes and validates a single ibGib address segment from a URL path.
|
|
84
|
-
* Uses the library's internal validateIbGibAddr for deep checking.
|
|
85
|
-
*/
|
|
86
|
-
export function validateAddressSegment(encodedAddr: string): string[] {
|
|
87
|
-
const lc = `[${validateAddressSegment.name}]`;
|
|
88
|
-
try {
|
|
89
|
-
if (logalot) { console.log(`${lc} starting... (I: 1bb9ff7e1aefb77b670b2519658cc126)`); }
|
|
90
|
-
if (!encodedAddr) {
|
|
91
|
-
throw new Error(`(UNEXPECTED) falsy encodedAddr? We're expecting to have already guaranteed there are no double-slashes (i.e. no empty segments) (E: 7be7668e7b8c9fbbdf56f0fe19f92e26)`);
|
|
92
|
-
}
|
|
93
|
-
const addr = decodeURIComponent(encodedAddr);
|
|
94
|
-
const errors = validateIbGibAddr({ addr });
|
|
95
|
-
return errors ?? [];
|
|
96
|
-
} catch (error) {
|
|
97
|
-
return runtimeErrorValidationStrings(lc, error);
|
|
98
|
-
} finally {
|
|
99
|
-
if (logalot) { console.log(`${lc} complete.`); }
|
|
100
|
-
}
|
|
101
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
// import { respecfully, iReckon, ifWe } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
|
|
2
|
-
// import {
|
|
3
|
-
// isSafePath, isValidStaticPath,
|
|
4
|
-
// // API_PATHS, getPathParams, MAX_PATH_LENGTH,
|
|
5
|
-
// validateAddressSegment
|
|
6
|
-
// } from './path-helper.mjs';
|
|
7
|
-
// import { MAX_PATH_LENGTH } from './path-constants.mts';
|
|
8
|
-
|
|
9
|
-
// const sir = `[${import.meta.url}]`;
|
|
10
|
-
|
|
11
|
-
// await respecfully(sir, 'Path Helper Validation', async () => {
|
|
12
|
-
|
|
13
|
-
// await respecfully(sir, 'isSafePath', async () => {
|
|
14
|
-
// await ifWe(sir, 'valid simple paths', async () => {
|
|
15
|
-
// iReckon(sir, isSafePath('/api/health')).isGonnaBeTrue();
|
|
16
|
-
// iReckon(sir, isSafePath('/index.html')).isGonnaBeTrue();
|
|
17
|
-
// });
|
|
18
|
-
|
|
19
|
-
// await ifWe(sir, 'too long paths', async () => {
|
|
20
|
-
// const longPath = '/'.repeat(MAX_PATH_LENGTH + 1);
|
|
21
|
-
// iReckon(sir, isSafePath(longPath)).isGonnaBeFalse();
|
|
22
|
-
// });
|
|
23
|
-
|
|
24
|
-
// await ifWe(sir, 'directory traversal attempts', async () => {
|
|
25
|
-
// iReckon(sir, isSafePath('/../etc/passwd')).isGonnaBeFalse();
|
|
26
|
-
// iReckon(sir, isSafePath('/api/../../etc/passwd')).isGonnaBeFalse();
|
|
27
|
-
// });
|
|
28
|
-
|
|
29
|
-
// await ifWe(sir, 'null bytes', async () => {
|
|
30
|
-
// iReckon(sir, isSafePath('/api/health\0')).isGonnaBeFalse();
|
|
31
|
-
// });
|
|
32
|
-
|
|
33
|
-
// await ifWe(sir, 'redundant slashes', async () => {
|
|
34
|
-
// iReckon(sir, isSafePath('/api//health')).isGonnaBeFalse();
|
|
35
|
-
// });
|
|
36
|
-
|
|
37
|
-
// await ifWe(sir, 'dot-file access', async () => {
|
|
38
|
-
// iReckon(sir, isSafePath('/.env')).isGonnaBeFalse();
|
|
39
|
-
// iReckon(sir, isSafePath('/scripts/.hidden')).isGonnaBeFalse();
|
|
40
|
-
// iReckon(sir, isSafePath('/.git/config')).isGonnaBeFalse();
|
|
41
|
-
// });
|
|
42
|
-
// });
|
|
43
|
-
|
|
44
|
-
// await respecfully(sir, 'validateAddressSegment', async () => {
|
|
45
|
-
// await ifWe(sir, 'valid encoded addresses', async () => {
|
|
46
|
-
// const validGib = 'a'.repeat(64);
|
|
47
|
-
// iReckon(sir, validateAddressSegment(`someib%5E${validGib}`)).isGonnaBeTrue();
|
|
48
|
-
// // gib with dots (common in sha256.sha256)
|
|
49
|
-
// const dotGib = `${validGib}.${validGib}`;
|
|
50
|
-
// iReckon(sir, validateAddressSegment(`someib%5E${dotGib}`)).isGonnaBeTrue();
|
|
51
|
-
// });
|
|
52
|
-
|
|
53
|
-
// await ifWe(sir, 'invalid encoded addresses', async () => {
|
|
54
|
-
// iReckon(sir, validateAddressSegment('no_delim')).isGonnaBeFalse();
|
|
55
|
-
// iReckon(sir, validateAddressSegment('%5Estarts_with_delim')).isGonnaBeFalse();
|
|
56
|
-
// iReckon(sir, validateAddressSegment('too%5Emany%5Edelims')).isGonnaBeFalse();
|
|
57
|
-
// iReckon(sir, validateAddressSegment('too%5Emany..dots')).isGonnaBeFalse();
|
|
58
|
-
// iReckon(sir, validateAddressSegment('invalid_chars_!%5Eabc')).isGonnaBeFalse();
|
|
59
|
-
// });
|
|
60
|
-
// });
|
|
61
|
-
|
|
62
|
-
// await respecfully(sir, 'API_PATHS Regexes', async () => {
|
|
63
|
-
// await ifWe(sir, 'HEALTH regex', async () => {
|
|
64
|
-
// iReckon(sir, API_PATHS.HEALTH.test('/api/health')).isGonnaBeTrue();
|
|
65
|
-
// iReckon(sir, API_PATHS.HEALTH.test('/api/health/')).isGonnaBeTrue();
|
|
66
|
-
// iReckon(sir, API_PATHS.HEALTH.test('/api/health/more')).isGonnaBeFalse();
|
|
67
|
-
// });
|
|
68
|
-
|
|
69
|
-
// await ifWe(sir, 'IBGIB_ADDR regex', async () => {
|
|
70
|
-
// // valid encoded addr: someib%5Esomegib
|
|
71
|
-
// const addr = '/api/ibgib/someib%5Esomegib';
|
|
72
|
-
// iReckon(sir, API_PATHS.IBGIB_ADDR.test(addr)).isGonnaBeTrue();
|
|
73
|
-
|
|
74
|
-
// const params = getPathParams(addr, API_PATHS.IBGIB_ADDR);
|
|
75
|
-
// iReckon(sir, params?.[0]).isGonnaBe('someib%5Esomegib');
|
|
76
|
-
// });
|
|
77
|
-
|
|
78
|
-
// await ifWe(sir, 'IBGIB_GRAPH regex', async () => {
|
|
79
|
-
// const path = '/api/ibgib/graph/someib%5Esomegib';
|
|
80
|
-
// iReckon(sir, API_PATHS.IBGIB_GRAPH.test(path)).isGonnaBeTrue();
|
|
81
|
-
|
|
82
|
-
// const params = getPathParams(path, API_PATHS.IBGIB_GRAPH);
|
|
83
|
-
// iReckon(sir, params?.[0]).isGonnaBe('someib%5Esomegib');
|
|
84
|
-
// });
|
|
85
|
-
|
|
86
|
-
// await ifWe(sir, 'KEYSTONE regex', async () => {
|
|
87
|
-
// iReckon(sir, API_PATHS.KEYSTONE.test('/api/keystone')).isGonnaBeTrue();
|
|
88
|
-
// iReckon(sir, API_PATHS.KEYSTONE.test('/api/keystone/')).isGonnaBeTrue();
|
|
89
|
-
// });
|
|
90
|
-
|
|
91
|
-
// await ifWe(sir, 'KEYSTONE_EVOLVE regex', async () => {
|
|
92
|
-
// const path = '/api/keystone/evolve/someib%5Esomegib';
|
|
93
|
-
// iReckon(sir, API_PATHS.KEYSTONE_EVOLVE.test(path)).isGonnaBeTrue();
|
|
94
|
-
|
|
95
|
-
// const params = getPathParams(path, API_PATHS.KEYSTONE_EVOLVE);
|
|
96
|
-
// iReckon(sir, params?.[0]).isGonnaBe('someib%5Esomegib');
|
|
97
|
-
// });
|
|
98
|
-
// });
|
|
99
|
-
// });
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# serve-gib changelog
|
|
2
|
-
|
|
3
|
-
_note: as you implement features/fixes/etc., please document them here under the "Working Version" section._
|
|
4
|
-
|
|
5
|
-
## Working Version
|
|
6
|
-
|
|
7
|
-
* impl: initial core infrastructure
|
|
8
|
-
* defined `RouteInfo`, `ResponseResult`, and `ServeGibHandler` foundational types.
|
|
9
|
-
* implemented `ServeGib_V1` with pipeline-based request processing.
|
|
10
|
-
* added robust `try..catch..finally` pattern with built-in access logging and timing.
|
|
11
|
-
* impl: handler "pit of success" plumbing
|
|
12
|
-
* implemented `ServeGibHandlerBase` abstract class to reduce boilerplate.
|
|
13
|
-
* automated route matching via `canHandleRoute` and `regex` properties.
|
|
14
|
-
* implemented `handleRouteImpl` pattern to separate plumbing from business logic.
|
|
15
|
-
* added generic `TQueryParams` support with automated parsing via `parseQueryParams`.
|
|
16
|
-
* feat: hierarchical handler organization
|
|
17
|
-
* established folder-based grouping mirroring URL paths (e.g., `handlers/api/ibgib/`).
|
|
18
|
-
* enforced "one handler per hard route" and "one handler per file" architectural guidelines.
|
|
19
|
-
* feat: concrete core handlers
|
|
20
|
-
* `HealthHandler`: Simple liveness probe.
|
|
21
|
-
* `IbGibHandler`: Single ibGib address resolution.
|
|
22
|
-
* `IbGibGraphHandler`: Complex graph/BFS retrieval.
|
|
23
|
-
* `KeystoneHandler`: Genesis and evolution turn persistence.
|
|
24
|
-
* `StaticFileHandler`: High-performance asset serving with SPA history fallback.
|
|
25
|
-
* `ErrorHandler`: Standardized 404 and fatal error reporting.
|
|
26
|
-
* docs: architectural guidance
|
|
27
|
-
* created `README.md` with detailed guidance on handler philosophy and plumbing usage.
|
|
28
|
-
* test: validation suite
|
|
29
|
-
* implemented `serve-gib.respec.mts` verifying orchestrator pipeline and `RouteInfo` construction.
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# serve-gib Microframework
|
|
2
|
-
|
|
3
|
-
A minimalist, high-observability HTTP microframework for ibgib servers.
|
|
4
|
-
|
|
5
|
-
## Architecture & Guidance
|
|
6
|
-
|
|
7
|
-
### 1. Handler Philosophy
|
|
8
|
-
* **One handler per "hard" route**: A handler should correspond to a unique URL path pattern, disregarding query parameters.
|
|
9
|
-
* **One regex per handler**: Concrete handlers should ideally define a single `regex` property for routing.
|
|
10
|
-
* **One handler per file**: Each route handler should live in its own file within the `handlers/` directory (e.g., `handlers/api/health.handler.mts`).
|
|
11
|
-
* The subfolders should match the hard route of the handler, e.g., `api/ibgib` route should be in the `handlers/api/ibgib` subfolder.
|
|
12
|
-
|
|
13
|
-
### 2. The "Pit of Success" Plumbing
|
|
14
|
-
The `ServeGibHandlerBase` class provides built-in plumbing to reduce boilerplate:
|
|
15
|
-
* **`handleRoute` (Plumbing)**: Automatically checks `canHandleRoute`, parses query parameters, and provides a top-level `try..catch` block for logging and error reporting.
|
|
16
|
-
* **`handleRouteImpl` (Implementation)**: This is where the core logic of the handler resides.
|
|
17
|
-
* **`canHandleRoute`**: Defaults to a regex match on `info.pathname`. Override this for more complex routing logic.
|
|
18
|
-
|
|
19
|
-
### 3. Query Parameters
|
|
20
|
-
Query parameters are handled via the `parseQueryParams` method in the base class.
|
|
21
|
-
* By default, it parses URL search params into a key-value object.
|
|
22
|
-
* Handlers can override this method to perform custom parsing or validation (e.g., parsing JSON strings from query params).
|
|
23
|
-
* The base class is generic: `ServeGibHandlerBase<TQueryParams = any>`.
|
|
24
|
-
|
|
25
|
-
### 4. RouteInfo
|
|
26
|
-
The `RouteInfo` object provides an enriched view of the incoming request:
|
|
27
|
-
* `decodedPathSegments`: Pre-split and decoded path parts.
|
|
28
|
-
* `protocol`, `method`, `body`: Basic HTTP info.
|
|
29
|
-
* `queryParams`: The result of the handler's `parseQueryParams` call.
|
|
30
|
-
|
|
31
|
-
## Pipeline Order
|
|
32
|
-
The `ServeGib_V1` executes handlers in the order they are provided in the `handlers` array. The first handler to return a non-undefined `ResponseResult` wins.
|
|
33
|
-
* **Tip**: Order specific routes (like `/api/ibgib/graph`) before more general ones (like `/api/ibgib`).
|
|
34
|
-
* **Fallback**: The `ErrorHandler` is used as a final catch-all for 404s and fatal errors.
|
|
35
|
-
|
|
36
|
-
## Docker / Server Resilience Note
|
|
37
|
-
When deploying or running this service (or any downstream applications) within a Docker Compose environment, ensure that the `docker-compose.yml` configuration specifies a restart policy such as:
|
|
38
|
-
```yaml
|
|
39
|
-
services:
|
|
40
|
-
space-gib:
|
|
41
|
-
restart: unless-stopped
|
|
42
|
-
```
|
|
43
|
-
This ensures that the container auto-recovers and restarts if the node process crashes due to unhandled events (such as raw TCP socket `ECONNRESET` exceptions from client refreshes).
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const GLOBAL_LOG_A_LOT = false;
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module handlers/api/debug/ws-echo.handler
|
|
3
|
-
*
|
|
4
|
-
* Minimal WebSocket echo handler for validating end-to-end WebSocket
|
|
5
|
-
* plumbing: Traefik passthrough → Node.js upgrade event → browser client.
|
|
6
|
-
*
|
|
7
|
-
* ## intent
|
|
8
|
-
*
|
|
9
|
-
* This is a diagnostic-only endpoint. The goal is to confirm that:
|
|
10
|
-
* 1. Traefik correctly forwards WS upgrade requests to the container
|
|
11
|
-
* 2. The Node.js `upgrade` event fires and we can accept the connect handshake
|
|
12
|
-
* 3. The browser client can open a wss:// connection and exchange frames
|
|
13
|
-
*
|
|
14
|
-
* Once WS sync is confirmed end-to-end, this handler can be removed or
|
|
15
|
-
* gated behind an env flag.
|
|
16
|
-
*
|
|
17
|
-
* ## endpoint
|
|
18
|
-
*
|
|
19
|
-
* `GET /api/debug/ws-echo` (with Upgrade: websocket header)
|
|
20
|
-
*
|
|
21
|
-
* On connect: server sends `{"status":"connected","msg":"ws-echo ready"}`
|
|
22
|
-
* On message: server echoes `{"echo":<original message>}`
|
|
23
|
-
* On close: server logs and cleans up
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
import { IncomingMessage } from 'node:http';
|
|
27
|
-
import { Socket } from 'node:net';
|
|
28
|
-
|
|
29
|
-
import { extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
30
|
-
|
|
31
|
-
import { GLOBAL_LOG_A_LOT } from '../../../constants.mjs';
|
|
32
|
-
import {
|
|
33
|
-
encodeTextFrame, decodeTextFrame, encodeCloseFrame, performConnect,
|
|
34
|
-
WebSocketFrameDecoder
|
|
35
|
-
} from '../../ws/ws-helper.mjs';
|
|
36
|
-
import { API_PATH_REGEXES } from '../../../../path-constants.mjs';
|
|
37
|
-
|
|
38
|
-
const logalot = GLOBAL_LOG_A_LOT;
|
|
39
|
-
|
|
40
|
-
const lc = `[WsEchoHandler]`;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// ---------------------------------------------------------------------------
|
|
44
|
-
// Public handler
|
|
45
|
-
// ---------------------------------------------------------------------------
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Returns true if this request is a WebSocket upgrade to the debug echo path.
|
|
49
|
-
* Call this from the server's `upgrade` event listener.
|
|
50
|
-
*/
|
|
51
|
-
export function canHandleWsUpgrade(req: IncomingMessage): boolean {
|
|
52
|
-
const pathname = new URL(req.url ?? '/', 'http://localhost').pathname;
|
|
53
|
-
return API_PATH_REGEXES.DEBUG_WS_ECHO.test(pathname);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Accepts the WebSocket upgrade and sets up the echo loop.
|
|
58
|
-
*
|
|
59
|
-
* Client will receive a JSON `{status:"connected"}` on open, and
|
|
60
|
-
* `{echo:<msg>}` for every message it sends.
|
|
61
|
-
*/
|
|
62
|
-
export function handleWsEchoUpgrade(req: IncomingMessage, socket: Socket, head: Buffer): void {
|
|
63
|
-
const lc_fn = `${lc}[handleWsEchoUpgrade]`;
|
|
64
|
-
try {
|
|
65
|
-
if (logalot) { console.log(`${lc_fn} starting...`); }
|
|
66
|
-
|
|
67
|
-
const ok = performConnect(req, socket);
|
|
68
|
-
if (!ok) { return; }
|
|
69
|
-
|
|
70
|
-
// Send the "connected" greeting
|
|
71
|
-
socket.write(encodeTextFrame(JSON.stringify({ status: 'connected', msg: 'ws-echo ready' })));
|
|
72
|
-
|
|
73
|
-
// Echo loop
|
|
74
|
-
const decoder = new WebSocketFrameDecoder();
|
|
75
|
-
socket.on('data', (data: Buffer) => {
|
|
76
|
-
try {
|
|
77
|
-
decoder.addChunk(data);
|
|
78
|
-
let frameText = decoder.nextFrame();
|
|
79
|
-
while (frameText !== null) {
|
|
80
|
-
if (logalot) { console.log(`${lc_fn} echo: ${frameText}`); }
|
|
81
|
-
socket.write(encodeTextFrame(JSON.stringify({ echo: frameText })));
|
|
82
|
-
frameText = decoder.nextFrame();
|
|
83
|
-
}
|
|
84
|
-
} catch (error) {
|
|
85
|
-
console.error(`${lc_fn} error in data handler: ${extractErrorMsg(error)}`);
|
|
86
|
-
socket.write(encodeCloseFrame(1000));
|
|
87
|
-
socket.end();
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
socket.on('error', (error) => {
|
|
92
|
-
console.error(`${lc_fn} socket error: ${extractErrorMsg(error)}`);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
socket.on('close', () => {
|
|
96
|
-
if (logalot) { console.log(`${lc_fn} socket closed.`); }
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
} catch (error) {
|
|
100
|
-
console.error(`${lc_fn} ${extractErrorMsg(error)}`);
|
|
101
|
-
socket.destroy();
|
|
102
|
-
} finally {
|
|
103
|
-
if (logalot) { console.log(`${lc_fn} complete.`); }
|
|
104
|
-
}
|
|
105
|
-
}
|