@tiflis-io/tiflis-code-tunnel 0.3.9 → 0.3.11
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/README.md +40 -3
- package/dist/main.js +40 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,15 +20,30 @@
|
|
|
20
20
|
|
|
21
21
|
## Overview
|
|
22
22
|
|
|
23
|
-
The Tunnel Server acts as a secure reverse proxy between
|
|
23
|
+
The Tunnel Server acts as a secure reverse proxy between clients (iOS/watchOS/Android/Web) and workstations running the Tiflis Code workstation server. It enables remote access to your workstation without requiring a public IP address.
|
|
24
24
|
|
|
25
25
|
```
|
|
26
26
|
┌─────────────┐ ┌─────────────┐ ┌─────────────────┐
|
|
27
|
-
│
|
|
28
|
-
│
|
|
27
|
+
│ Clients │◄───────►│ Tunnel │◄───────►│ Workstation │
|
|
28
|
+
│ iOS/Watch/ │ WSS │ Server │ WS │ Server │
|
|
29
|
+
│ Android/Web │ │ (+Web App) │ │ │
|
|
29
30
|
└─────────────┘ └─────────────┘ └─────────────────┘
|
|
30
31
|
```
|
|
31
32
|
|
|
33
|
+
### Web Client
|
|
34
|
+
|
|
35
|
+
The tunnel server includes a bundled web client built with Next.js and assistant-ui, accessible at the root URL of your tunnel server:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
https://your-tunnel-url.com/
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Features:
|
|
42
|
+
- Voice messaging with STT/TTS
|
|
43
|
+
- Mobile-first responsive design
|
|
44
|
+
- Cross-device message sync
|
|
45
|
+
- Lazy history loading (protocol v1.13)
|
|
46
|
+
|
|
32
47
|
## Installation
|
|
33
48
|
|
|
34
49
|
```bash
|
|
@@ -170,6 +185,16 @@ server {
|
|
|
170
185
|
ssl_certificate /path/to/cert.pem;
|
|
171
186
|
ssl_certificate_key /path/to/key.pem;
|
|
172
187
|
|
|
188
|
+
# Web client (static files)
|
|
189
|
+
location / {
|
|
190
|
+
proxy_pass http://tiflis_tunnel;
|
|
191
|
+
proxy_set_header Host $host;
|
|
192
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
193
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
194
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
# WebSocket endpoint
|
|
173
198
|
location /ws {
|
|
174
199
|
proxy_pass http://tiflis_tunnel;
|
|
175
200
|
proxy_http_version 1.1;
|
|
@@ -182,9 +207,19 @@ server {
|
|
|
182
207
|
proxy_read_timeout 86400s;
|
|
183
208
|
}
|
|
184
209
|
|
|
210
|
+
# Health check endpoint
|
|
185
211
|
location /health {
|
|
186
212
|
proxy_pass http://tiflis_tunnel;
|
|
187
213
|
}
|
|
214
|
+
|
|
215
|
+
# watchOS polling API
|
|
216
|
+
location /api/v1/watch/ {
|
|
217
|
+
proxy_pass http://tiflis_tunnel;
|
|
218
|
+
proxy_set_header Host $host;
|
|
219
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
220
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
221
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
222
|
+
}
|
|
188
223
|
}
|
|
189
224
|
```
|
|
190
225
|
|
|
@@ -196,10 +231,12 @@ See `deploy/docker-compose.traefik.yml` for a complete example with automatic Le
|
|
|
196
231
|
|
|
197
232
|
| Endpoint | Method | Description |
|
|
198
233
|
| ---------- | --------- | ------------------------------------------- |
|
|
234
|
+
| `/` | GET | Web client (bundled static files) |
|
|
199
235
|
| `/health` | GET | Detailed health check with connection stats |
|
|
200
236
|
| `/healthz` | GET | Simple liveness probe |
|
|
201
237
|
| `/readyz` | GET | Readiness probe |
|
|
202
238
|
| `/ws` | WebSocket | Main WebSocket endpoint |
|
|
239
|
+
| `/api/v1/watch/*` | HTTP | watchOS polling API endpoints |
|
|
203
240
|
|
|
204
241
|
### Health Check Response
|
|
205
242
|
|
package/dist/main.js
CHANGED
|
@@ -447,7 +447,10 @@ function registerWatchApiRoute(app, deps) {
|
|
|
447
447
|
return await reply.status(200).send({
|
|
448
448
|
messages,
|
|
449
449
|
current_sequence: result.currentSequence,
|
|
450
|
-
workstation_online: result.workstationOnline
|
|
450
|
+
workstation_online: result.workstationOnline,
|
|
451
|
+
// Gap detection fields - helps clients know if they missed messages
|
|
452
|
+
oldest_available_sequence: result.oldestAvailableSequence,
|
|
453
|
+
may_have_missed_messages: result.mayHaveMissedMessages
|
|
451
454
|
});
|
|
452
455
|
} catch (error) {
|
|
453
456
|
return await handleError(error, reply, log);
|
|
@@ -545,13 +548,15 @@ var SECURITY_HEADERS = {
|
|
|
545
548
|
// Content Security Policy
|
|
546
549
|
"Content-Security-Policy": [
|
|
547
550
|
"default-src 'self'",
|
|
548
|
-
|
|
551
|
+
// Allow wasm-unsafe-eval for WebAssembly (audio processing libraries)
|
|
552
|
+
"script-src 'self' 'wasm-unsafe-eval'",
|
|
549
553
|
"style-src 'self' 'unsafe-inline'",
|
|
550
554
|
// unsafe-inline needed for Tailwind
|
|
551
555
|
"img-src 'self' data: blob:",
|
|
552
556
|
"font-src 'self'",
|
|
553
557
|
"connect-src 'self' ws: wss:",
|
|
554
|
-
|
|
558
|
+
// Allow data: URIs for audio (TTS responses are base64-encoded)
|
|
559
|
+
"media-src 'self' blob: data:",
|
|
555
560
|
"worker-src 'self' blob:",
|
|
556
561
|
"frame-ancestors 'none'",
|
|
557
562
|
"base-uri 'self'",
|
|
@@ -1926,13 +1931,19 @@ var HttpClient = class _HttpClient {
|
|
|
1926
1931
|
/**
|
|
1927
1932
|
* Gets messages since a given sequence number.
|
|
1928
1933
|
* Also cleans up expired messages.
|
|
1934
|
+
* Returns both messages and metadata about potential gaps.
|
|
1929
1935
|
*/
|
|
1930
1936
|
getMessagesSince(sinceSequence) {
|
|
1931
1937
|
const now = Date.now();
|
|
1932
1938
|
this._messageQueue = this._messageQueue.filter(
|
|
1933
1939
|
(msg) => now - msg.timestamp.getTime() < _HttpClient.MESSAGE_TTL_MS
|
|
1934
1940
|
);
|
|
1935
|
-
|
|
1941
|
+
const cleanedQueue = this._messageQueue;
|
|
1942
|
+
const messages = cleanedQueue.filter((msg) => msg.sequence > sinceSequence);
|
|
1943
|
+
const firstMessage = cleanedQueue[0];
|
|
1944
|
+
const oldestAvailableSequence = firstMessage !== void 0 ? firstMessage.sequence : null;
|
|
1945
|
+
const mayHaveMissedMessages = sinceSequence > 0 && oldestAvailableSequence !== null && sinceSequence < oldestAvailableSequence - 1;
|
|
1946
|
+
return { messages, oldestAvailableSequence, mayHaveMissedMessages };
|
|
1936
1947
|
}
|
|
1937
1948
|
/**
|
|
1938
1949
|
* Clears messages up to a given sequence (acknowledged by client).
|
|
@@ -2107,24 +2118,34 @@ var HttpClientOperationsUseCase = class {
|
|
|
2107
2118
|
return {
|
|
2108
2119
|
messages: [],
|
|
2109
2120
|
currentSequence: 0,
|
|
2110
|
-
workstationOnline: false
|
|
2121
|
+
workstationOnline: false,
|
|
2122
|
+
oldestAvailableSequence: null,
|
|
2123
|
+
mayHaveMissedMessages: false
|
|
2111
2124
|
};
|
|
2112
2125
|
}
|
|
2113
2126
|
client.recordPoll();
|
|
2114
2127
|
if (acknowledgeSequence !== void 0 && acknowledgeSequence > 0) {
|
|
2115
2128
|
client.acknowledgeMessages(acknowledgeSequence);
|
|
2116
2129
|
}
|
|
2117
|
-
const messages = client.getMessagesSince(sinceSequence);
|
|
2130
|
+
const { messages, oldestAvailableSequence, mayHaveMissedMessages } = client.getMessagesSince(sinceSequence);
|
|
2118
2131
|
const workstation = this.workstationRegistry.get(client.tunnelId);
|
|
2119
2132
|
const workstationOnline = workstation?.isOnline ?? false;
|
|
2133
|
+
if (mayHaveMissedMessages) {
|
|
2134
|
+
this.logger.warn(
|
|
2135
|
+
{ deviceId, sinceSequence, oldestAvailableSequence, currentSequence: client.currentSequence },
|
|
2136
|
+
"HTTP client may have missed messages due to stale sequence"
|
|
2137
|
+
);
|
|
2138
|
+
}
|
|
2120
2139
|
this.logger.debug(
|
|
2121
|
-
{ deviceId, sinceSequence, messageCount: messages.length, currentSequence: client.currentSequence },
|
|
2140
|
+
{ deviceId, sinceSequence, messageCount: messages.length, currentSequence: client.currentSequence, mayHaveMissedMessages },
|
|
2122
2141
|
"HTTP client poll"
|
|
2123
2142
|
);
|
|
2124
2143
|
return {
|
|
2125
2144
|
messages,
|
|
2126
2145
|
currentSequence: client.currentSequence,
|
|
2127
|
-
workstationOnline
|
|
2146
|
+
workstationOnline,
|
|
2147
|
+
oldestAvailableSequence,
|
|
2148
|
+
mayHaveMissedMessages
|
|
2128
2149
|
};
|
|
2129
2150
|
}
|
|
2130
2151
|
/**
|
|
@@ -2191,15 +2212,23 @@ var HttpClientOperationsUseCase = class {
|
|
|
2191
2212
|
if (acknowledgeSequence !== void 0 && acknowledgeSequence > 0) {
|
|
2192
2213
|
client.acknowledgeMessages(acknowledgeSequence);
|
|
2193
2214
|
}
|
|
2194
|
-
const messages = client.getMessagesSince(sinceSequence);
|
|
2215
|
+
const { messages, oldestAvailableSequence, mayHaveMissedMessages } = client.getMessagesSince(sinceSequence);
|
|
2216
|
+
if (mayHaveMissedMessages) {
|
|
2217
|
+
this.logger.warn(
|
|
2218
|
+
{ deviceId, sinceSequence, oldestAvailableSequence, currentSequence: client.currentSequence },
|
|
2219
|
+
"HTTP client may have missed messages due to stale sequence (with auth)"
|
|
2220
|
+
);
|
|
2221
|
+
}
|
|
2195
2222
|
this.logger.debug(
|
|
2196
|
-
{ deviceId, sinceSequence, messageCount: messages.length, currentSequence: client.currentSequence },
|
|
2223
|
+
{ deviceId, sinceSequence, messageCount: messages.length, currentSequence: client.currentSequence, mayHaveMissedMessages },
|
|
2197
2224
|
"HTTP client poll with auth"
|
|
2198
2225
|
);
|
|
2199
2226
|
return {
|
|
2200
2227
|
messages,
|
|
2201
2228
|
currentSequence: client.currentSequence,
|
|
2202
|
-
workstationOnline: workstation.isOnline
|
|
2229
|
+
workstationOnline: workstation.isOnline,
|
|
2230
|
+
oldestAvailableSequence,
|
|
2231
|
+
mayHaveMissedMessages
|
|
2203
2232
|
};
|
|
2204
2233
|
}
|
|
2205
2234
|
/**
|
package/package.json
CHANGED