@tienne/gestalt 0.108.0 → 0.110.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/CLAUDE.md +1 -0
- package/dist/package.json +1 -1
- package/dist/src/cli/commands/peer.d.ts +49 -0
- package/dist/src/cli/commands/peer.d.ts.map +1 -0
- package/dist/src/cli/commands/peer.js +302 -0
- package/dist/src/cli/commands/peer.js.map +1 -0
- package/dist/src/cli/index.d.ts.map +1 -1
- package/dist/src/cli/index.js +81 -2
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/peer/client.d.ts +9 -0
- package/dist/src/peer/client.d.ts.map +1 -0
- package/dist/src/peer/client.js +51 -0
- package/dist/src/peer/client.js.map +1 -0
- package/dist/src/peer/daemon.d.ts +83 -0
- package/dist/src/peer/daemon.d.ts.map +1 -0
- package/dist/src/peer/daemon.js +516 -0
- package/dist/src/peer/daemon.js.map +1 -0
- package/dist/src/peer/errors.d.ts +2 -0
- package/dist/src/peer/errors.d.ts.map +1 -0
- package/dist/src/peer/errors.js +4 -0
- package/dist/src/peer/errors.js.map +1 -0
- package/dist/src/peer/feed.d.ts +103 -0
- package/dist/src/peer/feed.d.ts.map +1 -0
- package/dist/src/peer/feed.js +167 -0
- package/dist/src/peer/feed.js.map +1 -0
- package/dist/src/peer/index.d.ts +24 -0
- package/dist/src/peer/index.d.ts.map +1 -0
- package/dist/src/peer/index.js +14 -0
- package/dist/src/peer/index.js.map +1 -0
- package/dist/src/peer/keys.d.ts +26 -0
- package/dist/src/peer/keys.d.ts.map +1 -0
- package/dist/src/peer/keys.js +95 -0
- package/dist/src/peer/keys.js.map +1 -0
- package/dist/src/peer/locate.d.ts +44 -0
- package/dist/src/peer/locate.d.ts.map +1 -0
- package/dist/src/peer/locate.js +116 -0
- package/dist/src/peer/locate.js.map +1 -0
- package/dist/src/peer/lock.d.ts +32 -0
- package/dist/src/peer/lock.d.ts.map +1 -0
- package/dist/src/peer/lock.js +147 -0
- package/dist/src/peer/lock.js.map +1 -0
- package/dist/src/peer/mirror.d.ts +21 -0
- package/dist/src/peer/mirror.d.ts.map +1 -0
- package/dist/src/peer/mirror.js +68 -0
- package/dist/src/peer/mirror.js.map +1 -0
- package/dist/src/peer/network.d.ts +148 -0
- package/dist/src/peer/network.d.ts.map +1 -0
- package/dist/src/peer/network.js +809 -0
- package/dist/src/peer/network.js.map +1 -0
- package/dist/src/peer/paths.d.ts +23 -0
- package/dist/src/peer/paths.d.ts.map +1 -0
- package/dist/src/peer/paths.js +50 -0
- package/dist/src/peer/paths.js.map +1 -0
- package/dist/src/peer/protocol.d.ts +112 -0
- package/dist/src/peer/protocol.d.ts.map +1 -0
- package/dist/src/peer/protocol.js +8 -0
- package/dist/src/peer/protocol.js.map +1 -0
- package/dist/src/peer/redact.d.ts +30 -0
- package/dist/src/peer/redact.d.ts.map +1 -0
- package/dist/src/peer/redact.js +214 -0
- package/dist/src/peer/redact.js.map +1 -0
- package/dist/src/peer/relay.d.ts +70 -0
- package/dist/src/peer/relay.d.ts.map +1 -0
- package/dist/src/peer/relay.js +410 -0
- package/dist/src/peer/relay.js.map +1 -0
- package/dist/src/peer/roster.d.ts +89 -0
- package/dist/src/peer/roster.d.ts.map +1 -0
- package/dist/src/peer/roster.js +160 -0
- package/dist/src/peer/roster.js.map +1 -0
- package/dist/src/peer/seal.d.ts +37 -0
- package/dist/src/peer/seal.d.ts.map +1 -0
- package/dist/src/peer/seal.js +115 -0
- package/dist/src/peer/seal.js.map +1 -0
- package/dist/src/peer/share.d.ts +41 -0
- package/dist/src/peer/share.d.ts.map +1 -0
- package/dist/src/peer/share.js +70 -0
- package/dist/src/peer/share.js.map +1 -0
- package/dist/src/peer/state.d.ts +68 -0
- package/dist/src/peer/state.d.ts.map +1 -0
- package/dist/src/peer/state.js +161 -0
- package/dist/src/peer/state.js.map +1 -0
- package/dist/src/peer/tail.d.ts +55 -0
- package/dist/src/peer/tail.d.ts.map +1 -0
- package/dist/src/peer/tail.js +148 -0
- package/dist/src/peer/tail.js.map +1 -0
- package/dist/src/peer/transform.d.ts +79 -0
- package/dist/src/peer/transform.d.ts.map +1 -0
- package/dist/src/peer/transform.js +237 -0
- package/dist/src/peer/transform.js.map +1 -0
- package/dist/src/peer/transport.d.ts +89 -0
- package/dist/src/peer/transport.d.ts.map +1 -0
- package/dist/src/peer/transport.js +345 -0
- package/dist/src/peer/transport.js.map +1 -0
- package/dist/src/peer/viewer-html.d.ts +10 -0
- package/dist/src/peer/viewer-html.d.ts.map +1 -0
- package/dist/src/peer/viewer-html.js +165 -0
- package/dist/src/peer/viewer-html.js.map +1 -0
- package/dist/src/peer/viewer.d.ts +58 -0
- package/dist/src/peer/viewer.d.ts.map +1 -0
- package/dist/src/peer/viewer.js +249 -0
- package/dist/src/peer/viewer.js.map +1 -0
- package/dist/src/peer/wire.d.ts +90 -0
- package/dist/src/peer/wire.d.ts.map +1 -0
- package/dist/src/peer/wire.js +10 -0
- package/dist/src/peer/wire.js.map +1 -0
- package/dist/src/peer/ws.d.ts +47 -0
- package/dist/src/peer/ws.d.ts.map +1 -0
- package/dist/src/peer/ws.js +308 -0
- package/dist/src/peer/ws.js.map +1 -0
- package/package.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.mcp.json +1 -1
- package/plugin/mcp.json +1 -1
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { redactText, redactValue, truncateBytes, TOOL_RESULT_SUMMARY_BYTES } from './redact.js';
|
|
2
|
+
/** `tool_use` 입력 요약 길이. 결과보다 짧게 둔다 — 입력은 접힌 줄 제목처럼 읽힌다 */
|
|
3
|
+
const TOOL_INPUT_SUMMARY_BYTES = 1024;
|
|
4
|
+
/**
|
|
5
|
+
* 내용을 안 보내는 줄 종류. 2.1.311 기록에서 본 것들이다. 여기 있는 건 조용히
|
|
6
|
+
* 버리고 여기에도 허용 목록에도 없는 종류만 "모르는 줄"로 센다.
|
|
7
|
+
*
|
|
8
|
+
* 이 목록은 차단 목록이 아니다. 지워도 새는 게 없고 "모르는 줄" 숫자만 는다.
|
|
9
|
+
*/
|
|
10
|
+
const KNOWN_DROPPED = new Set([
|
|
11
|
+
'api-request',
|
|
12
|
+
'api-request-blob',
|
|
13
|
+
'api-request-shape',
|
|
14
|
+
'attachment',
|
|
15
|
+
'system',
|
|
16
|
+
'last-prompt',
|
|
17
|
+
'queue-operation',
|
|
18
|
+
'tools-used',
|
|
19
|
+
'cost-state',
|
|
20
|
+
'mode',
|
|
21
|
+
'atis-latch',
|
|
22
|
+
'summary',
|
|
23
|
+
'file-history-snapshot',
|
|
24
|
+
]);
|
|
25
|
+
const SYSTEM_REMINDER = /<system-reminder>[\s\S]*?(?:<\/system-reminder>|$)/g;
|
|
26
|
+
/** user 텍스트에서 `<system-reminder>` 블록을 지운다. 닫는 태그가 없으면 끝까지 지운다 */
|
|
27
|
+
export function stripSystemReminders(text) {
|
|
28
|
+
return text.replace(SYSTEM_REMINDER, '').trim();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 대화 기록 jsonl 한 줄씩 받아 허용 목록에 있는 것만 `PeerEvent`로 바꾼다.
|
|
32
|
+
*
|
|
33
|
+
* 허용 목록이다. 줄 종류든 content 블록 종류든 여기서 명시적으로 꺼내지 않은 건
|
|
34
|
+
* 아무것도 안 나간다. 형식이 바뀌어 새 필드나 새 종류가 생겨도 그대로 새지 않는다.
|
|
35
|
+
*
|
|
36
|
+
* 상태가 있다 — 서브에이전트 줄이 이어지면 표시를 한 번만 낸다.
|
|
37
|
+
*/
|
|
38
|
+
export class TranscriptTransformer {
|
|
39
|
+
options;
|
|
40
|
+
inSidechain = false;
|
|
41
|
+
unknown = 0;
|
|
42
|
+
failed = 0;
|
|
43
|
+
claudeVersion;
|
|
44
|
+
constructor(options) {
|
|
45
|
+
this.options = options;
|
|
46
|
+
}
|
|
47
|
+
/** 모르는 줄 종류와 JSON으로 안 읽힌 줄을 합친 수 */
|
|
48
|
+
get unknownLines() {
|
|
49
|
+
return this.unknown;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 바꾸다 예외가 난 줄 수. 모르는 줄과 따로 센다. 모르는 줄은 형식이 바뀐 신호라 감사
|
|
53
|
+
* 로그에 버전과 함께 남기는데 여기에 섞이면 그 신호가 흐려진다.
|
|
54
|
+
*/
|
|
55
|
+
get failedLines() {
|
|
56
|
+
return this.failed;
|
|
57
|
+
}
|
|
58
|
+
/** 마지막으로 본 Claude Code 버전. 모르는 줄이 생겼을 때 감사 로그에 남긴다 */
|
|
59
|
+
get version() {
|
|
60
|
+
return this.claudeVersion;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 한 줄을 바꾼다. 줄 하나에서 난 예외는 여기서 접고 그 줄만 버린다. 2만 단계로
|
|
64
|
+
* 중첩된 도구 입력이 가리기 재귀에서 스택을 넘기는 걸 봤다. 던지게 두면 이 공유의
|
|
65
|
+
* 나머지 줄을 더 못 읽는다. 읽던 중이면 tick이 그 공유를 error로 종료하고 공유를 여는
|
|
66
|
+
* 첫 poll이면 share 요청이 실패한다.
|
|
67
|
+
*/
|
|
68
|
+
push(line) {
|
|
69
|
+
try {
|
|
70
|
+
return this.parse(line);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
this.failed++;
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
parse(line) {
|
|
78
|
+
const trimmed = line.trim();
|
|
79
|
+
if (trimmed === '')
|
|
80
|
+
return [];
|
|
81
|
+
let parsed;
|
|
82
|
+
try {
|
|
83
|
+
parsed = JSON.parse(trimmed);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
this.unknown++;
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
if (!isRecord(parsed) || typeof parsed['type'] !== 'string') {
|
|
90
|
+
this.unknown++;
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
if (typeof parsed['version'] === 'string')
|
|
94
|
+
this.claudeVersion = parsed['version'];
|
|
95
|
+
const type = parsed['type'];
|
|
96
|
+
const at = typeof parsed['timestamp'] === 'string' ? parsed['timestamp'] : undefined;
|
|
97
|
+
if (type === 'ai-title') {
|
|
98
|
+
const title = parsed['aiTitle'];
|
|
99
|
+
return typeof title === 'string' && title.trim() !== ''
|
|
100
|
+
? [{ kind: 'title', text: this.redact(title) }]
|
|
101
|
+
: [];
|
|
102
|
+
}
|
|
103
|
+
if (type !== 'user' && type !== 'assistant') {
|
|
104
|
+
if (!KNOWN_DROPPED.has(type))
|
|
105
|
+
this.unknown++;
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
if (parsed['isSidechain'] === true) {
|
|
109
|
+
if (this.inSidechain)
|
|
110
|
+
return [];
|
|
111
|
+
this.inSidechain = true;
|
|
112
|
+
return [{ kind: 'sidechain', at }];
|
|
113
|
+
}
|
|
114
|
+
this.inSidechain = false;
|
|
115
|
+
// 하네스가 끼워 넣은 줄이다. 주인이 친 말이 아니고 CLAUDE.md나 명령 출력이 들어 있다
|
|
116
|
+
if (parsed['isMeta'] === true)
|
|
117
|
+
return [];
|
|
118
|
+
// 압축 요약은 지난 대화 전체를 다시 적은 글이라 그 안에 무엇이 섞였는지 모른다
|
|
119
|
+
if (parsed['isCompactSummary'] === true)
|
|
120
|
+
return [{ kind: 'compacted', at }];
|
|
121
|
+
const message = parsed['message'];
|
|
122
|
+
if (!isRecord(message))
|
|
123
|
+
return [];
|
|
124
|
+
const content = message['content'];
|
|
125
|
+
return type === 'user' ? this.fromUser(content, at) : this.fromAssistant(content, at);
|
|
126
|
+
}
|
|
127
|
+
fromUser(content, at) {
|
|
128
|
+
if (typeof content === 'string')
|
|
129
|
+
return this.prompt(content, at);
|
|
130
|
+
if (!Array.isArray(content))
|
|
131
|
+
return [];
|
|
132
|
+
const events = [];
|
|
133
|
+
for (const block of content) {
|
|
134
|
+
if (!isRecord(block))
|
|
135
|
+
continue;
|
|
136
|
+
if (block['type'] === 'text' && typeof block['text'] === 'string') {
|
|
137
|
+
events.push(...this.prompt(block['text'], at));
|
|
138
|
+
}
|
|
139
|
+
else if (block['type'] === 'tool_result') {
|
|
140
|
+
events.push(this.toolResult(block, at));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return events;
|
|
144
|
+
}
|
|
145
|
+
fromAssistant(content, at) {
|
|
146
|
+
if (typeof content === 'string') {
|
|
147
|
+
const text = this.redact(content).trim();
|
|
148
|
+
return text === '' ? [] : [{ kind: 'reply', text, at }];
|
|
149
|
+
}
|
|
150
|
+
if (!Array.isArray(content))
|
|
151
|
+
return [];
|
|
152
|
+
const events = [];
|
|
153
|
+
for (const block of content) {
|
|
154
|
+
if (!isRecord(block))
|
|
155
|
+
continue;
|
|
156
|
+
if (block['type'] === 'text' && typeof block['text'] === 'string') {
|
|
157
|
+
const text = this.redact(block['text']).trim();
|
|
158
|
+
if (text !== '')
|
|
159
|
+
events.push({ kind: 'reply', text, at });
|
|
160
|
+
}
|
|
161
|
+
else if (block['type'] === 'tool_use') {
|
|
162
|
+
events.push(this.toolCall(block, at));
|
|
163
|
+
}
|
|
164
|
+
// thinking, redacted_thinking, server_tool_use 등은 꺼내지 않는다
|
|
165
|
+
}
|
|
166
|
+
return events;
|
|
167
|
+
}
|
|
168
|
+
prompt(raw, at) {
|
|
169
|
+
const text = this.redact(stripSystemReminders(raw));
|
|
170
|
+
return text === '' ? [] : [{ kind: 'prompt', text, at }];
|
|
171
|
+
}
|
|
172
|
+
toolCall(block, at) {
|
|
173
|
+
const id = typeof block['id'] === 'string' ? block['id'] : '';
|
|
174
|
+
const name = typeof block['name'] === 'string' ? this.redact(block['name']) : '(이름 없음)';
|
|
175
|
+
if (this.options.tools === 'none')
|
|
176
|
+
return { kind: 'tool_call', id, name, at };
|
|
177
|
+
const redacted = redactValue(block['input'] ?? {}, this.options);
|
|
178
|
+
const json = JSON.stringify(redacted) ?? '';
|
|
179
|
+
const input = this.options.tools === 'full' ? json : truncateBytes(json, TOOL_INPUT_SUMMARY_BYTES).text;
|
|
180
|
+
return { kind: 'tool_call', id, name, input, at };
|
|
181
|
+
}
|
|
182
|
+
toolResult(block, at) {
|
|
183
|
+
const toolUseId = typeof block['tool_use_id'] === 'string' ? block['tool_use_id'] : '';
|
|
184
|
+
const isError = block['is_error'] === true;
|
|
185
|
+
if (this.options.tools === 'none') {
|
|
186
|
+
return { kind: 'tool_result', toolUseId, truncated: false, hidden: true, isError, at };
|
|
187
|
+
}
|
|
188
|
+
const full = this.redact(resultText(block['content']));
|
|
189
|
+
if (this.options.tools === 'full') {
|
|
190
|
+
return {
|
|
191
|
+
kind: 'tool_result',
|
|
192
|
+
toolUseId,
|
|
193
|
+
text: full,
|
|
194
|
+
truncated: false,
|
|
195
|
+
hidden: false,
|
|
196
|
+
isError,
|
|
197
|
+
at,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
const cut = truncateBytes(full, TOOL_RESULT_SUMMARY_BYTES);
|
|
201
|
+
return {
|
|
202
|
+
kind: 'tool_result',
|
|
203
|
+
toolUseId,
|
|
204
|
+
text: cut.text,
|
|
205
|
+
truncated: cut.truncated,
|
|
206
|
+
hidden: false,
|
|
207
|
+
isError,
|
|
208
|
+
at,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
redact(text) {
|
|
212
|
+
return redactText(text, this.options);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/** tool_result의 content는 문자열이거나 블록 배열이다. 텍스트 블록만 꺼낸다 */
|
|
216
|
+
function resultText(content) {
|
|
217
|
+
if (typeof content === 'string')
|
|
218
|
+
return stripSystemReminders(content);
|
|
219
|
+
if (!Array.isArray(content))
|
|
220
|
+
return '';
|
|
221
|
+
const parts = [];
|
|
222
|
+
for (const block of content) {
|
|
223
|
+
if (!isRecord(block))
|
|
224
|
+
continue;
|
|
225
|
+
if (block['type'] === 'text' && typeof block['text'] === 'string') {
|
|
226
|
+
parts.push(stripSystemReminders(block['text']));
|
|
227
|
+
}
|
|
228
|
+
else if (block['type'] === 'image') {
|
|
229
|
+
parts.push('[이미지]');
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return parts.join('\n');
|
|
233
|
+
}
|
|
234
|
+
function isRecord(value) {
|
|
235
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=transform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transform.js","sourceRoot":"","sources":["../../../src/peer/transform.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import type { MachineKey } from './keys.js';
|
|
3
|
+
import type { SignedRoster } from './roster.js';
|
|
4
|
+
import type { HelloMode } from './wire.js';
|
|
5
|
+
/**
|
|
6
|
+
* 데몬끼리 봉인한 바이트를 주고받는 통로. 1차 구현은 RelayTransport 하나다.
|
|
7
|
+
* 나중에 P2P 통로를 더할 때 같은 모양을 맞춘다.
|
|
8
|
+
*/
|
|
9
|
+
export interface PeerTransport {
|
|
10
|
+
connect(roster: SignedRoster | null): Promise<void>;
|
|
11
|
+
/** 릴레이가 받는 기기에게 넘겼으면 delivered. PR 2 릴레이는 쌓지 않아서 queued는 아직 안 나온다 */
|
|
12
|
+
send(to: string, sealed: Uint8Array): Promise<'queued' | 'delivered'>;
|
|
13
|
+
onReceive(handler: (from: string, sealed: Uint8Array) => void): void;
|
|
14
|
+
close(): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
export type TransportState = 'idle' | 'connecting' | 'online' | 'offline' | 'removed' | 'closed';
|
|
17
|
+
export interface RelayTransportOptions {
|
|
18
|
+
url: string;
|
|
19
|
+
team: string;
|
|
20
|
+
key: MachineKey;
|
|
21
|
+
mode?: HelloMode;
|
|
22
|
+
/** 끊기면 다시 붙는다. join 모드는 기본 끔 */
|
|
23
|
+
reconnect?: boolean;
|
|
24
|
+
minBackoffMs?: number;
|
|
25
|
+
maxBackoffMs?: number;
|
|
26
|
+
ackTimeoutMs?: number;
|
|
27
|
+
}
|
|
28
|
+
export declare class RelaySendError extends Error {
|
|
29
|
+
readonly reason: string;
|
|
30
|
+
constructor(reason: string);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 릴레이 접속 하나. 이벤트:
|
|
34
|
+
* - `state`(TransportState)
|
|
35
|
+
* - `welcome`(roster, online) — member로 붙었다. 다시 붙을 때마다 온다
|
|
36
|
+
* - `roster`(SignedRoster) — 릴레이가 새 명단을 돌렸다. 받을지는 부른 쪽이 다시 확인한다
|
|
37
|
+
* - `presence`(key, online)
|
|
38
|
+
* - `join`(from, sealed) — 관리자에게 온 join 요청
|
|
39
|
+
* - `relay-error`(code, message)
|
|
40
|
+
*/
|
|
41
|
+
export declare class RelayTransport extends EventEmitter implements PeerTransport {
|
|
42
|
+
private readonly options;
|
|
43
|
+
private ws;
|
|
44
|
+
private roster;
|
|
45
|
+
private status;
|
|
46
|
+
private nextId;
|
|
47
|
+
private acks;
|
|
48
|
+
private resolves;
|
|
49
|
+
private receivers;
|
|
50
|
+
private backoff;
|
|
51
|
+
private retry;
|
|
52
|
+
private announced;
|
|
53
|
+
constructor(options: RelayTransportOptions);
|
|
54
|
+
get state(): TransportState;
|
|
55
|
+
get bufferedAmount(): number;
|
|
56
|
+
/** 다음 hello에 들고 갈 명단 */
|
|
57
|
+
setRoster(roster: SignedRoster | null): void;
|
|
58
|
+
/**
|
|
59
|
+
* 처음 붙는다. member면 welcome, join 모드면 pending까지 기다린다. 첫 시도가 실패하면
|
|
60
|
+
* 던지고 reconnect가 켜져 있으면 뒤에서 계속 다시 붙는다.
|
|
61
|
+
*/
|
|
62
|
+
connect(roster: SignedRoster | null): Promise<void>;
|
|
63
|
+
onReceive(handler: (from: string, sealed: Uint8Array) => void): void;
|
|
64
|
+
send(to: string, sealed: Uint8Array): Promise<'queued' | 'delivered'>;
|
|
65
|
+
/**
|
|
66
|
+
* 답을 안 기다리고 보낸다. 공유 스트림 프레임처럼 자주 가는 것에 쓴다. 릴레이도 ack를
|
|
67
|
+
* 안 돌려준다. 받는 쪽이 없으면 조용히 버려진다.
|
|
68
|
+
*/
|
|
69
|
+
post(to: string, sealed: Uint8Array): boolean;
|
|
70
|
+
/** 공유 id를 연 기기를 릴레이에 묻는다 */
|
|
71
|
+
resolveShare(share: string, timeoutMs?: number): Promise<string | null>;
|
|
72
|
+
/** 지금 연 공유 id 전부를 알린다. 다시 붙을 때도 같은 목록을 다시 보낸다 */
|
|
73
|
+
announce(shares: string[]): void;
|
|
74
|
+
publishRoster(roster: SignedRoster): void;
|
|
75
|
+
sendJoin(to: string, sealed: Uint8Array): boolean;
|
|
76
|
+
close(): Promise<void>;
|
|
77
|
+
private attempt;
|
|
78
|
+
private lost;
|
|
79
|
+
/**
|
|
80
|
+
* 릴레이가 보낸 프레임. 릴레이도 믿지 않는 쪽이라 꼴이 틀린 프레임은 버린다. 여기서
|
|
81
|
+
* 던지면 소켓 data 핸들러 밖으로 나가 데몬이 죽는다.
|
|
82
|
+
*/
|
|
83
|
+
private onMessage;
|
|
84
|
+
private dispatch;
|
|
85
|
+
private write;
|
|
86
|
+
private failPending;
|
|
87
|
+
private setState;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../../src/peer/transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,OAAO,KAAK,EAAe,SAAS,EAAe,MAAM,WAAW,CAAC;AAGrE;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,qEAAqE;IACrE,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,GAAG,WAAW,CAAC,CAAC;IACtE,SAAS,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAAC;IACrE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,YAAY,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEjG,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,UAAU,CAAC;IAChB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,gCAAgC;IAChC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,qBAAa,cAAe,SAAQ,KAAK;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM;CAUpC;AAOD;;;;;;;;GAQG;AACH,qBAAa,cAAe,SAAQ,YAAa,YAAW,aAAa;IAY3D,OAAO,CAAC,QAAQ,CAAC,OAAO;IAXpC,OAAO,CAAC,EAAE,CAA6B;IACvC,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,IAAI,CAAiC;IAC7C,OAAO,CAAC,QAAQ,CAAqD;IACrE,OAAO,CAAC,SAAS,CAAsD;IACvE,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,SAAS,CAAgB;gBAEJ,OAAO,EAAE,qBAAqB;IAK3D,IAAI,KAAK,IAAI,cAAc,CAE1B;IAED,IAAI,cAAc,IAAI,MAAM,CAE3B;IAED,wBAAwB;IACxB,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,GAAG,IAAI;IAI5C;;;OAGG;IACH,OAAO,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAKnD,SAAS,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI;IAIpE,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,GAAG,WAAW,CAAC;IAsBrE;;;OAGG;IACH,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO;IAY7C,4BAA4B;IAC5B,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,SAAQ,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAoBtE,iDAAiD;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI;IAKhC,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI;IAKzC,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO;IAK3C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAU5B,OAAO,CAAC,OAAO;IAyCf,OAAO,CAAC,IAAI;IAgBZ;;;OAGG;IACH,OAAO,CAAC,SAAS;IAejB,OAAO,CAAC,QAAQ;IAmEhB,OAAO,CAAC,KAAK;IAIb,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,QAAQ;CAOjB"}
|