rol-websocket-channel 1.4.9 → 1.5.1
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.
|
@@ -69,28 +69,55 @@ async function exchangePairKey(key, endpoint, authOverride, existingMqttUrl) {
|
|
|
69
69
|
}
|
|
70
70
|
function normalizePairingPayload(raw, endpoint, existingMqttUrl) {
|
|
71
71
|
const root = unwrapPayload(raw);
|
|
72
|
-
const pluginId = pickString(root.pluginId) ?? DEFAULT_PLUGIN_ID;
|
|
73
|
-
const apiCoreBotValue =
|
|
74
|
-
const channelValue =
|
|
72
|
+
const pluginId = pickString(root.pluginId) ?? pickString(root.plugin_id) ?? DEFAULT_PLUGIN_ID;
|
|
73
|
+
const apiCoreBotValue = pickRecord(root.apiCoreBot, root.api_core_bot);
|
|
74
|
+
const channelValue = pickRecord(root.channel);
|
|
75
|
+
const channelConfigValue = pickRecord(channelValue.config);
|
|
75
76
|
const apiCoreBotAuthToken = pickString(apiCoreBotValue.authToken)
|
|
77
|
+
?? pickString(apiCoreBotValue.auth_token)
|
|
76
78
|
?? pickString(root.authToken)
|
|
79
|
+
?? pickString(root.auth_token)
|
|
77
80
|
?? pickString(root.token);
|
|
78
|
-
const rawValue = pickString(root.value)
|
|
81
|
+
const rawValue = pickString(root.value)
|
|
82
|
+
?? pickString(root.rawValue)
|
|
83
|
+
?? pickString(root.raw_value);
|
|
79
84
|
const mqttUrl = pickString(channelValue.mqttUrl)
|
|
85
|
+
?? pickString(channelValue.mqtt_url)
|
|
86
|
+
?? pickString(channelConfigValue.mqttUrl)
|
|
87
|
+
?? pickString(channelConfigValue.mqtt_url)
|
|
80
88
|
?? pickString(root.mqttUrl)
|
|
89
|
+
?? pickString(root.mqtt_url)
|
|
81
90
|
?? existingMqttUrl
|
|
82
91
|
?? null;
|
|
83
92
|
if (!mqttUrl) {
|
|
84
|
-
throwPairingError('PAIR_CHANNEL_CONFIG_INVALID', 'mqttUrl is missing from pairing payload'
|
|
93
|
+
throwPairingError('PAIR_CHANNEL_CONFIG_INVALID', 'mqttUrl is missing from pairing payload', {
|
|
94
|
+
endpoint,
|
|
95
|
+
rootKeys: Object.keys(root),
|
|
96
|
+
channelKeys: Object.keys(channelValue),
|
|
97
|
+
channelConfigKeys: Object.keys(channelConfigValue),
|
|
98
|
+
hasExistingMqttUrl: Boolean(existingMqttUrl)
|
|
99
|
+
});
|
|
85
100
|
}
|
|
86
101
|
const mqttTopic = pickString(channelValue.mqttTopic)
|
|
102
|
+
?? pickString(channelValue.mqtt_topic)
|
|
103
|
+
?? pickString(channelConfigValue.mqttTopic)
|
|
104
|
+
?? pickString(channelConfigValue.mqtt_topic)
|
|
87
105
|
?? pickString(root.mqttTopic)
|
|
106
|
+
?? pickString(root.mqtt_topic)
|
|
88
107
|
?? rawValue
|
|
89
108
|
?? 'announcement/tester';
|
|
90
109
|
const groupPolicy = normalizeGroupPolicy(pickString(channelValue.groupPolicy)
|
|
110
|
+
?? pickString(channelValue.group_policy)
|
|
91
111
|
?? pickString(channelValue.dmPolicy)
|
|
112
|
+
?? pickString(channelValue.dm_policy)
|
|
113
|
+
?? pickString(channelConfigValue.groupPolicy)
|
|
114
|
+
?? pickString(channelConfigValue.group_policy)
|
|
115
|
+
?? pickString(channelConfigValue.dmPolicy)
|
|
116
|
+
?? pickString(channelConfigValue.dm_policy)
|
|
92
117
|
?? pickString(root.groupPolicy)
|
|
118
|
+
?? pickString(root.group_policy)
|
|
93
119
|
?? pickString(root.dmPolicy)
|
|
120
|
+
?? pickString(root.dm_policy)
|
|
94
121
|
?? 'pairing');
|
|
95
122
|
return {
|
|
96
123
|
pluginId,
|
|
@@ -98,7 +125,7 @@ function normalizePairingPayload(raw, endpoint, existingMqttUrl) {
|
|
|
98
125
|
...(rawValue ? { rawValue } : {})
|
|
99
126
|
},
|
|
100
127
|
apiCoreBot: {
|
|
101
|
-
baseUrl: pickString(apiCoreBotValue.baseUrl) ?? inferBaseUrl(endpoint),
|
|
128
|
+
baseUrl: pickString(apiCoreBotValue.baseUrl) ?? pickString(apiCoreBotValue.base_url) ?? inferBaseUrl(endpoint),
|
|
102
129
|
...(apiCoreBotAuthToken ? { authToken: apiCoreBotAuthToken } : {})
|
|
103
130
|
},
|
|
104
131
|
channel: {
|
|
@@ -260,9 +287,20 @@ function pickString(value) {
|
|
|
260
287
|
const trimmed = value.trim();
|
|
261
288
|
return trimmed.length > 0 ? trimmed : null;
|
|
262
289
|
}
|
|
290
|
+
function pickRecord(...values) {
|
|
291
|
+
for (const value of values) {
|
|
292
|
+
if (isRecord(value)) {
|
|
293
|
+
return value;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return {};
|
|
297
|
+
}
|
|
263
298
|
function isRecord(value) {
|
|
264
299
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
265
300
|
}
|
|
266
|
-
function throwPairingError(code, message) {
|
|
267
|
-
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, message, {
|
|
301
|
+
function throwPairingError(code, message, debug) {
|
|
302
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, message, {
|
|
303
|
+
code,
|
|
304
|
+
...(debug ? { debug } : {})
|
|
305
|
+
});
|
|
268
306
|
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { afterEach, describe, test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import http from 'node:http';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { pairWithKey } from './pairing.js';
|
|
8
|
+
import { JsonRpcException } from '../jsonrpc.js';
|
|
9
|
+
const tempDirs = [];
|
|
10
|
+
const servers = [];
|
|
11
|
+
afterEach(async () => {
|
|
12
|
+
while (servers.length > 0) {
|
|
13
|
+
const server = servers.pop();
|
|
14
|
+
if (server) {
|
|
15
|
+
await new Promise((resolve, reject) => {
|
|
16
|
+
server.close((error) => error ? reject(error) : resolve());
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
while (tempDirs.length > 0) {
|
|
21
|
+
const dir = tempDirs.pop();
|
|
22
|
+
if (dir) {
|
|
23
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
describe('pairWithKey payload compatibility', () => {
|
|
28
|
+
test('accepts snake_case channel config from the pairing endpoint', async () => {
|
|
29
|
+
const context = await createMethodContext();
|
|
30
|
+
const endpoint = await createPairingEndpoint({
|
|
31
|
+
data: {
|
|
32
|
+
plugin_id: 'rol-websocket-channel',
|
|
33
|
+
api_core_bot: {
|
|
34
|
+
base_url: 'https://api.example.test',
|
|
35
|
+
auth_token: 'secret-token'
|
|
36
|
+
},
|
|
37
|
+
value: 'announcement/user/agent/#',
|
|
38
|
+
channel: {
|
|
39
|
+
config: {
|
|
40
|
+
mqtt_url: 'ws://mqtt.example.test:8083/mqtt',
|
|
41
|
+
mqtt_topic: 'announcement/user/agent/#',
|
|
42
|
+
group_policy: 'pairing'
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
const result = await pairWithKey({ key: 'abc123456', endpoint }, context);
|
|
48
|
+
const savedConfig = JSON.parse(await fs.readFile(path.join(context.openclawRoot, 'openclaw.json'), 'utf8'));
|
|
49
|
+
assert.equal(result.ok, true);
|
|
50
|
+
assert.equal(result.channel.mqttUrl, 'ws://mqtt.example.test:8083/mqtt');
|
|
51
|
+
assert.equal(result.channel.mqttTopic, 'announcement/user/agent/#');
|
|
52
|
+
assert.equal(result.channel.groupPolicy, 'pairing');
|
|
53
|
+
assert.deepEqual(savedConfig.plugins.allow, ['rol-websocket-channel']);
|
|
54
|
+
assert.equal(savedConfig.channels['rol-websocket-channel'].config.mqttUrl, 'ws://mqtt.example.test:8083/mqtt');
|
|
55
|
+
assert.equal(savedConfig.plugins.entries['rol-websocket-channel'].config.apiCoreBot.authToken, 'secret-token');
|
|
56
|
+
});
|
|
57
|
+
test('reports payload keys when mqttUrl is missing', async () => {
|
|
58
|
+
const context = await createMethodContext();
|
|
59
|
+
const endpoint = await createPairingEndpoint({
|
|
60
|
+
data: {
|
|
61
|
+
plugin_id: 'rol-websocket-channel',
|
|
62
|
+
channel: {
|
|
63
|
+
config: {
|
|
64
|
+
mqtt_topic: 'announcement/user/agent/#'
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
await assert.rejects(() => pairWithKey({ key: 'abc123456', endpoint }, context), (error) => {
|
|
70
|
+
assert.ok(error instanceof JsonRpcException);
|
|
71
|
+
assert.equal(error.message, 'mqttUrl is missing from pairing payload');
|
|
72
|
+
assert.deepEqual(error.data, {
|
|
73
|
+
code: 'PAIR_CHANNEL_CONFIG_INVALID',
|
|
74
|
+
debug: {
|
|
75
|
+
endpoint,
|
|
76
|
+
rootKeys: ['plugin_id', 'channel'],
|
|
77
|
+
channelKeys: ['config'],
|
|
78
|
+
channelConfigKeys: ['mqtt_topic'],
|
|
79
|
+
hasExistingMqttUrl: false
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
return true;
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
async function createMethodContext() {
|
|
87
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'rol-pairing-'));
|
|
88
|
+
tempDirs.push(dir);
|
|
89
|
+
await fs.writeFile(path.join(dir, 'openclaw.json'), '{}\n', 'utf8');
|
|
90
|
+
return {
|
|
91
|
+
projectRoot: dir,
|
|
92
|
+
openclawRoot: dir
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
async function createPairingEndpoint(payload) {
|
|
96
|
+
const server = http.createServer(async (request, response) => {
|
|
97
|
+
if (request.method !== 'POST') {
|
|
98
|
+
response.writeHead(405).end();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
for await (const _chunk of request) {
|
|
102
|
+
// Drain request body so Node can close the connection cleanly.
|
|
103
|
+
}
|
|
104
|
+
response.writeHead(200, { 'Content-Type': 'application/json' });
|
|
105
|
+
response.end(JSON.stringify(payload));
|
|
106
|
+
});
|
|
107
|
+
await new Promise((resolve) => {
|
|
108
|
+
server.listen(0, '127.0.0.1', resolve);
|
|
109
|
+
});
|
|
110
|
+
servers.push(server);
|
|
111
|
+
const address = server.address();
|
|
112
|
+
assert.ok(address && typeof address === 'object');
|
|
113
|
+
return `http://127.0.0.1:${address.port}/pair`;
|
|
114
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { afterEach, describe, test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import http from 'node:http';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
|
|
8
|
+
import { pairWithKey } from './pairing.js';
|
|
9
|
+
import { JsonRpcException } from '../jsonrpc.js';
|
|
10
|
+
import type { MethodContext } from '../types.js';
|
|
11
|
+
|
|
12
|
+
const tempDirs: string[] = [];
|
|
13
|
+
const servers: http.Server[] = [];
|
|
14
|
+
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
while (servers.length > 0) {
|
|
17
|
+
const server = servers.pop();
|
|
18
|
+
if (server) {
|
|
19
|
+
await new Promise<void>((resolve, reject) => {
|
|
20
|
+
server.close((error) => error ? reject(error) : resolve());
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
while (tempDirs.length > 0) {
|
|
26
|
+
const dir = tempDirs.pop();
|
|
27
|
+
if (dir) {
|
|
28
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('pairWithKey payload compatibility', () => {
|
|
34
|
+
test('accepts snake_case channel config from the pairing endpoint', async () => {
|
|
35
|
+
const context = await createMethodContext();
|
|
36
|
+
const endpoint = await createPairingEndpoint({
|
|
37
|
+
data: {
|
|
38
|
+
plugin_id: 'rol-websocket-channel',
|
|
39
|
+
api_core_bot: {
|
|
40
|
+
base_url: 'https://api.example.test',
|
|
41
|
+
auth_token: 'secret-token'
|
|
42
|
+
},
|
|
43
|
+
value: 'announcement/user/agent/#',
|
|
44
|
+
channel: {
|
|
45
|
+
config: {
|
|
46
|
+
mqtt_url: 'ws://mqtt.example.test:8083/mqtt',
|
|
47
|
+
mqtt_topic: 'announcement/user/agent/#',
|
|
48
|
+
group_policy: 'pairing'
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const result = await pairWithKey({ key: 'abc123456', endpoint }, context) as {
|
|
55
|
+
ok: boolean;
|
|
56
|
+
channel: {
|
|
57
|
+
mqttUrl: string;
|
|
58
|
+
mqttTopic: string;
|
|
59
|
+
groupPolicy: string;
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
const savedConfig = JSON.parse(
|
|
63
|
+
await fs.readFile(path.join(context.openclawRoot, 'openclaw.json'), 'utf8')
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
assert.equal(result.ok, true);
|
|
67
|
+
assert.equal(result.channel.mqttUrl, 'ws://mqtt.example.test:8083/mqtt');
|
|
68
|
+
assert.equal(result.channel.mqttTopic, 'announcement/user/agent/#');
|
|
69
|
+
assert.equal(result.channel.groupPolicy, 'pairing');
|
|
70
|
+
assert.deepEqual(savedConfig.plugins.allow, ['rol-websocket-channel']);
|
|
71
|
+
assert.equal(
|
|
72
|
+
savedConfig.channels['rol-websocket-channel'].config.mqttUrl,
|
|
73
|
+
'ws://mqtt.example.test:8083/mqtt'
|
|
74
|
+
);
|
|
75
|
+
assert.equal(
|
|
76
|
+
savedConfig.plugins.entries['rol-websocket-channel'].config.apiCoreBot.authToken,
|
|
77
|
+
'secret-token'
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('reports payload keys when mqttUrl is missing', async () => {
|
|
82
|
+
const context = await createMethodContext();
|
|
83
|
+
const endpoint = await createPairingEndpoint({
|
|
84
|
+
data: {
|
|
85
|
+
plugin_id: 'rol-websocket-channel',
|
|
86
|
+
channel: {
|
|
87
|
+
config: {
|
|
88
|
+
mqtt_topic: 'announcement/user/agent/#'
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
await assert.rejects(
|
|
95
|
+
() => pairWithKey({ key: 'abc123456', endpoint }, context),
|
|
96
|
+
(error: unknown) => {
|
|
97
|
+
assert.ok(error instanceof JsonRpcException);
|
|
98
|
+
assert.equal(error.message, 'mqttUrl is missing from pairing payload');
|
|
99
|
+
assert.deepEqual(error.data, {
|
|
100
|
+
code: 'PAIR_CHANNEL_CONFIG_INVALID',
|
|
101
|
+
debug: {
|
|
102
|
+
endpoint,
|
|
103
|
+
rootKeys: ['plugin_id', 'channel'],
|
|
104
|
+
channelKeys: ['config'],
|
|
105
|
+
channelConfigKeys: ['mqtt_topic'],
|
|
106
|
+
hasExistingMqttUrl: false
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
async function createMethodContext(): Promise<MethodContext> {
|
|
116
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'rol-pairing-'));
|
|
117
|
+
tempDirs.push(dir);
|
|
118
|
+
await fs.writeFile(path.join(dir, 'openclaw.json'), '{}\n', 'utf8');
|
|
119
|
+
return {
|
|
120
|
+
projectRoot: dir,
|
|
121
|
+
openclawRoot: dir
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function createPairingEndpoint(payload: unknown): Promise<string> {
|
|
126
|
+
const server = http.createServer(async (request, response) => {
|
|
127
|
+
if (request.method !== 'POST') {
|
|
128
|
+
response.writeHead(405).end();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
for await (const _chunk of request) {
|
|
133
|
+
// Drain request body so Node can close the connection cleanly.
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
response.writeHead(200, { 'Content-Type': 'application/json' });
|
|
137
|
+
response.end(JSON.stringify(payload));
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
await new Promise<void>((resolve) => {
|
|
141
|
+
server.listen(0, '127.0.0.1', resolve);
|
|
142
|
+
});
|
|
143
|
+
servers.push(server);
|
|
144
|
+
|
|
145
|
+
const address = server.address();
|
|
146
|
+
assert.ok(address && typeof address === 'object');
|
|
147
|
+
|
|
148
|
+
return `http://127.0.0.1:${address.port}/pair`;
|
|
149
|
+
}
|
|
@@ -46,6 +46,14 @@ interface PairingPayload {
|
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
interface PairingPayloadDebug {
|
|
50
|
+
endpoint: string;
|
|
51
|
+
rootKeys: string[];
|
|
52
|
+
channelKeys: string[];
|
|
53
|
+
channelConfigKeys: string[];
|
|
54
|
+
hasExistingMqttUrl: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
49
57
|
export async function pairWithKey(
|
|
50
58
|
options: PairingCommandOptions,
|
|
51
59
|
context: MethodContext
|
|
@@ -134,33 +142,60 @@ function normalizePairingPayload(
|
|
|
134
142
|
existingMqttUrl?: string | null
|
|
135
143
|
): PairingPayload {
|
|
136
144
|
const root = unwrapPayload(raw);
|
|
137
|
-
const pluginId = pickString(root.pluginId) ?? DEFAULT_PLUGIN_ID;
|
|
138
|
-
const apiCoreBotValue =
|
|
139
|
-
const channelValue =
|
|
145
|
+
const pluginId = pickString(root.pluginId) ?? pickString(root.plugin_id) ?? DEFAULT_PLUGIN_ID;
|
|
146
|
+
const apiCoreBotValue = pickRecord(root.apiCoreBot, root.api_core_bot);
|
|
147
|
+
const channelValue = pickRecord(root.channel);
|
|
148
|
+
const channelConfigValue = pickRecord(channelValue.config);
|
|
140
149
|
|
|
141
150
|
const apiCoreBotAuthToken = pickString(apiCoreBotValue.authToken)
|
|
151
|
+
?? pickString(apiCoreBotValue.auth_token)
|
|
142
152
|
?? pickString(root.authToken)
|
|
153
|
+
?? pickString(root.auth_token)
|
|
143
154
|
?? pickString(root.token);
|
|
144
|
-
const rawValue = pickString(root.value)
|
|
155
|
+
const rawValue = pickString(root.value)
|
|
156
|
+
?? pickString(root.rawValue)
|
|
157
|
+
?? pickString(root.raw_value);
|
|
145
158
|
|
|
146
159
|
const mqttUrl = pickString(channelValue.mqttUrl)
|
|
160
|
+
?? pickString(channelValue.mqtt_url)
|
|
161
|
+
?? pickString(channelConfigValue.mqttUrl)
|
|
162
|
+
?? pickString(channelConfigValue.mqtt_url)
|
|
147
163
|
?? pickString(root.mqttUrl)
|
|
164
|
+
?? pickString(root.mqtt_url)
|
|
148
165
|
?? existingMqttUrl
|
|
149
166
|
?? null;
|
|
150
167
|
if (!mqttUrl) {
|
|
151
|
-
throwPairingError('PAIR_CHANNEL_CONFIG_INVALID', 'mqttUrl is missing from pairing payload'
|
|
168
|
+
throwPairingError('PAIR_CHANNEL_CONFIG_INVALID', 'mqttUrl is missing from pairing payload', {
|
|
169
|
+
endpoint,
|
|
170
|
+
rootKeys: Object.keys(root),
|
|
171
|
+
channelKeys: Object.keys(channelValue),
|
|
172
|
+
channelConfigKeys: Object.keys(channelConfigValue),
|
|
173
|
+
hasExistingMqttUrl: Boolean(existingMqttUrl)
|
|
174
|
+
});
|
|
152
175
|
}
|
|
153
176
|
|
|
154
177
|
const mqttTopic = pickString(channelValue.mqttTopic)
|
|
178
|
+
?? pickString(channelValue.mqtt_topic)
|
|
179
|
+
?? pickString(channelConfigValue.mqttTopic)
|
|
180
|
+
?? pickString(channelConfigValue.mqtt_topic)
|
|
155
181
|
?? pickString(root.mqttTopic)
|
|
182
|
+
?? pickString(root.mqtt_topic)
|
|
156
183
|
?? rawValue
|
|
157
184
|
?? 'announcement/tester';
|
|
158
185
|
|
|
159
186
|
const groupPolicy = normalizeGroupPolicy(
|
|
160
187
|
pickString(channelValue.groupPolicy)
|
|
188
|
+
?? pickString(channelValue.group_policy)
|
|
161
189
|
?? pickString(channelValue.dmPolicy)
|
|
190
|
+
?? pickString(channelValue.dm_policy)
|
|
191
|
+
?? pickString(channelConfigValue.groupPolicy)
|
|
192
|
+
?? pickString(channelConfigValue.group_policy)
|
|
193
|
+
?? pickString(channelConfigValue.dmPolicy)
|
|
194
|
+
?? pickString(channelConfigValue.dm_policy)
|
|
162
195
|
?? pickString(root.groupPolicy)
|
|
196
|
+
?? pickString(root.group_policy)
|
|
163
197
|
?? pickString(root.dmPolicy)
|
|
198
|
+
?? pickString(root.dm_policy)
|
|
164
199
|
?? 'pairing'
|
|
165
200
|
);
|
|
166
201
|
|
|
@@ -170,7 +205,7 @@ function normalizePairingPayload(
|
|
|
170
205
|
...(rawValue ? { rawValue } : {})
|
|
171
206
|
},
|
|
172
207
|
apiCoreBot: {
|
|
173
|
-
baseUrl: pickString(apiCoreBotValue.baseUrl) ?? inferBaseUrl(endpoint),
|
|
208
|
+
baseUrl: pickString(apiCoreBotValue.baseUrl) ?? pickString(apiCoreBotValue.base_url) ?? inferBaseUrl(endpoint),
|
|
174
209
|
...(apiCoreBotAuthToken ? { authToken: apiCoreBotAuthToken } : {})
|
|
175
210
|
},
|
|
176
211
|
channel: {
|
|
@@ -352,10 +387,23 @@ function pickString(value: unknown): string | null {
|
|
|
352
387
|
return trimmed.length > 0 ? trimmed : null;
|
|
353
388
|
}
|
|
354
389
|
|
|
390
|
+
function pickRecord(...values: unknown[]): Record<string, any> {
|
|
391
|
+
for (const value of values) {
|
|
392
|
+
if (isRecord(value)) {
|
|
393
|
+
return value;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return {};
|
|
398
|
+
}
|
|
399
|
+
|
|
355
400
|
function isRecord(value: unknown): value is Record<string, any> {
|
|
356
401
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
357
402
|
}
|
|
358
403
|
|
|
359
|
-
function throwPairingError(code: string, message: string): never {
|
|
360
|
-
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, message, {
|
|
404
|
+
function throwPairingError(code: string, message: string, debug?: PairingPayloadDebug): never {
|
|
405
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, message, {
|
|
406
|
+
code,
|
|
407
|
+
...(debug ? { debug } : {})
|
|
408
|
+
});
|
|
361
409
|
}
|