tirtc-devtools-cli 0.0.4 → 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.
- package/README.md +3 -1
- package/USAGE.md +152 -352
- package/dist/devtools/cli/src/config.d.ts +4 -13
- package/dist/devtools/cli/src/config.js +7 -19
- package/dist/devtools/cli/src/facade.d.ts +39 -154
- package/dist/devtools/cli/src/facade.js +9 -23
- package/dist/devtools/cli/src/guide.js +10 -9
- package/dist/devtools/cli/src/index.js +135 -215
- package/dist/devtools/cli/src/token_command.d.ts +7 -0
- package/dist/devtools/cli/src/token_command.js +139 -0
- package/package.json +1 -1
- package/vendor/app-server/bin/native/macos-arm64/credential_napi.node +0 -0
- package/vendor/app-server/bin/native/macos-arm64/runtime_host_napi.node +0 -0
- package/vendor/app-server/bin/runtime/macos-arm64/include/tirtc/trp.h +30 -17
- package/vendor/app-server/bin/runtime/macos-arm64/lib/libmatrix_runtime_audio.a +0 -0
- package/vendor/app-server/bin/runtime/macos-arm64/lib/libmatrix_runtime_credential.a +0 -0
- package/vendor/app-server/bin/runtime/macos-arm64/lib/libmatrix_runtime_facade.a +0 -0
- package/vendor/app-server/bin/runtime/macos-arm64/lib/libmatrix_runtime_foundation_http.a +0 -0
- package/vendor/app-server/bin/runtime/macos-arm64/lib/libmatrix_runtime_foundation_logging.a +0 -0
- package/vendor/app-server/bin/runtime/macos-arm64/lib/libmatrix_runtime_media.a +0 -0
- package/vendor/app-server/bin/runtime/macos-arm64/lib/libmatrix_runtime_transport.a +0 -0
- package/vendor/app-server/bin/runtime/macos-arm64/lib/libmatrix_runtime_video.a +0 -0
- package/vendor/app-server/bin/runtime/macos-arm64/manifest.txt +10 -10
- package/vendor/app-server/dist/host/HostProtocol.d.ts +7 -5
- package/vendor/app-server/dist/host/HostProtocol.js +21 -10
- package/vendor/app-server/dist/host/HostServer.d.ts +2 -2
- package/vendor/app-server/dist/host/HostServer.js +93 -35
- package/vendor/app-server/dist/host/HostState.d.ts +0 -5
- package/vendor/app-server/dist/host/HostState.js +0 -1
- package/vendor/app-server/dist/host/RuntimeAdapter.d.ts +12 -0
- package/vendor/app-server/dist/host/RuntimeAdapter.js +42 -3
- package/vendor/app-server/dist/host/RuntimeSendWorker.d.ts +4 -0
- package/vendor/app-server/dist/host/RuntimeSendWorker.js +17 -0
- package/vendor/app-server/dist/protocol/contract.d.ts +72 -14
- package/vendor/app-server/dist/protocol/contract.js +11 -8
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
const commander_1 = require("commander");
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
4
9
|
const protocol_client_1 = require("../../../app-server/protocol-client");
|
|
5
10
|
const facade_1 = require("./facade");
|
|
6
11
|
const config_1 = require("./config");
|
|
7
12
|
const guide_1 = require("./guide");
|
|
8
13
|
const session_manager_1 = require("./session_manager");
|
|
9
14
|
const media_assets_1 = require("./media_assets");
|
|
15
|
+
const token_command_1 = require("./token_command");
|
|
10
16
|
const transport_1 = require("./transport");
|
|
11
|
-
const
|
|
12
|
-
const CLI_VERSION = '0.0.4';
|
|
17
|
+
const CLI_VERSION = '0.0.6';
|
|
13
18
|
const HOST_VERSION = '1.0.0';
|
|
14
19
|
const PROTOCOL_VERSION = '1.0.0';
|
|
15
20
|
if (process.argv.includes('--version') || process.argv.includes('-V')) {
|
|
@@ -101,57 +106,91 @@ function toResultObject(result) {
|
|
|
101
106
|
}
|
|
102
107
|
return { result };
|
|
103
108
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (Number.isFinite(videoStreamId)) {
|
|
134
|
-
const stream = await client.sendRequest('stream/sendStart', {
|
|
135
|
-
streamId: videoStreamId,
|
|
136
|
-
media: 'video',
|
|
137
|
-
source: {
|
|
138
|
-
mode: 'local_assets',
|
|
139
|
-
local_assets: {
|
|
140
|
-
assets_dir: assetsDir,
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
}, 180_000);
|
|
144
|
-
sendStreams.push({
|
|
145
|
-
streamId: videoStreamId,
|
|
146
|
-
assetsDir,
|
|
147
|
-
media: 'video',
|
|
148
|
-
stream,
|
|
149
|
-
});
|
|
150
|
-
}
|
|
109
|
+
function requireConfigPath(configPath) {
|
|
110
|
+
if (!configPath || configPath.trim().length === 0) {
|
|
111
|
+
throw new Error('server-only service start requires --config <path>');
|
|
112
|
+
}
|
|
113
|
+
return configPath;
|
|
114
|
+
}
|
|
115
|
+
function validateServerConfig(config) {
|
|
116
|
+
const server = config.server;
|
|
117
|
+
const license = server?.license?.trim() ?? '';
|
|
118
|
+
const mp4Path = server?.mp4_path?.trim() ?? '';
|
|
119
|
+
const audioStreamIdValue = server?.audio_stream_id;
|
|
120
|
+
const videoStreamIdValue = server?.video_stream_id;
|
|
121
|
+
if (license.length === 0) {
|
|
122
|
+
throw new Error('server-only config requires [server].license');
|
|
123
|
+
}
|
|
124
|
+
if (mp4Path.length === 0) {
|
|
125
|
+
throw new Error('server-only config requires [server].mp4_path');
|
|
126
|
+
}
|
|
127
|
+
if (typeof audioStreamIdValue !== 'number') {
|
|
128
|
+
throw new Error('server-only config requires [server].audio_stream_id as non-negative integer');
|
|
129
|
+
}
|
|
130
|
+
if (!Number.isInteger(audioStreamIdValue) || audioStreamIdValue < 0) {
|
|
131
|
+
throw new Error('server-only config requires [server].audio_stream_id as non-negative integer');
|
|
132
|
+
}
|
|
133
|
+
if (typeof videoStreamIdValue !== 'number') {
|
|
134
|
+
throw new Error('server-only config requires [server].video_stream_id as non-negative integer');
|
|
135
|
+
}
|
|
136
|
+
if (!Number.isInteger(videoStreamIdValue) || videoStreamIdValue < 0) {
|
|
137
|
+
throw new Error('server-only config requires [server].video_stream_id as non-negative integer');
|
|
151
138
|
}
|
|
139
|
+
const audioStreamId = audioStreamIdValue;
|
|
140
|
+
const videoStreamId = videoStreamIdValue;
|
|
141
|
+
if (audioStreamId === videoStreamId) {
|
|
142
|
+
throw new Error('server-only config requires distinct audio_stream_id and video_stream_id');
|
|
143
|
+
}
|
|
144
|
+
const resolvedMp4Path = path_1.default.resolve(mp4Path);
|
|
145
|
+
let stats;
|
|
146
|
+
try {
|
|
147
|
+
stats = fs_1.default.statSync(resolvedMp4Path);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
throw new Error('server-only config mp4_path does not exist: ' + resolvedMp4Path);
|
|
151
|
+
}
|
|
152
|
+
if (!stats.isFile()) {
|
|
153
|
+
throw new Error('server-only config mp4_path must be a readable file: ' + resolvedMp4Path);
|
|
154
|
+
}
|
|
155
|
+
const serviceEntry = server?.service_entry?.trim();
|
|
156
|
+
return {
|
|
157
|
+
serviceEntry: serviceEntry && serviceEntry.length > 0 ? serviceEntry : undefined,
|
|
158
|
+
license,
|
|
159
|
+
mp4Path: resolvedMp4Path,
|
|
160
|
+
audioStreamId,
|
|
161
|
+
videoStreamId,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function resolvePrepareOutputRoot(config) {
|
|
165
|
+
const loggingRoot = config.logging?.root_dir?.trim();
|
|
166
|
+
if (loggingRoot && loggingRoot.length > 0) {
|
|
167
|
+
return path_1.default.resolve(loggingRoot, '..', 'prepared-assets');
|
|
168
|
+
}
|
|
169
|
+
return path_1.default.resolve(process.cwd(), '.tmp', 'tirtc-devtools-cli', 'prepared-assets');
|
|
170
|
+
}
|
|
171
|
+
async function applyConfiguredServerBootstrap(client, config) {
|
|
172
|
+
const server = validateServerConfig(config);
|
|
173
|
+
const prepared = await (0, media_assets_1.prepareMediaAssets)({
|
|
174
|
+
source: server.mp4Path,
|
|
175
|
+
outputRoot: resolvePrepareOutputRoot(config),
|
|
176
|
+
});
|
|
177
|
+
const bootstrapSendStreams = [
|
|
178
|
+
{ streamId: server.audioStreamId, media: 'audio' },
|
|
179
|
+
{ streamId: server.videoStreamId, media: 'video' },
|
|
180
|
+
].map(({ streamId, media }) => ({
|
|
181
|
+
streamId,
|
|
182
|
+
media,
|
|
183
|
+
source: {
|
|
184
|
+
mode: 'local_assets',
|
|
185
|
+
local_assets: {
|
|
186
|
+
assets_dir: prepared.assets_dir,
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
}));
|
|
152
190
|
return {
|
|
153
|
-
|
|
154
|
-
|
|
191
|
+
preparedAssetsDir: prepared.assets_dir,
|
|
192
|
+
mediaSendPolicy: 'AUTO_ON_CONNECTED',
|
|
193
|
+
bootstrapSendStreams,
|
|
155
194
|
};
|
|
156
195
|
}
|
|
157
196
|
async function runCommand(cmdName, params, options) {
|
|
@@ -166,26 +205,25 @@ async function runCommand(cmdName, params, options) {
|
|
|
166
205
|
if (!method) {
|
|
167
206
|
throw new Error('Unknown command mapping for: ' + cmdName);
|
|
168
207
|
}
|
|
169
|
-
|
|
170
|
-
const mode = String(params.mode ?? '');
|
|
171
|
-
if (mode !== 'manual' && mode !== 'auto-if-bound') {
|
|
172
|
-
throw new Error('Invalid mode, expected manual|auto-if-bound');
|
|
173
|
-
}
|
|
174
|
-
return { mode };
|
|
175
|
-
})() : params;
|
|
176
|
-
const result = await client.sendRequest(method, commandParams);
|
|
177
|
-
let resultForPrint = result;
|
|
208
|
+
let resultForPrint;
|
|
178
209
|
if (cmdName === 'service start') {
|
|
179
|
-
const autoApplied = await
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
210
|
+
const autoApplied = await applyConfiguredServerBootstrap(client, config);
|
|
211
|
+
const requestParams = {
|
|
212
|
+
...params,
|
|
213
|
+
bootstrapSendStreams: autoApplied.bootstrapSendStreams,
|
|
214
|
+
};
|
|
215
|
+
const result = await client.sendRequest(method, requestParams, 180_000);
|
|
216
|
+
const resultObject = toResultObject(result);
|
|
217
|
+
resultForPrint = {
|
|
218
|
+
...resultObject,
|
|
219
|
+
autoApplied,
|
|
220
|
+
};
|
|
221
|
+
printSuccess(options, resultForPrint, session, cmdName);
|
|
222
|
+
transport.stop();
|
|
223
|
+
return 0;
|
|
224
|
+
}
|
|
225
|
+
const result = await client.sendRequest(method, params);
|
|
226
|
+
resultForPrint = result;
|
|
189
227
|
printSuccess(options, resultForPrint, session, cmdName);
|
|
190
228
|
transport.stop();
|
|
191
229
|
return 0;
|
|
@@ -288,57 +326,11 @@ async function runMediaAssetsPrepare(source, outputRoot, outputDir, overwrite, o
|
|
|
288
326
|
return exitCode;
|
|
289
327
|
}
|
|
290
328
|
}
|
|
291
|
-
async function runTokenIssue(params, options) {
|
|
292
|
-
try {
|
|
293
|
-
const result = await (0, token_tool_1.issueTokenWithQrcode)(params);
|
|
294
|
-
if (options.json) {
|
|
295
|
-
console.log(JSON.stringify({
|
|
296
|
-
code: 0,
|
|
297
|
-
message: 'OK',
|
|
298
|
-
data: {
|
|
299
|
-
payload: result.payload,
|
|
300
|
-
payloadJson: result.payloadJson,
|
|
301
|
-
token: result.token,
|
|
302
|
-
qrCodePngPath: result.qrCodePngPath,
|
|
303
|
-
},
|
|
304
|
-
}));
|
|
305
|
-
}
|
|
306
|
-
else {
|
|
307
|
-
console.log((0, token_tool_1.formatTokenIssueConsoleOutput)(result));
|
|
308
|
-
}
|
|
309
|
-
return 0;
|
|
310
|
-
}
|
|
311
|
-
catch (error) {
|
|
312
|
-
const normalized = normalizeError(error);
|
|
313
|
-
const reasonCode = normalized.reasonCode;
|
|
314
|
-
const exitCode = facade_1.ErrorReasonCodeMapping[reasonCode] ?? 1;
|
|
315
|
-
if (options.json) {
|
|
316
|
-
console.log(JSON.stringify({
|
|
317
|
-
code: exitCode,
|
|
318
|
-
message: normalized.message,
|
|
319
|
-
data: normalized.data,
|
|
320
|
-
}));
|
|
321
|
-
}
|
|
322
|
-
else {
|
|
323
|
-
console.error('Error (' + reasonCode + '): ' + normalized.message);
|
|
324
|
-
}
|
|
325
|
-
return exitCode;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
329
|
function runAndExit(promise) {
|
|
329
330
|
promise.then((code) => {
|
|
330
331
|
process.exit(code);
|
|
331
332
|
});
|
|
332
333
|
}
|
|
333
|
-
function mustParseJsonPayload(input) {
|
|
334
|
-
try {
|
|
335
|
-
const parsed = JSON.parse(input);
|
|
336
|
-
return JSON.stringify(parsed);
|
|
337
|
-
}
|
|
338
|
-
catch {
|
|
339
|
-
throw new Error('payloadJson must be valid JSON text');
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
334
|
program.name('tirtc-devtools-cli')
|
|
343
335
|
.description('TiRTC DevTools CLI')
|
|
344
336
|
.option('--config <path>', '配置文件路径(TOML)')
|
|
@@ -483,22 +475,18 @@ mediaAssets.command('prepare')
|
|
|
483
475
|
.action((commandOptions) => {
|
|
484
476
|
runAndExit(runMediaAssetsPrepare(commandOptions.source, commandOptions.outputRoot, commandOptions.outputDir, commandOptions.overwrite === true, getCliOptions()));
|
|
485
477
|
});
|
|
486
|
-
// Backward-compatible alias.
|
|
487
478
|
const hostConfig = host.command('config').description('配置辅助(兼容别名,推荐用顶层 config)');
|
|
488
479
|
hostConfig.command('init [outputPath]').description('生成配置模板(等价于 config init)').action((outputPath) => {
|
|
489
480
|
handleConfigInit(outputPath);
|
|
490
481
|
});
|
|
491
482
|
const service = program.command('service').description('服务端能力:启动/停止服务,等待远端接入');
|
|
492
|
-
service.command('start
|
|
493
|
-
.description('
|
|
494
|
-
.action((
|
|
495
|
-
const
|
|
496
|
-
const
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
throw new Error('service start requires license (service_entry can be omitted)');
|
|
500
|
-
}
|
|
501
|
-
runAndExit(runCommand('service start', { serviceEntry: finalServiceEntry, license: finalLicense, timeoutMs: 5000 }, getCliOptions()));
|
|
483
|
+
service.command('start')
|
|
484
|
+
.description('启动 server-only 会话(必须显式提供 --config)')
|
|
485
|
+
.action(() => {
|
|
486
|
+
const options = getCliOptions();
|
|
487
|
+
const config = (0, config_1.loadConfig)(requireConfigPath(options.config));
|
|
488
|
+
const server = validateServerConfig(config);
|
|
489
|
+
runAndExit(runCommand('service start', { serviceEntry: server.serviceEntry, license: server.license, timeoutMs: 5000 }, options));
|
|
502
490
|
});
|
|
503
491
|
service.command('stop').description('停止服务端监听').action(() => {
|
|
504
492
|
runAndExit(runCommand('service stop', {}, getCliOptions()));
|
|
@@ -509,9 +497,10 @@ connection.command('show').description('查看连接状态').action(() => {
|
|
|
509
497
|
});
|
|
510
498
|
connection.command('connect [service_entry] <peer_id> [token]')
|
|
511
499
|
.description('作为客户端连接远端(总是新建 session;不传 token 时自动签发)')
|
|
512
|
-
.action((
|
|
500
|
+
.action((serviceEntry, peerId, token) => {
|
|
513
501
|
const config = (0, config_1.loadConfig)(getCliOptions().config);
|
|
514
|
-
const finalServiceEntry = [
|
|
502
|
+
const finalServiceEntry = [serviceEntry, config.connection?.service_entry, config.server?.service_entry]
|
|
503
|
+
.find((value) => typeof value === 'string' && value.trim().length > 0);
|
|
515
504
|
const configuredToken = config.connection?.token?.trim();
|
|
516
505
|
const manualToken = (token ?? configuredToken)?.trim();
|
|
517
506
|
const autoTokenCfg = config.connection?.auto_token;
|
|
@@ -521,7 +510,7 @@ connection.command('connect [service_entry] <peer_id> [token]')
|
|
|
521
510
|
}
|
|
522
511
|
const params = {
|
|
523
512
|
serviceEntry: finalServiceEntry,
|
|
524
|
-
peerId
|
|
513
|
+
peerId,
|
|
525
514
|
timeoutMs: 5000,
|
|
526
515
|
};
|
|
527
516
|
if (manualToken && manualToken.length > 0) {
|
|
@@ -534,7 +523,7 @@ connection.command('connect [service_entry] <peer_id> [token]')
|
|
|
534
523
|
openapiEntry: autoTokenCfg?.openapi_entry,
|
|
535
524
|
accessId: autoTokenCfg?.access_id,
|
|
536
525
|
secretKey: autoTokenCfg?.secret_key,
|
|
537
|
-
localId: autoTokenCfg?.local_id ??
|
|
526
|
+
localId: autoTokenCfg?.local_id ?? peerId,
|
|
538
527
|
userTtlSeconds: autoTokenCfg?.user_ttl_seconds,
|
|
539
528
|
channelTtlSeconds: autoTokenCfg?.channel_ttl_seconds,
|
|
540
529
|
};
|
|
@@ -544,64 +533,22 @@ connection.command('connect [service_entry] <peer_id> [token]')
|
|
|
544
533
|
connection.command('disconnect').description('断开当前连接').action(() => {
|
|
545
534
|
runAndExit(runCommand('connection disconnect', {}, getCliOptions()));
|
|
546
535
|
});
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
.description('使用 access/secret/local/peer 签发 token;输出摘要、payload JSON、token 和二维码 PNG 路径')
|
|
550
|
-
.option('--openapi-entry <url>', '可选 openapi entry;留空时走 runtime 默认值')
|
|
551
|
-
.option('--service-entry <entry>', '可选 service entry;用于组合连接 payload 与二维码 PNG')
|
|
552
|
-
.option('--user-ttl-seconds <seconds>', '可选 user token ttl(秒)')
|
|
553
|
-
.option('--channel-ttl-seconds <seconds>', '可选 channel token ttl(秒)')
|
|
554
|
-
.option('--qr-error-correction-level <level>', '二维码纠错级别:L/M/Q/H;默认 M')
|
|
555
|
-
.option('--ascii-max-columns <columns>', 'ASCII 二维码最大宽度;不传时优先读取当前终端宽度或 COLUMNS')
|
|
556
|
-
.action((access_id, secret_key, local_id, peer_id, commandOptions) => {
|
|
557
|
-
const parsePositiveInt = (name, raw) => {
|
|
558
|
-
if (raw === undefined) {
|
|
559
|
-
return undefined;
|
|
560
|
-
}
|
|
561
|
-
const parsed = Number.parseInt(raw, 10);
|
|
562
|
-
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
563
|
-
throw new Error(name + ' must be a positive integer');
|
|
564
|
-
}
|
|
565
|
-
return parsed;
|
|
566
|
-
};
|
|
567
|
-
const parseQrErrorCorrectionLevel = (raw) => {
|
|
568
|
-
if (raw === undefined) {
|
|
569
|
-
return undefined;
|
|
570
|
-
}
|
|
571
|
-
const normalized = raw.trim().toUpperCase();
|
|
572
|
-
if (normalized !== 'L' && normalized !== 'M' && normalized !== 'Q' && normalized !== 'H') {
|
|
573
|
-
throw new Error('qr-error-correction-level must be one of: L, M, Q, H');
|
|
574
|
-
}
|
|
575
|
-
return normalized;
|
|
576
|
-
};
|
|
577
|
-
runAndExit(runTokenIssue({
|
|
578
|
-
accessId: access_id,
|
|
579
|
-
secretKey: secret_key,
|
|
580
|
-
localId: local_id,
|
|
581
|
-
peerId: peer_id,
|
|
582
|
-
openapiEntry: commandOptions.openapiEntry,
|
|
583
|
-
serviceEntry: commandOptions.serviceEntry,
|
|
584
|
-
userTtlSeconds: parsePositiveInt('user-ttl-seconds', commandOptions.userTtlSeconds),
|
|
585
|
-
channelTtlSeconds: parsePositiveInt('channel-ttl-seconds', commandOptions.channelTtlSeconds),
|
|
586
|
-
qrErrorCorrectionLevel: parseQrErrorCorrectionLevel(commandOptions.qrErrorCorrectionLevel),
|
|
587
|
-
asciiMaxColumns: parsePositiveInt('ascii-max-columns', commandOptions.asciiMaxColumns),
|
|
588
|
-
}, getCliOptions()));
|
|
589
|
-
});
|
|
590
|
-
const stream = program.command('stream').description('流控制:发送/接收/请求策略');
|
|
536
|
+
(0, token_command_1.registerTokenCommands)(program, getCliOptions, runAndExit);
|
|
537
|
+
const stream = program.command('stream').description('流控制:发送/接收/消息');
|
|
591
538
|
stream.command('list').description('查看流快照列表').action(() => {
|
|
592
539
|
runAndExit(runCommand('stream list', {}, getCliOptions()));
|
|
593
540
|
});
|
|
594
541
|
const streamSend = stream.command('send').description('发送流:绑定 prepared local assets 并发起上行');
|
|
595
542
|
streamSend.command('start <streamId> <media> <assets_dir>')
|
|
596
543
|
.description('开始发送流(绑定 source.local_assets 并触发上行)')
|
|
597
|
-
.action((streamId, media,
|
|
544
|
+
.action((streamId, media, assetsDir) => {
|
|
598
545
|
runAndExit(runCommand('stream send start', {
|
|
599
546
|
streamId: Number.parseInt(streamId, 10),
|
|
600
547
|
media,
|
|
601
548
|
source: {
|
|
602
549
|
mode: 'local_assets',
|
|
603
550
|
local_assets: {
|
|
604
|
-
assets_dir,
|
|
551
|
+
assets_dir: assetsDir,
|
|
605
552
|
},
|
|
606
553
|
},
|
|
607
554
|
}, getCliOptions()));
|
|
@@ -623,18 +570,13 @@ streamReceive.command('stop <streamId>')
|
|
|
623
570
|
.action((streamId) => {
|
|
624
571
|
runAndExit(runCommand('stream receive stop', { streamId: Number.parseInt(streamId, 10) }, getCliOptions()));
|
|
625
572
|
});
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
});
|
|
630
|
-
streamRequestPolicy.command('set <mode>')
|
|
631
|
-
.description('设置远端请求策略(manual|auto-if-bound)')
|
|
632
|
-
.action((mode) => {
|
|
633
|
-
runAndExit(runCommand('stream request-policy set', { mode }, getCliOptions()));
|
|
573
|
+
stream.command('message send <streamId> <payload>')
|
|
574
|
+
.description('发送 UTF-8 字符串型 stream message')
|
|
575
|
+
.action((streamId, payload) => {
|
|
576
|
+
runAndExit(runCommand('stream message send', { streamId: Number.parseInt(streamId, 10), payload }, getCliOptions()));
|
|
634
577
|
});
|
|
635
578
|
const output = program.command('output').description('输出挂载:文件等消费者 attach/detach');
|
|
636
|
-
output
|
|
637
|
-
.command('attach <streamId> <consumer> <mediaView> <format> <delivery> [targetPath] [maxFiles]')
|
|
579
|
+
output.command('attach <streamId> <consumer> <mediaView> <format> <delivery> [targetPath] [maxFiles]')
|
|
638
580
|
.description('挂载输出消费者(如 file_sink)')
|
|
639
581
|
.action((streamId, consumer, mediaView, format, delivery, targetPath, maxFiles) => {
|
|
640
582
|
const parsed = facade_1.OutputAttachCliParamsSchema.safeParse({
|
|
@@ -665,7 +607,7 @@ output.command('detach <outputId>').description('卸载输出消费者').action(
|
|
|
665
607
|
});
|
|
666
608
|
const command = program.command('command').description('命令通道:发送命令与事件跟踪');
|
|
667
609
|
command.command('send <commandId> <kind> <payloadEncoding> <payload>')
|
|
668
|
-
.description('
|
|
610
|
+
.description('发送字符串型命令并等待响应')
|
|
669
611
|
.action((commandId, kind, payloadEncoding, payload) => {
|
|
670
612
|
runAndExit(runCommand('command send', {
|
|
671
613
|
commandId: Number.parseInt(commandId, 10),
|
|
@@ -676,28 +618,6 @@ command.command('send <commandId> <kind> <payloadEncoding> <payload>')
|
|
|
676
618
|
timeoutMs: 5000,
|
|
677
619
|
}, getCliOptions()));
|
|
678
620
|
});
|
|
679
|
-
command.command('send-json <commandId> <kind> <payloadJson>')
|
|
680
|
-
.description('发送 JSON 命令并等待响应(JSON-first)')
|
|
681
|
-
.action((commandId, kind, payloadJson) => {
|
|
682
|
-
const parsed = facade_1.CommandSendJsonParamsSchema.safeParse({
|
|
683
|
-
commandId: Number.parseInt(commandId, 10),
|
|
684
|
-
kind,
|
|
685
|
-
payloadJson,
|
|
686
|
-
});
|
|
687
|
-
if (!parsed.success) {
|
|
688
|
-
const msg = parsed.error.issues.map((issue) => issue.message).join('; ');
|
|
689
|
-
throw new Error('invalid command send-json params: ' + msg);
|
|
690
|
-
}
|
|
691
|
-
const payload = mustParseJsonPayload(payloadJson);
|
|
692
|
-
runAndExit(runCommand('command send', {
|
|
693
|
-
commandId: parsed.data.commandId,
|
|
694
|
-
kind: parsed.data.kind,
|
|
695
|
-
payloadEncoding: 'json',
|
|
696
|
-
payload,
|
|
697
|
-
awaitResponse: true,
|
|
698
|
-
timeoutMs: 5000,
|
|
699
|
-
}, getCliOptions()));
|
|
700
|
-
});
|
|
701
621
|
command.command('tail').description('持续监听命令相关事件').action(() => {
|
|
702
622
|
runAndExit(tailEvents('command tail', ['command'], getCliOptions()));
|
|
703
623
|
});
|
|
@@ -705,14 +625,14 @@ const debug = program.command('debug').description('调试工具:连接 bootst
|
|
|
705
625
|
const debugBootstrap = debug.command('bootstrap').description('连接 bootstrap(不在 App Server 侧签发 token)');
|
|
706
626
|
debugBootstrap.command('qrcode <access_id> <secret_key> <peer_id> [service_entry]')
|
|
707
627
|
.description('生成客户端连接 bootstrap 二维码(payload 包含 access_id/secret_key/peer_id)')
|
|
708
|
-
.action((
|
|
628
|
+
.action((accessId, secretKey, peerId, serviceEntry) => {
|
|
709
629
|
const payload = JSON.stringify({
|
|
710
630
|
version: 1,
|
|
711
631
|
type: 'tirtc-connect-bootstrap',
|
|
712
|
-
access_id,
|
|
713
|
-
secret_key,
|
|
714
|
-
peer_id,
|
|
715
|
-
service_entry:
|
|
632
|
+
access_id: accessId,
|
|
633
|
+
secret_key: secretKey,
|
|
634
|
+
peer_id: peerId,
|
|
635
|
+
service_entry: serviceEntry ?? '',
|
|
716
636
|
generated_at: new Date().toISOString(),
|
|
717
637
|
});
|
|
718
638
|
runAndExit(runCommand('debug bootstrap qrcode', { payload, outputStem: 'connect-bootstrap' }, getCliOptions()));
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
export type CliOptions = {
|
|
3
|
+
config?: string;
|
|
4
|
+
json?: boolean;
|
|
5
|
+
session?: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function registerTokenCommands(program: Command, getCliOptions: () => CliOptions, runAndExit: (promise: Promise<number>) => void): void;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerTokenCommands = registerTokenCommands;
|
|
4
|
+
const facade_1 = require("./facade");
|
|
5
|
+
const token_tool_1 = require("./token_tool");
|
|
6
|
+
const kTokenIssueAccessIdEnvVar = 'TIRTC_CONN_ACCESS_ID';
|
|
7
|
+
const kTokenIssueSecretKeyEnvVar = 'TIRTC_CONN_SECRET_KEY';
|
|
8
|
+
function normalizeTokenCommandError(error) {
|
|
9
|
+
if (error instanceof Error) {
|
|
10
|
+
return {
|
|
11
|
+
reasonCode: 'internal_error',
|
|
12
|
+
message: error.message,
|
|
13
|
+
data: undefined,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
if (typeof error === 'object' && error !== null) {
|
|
17
|
+
const typed = error;
|
|
18
|
+
return {
|
|
19
|
+
reasonCode: typed.reasonCode ?? 'internal_error',
|
|
20
|
+
message: typed.message ?? 'Internal error',
|
|
21
|
+
data: typed.data,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
reasonCode: 'internal_error',
|
|
26
|
+
message: 'Internal error',
|
|
27
|
+
data: undefined,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function printTokenCommandError(error, options) {
|
|
31
|
+
const normalized = normalizeTokenCommandError(error);
|
|
32
|
+
const reasonCode = normalized.reasonCode;
|
|
33
|
+
const exitCode = facade_1.ErrorReasonCodeMapping[reasonCode] ?? 1;
|
|
34
|
+
if (options.json) {
|
|
35
|
+
console.log(JSON.stringify({
|
|
36
|
+
code: exitCode,
|
|
37
|
+
message: normalized.message,
|
|
38
|
+
data: normalized.data,
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
console.error('Error (' + reasonCode + '): ' + normalized.message);
|
|
43
|
+
}
|
|
44
|
+
return exitCode;
|
|
45
|
+
}
|
|
46
|
+
async function runTokenIssue(params, options) {
|
|
47
|
+
try {
|
|
48
|
+
const result = await (0, token_tool_1.issueTokenWithQrcode)(params);
|
|
49
|
+
if (options.json) {
|
|
50
|
+
console.log(JSON.stringify({
|
|
51
|
+
code: 0,
|
|
52
|
+
message: 'OK',
|
|
53
|
+
data: {
|
|
54
|
+
payload: result.payload,
|
|
55
|
+
payloadJson: result.payloadJson,
|
|
56
|
+
token: result.token,
|
|
57
|
+
qrCodePngPath: result.qrCodePngPath,
|
|
58
|
+
},
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
console.log((0, token_tool_1.formatTokenIssueConsoleOutput)(result));
|
|
63
|
+
}
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
return printTokenCommandError(error, options);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function resolveTokenIssueCredential(explicitValue, envVarName, optionName) {
|
|
71
|
+
const normalizedExplicit = explicitValue?.trim();
|
|
72
|
+
if (normalizedExplicit) {
|
|
73
|
+
return normalizedExplicit;
|
|
74
|
+
}
|
|
75
|
+
const normalizedEnv = process.env[envVarName]?.trim();
|
|
76
|
+
if (normalizedEnv) {
|
|
77
|
+
return normalizedEnv;
|
|
78
|
+
}
|
|
79
|
+
throw new Error('missing credential: set environment variable ' + envVarName +
|
|
80
|
+
' or pass ' + optionName + ' explicitly');
|
|
81
|
+
}
|
|
82
|
+
async function runTokenIssueFromCli(peerId, commandOptions, options) {
|
|
83
|
+
const parsePositiveInt = (name, raw) => {
|
|
84
|
+
if (raw === undefined) {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
const parsed = Number.parseInt(raw, 10);
|
|
88
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
89
|
+
throw new Error(name + ' must be a positive integer');
|
|
90
|
+
}
|
|
91
|
+
return parsed;
|
|
92
|
+
};
|
|
93
|
+
const parseQrErrorCorrectionLevel = (raw) => {
|
|
94
|
+
if (raw === undefined) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
const normalized = raw.trim().toUpperCase();
|
|
98
|
+
if (normalized !== 'L' && normalized !== 'M' && normalized !== 'Q' && normalized !== 'H') {
|
|
99
|
+
throw new Error('qr-error-correction-level must be one of: L, M, Q, H');
|
|
100
|
+
}
|
|
101
|
+
return normalized;
|
|
102
|
+
};
|
|
103
|
+
try {
|
|
104
|
+
const accessId = resolveTokenIssueCredential(commandOptions.accessId, kTokenIssueAccessIdEnvVar, '--access-id');
|
|
105
|
+
const secretKey = resolveTokenIssueCredential(commandOptions.secretKey, kTokenIssueSecretKeyEnvVar, '--secret-key');
|
|
106
|
+
return await runTokenIssue({
|
|
107
|
+
accessId,
|
|
108
|
+
secretKey,
|
|
109
|
+
localId: commandOptions.localId?.trim() || peerId,
|
|
110
|
+
peerId,
|
|
111
|
+
openapiEntry: commandOptions.openapiEntry,
|
|
112
|
+
serviceEntry: commandOptions.serviceEntry,
|
|
113
|
+
userTtlSeconds: parsePositiveInt('user-ttl-seconds', commandOptions.userTtlSeconds),
|
|
114
|
+
channelTtlSeconds: parsePositiveInt('channel-ttl-seconds', commandOptions.channelTtlSeconds),
|
|
115
|
+
qrErrorCorrectionLevel: parseQrErrorCorrectionLevel(commandOptions.qrErrorCorrectionLevel),
|
|
116
|
+
asciiMaxColumns: parsePositiveInt('ascii-max-columns', commandOptions.asciiMaxColumns),
|
|
117
|
+
}, options);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
return printTokenCommandError(error, options);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function registerTokenCommands(program, getCliOptions, runAndExit) {
|
|
124
|
+
const token = program.command('token').description('Token 工具:签发 token,并输出可直接使用的 JSON 与本地二维码 PNG');
|
|
125
|
+
token.command('issue <peer_id>')
|
|
126
|
+
.description('默认从环境变量读取 access/secret,并以 peer_id 作为默认 local_id 来签发 token')
|
|
127
|
+
.option('--access-id <accessId>', '显式 access id;不传时读取 ' + kTokenIssueAccessIdEnvVar)
|
|
128
|
+
.option('--secret-key <secretKey>', '显式 secret key;不传时读取 ' + kTokenIssueSecretKeyEnvVar)
|
|
129
|
+
.option('--local-id <localId>', '显式 local id;不传时默认使用 peer_id')
|
|
130
|
+
.option('--openapi-entry <url>', '可选 openapi entry;留空时走 runtime 默认值')
|
|
131
|
+
.option('--service-entry <entry>', '可选 service entry;用于组合连接 payload 与二维码 PNG')
|
|
132
|
+
.option('--user-ttl-seconds <seconds>', '可选 user token ttl(秒)')
|
|
133
|
+
.option('--channel-ttl-seconds <seconds>', '可选 channel token ttl(秒)')
|
|
134
|
+
.option('--qr-error-correction-level <level>', '二维码纠错级别:L/M/Q/H;默认 M')
|
|
135
|
+
.option('--ascii-max-columns <columns>', 'ASCII 二维码最大宽度;不传时优先读取当前终端宽度或 COLUMNS')
|
|
136
|
+
.action((peerId, commandOptions) => {
|
|
137
|
+
runAndExit(runTokenIssueFromCli(peerId, commandOptions, getCliOptions()));
|
|
138
|
+
});
|
|
139
|
+
}
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|