tailii-host 0.1.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.
- package/LICENSE +21 -0
- package/README.md +51 -0
- package/dist/apnsConfigStore.d.ts +22 -0
- package/dist/apnsConfigStore.js +50 -0
- package/dist/apnsConfigStore.js.map +1 -0
- package/dist/apnsJwtProvider.d.ts +50 -0
- package/dist/apnsJwtProvider.js +158 -0
- package/dist/apnsJwtProvider.js.map +1 -0
- package/dist/apnsSender.d.ts +69 -0
- package/dist/apnsSender.js +155 -0
- package/dist/apnsSender.js.map +1 -0
- package/dist/approvalPushNotifier.d.ts +100 -0
- package/dist/approvalPushNotifier.js +236 -0
- package/dist/approvalPushNotifier.js.map +1 -0
- package/dist/broker.d.ts +24 -0
- package/dist/broker.js +197 -0
- package/dist/broker.js.map +1 -0
- package/dist/chatTailController.d.ts +87 -0
- package/dist/chatTailController.js +292 -0
- package/dist/chatTailController.js.map +1 -0
- package/dist/claudeSessionStore.d.ts +15 -0
- package/dist/claudeSessionStore.js +201 -0
- package/dist/claudeSessionStore.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +53 -0
- package/dist/cli.js.map +1 -0
- package/dist/codexRolloutTailer.d.ts +56 -0
- package/dist/codexRolloutTailer.js +303 -0
- package/dist/codexRolloutTailer.js.map +1 -0
- package/dist/codexSessionStore.d.ts +23 -0
- package/dist/codexSessionStore.js +274 -0
- package/dist/codexSessionStore.js.map +1 -0
- package/dist/codexUsage.d.ts +16 -0
- package/dist/codexUsage.js +144 -0
- package/dist/codexUsage.js.map +1 -0
- package/dist/deviceTokenStore.d.ts +38 -0
- package/dist/deviceTokenStore.js +73 -0
- package/dist/deviceTokenStore.js.map +1 -0
- package/dist/dirLister.d.ts +9 -0
- package/dist/dirLister.js +98 -0
- package/dist/dirLister.js.map +1 -0
- package/dist/doctor.d.ts +36 -0
- package/dist/doctor.js +162 -0
- package/dist/doctor.js.map +1 -0
- package/dist/engine.d.ts +85 -0
- package/dist/engine.js +962 -0
- package/dist/engine.js.map +1 -0
- package/dist/hook.d.ts +67 -0
- package/dist/hook.js +511 -0
- package/dist/hook.js.map +1 -0
- package/dist/hookSettings.d.ts +40 -0
- package/dist/hookSettings.js +128 -0
- package/dist/hookSettings.js.map +1 -0
- package/dist/idleReaper.d.ts +13 -0
- package/dist/idleReaper.js +30 -0
- package/dist/idleReaper.js.map +1 -0
- package/dist/imageService.d.ts +53 -0
- package/dist/imageService.js +280 -0
- package/dist/imageService.js.map +1 -0
- package/dist/kick.d.ts +8 -0
- package/dist/kick.js +60 -0
- package/dist/kick.js.map +1 -0
- package/dist/launch.d.ts +107 -0
- package/dist/launch.js +396 -0
- package/dist/launch.js.map +1 -0
- package/dist/legacyHomeMigration.d.ts +1 -0
- package/dist/legacyHomeMigration.js +23 -0
- package/dist/legacyHomeMigration.js.map +1 -0
- package/dist/lineWriter.d.ts +9 -0
- package/dist/lineWriter.js +19 -0
- package/dist/lineWriter.js.map +1 -0
- package/dist/mdnsAdvertiser.d.ts +46 -0
- package/dist/mdnsAdvertiser.js +268 -0
- package/dist/mdnsAdvertiser.js.map +1 -0
- package/dist/observationLog.d.ts +54 -0
- package/dist/observationLog.js +108 -0
- package/dist/observationLog.js.map +1 -0
- package/dist/pairingCode.d.ts +60 -0
- package/dist/pairingCode.js +282 -0
- package/dist/pairingCode.js.map +1 -0
- package/dist/paths.d.ts +13 -0
- package/dist/paths.js +46 -0
- package/dist/paths.js.map +1 -0
- package/dist/permissionMode.d.ts +8 -0
- package/dist/permissionMode.js +32 -0
- package/dist/permissionMode.js.map +1 -0
- package/dist/planUsageFetcher.d.ts +35 -0
- package/dist/planUsageFetcher.js +172 -0
- package/dist/planUsageFetcher.js.map +1 -0
- package/dist/protocol.d.ts +329 -0
- package/dist/protocol.js +544 -0
- package/dist/protocol.js.map +1 -0
- package/dist/pushTokenCommand.d.ts +9 -0
- package/dist/pushTokenCommand.js +85 -0
- package/dist/pushTokenCommand.js.map +1 -0
- package/dist/pushTypes.d.ts +41 -0
- package/dist/pushTypes.js +68 -0
- package/dist/pushTypes.js.map +1 -0
- package/dist/sendLog.d.ts +14 -0
- package/dist/sendLog.js +64 -0
- package/dist/sendLog.js.map +1 -0
- package/dist/sessionActivityProvider.d.ts +7 -0
- package/dist/sessionActivityProvider.js +44 -0
- package/dist/sessionActivityProvider.js.map +1 -0
- package/dist/sessionIdleTracker.d.ts +19 -0
- package/dist/sessionIdleTracker.js +42 -0
- package/dist/sessionIdleTracker.js.map +1 -0
- package/dist/sessionListService.d.ts +27 -0
- package/dist/sessionListService.js +85 -0
- package/dist/sessionListService.js.map +1 -0
- package/dist/sessionMetadataStore.d.ts +33 -0
- package/dist/sessionMetadataStore.js +122 -0
- package/dist/sessionMetadataStore.js.map +1 -0
- package/dist/sessionSearch.d.ts +29 -0
- package/dist/sessionSearch.js +139 -0
- package/dist/sessionSearch.js.map +1 -0
- package/dist/setup.d.ts +83 -0
- package/dist/setup.js +412 -0
- package/dist/setup.js.map +1 -0
- package/dist/sleep.d.ts +4 -0
- package/dist/sleep.js +25 -0
- package/dist/sleep.js.map +1 -0
- package/dist/socketPath.d.ts +12 -0
- package/dist/socketPath.js +51 -0
- package/dist/socketPath.js.map +1 -0
- package/dist/subagentTailer.d.ts +15 -0
- package/dist/subagentTailer.js +399 -0
- package/dist/subagentTailer.js.map +1 -0
- package/dist/tmux.d.ts +62 -0
- package/dist/tmux.js +163 -0
- package/dist/tmux.js.map +1 -0
- package/dist/transcriptTailer.d.ts +61 -0
- package/dist/transcriptTailer.js +606 -0
- package/dist/transcriptTailer.js.map +1 -0
- package/dist/usageAggregator.d.ts +12 -0
- package/dist/usageAggregator.js +55 -0
- package/dist/usageAggregator.js.map +1 -0
- package/dist/version.d.ts +10 -0
- package/dist/version.js +45 -0
- package/dist/version.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { ApnsConfigStore } from "./apnsConfigStore.js";
|
|
2
|
+
import { type APNsJWTProviding } from "./apnsJwtProvider.js";
|
|
3
|
+
import { type ApnsSending } from "./apnsSender.js";
|
|
4
|
+
import { type DeviceTokenStoring } from "./deviceTokenStore.js";
|
|
5
|
+
import { type PushError } from "./pushTypes.js";
|
|
6
|
+
/** connect 不能契機に push 送信を求める1件のリクエスト(最小情報のみ, 2.3/6.5)。 */
|
|
7
|
+
export interface ApprovalPushRequest {
|
|
8
|
+
approvalId: string;
|
|
9
|
+
tool: string;
|
|
10
|
+
session: string;
|
|
11
|
+
}
|
|
12
|
+
/** push 送信を見送った理由。 */
|
|
13
|
+
export type SkipReason = "no-config" | "no-token" | "rate-limited";
|
|
14
|
+
/** notify(...) の結果。送信可否に関わらず必ず返る(gate 非阻害)。 */
|
|
15
|
+
export type PushOutcome = {
|
|
16
|
+
kind: "sent";
|
|
17
|
+
} | {
|
|
18
|
+
kind: "skipped";
|
|
19
|
+
reason: SkipReason;
|
|
20
|
+
} | {
|
|
21
|
+
kind: "failed";
|
|
22
|
+
reason: PushError;
|
|
23
|
+
};
|
|
24
|
+
/** push 統括が観測ログへ記録する契約(送信/見送り/失敗)。秘密・diff は運ばない(6.5)。 */
|
|
25
|
+
export interface PushObserving {
|
|
26
|
+
recordSent(id: string, tool: string, session: string): void;
|
|
27
|
+
recordSkipped(id: string, reason: string, session: string): void;
|
|
28
|
+
recordFailed(id: string, reason: string, session: string): void;
|
|
29
|
+
}
|
|
30
|
+
/** ObservationLog を包む本番 PushObserving 実体。追記失敗は握り潰す(gate 非阻害)。 */
|
|
31
|
+
export declare class ObservationLogPushObserver implements PushObserving {
|
|
32
|
+
private readonly log;
|
|
33
|
+
private readonly now;
|
|
34
|
+
constructor(base?: string, now?: () => number);
|
|
35
|
+
recordSent(id: string, tool: string, session: string): void;
|
|
36
|
+
recordSkipped(id: string, reason: string, session: string): void;
|
|
37
|
+
recordFailed(id: string, reason: string, session: string): void;
|
|
38
|
+
}
|
|
39
|
+
/** バースト抑制の既定最小間隔(秒)。同一セッションへこの間隔内の連続送信は間引く(7.2)。 */
|
|
40
|
+
export declare const DEFAULT_MIN_INTERVAL_SECONDS = 30;
|
|
41
|
+
/** apns-expiration の既定(秒)。awareness 用に数分。 */
|
|
42
|
+
export declare const DEFAULT_EXPIRATION_SECONDS = 300;
|
|
43
|
+
export interface ApprovalPushNotifierDeps {
|
|
44
|
+
configStore: ApnsConfigStore;
|
|
45
|
+
tokenStore: DeviceTokenStoring;
|
|
46
|
+
jwtProvider: APNsJWTProviding;
|
|
47
|
+
sender: ApnsSending;
|
|
48
|
+
observer: PushObserving;
|
|
49
|
+
/** send-log.json の base。省略時は `~/.tailii/apns`。 */
|
|
50
|
+
sendLogBase?: string;
|
|
51
|
+
/** バースト抑制の最小間隔(秒)。省略時 30s。 */
|
|
52
|
+
minInterval?: number;
|
|
53
|
+
/** apns-expiration(秒)。省略時 300s。 */
|
|
54
|
+
expiration?: number;
|
|
55
|
+
/** 現在時刻(Unix 秒)を返す関数。rate-limit の決定化に使う。 */
|
|
56
|
+
now?: () => number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* connect 不能契機に APNs 送信を統括する実体(バースト抑制・観測記録・gate 非阻害)。
|
|
60
|
+
* 協力者は注入し、テストは mock/spy を差し込む。now / send-log base を注入して rate-limit を決定化する。
|
|
61
|
+
*/
|
|
62
|
+
export declare class ApprovalPushNotifier {
|
|
63
|
+
private readonly configStore;
|
|
64
|
+
private readonly tokenStore;
|
|
65
|
+
private readonly jwtProvider;
|
|
66
|
+
private readonly sender;
|
|
67
|
+
private readonly observer;
|
|
68
|
+
private readonly sendLog;
|
|
69
|
+
private readonly minInterval;
|
|
70
|
+
private readonly expiration;
|
|
71
|
+
private readonly now;
|
|
72
|
+
constructor(deps: ApprovalPushNotifierDeps);
|
|
73
|
+
/**
|
|
74
|
+
* connect 不能契機の送信を統括する。timeLimit 内で完了を試み、超過しても PushOutcome に
|
|
75
|
+
* 畳んで返す(gate 非阻害)。
|
|
76
|
+
*/
|
|
77
|
+
notify(req: ApprovalPushRequest, timeLimitMs: number): Promise<PushOutcome>;
|
|
78
|
+
/** timeLimit を除いた送信本体。 */
|
|
79
|
+
private performNotify;
|
|
80
|
+
/**
|
|
81
|
+
* APNs へ送信し、ExpiredProviderToken 時は JWT 無効化 → 再署名 → 1回だけ再試行する(6.2)。
|
|
82
|
+
* Unregistered 時は device token を削除する(6.3)。
|
|
83
|
+
*/
|
|
84
|
+
private sendWithRetry;
|
|
85
|
+
/** payload と JWT から ApnsRequest を組み立てる。collapse-id は approvalId で一意化(7.2/7.3)。 */
|
|
86
|
+
private makeRequest;
|
|
87
|
+
}
|
|
88
|
+
/** PushError を観測ログ用の短い理由コードへ写像する(秘密を出さない, 6.5)。 */
|
|
89
|
+
export declare function reasonCode(error: PushError): string;
|
|
90
|
+
/**
|
|
91
|
+
* connect 不能時に用いる本番 push notifier を構築する(Swift 版 makeProductionPushNotifier 相当)。
|
|
92
|
+
*
|
|
93
|
+
* ApnsConfigStore / DeviceTokenStore / APNsJWTProvider / APNsSender(Http2HttpClient) /
|
|
94
|
+
* ObservationLogPushObserver を既定パス(`~/.tailii/apns`, ObservationLog 既定)で束ねる。
|
|
95
|
+
* 実際に送るかは notifier 内部が config/token の有無で判断する(未設定なら送らず観測記録, 3.5/6.4)。
|
|
96
|
+
*
|
|
97
|
+
* 返り値は hook.ts の `ApprovalPushNotifier` 型(`(req, timeLimitMs) => Promise<void>`)に適合する
|
|
98
|
+
* アダプタ。送信可否(PushOutcome)は deny 判断に影響しないため捨てる(観測は内部で完結)。
|
|
99
|
+
*/
|
|
100
|
+
export declare function makeProductionPushNotifier(): (request: ApprovalPushRequest, timeLimitMs: number) => Promise<void>;
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
// approvalPushNotifier.ts
|
|
2
|
+
// tailii (TS host) — 承認 push 統括(Mac-owned)
|
|
3
|
+
// Swift 版 ApprovalPushNotifier.swift の移植。
|
|
4
|
+
//
|
|
5
|
+
// hook の connect 不能契機に、時間上限内で APNs 送信を統括する(Task 2.3 / Req 2.1):
|
|
6
|
+
// 1. config 読込 → 欠落なら skipped(no-config) + pushSkipped 観測(6.4)。
|
|
7
|
+
// 2. device token 読込 → 欠落なら skipped(no-token) + pushSkipped 観測(3.5)。
|
|
8
|
+
// 3. バースト抑制(7.2): send-log.json を見て最小間隔(既定 30s)内の同一セッション向けは
|
|
9
|
+
// skipped(rate-limited) + pushSkipped 観測。
|
|
10
|
+
// 4. 最小 payload(tool·session·approvalId のみ。diff/cwd/秘密なし)を組む(2.3/6.5)。
|
|
11
|
+
// 5. JWT を取得(未設定/署名失敗は failed に畳む)。
|
|
12
|
+
// 6. 送信(+ ExpiredProviderToken 時の再署名・1回再試行 / Unregistered 時の token 削除)。
|
|
13
|
+
// 7. 成功 → pushSent 観測 → sent。失敗 → pushFailed 観測 → failed。
|
|
14
|
+
//
|
|
15
|
+
// gate 非阻害: config/token 欠落・署名失敗・送信失敗・timeLimit 超過のいずれでも例外を
|
|
16
|
+
// hook に伝播させず PushOutcome に畳む。timeLimit 内に完了しなければ failed(transport("timeout"))。
|
|
17
|
+
// 相互排他(7.1)は呼び出し側(connect 不能ブランチのみ呼ぶ)で担保する。
|
|
18
|
+
//
|
|
19
|
+
// ネットワーク・時刻は全て注入式(テストは実物を叩かない)。秘密・diff は payload/ログに載せない。
|
|
20
|
+
import { ApnsConfigStore } from "./apnsConfigStore.js";
|
|
21
|
+
import { APNsJWTProvider } from "./apnsJwtProvider.js";
|
|
22
|
+
import { APNsSender, Http2HttpClient } from "./apnsSender.js";
|
|
23
|
+
import { DeviceTokenStore } from "./deviceTokenStore.js";
|
|
24
|
+
import { ObservationLog } from "./observationLog.js";
|
|
25
|
+
import { defaultApnsBase, pushPayloadBody } from "./pushTypes.js";
|
|
26
|
+
import { SendLog } from "./sendLog.js";
|
|
27
|
+
/** ObservationLog を包む本番 PushObserving 実体。追記失敗は握り潰す(gate 非阻害)。 */
|
|
28
|
+
export class ObservationLogPushObserver {
|
|
29
|
+
log;
|
|
30
|
+
now;
|
|
31
|
+
constructor(base, now = () => Math.floor(Date.now() / 1000)) {
|
|
32
|
+
this.log = new ObservationLog(base);
|
|
33
|
+
this.now = now;
|
|
34
|
+
}
|
|
35
|
+
recordSent(id, tool, session) {
|
|
36
|
+
try {
|
|
37
|
+
this.log.append({ kind: "pushSent", id, tool }, session, this.now());
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
/* 副次作業のため無視 */
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
recordSkipped(id, reason, session) {
|
|
44
|
+
try {
|
|
45
|
+
this.log.append({ kind: "pushSkipped", id, reason }, session, this.now());
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
/* 副次作業のため無視 */
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
recordFailed(id, reason, session) {
|
|
52
|
+
try {
|
|
53
|
+
this.log.append({ kind: "pushFailed", id, reason }, session, this.now());
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
/* 副次作業のため無視 */
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// MARK: - ApprovalPushNotifier
|
|
61
|
+
/** バースト抑制の既定最小間隔(秒)。同一セッションへこの間隔内の連続送信は間引く(7.2)。 */
|
|
62
|
+
export const DEFAULT_MIN_INTERVAL_SECONDS = 30;
|
|
63
|
+
/** apns-expiration の既定(秒)。awareness 用に数分。 */
|
|
64
|
+
export const DEFAULT_EXPIRATION_SECONDS = 300;
|
|
65
|
+
/**
|
|
66
|
+
* connect 不能契機に APNs 送信を統括する実体(バースト抑制・観測記録・gate 非阻害)。
|
|
67
|
+
* 協力者は注入し、テストは mock/spy を差し込む。now / send-log base を注入して rate-limit を決定化する。
|
|
68
|
+
*/
|
|
69
|
+
export class ApprovalPushNotifier {
|
|
70
|
+
configStore;
|
|
71
|
+
tokenStore;
|
|
72
|
+
jwtProvider;
|
|
73
|
+
sender;
|
|
74
|
+
observer;
|
|
75
|
+
sendLog;
|
|
76
|
+
minInterval;
|
|
77
|
+
expiration;
|
|
78
|
+
now;
|
|
79
|
+
constructor(deps) {
|
|
80
|
+
this.configStore = deps.configStore;
|
|
81
|
+
this.tokenStore = deps.tokenStore;
|
|
82
|
+
this.jwtProvider = deps.jwtProvider;
|
|
83
|
+
this.sender = deps.sender;
|
|
84
|
+
this.observer = deps.observer;
|
|
85
|
+
this.sendLog = new SendLog(deps.sendLogBase);
|
|
86
|
+
this.minInterval = deps.minInterval ?? DEFAULT_MIN_INTERVAL_SECONDS;
|
|
87
|
+
this.expiration = deps.expiration ?? DEFAULT_EXPIRATION_SECONDS;
|
|
88
|
+
this.now = deps.now ?? (() => Math.floor(Date.now() / 1000));
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* connect 不能契機の送信を統括する。timeLimit 内で完了を試み、超過しても PushOutcome に
|
|
92
|
+
* 畳んで返す(gate 非阻害)。
|
|
93
|
+
*/
|
|
94
|
+
async notify(req, timeLimitMs) {
|
|
95
|
+
const timeoutOutcome = {
|
|
96
|
+
kind: "failed",
|
|
97
|
+
reason: { kind: "transport", detail: "timeout" },
|
|
98
|
+
};
|
|
99
|
+
let timer;
|
|
100
|
+
const timeout = new Promise((resolve) => {
|
|
101
|
+
timer = setTimeout(() => resolve(timeoutOutcome), Math.max(0, timeLimitMs));
|
|
102
|
+
});
|
|
103
|
+
try {
|
|
104
|
+
return await Promise.race([this.performNotify(req), timeout]);
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
if (timer !== undefined)
|
|
108
|
+
clearTimeout(timer);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** timeLimit を除いた送信本体。 */
|
|
112
|
+
async performNotify(req) {
|
|
113
|
+
// 1. config 未設定 → skipped(no-config)(6.4)
|
|
114
|
+
const config = this.configStore.load();
|
|
115
|
+
if (config === null) {
|
|
116
|
+
this.observer.recordSkipped(req.approvalId, "no-config", req.session);
|
|
117
|
+
return { kind: "skipped", reason: "no-config" };
|
|
118
|
+
}
|
|
119
|
+
// 2. device token 未保存 → skipped(no-token)(3.5)
|
|
120
|
+
const tokenRecord = this.tokenStore.load();
|
|
121
|
+
if (tokenRecord === null) {
|
|
122
|
+
this.observer.recordSkipped(req.approvalId, "no-token", req.session);
|
|
123
|
+
return { kind: "skipped", reason: "no-token" };
|
|
124
|
+
}
|
|
125
|
+
// 3. バースト抑制(7.2): 同一セッションへ最小間隔内の連続送信を間引く。
|
|
126
|
+
const currentTime = this.now();
|
|
127
|
+
const last = this.sendLog.lastSent(req.session);
|
|
128
|
+
if (last !== null && currentTime - last < this.minInterval && currentTime >= last) {
|
|
129
|
+
this.observer.recordSkipped(req.approvalId, "rate-limited", req.session);
|
|
130
|
+
return { kind: "skipped", reason: "rate-limited" };
|
|
131
|
+
}
|
|
132
|
+
// 5. JWT を取得(未設定/署名失敗は failed に畳む)。
|
|
133
|
+
let jwt;
|
|
134
|
+
try {
|
|
135
|
+
jwt = this.jwtProvider.currentToken();
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
this.observer.recordFailed(req.approvalId, "jwt-unavailable", req.session);
|
|
139
|
+
return { kind: "failed", reason: { kind: "transport", detail: "jwt-unavailable" } };
|
|
140
|
+
}
|
|
141
|
+
// 6. 送信(+ ExpiredProviderToken 時の再署名・1回再試行 / Unregistered 時の token 削除)。
|
|
142
|
+
const result = await this.sendWithRetry({ approvalId: req.approvalId, tool: req.tool, session: req.session }, tokenRecord, config.topic, jwt);
|
|
143
|
+
if (result.ok) {
|
|
144
|
+
// 送信成功 → send-log 更新(次回のバースト抑制に効かせる)+ pushSent 観測。
|
|
145
|
+
this.sendLog.record(req.session, currentTime);
|
|
146
|
+
this.observer.recordSent(req.approvalId, req.tool, req.session);
|
|
147
|
+
return { kind: "sent" };
|
|
148
|
+
}
|
|
149
|
+
this.observer.recordFailed(req.approvalId, reasonCode(result.error), req.session);
|
|
150
|
+
return { kind: "failed", reason: result.error };
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* APNs へ送信し、ExpiredProviderToken 時は JWT 無効化 → 再署名 → 1回だけ再試行する(6.2)。
|
|
154
|
+
* Unregistered 時は device token を削除する(6.3)。
|
|
155
|
+
*/
|
|
156
|
+
async sendWithRetry(req, token, topic, jwt) {
|
|
157
|
+
const first = await this.sender.send(this.makeRequest(req, token, topic, jwt));
|
|
158
|
+
if (first.ok)
|
|
159
|
+
return { ok: true };
|
|
160
|
+
if (first.error.kind === "expiredProviderToken") {
|
|
161
|
+
// JWT を無効化し再署名して1回だけ再試行(6.2)。
|
|
162
|
+
this.jwtProvider.invalidate();
|
|
163
|
+
let fresh;
|
|
164
|
+
try {
|
|
165
|
+
fresh = this.jwtProvider.currentToken();
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return { ok: false, error: { kind: "transport", detail: "jwt-unavailable" } };
|
|
169
|
+
}
|
|
170
|
+
const retried = await this.sender.send(this.makeRequest(req, token, topic, fresh));
|
|
171
|
+
if (!retried.ok && retried.error.kind === "unregistered") {
|
|
172
|
+
this.tokenStore.delete();
|
|
173
|
+
}
|
|
174
|
+
return retried;
|
|
175
|
+
}
|
|
176
|
+
if (first.error.kind === "unregistered") {
|
|
177
|
+
// 宛先未登録 → token 削除し以後停止(6.3)。
|
|
178
|
+
this.tokenStore.delete();
|
|
179
|
+
return { ok: false, error: { kind: "unregistered" } };
|
|
180
|
+
}
|
|
181
|
+
return first;
|
|
182
|
+
}
|
|
183
|
+
/** payload と JWT から ApnsRequest を組み立てる。collapse-id は approvalId で一意化(7.2/7.3)。 */
|
|
184
|
+
makeRequest(req, token, topic, jwt) {
|
|
185
|
+
return {
|
|
186
|
+
host: token.environment,
|
|
187
|
+
deviceToken: token.token,
|
|
188
|
+
bearerJWT: jwt,
|
|
189
|
+
topic,
|
|
190
|
+
collapseId: req.approvalId,
|
|
191
|
+
expiration: this.expiration,
|
|
192
|
+
body: pushPayloadBody(req.approvalId, req.tool, req.session),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/** PushError を観測ログ用の短い理由コードへ写像する(秘密を出さない, 6.5)。 */
|
|
197
|
+
export function reasonCode(error) {
|
|
198
|
+
switch (error.kind) {
|
|
199
|
+
case "expiredProviderToken":
|
|
200
|
+
return "ExpiredProviderToken";
|
|
201
|
+
case "unregistered":
|
|
202
|
+
return "Unregistered";
|
|
203
|
+
case "tooManyProviderTokenUpdates":
|
|
204
|
+
return "TooManyProviderTokenUpdates";
|
|
205
|
+
case "transport":
|
|
206
|
+
return "transport";
|
|
207
|
+
case "http":
|
|
208
|
+
return `http-${error.status}`;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// MARK: - 本番 push notifier ファクトリ
|
|
212
|
+
/**
|
|
213
|
+
* connect 不能時に用いる本番 push notifier を構築する(Swift 版 makeProductionPushNotifier 相当)。
|
|
214
|
+
*
|
|
215
|
+
* ApnsConfigStore / DeviceTokenStore / APNsJWTProvider / APNsSender(Http2HttpClient) /
|
|
216
|
+
* ObservationLogPushObserver を既定パス(`~/.tailii/apns`, ObservationLog 既定)で束ねる。
|
|
217
|
+
* 実際に送るかは notifier 内部が config/token の有無で判断する(未設定なら送らず観測記録, 3.5/6.4)。
|
|
218
|
+
*
|
|
219
|
+
* 返り値は hook.ts の `ApprovalPushNotifier` 型(`(req, timeLimitMs) => Promise<void>`)に適合する
|
|
220
|
+
* アダプタ。送信可否(PushOutcome)は deny 判断に影響しないため捨てる(観測は内部で完結)。
|
|
221
|
+
*/
|
|
222
|
+
export function makeProductionPushNotifier() {
|
|
223
|
+
const configStore = new ApnsConfigStore(defaultApnsBase());
|
|
224
|
+
const notifier = new ApprovalPushNotifier({
|
|
225
|
+
configStore,
|
|
226
|
+
tokenStore: new DeviceTokenStore(defaultApnsBase()),
|
|
227
|
+
jwtProvider: new APNsJWTProvider(configStore, { base: defaultApnsBase() }),
|
|
228
|
+
sender: new APNsSender(new Http2HttpClient()),
|
|
229
|
+
observer: new ObservationLogPushObserver(),
|
|
230
|
+
sendLogBase: defaultApnsBase(),
|
|
231
|
+
});
|
|
232
|
+
return async (request, timeLimitMs) => {
|
|
233
|
+
await notifier.notify(request, timeLimitMs);
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=approvalPushNotifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approvalPushNotifier.js","sourceRoot":"","sources":["../src/approvalPushNotifier.ts"],"names":[],"mappings":"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"}
|
package/dist/broker.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Readable, Writable } from "node:stream";
|
|
2
|
+
import { type StaleDistGuard } from "./version.js";
|
|
3
|
+
export interface RunBrokerOptions {
|
|
4
|
+
socketPath: string;
|
|
5
|
+
/** "stdin" 側(iOS→hook 方向の決定が流れてくる)。 */
|
|
6
|
+
input: Readable;
|
|
7
|
+
/** "stdout" 側(hook→iOS 方向の要求を流す)。 */
|
|
8
|
+
output: Writable;
|
|
9
|
+
/** 診断ログ出力先(省略時は無音。CLI は stderr を渡す)。 */
|
|
10
|
+
log?: (message: string) => void;
|
|
11
|
+
/** 起動時 package version と現在の package version を比較する stale 判定(テスト注入用)。 */
|
|
12
|
+
staleDistGuard?: StaleDistGuard | null;
|
|
13
|
+
/** stale dist 検出時の通知(テスト観測用)。 */
|
|
14
|
+
onStaleDist?: () => void;
|
|
15
|
+
/** 起動直後に SSH 側へ channel_hello を送る(CLI の serve 経路で有効)。 */
|
|
16
|
+
sendHello?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* unix socket と input/output の間を双方向中継する非解釈リレーの本体。
|
|
20
|
+
* input EOF(SSH 断)で全クライアントを閉じ、socket ファイルを削除して resolve する。
|
|
21
|
+
*/
|
|
22
|
+
export declare function runBroker(options: RunBrokerOptions): Promise<void>;
|
|
23
|
+
/** serve サブコマンドのエントリポイント(`--session <name>` | `--socket <path>`)。 */
|
|
24
|
+
export declare function runServeCommand(args: string[]): Promise<number>;
|
package/dist/broker.js
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// broker.ts
|
|
2
|
+
// tailii (TS host) — serve サブコマンド実装(Swift 版 Broker.swift の移植)
|
|
3
|
+
//
|
|
4
|
+
// unix domain socket と SSH stdio (input/output) の間で NDJSON 行を純中継する。
|
|
5
|
+
// 承認判断・メッセージ解釈は一切行わない(Hook が唯一の意思決定点)。
|
|
6
|
+
//
|
|
7
|
+
// 非解釈リレー(N クライアント対応、Swift 版と同一挙動):
|
|
8
|
+
// - 複数 hook クライアントを同時受理し、hook→SSH 方向は完全行単位で output へ fan-in
|
|
9
|
+
// (Node は単一スレッドなので write は行単位に自然直列化され混線しない)。
|
|
10
|
+
// - SSH→hook 方向は input の完全行を登録中の全クライアントへブロードキャスト
|
|
11
|
+
// (宛先選別なし=各 hook が自 id を照合)。購読者ゼロ時は行を保持しない(非永続)。
|
|
12
|
+
// - channel_hello を含むあらゆる行を非解釈で透過する。
|
|
13
|
+
// - SSH 断(input EOF)→ 全クライアント socket を閉じ(各 hook EOF→deny, 5.6)、
|
|
14
|
+
// listen を止めて socket ファイルを削除して終了する。
|
|
15
|
+
// - EOF 時の残余バッファ(改行なしの部分行)は Swift 版同様そのまま転送する。
|
|
16
|
+
import * as fs from "node:fs";
|
|
17
|
+
import * as net from "node:net";
|
|
18
|
+
import { resolveSocketPath } from "./socketPath.js";
|
|
19
|
+
import { encodeControlMessage, PROTOCOL_MAX_SUPPORTED, PROTOCOL_V1 } from "./protocol.js";
|
|
20
|
+
import { createStaleDistGuard, isStaleDist, readPackageVersion } from "./version.js";
|
|
21
|
+
/** 行分割器: チャンクを積み、完全行(末尾 \n 込み)ごとに onLine を呼ぶ。 */
|
|
22
|
+
function makeLineFeeder(onLine) {
|
|
23
|
+
let buf = Buffer.alloc(0);
|
|
24
|
+
return {
|
|
25
|
+
push(chunk) {
|
|
26
|
+
buf = buf.length === 0 ? chunk : Buffer.concat([buf, chunk]);
|
|
27
|
+
let idx;
|
|
28
|
+
while ((idx = buf.indexOf(0x0a)) >= 0) {
|
|
29
|
+
onLine(buf.subarray(0, idx + 1));
|
|
30
|
+
buf = buf.subarray(idx + 1);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
flush() {
|
|
34
|
+
if (buf.length > 0) {
|
|
35
|
+
onLine(buf);
|
|
36
|
+
buf = Buffer.alloc(0);
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* unix socket と input/output の間を双方向中継する非解釈リレーの本体。
|
|
43
|
+
* input EOF(SSH 断)で全クライアントを閉じ、socket ファイルを削除して resolve する。
|
|
44
|
+
*/
|
|
45
|
+
export async function runBroker(options) {
|
|
46
|
+
const { socketPath, input, output } = options;
|
|
47
|
+
const log = options.log ?? (() => { });
|
|
48
|
+
const staleDistGuard = options.staleDistGuard ?? createStaleDistGuard();
|
|
49
|
+
// 既存 socket ファイルを削除してから listen(Swift 版 unlink→bind と同一)。
|
|
50
|
+
fs.rmSync(socketPath, { force: true });
|
|
51
|
+
const clients = new Set();
|
|
52
|
+
let registryClosed = false;
|
|
53
|
+
let shutdownRequested = false;
|
|
54
|
+
let resolveBroker = null;
|
|
55
|
+
let server;
|
|
56
|
+
const finishBroker = () => {
|
|
57
|
+
if (registryClosed)
|
|
58
|
+
return;
|
|
59
|
+
registryClosed = true;
|
|
60
|
+
for (const client of clients)
|
|
61
|
+
client.end();
|
|
62
|
+
server.close(() => { });
|
|
63
|
+
fs.rmSync(socketPath, { force: true });
|
|
64
|
+
resolveBroker?.();
|
|
65
|
+
};
|
|
66
|
+
/** 1 完全行を SSH (output) へ書き込む(失敗は無視 — Swift 版 try? と同一)。 */
|
|
67
|
+
const writeLineToSSH = (line) => {
|
|
68
|
+
try {
|
|
69
|
+
output.write(line);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// 書込先が閉じていても中継自体は継続する。
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
/** 1 完全行を全生存クライアントへブロードキャストする(5.4)。 */
|
|
76
|
+
const broadcast = (line) => {
|
|
77
|
+
for (const client of clients) {
|
|
78
|
+
try {
|
|
79
|
+
client.write(line);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
client.destroy();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
server = net.createServer((client) => {
|
|
87
|
+
if (registryClosed) {
|
|
88
|
+
client.destroy();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
clients.add(client);
|
|
92
|
+
log(`[tailii-host serve] hook クライアント接続\n`);
|
|
93
|
+
if (isStaleDist(staleDistGuard)) {
|
|
94
|
+
shutdownRequested = true;
|
|
95
|
+
log("[tailii-host serve] stale dist を検出、再起動のため終了\n");
|
|
96
|
+
options.onStaleDist?.();
|
|
97
|
+
}
|
|
98
|
+
const feeder = makeLineFeeder(writeLineToSSH);
|
|
99
|
+
client.on("data", (chunk) => feeder.push(chunk));
|
|
100
|
+
// クライアント切断: 残余バッファをフラッシュして登録簿から除去する。
|
|
101
|
+
client.on("end", () => feeder.flush());
|
|
102
|
+
client.on("error", () => { });
|
|
103
|
+
client.on("close", () => {
|
|
104
|
+
clients.delete(client);
|
|
105
|
+
log(`[tailii-host serve] hook クライアント切断\n`);
|
|
106
|
+
if (shutdownRequested && clients.size === 0)
|
|
107
|
+
finishBroker();
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
server.maxConnections = 16;
|
|
111
|
+
await new Promise((resolve, reject) => {
|
|
112
|
+
server.once("error", reject);
|
|
113
|
+
server.listen(socketPath, () => {
|
|
114
|
+
server.removeListener("error", reject);
|
|
115
|
+
resolve();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
log(`[tailii-host serve] listening on ${socketPath}\n`);
|
|
119
|
+
if (options.sendHello === true) {
|
|
120
|
+
const helloVersion = staleDistGuard?.startupVersion ?? readPackageVersion() ?? undefined;
|
|
121
|
+
writeLineToSSH(Buffer.from(encodeControlMessage({
|
|
122
|
+
type: "channel_hello",
|
|
123
|
+
v: PROTOCOL_V1,
|
|
124
|
+
maxVersion: PROTOCOL_MAX_SUPPORTED,
|
|
125
|
+
...(helloVersion !== undefined ? { serverVersion: helloVersion } : {}),
|
|
126
|
+
}) + "\n", "utf8"));
|
|
127
|
+
}
|
|
128
|
+
if (isStaleDist(staleDistGuard)) {
|
|
129
|
+
shutdownRequested = true;
|
|
130
|
+
log("[tailii-host serve] stale dist を検出、再起動のため終了\n");
|
|
131
|
+
options.onStaleDist?.();
|
|
132
|
+
finishBroker();
|
|
133
|
+
}
|
|
134
|
+
// SSH リーダ: input → 全クライアントへブロードキャスト。EOF で全体を終了させる。
|
|
135
|
+
await new Promise((resolve) => {
|
|
136
|
+
resolveBroker = resolve;
|
|
137
|
+
if (registryClosed) {
|
|
138
|
+
resolve();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const feeder = makeLineFeeder(broadcast);
|
|
142
|
+
const finish = () => {
|
|
143
|
+
feeder.flush();
|
|
144
|
+
finishBroker();
|
|
145
|
+
log(`[tailii-host serve] stdin→broadcast: EOF\n`);
|
|
146
|
+
};
|
|
147
|
+
input.on("data", (chunk) => feeder.push(chunk));
|
|
148
|
+
input.once("end", finish);
|
|
149
|
+
input.once("error", finish);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
/** serve サブコマンドのエントリポイント(`--session <name>` | `--socket <path>`)。 */
|
|
153
|
+
export async function runServeCommand(args) {
|
|
154
|
+
let socketPath = null;
|
|
155
|
+
try {
|
|
156
|
+
socketPath = parseServeArgs(args);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
process.stderr.write(`[tailii-host serve] エラー: ${String(error)}\n`);
|
|
160
|
+
process.stderr.write("使い方: tailii serve --session <name> | --socket <path>\n");
|
|
161
|
+
return 1;
|
|
162
|
+
}
|
|
163
|
+
process.stderr.write(`[tailii-host serve] socket: ${socketPath}\n`);
|
|
164
|
+
try {
|
|
165
|
+
await runBroker({
|
|
166
|
+
socketPath,
|
|
167
|
+
input: process.stdin,
|
|
168
|
+
output: process.stdout,
|
|
169
|
+
log: (message) => process.stderr.write(message),
|
|
170
|
+
sendHello: true,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
process.stderr.write(`[tailii-host serve] 異常終了: ${String(error)}\n`);
|
|
175
|
+
return 1;
|
|
176
|
+
}
|
|
177
|
+
return 0;
|
|
178
|
+
}
|
|
179
|
+
/** 引数から socket パスを導出する(Swift 版 parseServeArgs と同一: 最初の一致で確定)。 */
|
|
180
|
+
function parseServeArgs(args) {
|
|
181
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
182
|
+
if (args[i] === "--session") {
|
|
183
|
+
const value = args[i + 1];
|
|
184
|
+
if (value === undefined)
|
|
185
|
+
throw new Error("--session の値がありません");
|
|
186
|
+
return resolveSocketPath(value);
|
|
187
|
+
}
|
|
188
|
+
if (args[i] === "--socket") {
|
|
189
|
+
const value = args[i + 1];
|
|
190
|
+
if (value === undefined)
|
|
191
|
+
throw new Error("--socket の値がありません");
|
|
192
|
+
return value;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
throw new Error("--session または --socket が必要");
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=broker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"broker.js","sourceRoot":"","sources":["../src/broker.ts"],"names":[],"mappings":"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"}
|