@soyeht/soyeht 0.2.1 → 0.2.2

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.
@@ -5,7 +5,7 @@
5
5
  ],
6
6
  "name": "Soyeht",
7
7
  "description": "Channel plugin for the Soyeht Flutter mobile app",
8
- "version": "0.2.1",
8
+ "version": "0.2.2",
9
9
  "configSchema": {
10
10
  "type": "object",
11
11
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soyeht/soyeht",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "OpenClaw channel plugin for the Soyeht Flutter mobile app",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/index.ts CHANGED
@@ -23,6 +23,7 @@ import {
23
23
  import {
24
24
  handleSecurityIdentity,
25
25
  handleSecurityPair,
26
+ handleSecurityPairingInfo,
26
27
  handleSecurityPairingStart,
27
28
  } from "./pairing.js";
28
29
  import { PLUGIN_VERSION } from "./version.js";
@@ -89,6 +90,7 @@ const soyehtPlugin: OpenClawPluginDefinition = {
89
90
  // Security RPC
90
91
  api.registerGatewayMethod("soyeht.security.identity", handleSecurityIdentity(api, v2deps));
91
92
  api.registerGatewayMethod("soyeht.security.pairing.start", handleSecurityPairingStart(api, v2deps));
93
+ api.registerGatewayMethod("soyeht.security.pairing.info", handleSecurityPairingInfo(api, v2deps));
92
94
  api.registerGatewayMethod("soyeht.security.pair", handleSecurityPair(api, v2deps));
93
95
  api.registerGatewayMethod("soyeht.security.handshake.init", handleSecurityHandshake(api, v2deps));
94
96
  api.registerGatewayMethod("soyeht.security.handshake.finish", handleSecurityHandshakeFinish(api, v2deps));
package/src/pairing.ts CHANGED
@@ -136,6 +136,61 @@ function resolveGatewayUrl(api: OpenClawPluginApi, configGatewayUrl: string): st
136
136
  return "";
137
137
  }
138
138
 
139
+ // ---------------------------------------------------------------------------
140
+ // soyeht.security.pairing.info — return session data for a given pairing token
141
+ // ---------------------------------------------------------------------------
142
+
143
+ export function handleSecurityPairingInfo(
144
+ _api: OpenClawPluginApi,
145
+ v2deps: SecurityV2Deps,
146
+ ): GatewayRequestHandler {
147
+ return async ({ params, respond }) => {
148
+ if (!v2deps.ready) {
149
+ respond(false, undefined, { code: "NOT_READY", message: "Service not ready" });
150
+ return;
151
+ }
152
+ if (!v2deps.identity) {
153
+ respond(false, undefined, { code: "NO_IDENTITY", message: "Identity not initialized" });
154
+ return;
155
+ }
156
+
157
+ const pairingToken = params["pairingToken"] as string | undefined;
158
+ if (!pairingToken) {
159
+ respond(false, undefined, {
160
+ code: "INVALID_PARAMS",
161
+ message: "Missing required param: pairingToken",
162
+ });
163
+ return;
164
+ }
165
+
166
+ const session = v2deps.pairingSessions.get(pairingToken);
167
+ if (!session) {
168
+ respond(false, undefined, {
169
+ code: "PAIRING_REQUIRED",
170
+ message: "No active pairing session. Scan a fresh QR code first.",
171
+ });
172
+ return;
173
+ }
174
+
175
+ if (session.expiresAt <= Date.now()) {
176
+ respond(false, undefined, {
177
+ code: "PAIRING_EXPIRED",
178
+ message: "Pairing QR expired. Scan a fresh QR code first.",
179
+ });
180
+ return;
181
+ }
182
+
183
+ respond(true, {
184
+ accountId: session.accountId,
185
+ expiresAt: session.expiresAt,
186
+ allowOverwrite: session.allowOverwrite,
187
+ pluginIdentityKey: v2deps.identity.signKey.publicKeyB64,
188
+ pluginDhKey: v2deps.identity.dhKey.publicKeyB64,
189
+ fingerprint: computeFingerprint(v2deps.identity),
190
+ });
191
+ };
192
+ }
193
+
139
194
  // ---------------------------------------------------------------------------
140
195
  // soyeht.security.identity — expose plugin public keys
141
196
  // ---------------------------------------------------------------------------
package/src/service.ts CHANGED
@@ -10,13 +10,11 @@ import {
10
10
  } from "./security.js";
11
11
  import { loadOrGenerateIdentity, loadPeers, loadSessions, saveSession } from "./identity.js";
12
12
  import { zeroBuffer } from "./ratchet.js";
13
- import { base64UrlEncode, computeFingerprint, ed25519Sign, type X25519KeyPair } from "./crypto.js";
13
+ import { base64UrlEncode, computeFingerprint, type X25519KeyPair } from "./crypto.js";
14
14
  import type { IdentityBundle, PeerIdentity } from "./identity.js";
15
15
  import type { RatchetState } from "./ratchet.js";
16
16
  import { createOutboundQueue, type OutboundQueue } from "./outbound-queue.js";
17
17
  import { renderQrTerminal } from "./qr.js";
18
- import { resolveSoyehtAccount } from "./config.js";
19
- import { buildPairingQrTranscript, buildPairingQrTranscriptV2 } from "./pairing.js";
20
18
 
21
19
  const HEARTBEAT_INTERVAL_MS = 60_000; // 60s
22
20
 
@@ -93,49 +91,12 @@ async function showPairingQr(api: OpenClawPluginApi, v2deps: SecurityV2Deps): Pr
93
91
  const fingerprint = computeFingerprint(identity);
94
92
 
95
93
  // Resolve gatewayUrl from config or runtime
96
- const cfg = await api.runtime.config.loadConfig();
97
- const account = resolveSoyehtAccount(cfg, accountId);
98
- let gatewayUrl = account.gatewayUrl;
99
- if (!gatewayUrl) {
100
- const runtime = api.runtime as Record<string, unknown>;
101
- if (typeof runtime["gatewayUrl"] === "string" && runtime["gatewayUrl"]) {
102
- gatewayUrl = runtime["gatewayUrl"];
103
- } else if (typeof runtime["baseUrl"] === "string" && runtime["baseUrl"]) {
104
- gatewayUrl = runtime["baseUrl"];
105
- }
106
- }
107
-
108
- const basePayload = {
109
- accountId,
110
- pairingToken,
111
- expiresAt,
112
- allowOverwrite: false,
113
- pluginIdentityKey: identity.signKey.publicKeyB64,
114
- pluginDhKey: identity.dhKey.publicKeyB64,
115
- fingerprint,
116
- };
117
-
118
- let qrPayload: Record<string, unknown>;
119
-
120
- if (gatewayUrl) {
121
- const transcript = buildPairingQrTranscriptV2({ gatewayUrl, ...basePayload });
122
- const signature = base64UrlEncode(ed25519Sign(identity.signKey.privateKey, transcript));
123
- qrPayload = {
124
- version: 2,
125
- type: "soyeht_pairing_qr",
126
- gatewayUrl,
127
- ...basePayload,
128
- signature,
129
- };
130
- } else {
131
- const transcript = buildPairingQrTranscript(basePayload);
132
- const signature = base64UrlEncode(ed25519Sign(identity.signKey.privateKey, transcript));
133
- qrPayload = {
134
- version: 1,
135
- type: "soyeht_pairing_qr",
136
- ...basePayload,
137
- signature,
138
- };
94
+ let gatewayUrl = "";
95
+ const runtime = api.runtime as Record<string, unknown>;
96
+ if (typeof runtime["gatewayUrl"] === "string" && runtime["gatewayUrl"]) {
97
+ gatewayUrl = runtime["gatewayUrl"];
98
+ } else if (typeof runtime["baseUrl"] === "string" && runtime["baseUrl"]) {
99
+ gatewayUrl = runtime["baseUrl"];
139
100
  }
140
101
 
141
102
  v2deps.pairingSessions.set(pairingToken, {
@@ -145,11 +106,15 @@ async function showPairingQr(api: OpenClawPluginApi, v2deps: SecurityV2Deps): Pr
145
106
  allowOverwrite: false,
146
107
  });
147
108
 
148
- const qrText = JSON.stringify(qrPayload);
109
+ // Compact QR: soyeht://pair?g=<gatewayUrl>&t=<token>&fp=<fingerprint>
110
+ // App fetches full key material via RPC soyeht.security.pairing.info
111
+ const qrText = `soyeht://pair?g=${encodeURIComponent(gatewayUrl)}&t=${pairingToken}&fp=${fingerprint}`;
149
112
  const rendered = renderQrTerminal(qrText);
150
113
 
151
114
  if (rendered) {
152
- api.logger.info("[soyeht] Scan this QR code with the Soyeht app to pair:\n\n" + rendered);
115
+ // Write QR directly to stdout to avoid logger prefixes breaking ANSI escape codes
116
+ process.stdout.write("\n" + rendered + "\n\n");
117
+ api.logger.info(`[soyeht] Scan the QR code above with the Soyeht app to pair`);
153
118
  api.logger.info(`[soyeht] Fingerprint: ${fingerprint}`);
154
119
  api.logger.info(`[soyeht] QR expires in ${AUTO_PAIRING_TTL_MS / 1000}s — restart plugin to generate a new one`);
155
120
  } else {
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const PLUGIN_VERSION = "0.2.1";
1
+ export const PLUGIN_VERSION = "0.2.2";