sesame-kit 0.4.0

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.
Files changed (170) hide show
  1. package/LICENSE +26 -0
  2. package/LICENSE.biz3 +21 -0
  3. package/README.ja.md +225 -0
  4. package/README.md +222 -0
  5. package/bin/sesame.js +8 -0
  6. package/clients/js/sesame-client.mjs +208 -0
  7. package/clients/python/pyproject.toml +5 -0
  8. package/clients/python/sesame_client.py +323 -0
  9. package/clients/python/setup.cfg +11 -0
  10. package/docs/architecture.ja.md +132 -0
  11. package/docs/architecture.md +105 -0
  12. package/docs/commands.ja.md +316 -0
  13. package/docs/commands.md +308 -0
  14. package/docs/library.ja.md +152 -0
  15. package/docs/library.md +152 -0
  16. package/docs/migration.ja.md +13 -0
  17. package/docs/migration.md +13 -0
  18. package/package.json +114 -0
  19. package/src/access.js +375 -0
  20. package/src/account.js +36 -0
  21. package/src/auth.js +248 -0
  22. package/src/ble/devicemodel.js +164 -0
  23. package/src/ble/index.js +185 -0
  24. package/src/ble/protocol.js +319 -0
  25. package/src/ble/session.js +235 -0
  26. package/src/ble/transport.js +279 -0
  27. package/src/cli/access.js +373 -0
  28. package/src/cli/company.js +104 -0
  29. package/src/cli/iot.js +400 -0
  30. package/src/cli/org.js +788 -0
  31. package/src/cli/presetir.js +188 -0
  32. package/src/cli/schedule.js +83 -0
  33. package/src/cli/serve.js +308 -0
  34. package/src/cli.js +1815 -0
  35. package/src/client.js +957 -0
  36. package/src/company.js +147 -0
  37. package/src/config.js +575 -0
  38. package/src/crypto.js +162 -0
  39. package/src/devices.js +228 -0
  40. package/src/index.js +55 -0
  41. package/src/iot.js +513 -0
  42. package/src/ir.js +341 -0
  43. package/src/itemcodes.js +29 -0
  44. package/src/lock.js +194 -0
  45. package/src/org.js +803 -0
  46. package/src/paths.js +30 -0
  47. package/src/presetir.js +525 -0
  48. package/src/prompts.js +74 -0
  49. package/src/schedule.js +108 -0
  50. package/src/serve/daemon.js +251 -0
  51. package/src/serve/framing/grpc.js +145 -0
  52. package/src/serve/framing/http.js +144 -0
  53. package/src/serve/framing/ndjson.js +75 -0
  54. package/src/serve/framing/socket.js +73 -0
  55. package/src/serve/framing/stdio.js +28 -0
  56. package/src/serve/framing/token.js +36 -0
  57. package/src/serve/framing/ws.js +56 -0
  58. package/src/serve/grpc-methods.generated.json +378 -0
  59. package/src/serve/jsonrpc.js +164 -0
  60. package/src/serve/registry.js +226 -0
  61. package/src/serve/rpc-params.generated.json +1746 -0
  62. package/src/serve/sesame.proto +470 -0
  63. package/src/session-ui.js +181 -0
  64. package/src/sharekey.js +130 -0
  65. package/src/tokens.js +53 -0
  66. package/src/transport.js +634 -0
  67. package/src/util.js +26 -0
  68. package/types/access.d.ts +193 -0
  69. package/types/access.d.ts.map +1 -0
  70. package/types/account.d.ts +13 -0
  71. package/types/account.d.ts.map +1 -0
  72. package/types/auth.d.ts +80 -0
  73. package/types/auth.d.ts.map +1 -0
  74. package/types/ble/devicemodel.d.ts +212 -0
  75. package/types/ble/devicemodel.d.ts.map +1 -0
  76. package/types/ble/index.d.ts +160 -0
  77. package/types/ble/index.d.ts.map +1 -0
  78. package/types/ble/protocol.d.ts +201 -0
  79. package/types/ble/protocol.d.ts.map +1 -0
  80. package/types/ble/session.d.ts +129 -0
  81. package/types/ble/session.d.ts.map +1 -0
  82. package/types/ble/transport.d.ts +67 -0
  83. package/types/ble/transport.d.ts.map +1 -0
  84. package/types/cli/access.d.ts +6 -0
  85. package/types/cli/access.d.ts.map +1 -0
  86. package/types/cli/company.d.ts +6 -0
  87. package/types/cli/company.d.ts.map +1 -0
  88. package/types/cli/iot.d.ts +6 -0
  89. package/types/cli/iot.d.ts.map +1 -0
  90. package/types/cli/org.d.ts +6 -0
  91. package/types/cli/org.d.ts.map +1 -0
  92. package/types/cli/presetir.d.ts +6 -0
  93. package/types/cli/presetir.d.ts.map +1 -0
  94. package/types/cli/schedule.d.ts +6 -0
  95. package/types/cli/schedule.d.ts.map +1 -0
  96. package/types/cli/serve.d.ts +2 -0
  97. package/types/cli/serve.d.ts.map +1 -0
  98. package/types/cli.d.ts +2 -0
  99. package/types/cli.d.ts.map +1 -0
  100. package/types/client.d.ts +463 -0
  101. package/types/client.d.ts.map +1 -0
  102. package/types/company.d.ts +94 -0
  103. package/types/company.d.ts.map +1 -0
  104. package/types/config.d.ts +111 -0
  105. package/types/config.d.ts.map +1 -0
  106. package/types/crypto.d.ts +61 -0
  107. package/types/crypto.d.ts.map +1 -0
  108. package/types/devices.d.ts +116 -0
  109. package/types/devices.d.ts.map +1 -0
  110. package/types/index.d.ts +23 -0
  111. package/types/index.d.ts.map +1 -0
  112. package/types/iot.d.ts +312 -0
  113. package/types/iot.d.ts.map +1 -0
  114. package/types/ir.d.ts +147 -0
  115. package/types/ir.d.ts.map +1 -0
  116. package/types/itemcodes.d.ts +21 -0
  117. package/types/itemcodes.d.ts.map +1 -0
  118. package/types/lock.d.ts +89 -0
  119. package/types/lock.d.ts.map +1 -0
  120. package/types/org.d.ts +468 -0
  121. package/types/org.d.ts.map +1 -0
  122. package/types/paths.d.ts +10 -0
  123. package/types/paths.d.ts.map +1 -0
  124. package/types/presetir.d.ts +286 -0
  125. package/types/presetir.d.ts.map +1 -0
  126. package/types/prompts.d.ts +39 -0
  127. package/types/prompts.d.ts.map +1 -0
  128. package/types/schedule.d.ts +71 -0
  129. package/types/schedule.d.ts.map +1 -0
  130. package/types/serve/daemon.d.ts +133 -0
  131. package/types/serve/daemon.d.ts.map +1 -0
  132. package/types/serve/framing/grpc.d.ts +14 -0
  133. package/types/serve/framing/grpc.d.ts.map +1 -0
  134. package/types/serve/framing/http.d.ts +14 -0
  135. package/types/serve/framing/http.d.ts.map +1 -0
  136. package/types/serve/framing/ndjson.d.ts +19 -0
  137. package/types/serve/framing/ndjson.d.ts.map +1 -0
  138. package/types/serve/framing/socket.d.ts +14 -0
  139. package/types/serve/framing/socket.d.ts.map +1 -0
  140. package/types/serve/framing/stdio.d.ts +11 -0
  141. package/types/serve/framing/stdio.d.ts.map +1 -0
  142. package/types/serve/framing/token.d.ts +11 -0
  143. package/types/serve/framing/token.d.ts.map +1 -0
  144. package/types/serve/framing/ws.d.ts +13 -0
  145. package/types/serve/framing/ws.d.ts.map +1 -0
  146. package/types/serve/jsonrpc.d.ts +118 -0
  147. package/types/serve/jsonrpc.d.ts.map +1 -0
  148. package/types/serve/registry.d.ts +41 -0
  149. package/types/serve/registry.d.ts.map +1 -0
  150. package/types/session-ui.d.ts +36 -0
  151. package/types/session-ui.d.ts.map +1 -0
  152. package/types/sharekey.d.ts +35 -0
  153. package/types/sharekey.d.ts.map +1 -0
  154. package/types/tokens.d.ts +20 -0
  155. package/types/tokens.d.ts.map +1 -0
  156. package/types/transport.d.ts +138 -0
  157. package/types/transport.d.ts.map +1 -0
  158. package/types/util.d.ts +20 -0
  159. package/types/util.d.ts.map +1 -0
  160. package/vendor/biz3/README.md +37 -0
  161. package/vendor/biz3/constants/cmdCode.d.ts +48 -0
  162. package/vendor/biz3/constants/cmdCode.d.ts.map +1 -0
  163. package/vendor/biz3/constants/cmdCode.js +92 -0
  164. package/vendor/biz3/constants/messageConstants.d.ts +28 -0
  165. package/vendor/biz3/constants/messageConstants.d.ts.map +1 -0
  166. package/vendor/biz3/constants/messageConstants.js +30 -0
  167. package/vendor/biz3/constants/sesameDeviceModel.d.ts +75 -0
  168. package/vendor/biz3/constants/sesameDeviceModel.d.ts.map +1 -0
  169. package/vendor/biz3/constants/sesameDeviceModel.js +77 -0
  170. package/vendor/biz3/package.json +5 -0
