mqtt-crypto-client 0.2.1 → 0.2.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mqtt-crypto-client",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "MQTT over WebSocket client with CryptoJS-compatible AES envelope encryption, heartbeat, message deduplication and request/response helpers.",
5
5
  "keywords": [
6
6
  "mqtt",
@@ -14,12 +14,14 @@
14
14
  "type": "module",
15
15
  "main": "./dist/index.cjs",
16
16
  "module": "./src/index.js",
17
+ "browser": "./dist/index.browser.js",
17
18
  "types": "./types/index.d.ts",
18
19
  "unpkg": "./dist/index.iife.js",
19
20
  "jsdelivr": "./dist/index.iife.js",
20
21
  "exports": {
21
22
  ".": {
22
23
  "types": "./types/index.d.ts",
24
+ "browser": "./dist/index.browser.js",
23
25
  "import": "./src/index.js",
24
26
  "require": "./dist/index.cjs"
25
27
  },
@@ -37,7 +39,7 @@
37
39
  "node": ">=18"
38
40
  },
39
41
  "scripts": {
40
- "build": "node scripts/build-cjs.mjs && node scripts/build-iife.mjs && node scripts/check-iife.mjs",
42
+ "build": "node scripts/build-cjs.mjs && node scripts/build-browser.mjs && node scripts/check-browser.mjs",
41
43
  "test": "node --test",
42
44
  "typecheck": "tsc -p tsconfig.json",
43
45
  "prepack": "npm run build",
@@ -49,6 +51,8 @@
49
51
  },
50
52
  "devDependencies": {
51
53
  "@types/node": "^22.10.0",
54
+ "acorn": "^8.19.0",
55
+ "acorn-walk": "^8.3.5",
52
56
  "crypto-js": "^4.2.0",
53
57
  "esbuild": "^0.25.0",
54
58
  "typescript": "^5.7.0"
@@ -0,0 +1,117 @@
1
+ /**
2
+ * MQTT 配置字符串(mqtthub 风格)的序列化 / 反序列化。
3
+ *
4
+ * 格式:`key=value&key=value`,每个 value 经 encodeURIComponent 编码。整串既可作为 URL
5
+ * 查询串(如 `?broker=...&topic=...&password=...`),也可原样粘贴进设置框,是跨系统的
6
+ * 便捷交换格式(不是持久化格式;持久化仍以对象/JSON 保存)。
7
+ *
8
+ * 已知 key:
9
+ * - broker (必填) MQTT broker URL,如 `wss://broker.emqx.io:8084/mqtt`
10
+ * - topic (必填) 订阅 / 发布的 topic
11
+ * - password 对应本库的 `secret`(AES 口令);为空表示不加密(encrypt=false)
12
+ * - name (可选) 可读标签,仅用于 UI,不参与连接
13
+ *
14
+ * 其它 key 原样保留为扩展字段(不参与连接,但可被 UI 使用)。
15
+ *
16
+ * 加密互通说明:本库 `secret` 的加密方案与 CryptoJS `AES.encrypt(payload, passphrase)`
17
+ * 完全一致(OpenSSL "Salted__" 格式 + EVP_BytesToKey,AES-256-CBC),因此同一 `password`
18
+ * 在 mqtthub 与本库之间可互解密——只要连接的是同一个 broker/topic 且口令相同。
19
+ */
20
+ import { InvalidConfigError } from './errors.js';
21
+
22
+ /** 已知 key:这些 key 在序列化/反序列化时有固定语义,不参与"扩展字段"透传逻辑的特殊处理 */
23
+ const KNOWN_KEYS = new Set(['name', 'broker', 'topic', 'password']);
24
+
25
+ /**
26
+ * 解析配置字符串,返回本库 options 子集(broker / topic / secret / encrypt + 扩展字段)。
27
+ * 与 serializeConfigString 在 broker / topic / secret(password) / name 范围内互逆。
28
+ *
29
+ * @param {string} str 配置字符串(`key=value&...`)
30
+ * @returns {{ broker: string, topic: string, secret: string, encrypt: boolean, [k: string]: any }}
31
+ */
32
+ export function parseConfigString(str) {
33
+ if (typeof str !== 'string' || str.trim().length === 0) {
34
+ throw new InvalidConfigError('配置字符串为空');
35
+ }
36
+
37
+ /** @type {Record<string, string>} */
38
+ const raw = {};
39
+ for (const pair of str.trim().split('&')) {
40
+ if (pair.length === 0) continue;
41
+ const idx = pair.indexOf('=');
42
+ if (idx < 0) continue; // 容忍缺少 = 的片段
43
+ const k = pair.slice(0, idx).trim();
44
+ if (k.length === 0) continue;
45
+ const v = decodeURIComponent(pair.slice(idx + 1));
46
+ raw[k] = v;
47
+ }
48
+
49
+ const broker = raw.broker;
50
+ const topic = raw.topic;
51
+ if (!broker || !topic) {
52
+ throw new InvalidConfigError('配置字符串缺少必填字段 broker 或 topic');
53
+ }
54
+
55
+ const password = raw.password || '';
56
+ /** @type {{ broker: string, topic: string, secret: string, encrypt: boolean, [k: string]: any }} */
57
+ const result = {
58
+ broker,
59
+ topic,
60
+ // 库的 secret 对应字符串的 password
61
+ secret: password,
62
+ // 口令非空即视为加密;空口令 = 不加密
63
+ encrypt: password.length > 0,
64
+ };
65
+
66
+ // name 是可选可读标签,原样保留
67
+ if (raw.name !== undefined) result.name = raw.name;
68
+
69
+ // 透传扩展字段(不参与连接,但可被 UI 使用)
70
+ for (const k of Object.keys(raw)) {
71
+ if (KNOWN_KEYS.has(k)) continue;
72
+ result[k] = raw[k];
73
+ }
74
+
75
+ return result;
76
+ }
77
+
78
+ /**
79
+ * 将配置对象序列化为 mqtthub 风格配置字符串。
80
+ * secret 仅在 encrypt !== false 时映射为 password;encrypt=false 时 password 为空(不加密)。
81
+ *
82
+ * @param {{ broker: string, topic: string, secret?: string, encrypt?: boolean, name?: string, [k: string]: any }} opts
83
+ * @returns {string}
84
+ */
85
+ export function serializeConfigString(opts) {
86
+ if (!opts || typeof opts !== 'object' || Array.isArray(opts)) {
87
+ throw new InvalidConfigError('serializeConfigString 需要配置对象');
88
+ }
89
+
90
+ /** @type {string[]} */
91
+ const entries = [];
92
+ /**
93
+ * @param {string} k
94
+ * @param {any} v
95
+ */
96
+ const push = (k, v) => {
97
+ if (v === undefined || v === null) return;
98
+ entries.push(`${k}=${encodeURIComponent(String(v))}`);
99
+ };
100
+
101
+ // name 在前,便于人工阅读
102
+ if (opts.name !== undefined) push('name', opts.name);
103
+ push('broker', opts.broker);
104
+ push('topic', opts.topic);
105
+
106
+ // 库的 secret 映射为字符串的 password;encrypt=false 时视为不加密(password 为空)
107
+ const password = opts.encrypt === false ? '' : (opts.secret ?? '');
108
+ push('password', password);
109
+
110
+ // 透传扩展字段(排除已显式映射的字段)
111
+ for (const k of Object.keys(opts)) {
112
+ if (['broker', 'topic', 'secret', 'encrypt', 'name'].includes(k)) continue;
113
+ push(k, opts[k]);
114
+ }
115
+
116
+ return entries.join('&');
117
+ }
package/src/index.js CHANGED
@@ -31,6 +31,8 @@ export {
31
31
  resolveOptions,
32
32
  } from './options.js';
33
33
 
34
+ export { parseConfigString, serializeConfigString } from './config-string.js';
35
+
34
36
  export { DEFAULT_TOPIC, PRESET_BROKERS, buildTopic, validateTopic } from './topic.js';
35
37
 
36
38
  export {
package/src/options.js CHANGED
@@ -5,6 +5,7 @@
5
5
  import { InvalidConfigError } from './errors.js';
6
6
  import { DEFAULT_CLIENT_ID_PREFIX, DEFAULT_USER, createClientId } from './envelope.js';
7
7
  import { DEFAULT_TOPIC, validateTopic } from './topic.js';
8
+ import { parseConfigString } from './config-string.js';
8
9
 
9
10
  /** 支持的 broker 协议(库以 MQTT over WebSocket 为主,同时保留 TCP/TLS 能力) */
10
11
  const SUPPORTED_PROTOCOLS = new Set(['ws:', 'wss:', 'mqtt:', 'mqtts:', 'tcp:', 'tls:']);
@@ -81,8 +82,12 @@ function validateBroker(broker) {
81
82
  * @returns {Readonly<Record<string, any>>}
82
83
  */
83
84
  export function resolveOptions(input = {}) {
85
+ // 支持直接传入 mqtthub 风格配置字符串(key=value&...)
86
+ if (typeof input === 'string') {
87
+ input = parseConfigString(input);
88
+ }
84
89
  if (!input || typeof input !== 'object' || Array.isArray(input)) {
85
- throw new InvalidConfigError('options 必须是对象');
90
+ throw new InvalidConfigError('options 必须是对象或配置字符串');
86
91
  }
87
92
 
88
93
  const options = { ...DEFAULT_OPTIONS, ...input };
package/types/index.d.ts CHANGED
@@ -71,8 +71,20 @@ export interface ClientStatus {
71
71
  agentOnline: boolean;
72
72
  }
73
73
 
74
+ export interface ParsedConfigString {
75
+ broker: string;
76
+ topic: string;
77
+ /** AES 口令(对应字符串的 password 字段) */
78
+ secret: string;
79
+ /** password 非空为 true,空为 false(不加密) */
80
+ encrypt: boolean;
81
+ /** 可选可读标签(对应字符串的 name 字段) */
82
+ name?: string;
83
+ [key: string]: any;
84
+ }
85
+
74
86
  export declare class MqttCryptoClient extends EventEmitter {
75
- constructor(options?: MqttOptions);
87
+ constructor(options?: MqttOptions | string);
76
88
 
77
89
  readonly options: Readonly<Required<Omit<MqttOptions, 'auth' | 'protocolVersion'>> & {
78
90
  auth: { username?: string; password?: string } | null;
@@ -119,10 +131,27 @@ export declare class MqttCryptoClient extends EventEmitter {
119
131
  on(event: string | symbol, listener: (...args: any[]) => void): this;
120
132
  }
121
133
 
122
- export declare function createClient(options?: MqttOptions): MqttCryptoClient;
134
+ export declare function createClient(options?: MqttOptions | string): MqttCryptoClient;
123
135
 
124
- /** 默认值合并并校验;非法配置会抛出 InvalidConfigError */
125
- export declare function resolveOptions(input?: MqttOptions): Readonly<Record<string, any>>;
136
+ /** 默认值合并并校验;非法配置会抛出 InvalidConfigError。支持直接传入 mqtthub 风格配置字符串 */
137
+ export declare function resolveOptions(input?: MqttOptions | string): Readonly<Record<string, any>>;
138
+
139
+ /**
140
+ * 解析 mqtthub 风格 MQTT 配置字符串(key=value&...,value 经 encodeURIComponent 编码)。
141
+ * 返回 broker/topic/secret/encrypt + 扩展字段。
142
+ */
143
+ export declare function parseConfigString(str: string): ParsedConfigString;
144
+ /**
145
+ * 将配置对象序列化为 mqtthub 风格配置字符串。secret 仅在 encrypt !== false 时映射为 password。
146
+ */
147
+ export declare function serializeConfigString(opts: {
148
+ broker: string;
149
+ topic: string;
150
+ secret?: string;
151
+ encrypt?: boolean;
152
+ name?: string;
153
+ [key: string]: any;
154
+ }): string;
126
155
  /** 生成 mqtt.connect 的 options(clientId、reconnectPeriod、connectTimeout、clean、auth 等) */
127
156
  export declare function buildMqttOptions(options: Record<string, any>): Record<string, any>;
128
157