@phystack/cli 4.5.41-dev → 4.5.42-dev

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.
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DeviceSimulator = void 0;
7
+ /* eslint-disable import/prefer-default-export */
8
+ const crypto_1 = __importDefault(require("crypto"));
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ const uuid_1 = require("uuid");
11
+ const types_1 = require("./types");
12
+ const twin_cache_1 = require("./twin-cache");
13
+ const local_server_1 = require("./local-server");
14
+ const message_router_1 = require("./message-router");
15
+ const logger_1 = require("./logger");
16
+ const simulator_config_1 = require("../utils/simulator-config");
17
+ const utils_1 = require("../utils");
18
+ /**
19
+ * Generate a deterministic 24-char hex string (MongoDB ObjectID format)
20
+ * from a human-readable name. Same input always produces same output.
21
+ */
22
+ function mockObjectId(name) {
23
+ return crypto_1.default.createHash('sha256').update(name).digest('hex').slice(0, 24);
24
+ }
25
+ class DeviceSimulator {
26
+ constructor(port = 55000, deviceDisplayName = 'Local Simulator') {
27
+ this.router = null;
28
+ this.deviceId = '';
29
+ this.tenantId = '';
30
+ this.port = port;
31
+ this.deviceDisplayName = deviceDisplayName;
32
+ this.twinCache = new twin_cache_1.TwinCache();
33
+ this.localServer = new local_server_1.LocalServer(port);
34
+ }
35
+ async start() {
36
+ // 1. Load or create device identity
37
+ // Config storage uses this as directory name. Twins need a 24-char hex ID
38
+ // to pass hub-client validation (e.g. signals zod schemas), so we derive
39
+ // one deterministically via mockObjectId.
40
+ const tenantLabel = 'local';
41
+ const tenantObjectId = mockObjectId(tenantLabel);
42
+ this.tenantId = tenantObjectId;
43
+ const existingConfig = await (0, simulator_config_1.getDeviceConfig)(tenantLabel);
44
+ let deviceTwinId;
45
+ if (existingConfig) {
46
+ this.deviceId = existingConfig.deviceId;
47
+ deviceTwinId = existingConfig.deviceTwinId;
48
+ }
49
+ else {
50
+ this.deviceId = (0, uuid_1.v4)();
51
+ deviceTwinId = (0, uuid_1.v4)();
52
+ await (0, simulator_config_1.saveDeviceConfig)(tenantLabel, {
53
+ deviceId: this.deviceId,
54
+ tenantId: this.tenantId,
55
+ deviceTwinId,
56
+ });
57
+ }
58
+ // 2. Create a local Device twin in cache
59
+ const deviceTwin = {
60
+ id: deviceTwinId,
61
+ deviceId: this.deviceId,
62
+ tenantId: this.tenantId,
63
+ type: types_1.TwinTypeEnum.Device,
64
+ properties: {
65
+ desired: {
66
+ displayName: this.deviceDisplayName,
67
+ spaceId: tenantObjectId,
68
+ env: 'development',
69
+ deviceSerial: `SIM-${this.deviceId.slice(0, 8)}`,
70
+ accessKey: 'simulator-local-key',
71
+ },
72
+ reported: {
73
+ ip: [{ interface: 'lo', ipv4: '127.0.0.1', ipv6: '::1' }],
74
+ os: { osVersion: 'Simulator' },
75
+ env: { gridEnv: 'development' },
76
+ },
77
+ },
78
+ };
79
+ this.twinCache.addTwin(deviceTwin);
80
+ // 3. Start local server
81
+ await this.localServer.start();
82
+ const io = this.localServer.getIO();
83
+ this.localServer.on('instanceConnected', (event) => {
84
+ const twin = this.twinCache.getTwin(event.twinId);
85
+ const label = twin?.properties?.desired?.appName || 'unknown';
86
+ logger_1.simulatorLog.success(`Instance connected: ${label} (${event.twinId}) — ${event.activeCount} active`);
87
+ });
88
+ this.localServer.on('instanceDisconnected', (event) => {
89
+ const twin = this.twinCache.getTwin(event.twinId);
90
+ const label = twin?.properties?.desired?.appName || 'unknown';
91
+ logger_1.simulatorLog.warn(`Instance disconnected: ${label} (${event.twinId}) — ${event.activeCount} active`);
92
+ });
93
+ // 4. Create router (no upstream)
94
+ this.router = new message_router_1.MessageRouter(this.twinCache, io);
95
+ this.localServer.setRouter(this.router);
96
+ // 5. Print banner
97
+ this.printBanner();
98
+ if (existingConfig) {
99
+ logger_1.simulatorLog.dim(`Reusing device identity ${this.deviceId}`);
100
+ }
101
+ logger_1.simulatorLog.dim(`Apps connect to http://localhost:${this.port}`);
102
+ }
103
+ async stop() {
104
+ logger_1.simulatorLog.dim('Shutting down simulator...');
105
+ await this.localServer.stop();
106
+ logger_1.simulatorLog.dim('Simulator stopped.');
107
+ }
108
+ async createTwinForApp(type, desiredProperties, reuseId) {
109
+ const twin = {
110
+ id: reuseId || (0, uuid_1.v4)(),
111
+ deviceId: this.deviceId,
112
+ tenantId: this.tenantId,
113
+ type,
114
+ properties: {
115
+ desired: desiredProperties || {},
116
+ reported: {},
117
+ },
118
+ };
119
+ this.twinCache.addTwin(twin);
120
+ return twin;
121
+ }
122
+ createPeripheralTwin(instanceId, name, hardwareId, desiredProperties) {
123
+ const twin = {
124
+ id: (0, uuid_1.v4)(),
125
+ deviceId: this.deviceId,
126
+ tenantId: this.tenantId,
127
+ type: types_1.TwinTypeEnum.Peripheral,
128
+ properties: {
129
+ desired: { ...desiredProperties, instanceId, name, hardwareId },
130
+ reported: {},
131
+ },
132
+ descriptors: { instanceId, name, hardwareId },
133
+ };
134
+ this.twinCache.addTwin(twin);
135
+ return twin;
136
+ }
137
+ verifyTwinExists(twinId) {
138
+ return this.twinCache.hasTwin(twinId);
139
+ }
140
+ getDeviceId() {
141
+ return this.deviceId;
142
+ }
143
+ printBanner() {
144
+ logger_1.simulatorLog.printBanner([
145
+ '',
146
+ chalk_1.default.hex('#00e676')(' ██▀▀▄ █ █ █ █ ▄▀▀▀ ▀▀█▀▀ ▄▀▀█ ▄▀▀▀ █ ▄▀'),
147
+ chalk_1.default.hex('#00c853')(' █▄▄▀ █▄▄█ ▀█ ▀▀▄ █ █▄▄█ █ █▀▄ '),
148
+ chalk_1.default.hex('#00a846')(' █ █ █ █ ▀▄▄▀ █ █ █ ▀▄▄▀ █ ▀▄'),
149
+ chalk_1.default.dim(` Device Simulator v${(0, utils_1.getPackageVersion)()}`),
150
+ chalk_1.default.dim('─'.repeat(50)),
151
+ ` ${chalk_1.default.cyan('Mode:')} ${chalk_1.default.yellow('local')}`,
152
+ ` ${chalk_1.default.cyan('Port:')} ${this.port}`,
153
+ ` ${chalk_1.default.cyan('Device:')} ${this.deviceDisplayName}`,
154
+ ` ${chalk_1.default.cyan('Device ID:')} ${chalk_1.default.dim(this.deviceId)} ${chalk_1.default.yellow('(local)')}`,
155
+ ` ${chalk_1.default.cyan('Tenant:')} ${chalk_1.default.dim(this.tenantId)} ${chalk_1.default.yellow('(local)')}`,
156
+ chalk_1.default.dim('─'.repeat(50)),
157
+ '',
158
+ ]);
159
+ }
160
+ }
161
+ exports.DeviceSimulator = DeviceSimulator;
162
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/simulator/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LocalServer = void 0;
4
+ /* eslint-disable import/prefer-default-export */
5
+ const http_1 = require("http");
6
+ const events_1 = require("events");
7
+ const socket_io_1 = require("socket.io");
8
+ class LocalServer extends events_1.EventEmitter {
9
+ constructor(port = 55000) {
10
+ super();
11
+ this.httpServer = null;
12
+ this.io = null;
13
+ this.router = null;
14
+ this.connectedClients = new Map();
15
+ this.eventDebounceTimers = new Map();
16
+ this.port = port;
17
+ }
18
+ getIO() {
19
+ return this.io;
20
+ }
21
+ setRouter(router) {
22
+ this.router = router;
23
+ }
24
+ start() {
25
+ this.httpServer = (0, http_1.createServer)((_req, res) => {
26
+ res.writeHead(200, { 'Content-Type': 'application/json' });
27
+ res.end(JSON.stringify({ status: 'ok', simulator: true }));
28
+ });
29
+ this.io = new socket_io_1.Server(this.httpServer, {
30
+ cors: { origin: '*' },
31
+ });
32
+ this.io.on('connection', (socket) => {
33
+ this.setupSocketHandlers(socket);
34
+ });
35
+ return new Promise((resolve, reject) => {
36
+ this.httpServer.listen(this.port, () => {
37
+ resolve();
38
+ });
39
+ this.httpServer.on('error', reject);
40
+ });
41
+ }
42
+ setupSocketHandlers(socket) {
43
+ const auth = (socket.handshake.auth || {});
44
+ const twinId = auth.instanceId || auth.moduleName;
45
+ // Generic channel for CLI utility calls (no twin ID needed)
46
+ socket.on('simulator', async (payload, callback) => {
47
+ if (this.router) {
48
+ await this.router.handleMessage(socket, '', payload, callback);
49
+ }
50
+ });
51
+ socket.on('ping', (data) => {
52
+ setTimeout(() => {
53
+ socket.emit('pong', { count: (data?.count || 0) + 1 });
54
+ }, 100);
55
+ });
56
+ // App connections provide a twin ID via auth — set up twin-specific handlers
57
+ if (!twinId)
58
+ return;
59
+ if (!this.connectedClients.has(twinId)) {
60
+ this.connectedClients.set(twinId, []);
61
+ }
62
+ this.connectedClients.get(twinId).push(socket);
63
+ this.emitDebounced(twinId);
64
+ socket.join(twinId);
65
+ socket.on('disconnect', () => {
66
+ const sockets = this.connectedClients.get(twinId);
67
+ if (sockets) {
68
+ const idx = sockets.indexOf(socket);
69
+ if (idx !== -1)
70
+ sockets.splice(idx, 1);
71
+ if (sockets.length === 0)
72
+ this.connectedClients.delete(twinId);
73
+ }
74
+ this.emitDebounced(twinId);
75
+ });
76
+ // Hub-client protocol: client emits on its own twin ID channel
77
+ socket.on(twinId, async (payload, callback) => {
78
+ if (this.router) {
79
+ await this.router.handleMessage(socket, twinId, payload, callback);
80
+ }
81
+ });
82
+ socket.on('reconnect', () => {
83
+ socket.join(twinId);
84
+ });
85
+ socket.on('getDeviceTwin', (callback) => {
86
+ if (this.router) {
87
+ const result = this.router.handleLocalMethod('getDeviceInstance', {});
88
+ if (result && typeof result.then === 'function') {
89
+ result.then((r) => callback(r?.twin || {}));
90
+ }
91
+ else {
92
+ callback(result?.twin || {});
93
+ }
94
+ }
95
+ });
96
+ }
97
+ stop() {
98
+ return new Promise((resolve) => {
99
+ if (this.io) {
100
+ this.io.disconnectSockets(true);
101
+ this.io.close();
102
+ }
103
+ if (this.httpServer) {
104
+ this.httpServer.close(() => resolve());
105
+ }
106
+ else {
107
+ resolve();
108
+ }
109
+ });
110
+ }
111
+ getConnectedClients() {
112
+ return Array.from(this.connectedClients.keys());
113
+ }
114
+ /**
115
+ * Debounce connect/disconnect events per twin ID.
116
+ *
117
+ * Hub-client's getPhyHubSocket() uses a two-phase connection strategy:
118
+ * 1. PROBE — connects with reconnectionAttempts:1 to discover which URL works
119
+ * (tries localhost:55000, phyos:55500, phyhub.eu.omborigrid.net in order)
120
+ * 2. DISCONNECT — kills the probe socket, extracts the winning URL from socket.io.uri
121
+ * 3. RECONNECT — creates a new socket to the same URL with default (production) settings
122
+ *
123
+ * In simulator mode this is redundant (URL is always localhost:55000) but hub-client
124
+ * doesn't know that — it runs the same code path as production. The result is 3 rapid
125
+ * socket events: connect → disconnect → connect, all within milliseconds.
126
+ *
127
+ * Additionally, Vite HMR can cause the browser to re-initialize the app, triggering
128
+ * the entire probe cycle again.
129
+ *
130
+ * We debounce per twin ID so that after the flurry settles (500ms), we emit a single
131
+ * event reflecting the final state — either instanceConnected or instanceDisconnected.
132
+ */
133
+ emitDebounced(twinId) {
134
+ const existing = this.eventDebounceTimers.get(twinId);
135
+ if (existing)
136
+ clearTimeout(existing);
137
+ const timer = setTimeout(() => {
138
+ this.eventDebounceTimers.delete(twinId);
139
+ const isConnected = this.connectedClients.has(twinId);
140
+ const event = isConnected ? 'instanceConnected' : 'instanceDisconnected';
141
+ this.emit(event, { twinId, activeCount: this.connectedClients.size });
142
+ }, 500);
143
+ this.eventDebounceTimers.set(twinId, timer);
144
+ }
145
+ }
146
+ exports.LocalServer = LocalServer;
147
+ //# sourceMappingURL=local-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-server.js","sourceRoot":"","sources":["../../src/simulator/local-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.simulatorLog = exports.SimulatorLogger = void 0;
7
+ /* eslint-disable import/prefer-default-export */
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ function timestamp() {
10
+ const now = new Date();
11
+ const h = String(now.getHours()).padStart(2, '0');
12
+ const m = String(now.getMinutes()).padStart(2, '0');
13
+ const s = String(now.getSeconds()).padStart(2, '0');
14
+ return chalk_1.default.dim(`[${h}:${m}:${s}]`);
15
+ }
16
+ class SimulatorLogger {
17
+ log(message) {
18
+ console.log(`${timestamp()} ${message}`);
19
+ }
20
+ info(message) {
21
+ this.log(message);
22
+ }
23
+ success(message) {
24
+ this.log(chalk_1.default.green(message));
25
+ }
26
+ warn(message) {
27
+ this.log(chalk_1.default.yellow(message));
28
+ }
29
+ error(message) {
30
+ this.log(chalk_1.default.red(message));
31
+ }
32
+ dim(message) {
33
+ this.log(chalk_1.default.dim(message));
34
+ }
35
+ printBanner(lines) {
36
+ for (const line of lines) {
37
+ console.log(line);
38
+ }
39
+ }
40
+ }
41
+ exports.SimulatorLogger = SimulatorLogger;
42
+ exports.simulatorLog = new SimulatorLogger();
43
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/simulator/logger.ts"],"names":[],"mappings":";;;;;;AAAA,iDAAiD;AACjD,kDAA0B;AAE1B,SAAS,SAAS;IAChB,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAClD,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACpD,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACpD,OAAO,eAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACvC,CAAC;AAED,MAAa,eAAe;IAC1B,GAAG,CAAC,OAAe;QACjB,OAAO,CAAC,GAAG,CAAC,GAAG,SAAS,EAAE,IAAI,OAAO,EAAE,CAAC,CAAC;IAC3C,CAAC;IAED,IAAI,CAAC,OAAe;QAClB,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACpB,CAAC;IAED,OAAO,CAAC,OAAe;QACrB,IAAI,CAAC,GAAG,CAAC,eAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;IACjC,CAAC;IAED,IAAI,CAAC,OAAe;QAClB,IAAI,CAAC,GAAG,CAAC,eAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,OAAe;QACnB,IAAI,CAAC,GAAG,CAAC,eAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/B,CAAC;IAED,GAAG,CAAC,OAAe;QACjB,IAAI,CAAC,GAAG,CAAC,eAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/B,CAAC;IAED,WAAW,CAAC,KAAe;QACzB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;CACF;AA9BD,0CA8BC;AAEY,QAAA,YAAY,GAAG,IAAI,eAAe,EAAE,CAAC"}