@@ -0,0 +1,164 @@
1
+ // JSON-RPC 2.0 プロトコルコア (transport 非依存)。
2
+ //
3
+ // 全フレーミング (stdio / UDS / HTTP / WS / gRPC) が共有する純粋な protocol 層。
4
+ // ここは「1 メッセージの parse / 分類 / 整形」だけを担い、メソッド解決やイベント配信、
5
+ // 並行制御は Daemon 側に置く (この層は registry も hub も知らない)。
6
+ //
7
+ // 仕様準拠の要点:
8
+ // - 応答は `id` あり、通知は `id` なし+`method` あり。両者を混同しない。
9
+ // - 通知にはエラーでも応答を返さない。
10
+ // - batch (配列) は v1 では受け付けない (明示的に -32600)。
11
+ // - 予約名前空間 `rpc.*` は `rpc.discover` 以外 method-not-found。
12
+ // - エラーの `data` に inbound params を絶対に echo しない (secretKey 漏洩防止)。
13
+
14
+ /**
15
+ * 機械向け契約 (RPC メソッド名/params 形/結果形/event 名/error.kind) の SemVer。
16
+ * **破壊的変更でだけ major を上げる** (パッケージ version とは独立。pkg は無害な変更でも上がる)。
17
+ * 消費者はこれを `status.contractVersion` か discover の `info["x-contractVersion"]` で読み、
18
+ * major 不一致なら fail-fast できる。後方互換な追加は minor、説明のみは patch。
19
+ * 1.0.0: 初版 (5 framing / 79 method / event.lockState・deviceUpdate / 6 kind)
20
+ */
21
+ export const CONTRACT_VERSION = "1.0.0";
22
+
23
+ /** JSON-RPC 2.0 標準エラーコード + アプリ域 (-32000)。 */
24
+ export const RPC = Object.freeze({
25
+ PARSE_ERROR: -32700,
26
+ INVALID_REQUEST: -32600,
27
+ METHOD_NOT_FOUND: -32601,
28
+ INVALID_PARAMS: -32602,
29
+ INTERNAL_ERROR: -32603,
30
+ APP_ERROR: -32000, // ドメインエラーの既定コード (kind で細分化)
31
+ });
32
+
33
+ /**
34
+ * ドメイン/プロトコルエラー。handler はこれを throw するとそのまま JSON-RPC error になる。
35
+ * `kind` は機械可読な分類で、`error.data.kind` に載る (クライアントが分岐できる)。
36
+ * @param {string} message
37
+ * @param {{ code?: number, kind?: string, data?: object }} [opts]
38
+ */
39
+ export class RpcError extends Error {
40
+ constructor(message, { code = RPC.APP_ERROR, kind = "internal", data = null } = {}) {
41
+ super(message);
42
+ this.name = "RpcError";
43
+ this.code = code;
44
+ this.kind = kind;
45
+ this.data = data;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * error.data.kind の enum。**実際に emit される値だけ**を載せる (利用者が switch できるよう、
51
+ * 出ないものは定義しない — 出さない値を契約に書かない)。
52
+ * not_authenticated : 未ログイン/トークン失効 (CLI で sesame login)
53
+ * connection_lost : クラウド WS 未接続/切断
54
+ * timeout : op がタイムアウト (transport の request timeout 由来)
55
+ * bad_params : 引数不正/parse 不能
56
+ * not_implemented : 未知メソッド
57
+ * internal : 上記以外 (ライブラリ/サーバ由来の想定外エラー。message に詳細)
58
+ */
59
+ export const KIND = Object.freeze({
60
+ NOT_AUTHENTICATED: "not_authenticated",
61
+ BAD_PARAMS: "bad_params",
62
+ TIMEOUT: "timeout",
63
+ CONNECTION_LOST: "connection_lost",
64
+ INTERNAL: "internal",
65
+ NOT_IMPLEMENTED: "not_implemented",
66
+ });
67
+
68
+ /** 成功応答を組み立てる。 */
69
+ export function makeResult(id, result) {
70
+ return { jsonrpc: "2.0", id, result: result === undefined ? null : result };
71
+ }
72
+
73
+ /** エラー応答を組み立てる。data は kind 等のみ。inbound params は決して入れない。 */
74
+ export function makeError(id, code, message, kind, data = null) {
75
+ const errorData = {};
76
+ if (data && typeof data === "object") Object.assign(errorData, data);
77
+ if (kind) errorData.kind = kind; // kind は契約なので最後に置き、caller data に上書きさせない
78
+ const error = { code, message };
79
+ if (Object.keys(errorData).length) error.data = errorData;
80
+ return { jsonrpc: "2.0", id, error };
81
+ }
82
+
83
+ /** 任意の throw を JSON-RPC error オブジェクトへ正規化 (params は echo しない)。 */
84
+ export function errorFromThrow(id, err) {
85
+ if (err instanceof RpcError) {
86
+ return makeError(id, err.code, err.message, err.kind, err.data);
87
+ }
88
+ // 想定外の内部エラー: メッセージは出すが stack/params は出さない。
89
+ const message = (err && err.message) ? String(err.message) : "internal error";
90
+ return makeError(id, RPC.INTERNAL_ERROR, message, KIND.INTERNAL);
91
+ }
92
+
93
+ /**
94
+ * 1 行 (1 メッセージ) を parse して分類する。
95
+ * @param {string} raw
96
+ * @returns {{type:"parse-error"}
97
+ * | {type:"batch"}
98
+ * | {type:"invalid", id:(string|number|null)}
99
+ * | {type:"request", id:(string|number), method:string, params:any}
100
+ * | {type:"notification", method:string, params:any}}
101
+ */
102
+ export function classify(raw) {
103
+ let msg;
104
+ try {
105
+ msg = JSON.parse(raw);
106
+ } catch {
107
+ return { type: "parse-error" };
108
+ }
109
+ if (Array.isArray(msg)) return { type: "batch" };
110
+ if (msg === null || typeof msg !== "object") return { type: "invalid", id: null };
111
+ if (typeof msg.method !== "string" || !msg.method) {
112
+ // id を取れるなら拾う (応答整形用)。型は string/number/null のみ許容。
113
+ return { type: "invalid", id: normalizeId(msg.id) };
114
+ }
115
+ const params = msg.params === undefined ? {} : msg.params;
116
+ // `id` フィールドの「欠落」= 通知。`id:null` は通知ではなく id=null の request。
117
+ if (!("id" in msg)) return { type: "notification", method: msg.method, params };
118
+ return { type: "request", id: normalizeId(msg.id), method: msg.method, params };
119
+ }
120
+
121
+ function normalizeId(id) {
122
+ if (typeof id === "string" || typeof id === "number" || id === null) return id;
123
+ return null; // 不正な id 型は null に丸める (応答は返せるように)
124
+ }
125
+
126
+ /**
127
+ * 1 メッセージを処理して応答オブジェクト (通知なら null) を返す。
128
+ * メソッド実行は `invoke(method, params)` に委譲 (Daemon が registry 解決 + 直列化 + 認可を仕込む)。
129
+ * この関数は throw しない。
130
+ *
131
+ * @param {string} raw 1 行の生メッセージ
132
+ * @param {(method:string, params:any) => Promise<any>} invoke
133
+ * @returns {Promise<object|null>} 応答 (通知なら null)
134
+ */
135
+ export async function handleMessage(raw, invoke) {
136
+ const c = classify(raw);
137
+ switch (c.type) {
138
+ case "parse-error":
139
+ // NDJSON は 1 行 1 JSON。pretty-print (改行入り) すると行ごとに parse 失敗するので明示する。
140
+ return makeError(null, RPC.PARSE_ERROR, "Parse error (1 行 1 JSON で送ること。pretty-print 不可)", KIND.BAD_PARAMS);
141
+ case "batch":
142
+ return makeError(null, RPC.INVALID_REQUEST, "Batch requests are not supported", KIND.BAD_PARAMS);
143
+ case "invalid":
144
+ return makeError(c.id, RPC.INVALID_REQUEST, "Invalid Request", KIND.BAD_PARAMS);
145
+ case "notification":
146
+ // 通知: 実行はするが応答は一切返さない (エラーでも沈黙)。
147
+ try { await invoke(c.method, c.params); } catch { /* 通知はサイレント */ }
148
+ return null;
149
+ case "request":
150
+ try {
151
+ const result = await invoke(c.method, c.params);
152
+ return makeResult(c.id, result);
153
+ } catch (err) {
154
+ return errorFromThrow(c.id, err);
155
+ }
156
+ default:
157
+ return makeError(null, RPC.INTERNAL_ERROR, "internal", KIND.INTERNAL);
158
+ }
159
+ }
160
+
161
+ /** サーバ発のイベント通知フレームを作る (予約名 `event.<topic>`)。 */
162
+ export function makeEvent(topic, payload) {
163
+ return { jsonrpc: "2.0", method: `event.${topic}`, params: payload };
164
+ }
@@ -0,0 +1,226 @@
1
+ // メソッドレジストリ — デーモンが公開する全 RPC メソッドの単一カタログ。
2
+ //
3
+ // 設計: 「全機能を一様に公開」。名前空間 op (org/company/access/iot/presetir/schedule) は
4
+ // 各モジュールの `NAMESPACE_OPS`(公開 op の単一の真実) から **自動生成** し、
5
+ // ハンドラは一様に `hub[ns][op](params)`。位置引数を持つ高レベル op だけ明示の薄い表で橋渡し。
6
+ // 危険な少数 (IR learn=Hub3 グローバル mode、events 購読) は daemon に委譲する特別扱い。
7
+ //
8
+ // ハンドラ署名: `handler({ hub, params, conn, daemon }) => Promise<result>`
9
+ // - hub: 常駐 SesameHub3
10
+ // - params: JSON-RPC params (オブジェクト)
11
+ // - conn: 呼び出し元 Connection (events/lease 用)
12
+ // - daemon: Daemon (購読/リース/authState 用)
13
+
14
+ import { readFileSync } from "node:fs";
15
+ import { RpcError, RPC, KIND, CONTRACT_VERSION } from "./jsonrpc.js";
16
+ import * as schedule from "../schedule.js";
17
+ import * as org from "../org.js";
18
+ import * as company from "../company.js";
19
+ import * as access from "../access.js";
20
+ import * as iot from "../iot.js";
21
+ import * as presetir from "../presetir.js";
22
+
23
+ // ビルド時に .d.ts から抽出した名前空間 op の param 型 (scripts/gen-rpc-schema.mjs)。
24
+ // これにより discover が「名前空間 op の引数名・型」を自己記述できる。
25
+ let GEN_PARAMS = {};
26
+ try {
27
+ GEN_PARAMS = JSON.parse(readFileSync(new URL("./rpc-params.generated.json", import.meta.url), "utf8"));
28
+ } catch { /* 未生成なら空 (フォールバックの (params) になる) */ }
29
+
30
+ // 自動公開する名前空間 (getter 名 → モジュール)。getter は SesameHub3 のプロパティ名と一致。
31
+ const NS_MODULES = { schedule, org, company, access, iot, presetir };
32
+
33
+ /** params 必須キーの存在チェック (軽量バリデータ)。欠落は bad_params。 */
34
+ function need(params, keys) {
35
+ for (const k of keys) {
36
+ if (params[k] === undefined || params[k] === null || params[k] === "") {
37
+ throw new RpcError(`missing required param: ${k}`, { code: RPC.INVALID_PARAMS, kind: KIND.BAD_PARAMS });
38
+ }
39
+ }
40
+ }
41
+
42
+ /** クラウド接続が要る op の前段ガード (未認証/未接続を明示エラーに)。 */
43
+ function requireAuth(daemon) {
44
+ if (daemon.authState === "expired") {
45
+ throw new RpcError("not authenticated — run: sesame login <email> (then restart the daemon)", { kind: KIND.NOT_AUTHENTICATED });
46
+ }
47
+ if (!daemon.hub.connected) {
48
+ throw new RpcError("cloud not connected", { kind: KIND.CONNECTION_LOST });
49
+ }
50
+ }
51
+
52
+ /**
53
+ * 位置引数を持つ高レベル op の薄い橋渡し表。
54
+ * 各 entry: { summary, params:[{name,required,desc}], result, handler }
55
+ */
56
+ function topLevelEntries() {
57
+ /** name (config) もしくは {deviceUUID,secretKey} で lock op を発行する共通 helper。 */
58
+ const lockOp = (verb) => ({ hub, params, daemon }) => {
59
+ requireAuth(daemon);
60
+ if (params.deviceUUID) {
61
+ need(params, ["deviceUUID", "secretKey"]);
62
+ return hub[`${verb}Device`]({ deviceUUID: params.deviceUUID, secretKey: params.secretKey });
63
+ }
64
+ need(params, ["name"]);
65
+ return hub[verb](params.name);
66
+ };
67
+ const S = { type: "string" };
68
+ const N = { type: "number" };
69
+ const lockParams = [
70
+ { name: "name", required: false, desc: "config 上のロック名 (deviceUUID 指定時は不要)", schema: S },
71
+ { name: "deviceUUID", required: false, desc: "直接指定する deviceUUID", schema: S },
72
+ { name: "secretKey", required: false, desc: "deviceUUID 指定時の 32hex 共通鍵", schema: S },
73
+ ];
74
+
75
+ return {
76
+ "status": {
77
+ summary: "デーモン状態 (接続/認証/ユーザ/契約版)",
78
+ params: [], result: "{ connected, authState, subUUID, contractVersion }",
79
+ // contractVersion: 消費者が major 不一致を fail-fast できるよう毎回返す。
80
+ handler: ({ hub, daemon }) => ({ connected: hub.connected, authState: daemon.authState, subUUID: hub.subUUID, contractVersion: CONTRACT_VERSION }),
81
+ },
82
+ "account.whoami": {
83
+ summary: "ログインユーザ情報 (biz3GetLoginUser)",
84
+ params: [], result: "customerInfo 等",
85
+ handler: ({ hub, daemon }) => { requireAuth(daemon); return hub.getLoginUser(); },
86
+ },
87
+ "lock.lock": { summary: "施錠", params: lockParams, result: "状態 push", handler: lockOp("lock") },
88
+ "lock.unlock": { summary: "解錠", params: lockParams, result: "状態 push", handler: lockOp("unlock") },
89
+ "lock.toggle": { summary: "トグル", params: lockParams, result: "状態 push", handler: lockOp("toggle") },
90
+ "lock.click": { summary: "Bot クリック", params: lockParams, result: "状態 push", handler: lockOp("botClick") },
91
+ "lock.status": {
92
+ summary: "デバイスの現在状態 (lock state/battery)",
93
+ params: [{ name: "deviceUUID", required: true, desc: "対象 deviceUUID", schema: S }], result: "status",
94
+ handler: ({ hub, params, daemon }) => { requireAuth(daemon); need(params, ["deviceUUID"]); return hub.getDeviceStatus(params.deviceUUID); },
95
+ },
96
+ "devices.list": {
97
+ summary: "全 SESAME デバイス一覧 (secretKey 含む)",
98
+ params: [], result: "device[]",
99
+ handler: ({ hub, daemon }) => { requireAuth(daemon); return hub.listDevices(); },
100
+ },
101
+ "device.history": {
102
+ summary: "開閉履歴",
103
+ params: [{ name: "deviceUUID", required: true, schema: S }, { name: "pageSize", required: false, schema: N }], result: "history[]",
104
+ handler: ({ hub, params, daemon }) => { requireAuth(daemon); need(params, ["deviceUUID"]); return hub.getDeviceHistory([params.deviceUUID], params.pageSize); },
105
+ },
106
+ "device.battery": {
107
+ summary: "電池履歴",
108
+ params: [{ name: "deviceUUID", required: true, schema: S }, { name: "pageSize", required: false, schema: N }], result: "battery[]",
109
+ handler: ({ hub, params, daemon }) => { requireAuth(daemon); need(params, ["deviceUUID"]); return hub.getDeviceBattery(params.deviceUUID, { pageSize: params.pageSize }); },
110
+ },
111
+ "ir.send": {
112
+ summary: "IR リモコンのキーを送信",
113
+ params: [{ name: "remote", required: false, desc: "リモコン名 (省略時 default)", schema: S }, { name: "key", required: true, desc: "キー名 or keyUUID", schema: S }],
114
+ result: "送信応答",
115
+ handler: ({ hub, params, daemon }) => { requireAuth(daemon); need(params, ["key"]); return hub.send(params.remote ?? null, params.key); },
116
+ },
117
+ "ir.listKeys": {
118
+ summary: "リモコンの学習済みキー一覧",
119
+ params: [{ name: "remote", required: false, schema: S }], result: "key[]",
120
+ handler: ({ hub, params, daemon }) => { requireAuth(daemon); return hub.listKeys(params.remote ?? null); },
121
+ },
122
+ };
123
+ }
124
+
125
+ /** events.subscribe / unsubscribe (daemon に委譲)。 */
126
+ function eventEntries() {
127
+ const TOPICS = ["lockState", "deviceUpdate"];
128
+ return {
129
+ "events.subscribe": {
130
+ summary: `イベント購読 (topics: ${TOPICS.join("/")})。以後 event.<topic> 通知が届く`,
131
+ params: [{ name: "topics", required: true, desc: `購読する topic 配列 (${TOPICS.join("/")})` }],
132
+ result: "{ subscribed: string[] }",
133
+ handler: ({ params, conn, daemon }) => {
134
+ if (conn?.ephemeral) {
135
+ throw new RpcError("events.* は持続接続が必要です (UDS/WebSocket/SSE/gRPC Subscribe)。HTTP POST /rpc や gRPC Invoke では購読できません",
136
+ { code: RPC.INVALID_REQUEST, kind: KIND.BAD_PARAMS });
137
+ }
138
+ const topics = Array.isArray(params.topics) ? params.topics : [params.topics];
139
+ const bad = topics.filter((t) => !TOPICS.includes(t));
140
+ if (bad.length) throw new RpcError(`unknown topic(s): ${bad.join(",")}`, { code: RPC.INVALID_PARAMS, kind: KIND.BAD_PARAMS });
141
+ return daemon.subscribe(conn, topics);
142
+ },
143
+ },
144
+ "events.unsubscribe": {
145
+ summary: "イベント購読解除",
146
+ params: [{ name: "topics", required: true }],
147
+ result: "{ subscribed: string[] }",
148
+ handler: ({ params, conn, daemon }) => {
149
+ const topics = Array.isArray(params.topics) ? params.topics : [params.topics];
150
+ return daemon.unsubscribe(conn, topics);
151
+ },
152
+ },
153
+ };
154
+ }
155
+
156
+ /**
157
+ * 全エントリを {name → entry} で構築する。
158
+ * @returns {Map<string, {summary:string, params:any[], result:string, handler:Function, namespace?:string}>}
159
+ */
160
+ export function buildRegistry() {
161
+ const reg = new Map();
162
+
163
+ // 1) 名前空間 op を NAMESPACE_OPS から自動公開。
164
+ for (const [ns, mod] of Object.entries(NS_MODULES)) {
165
+ const ops = Array.isArray(mod.NAMESPACE_OPS) ? mod.NAMESPACE_OPS : [];
166
+ for (const op of ops) {
167
+ const gen = GEN_PARAMS[`${ns}.${op}`];
168
+ // 抽出済みなら実 param (名前/required/型) を、無ければ汎用 (params) を出す。
169
+ const params = gen
170
+ ? gen.map((p) => ({ name: p.name, required: p.required, desc: p.tsType, schema: p.schema }))
171
+ : [{ name: "(params)", required: false, desc: "biz3 op の params をそのまま渡す" }];
172
+ reg.set(`${ns}.${op}`, {
173
+ summary: `${ns} の ${op} (自動公開)`,
174
+ params,
175
+ result: "op 応答",
176
+ namespace: ns,
177
+ handler: ({ hub, params: p, daemon }) => { requireAuth(daemon); return hub[ns][op](p); },
178
+ });
179
+ }
180
+ }
181
+
182
+ // 2) 位置引数を持つ高レベル op の明示表。
183
+ for (const [name, entry] of Object.entries(topLevelEntries())) reg.set(name, entry);
184
+
185
+ // 3) events 特別扱い。
186
+ for (const [name, entry] of Object.entries(eventEntries())) reg.set(name, entry);
187
+
188
+ return reg;
189
+ }
190
+
191
+ /**
192
+ * OpenRPC 文書を組み立てる (rpc.discover 応答)。param スキーマは初期は粗いが
193
+ * 「何の op が在るか」は完全網羅する。secretKey 等の実値 example は載せない。
194
+ */
195
+ export function buildOpenRpcDoc(reg, version) {
196
+ const methods = [];
197
+ for (const [name, e] of reg) {
198
+ methods.push({
199
+ name,
200
+ summary: e.summary,
201
+ params: (e.params || []).map((p) => ({
202
+ name: p.name,
203
+ required: !!p.required,
204
+ description: p.desc || "",
205
+ schema: p.schema || {}, // 抽出できた型のみ。不明は {} (嘘の型を主張しない)
206
+ })),
207
+ result: { name: "result", schema: { description: e.result || "", type: "object" } },
208
+ });
209
+ }
210
+ // サーバ発イベントも記述 (予約名 event.<topic>)。
211
+ return {
212
+ openrpc: "1.2.6",
213
+ info: {
214
+ title: "sesame serve",
215
+ version, // パッケージ version (無害な変更でも上がる)
216
+ "x-contractVersion": CONTRACT_VERSION, // 機械契約の SemVer (破壊的変更でだけ major)
217
+ description: "SESAME 制御の言語非依存 JSON-RPC バックエンド",
218
+ },
219
+ methods,
220
+ "x-events": [
221
+ { name: "event.lockState", description: "ロック状態変化 push (events.subscribe lockState)" },
222
+ { name: "event.deviceUpdate", description: "デバイス状態更新 push (events.subscribe deviceUpdate)" },
223
+ { name: "event.ready", description: "起動完了通知 (stdio framing のみ。接続直後に 1 回。他経路では飛ばない)" },
224
+ ],
225
+ };
226
+ }