@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.
Files changed (3) hide show
  1. package/README.md +40 -3
  2. package/dist/main.js +40 -11
  3. 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 mobile clients (iOS/watchOS) and workstations running the Tiflis Code workstation server. It enables remote access to your workstation without requiring a public IP address.
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
- Mobile │◄───────►│ Tunnel │◄───────►│ Workstation │
28
- (iOS/Watch)│ WSS │ Server │ WS │ Server │
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
- "script-src 'self'",
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
- "media-src 'self' blob:",
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
- return this._messageQueue.filter((msg) => msg.sequence > sinceSequence);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiflis-io/tiflis-code-tunnel",
3
- "version": "0.3.9",
3
+ "version": "0.3.11",
4
4
  "description": "Tunnel server for tiflis-code - reverse proxy for workstation connections",
5
5
  "author": "Roman Barinov <rbarinov@gmail.com>",
6
6
  "license": "FSL-1.1-NC",