@telepath-computer/television-desktop 0.1.160 → 0.1.161

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.
@@ -8,6 +8,138 @@
8
8
  return ipcMatch?.[1]?.trim() || message;
9
9
  }
10
10
 
11
+ // ../layout/src/constants.ts
12
+ var LAYOUT_FULL_WIDTH_UNITS = 4;
13
+ var LAYOUT_FULL_HEIGHT_UNITS = 6;
14
+ var DEFAULT_LAYOUT_UNIT = 128;
15
+ var CARD_GAP = 16;
16
+ var CARD_WIDTH = DEFAULT_LAYOUT_UNIT * LAYOUT_FULL_WIDTH_UNITS + CARD_GAP * (LAYOUT_FULL_WIDTH_UNITS - 1);
17
+ var CARD_HEIGHT = DEFAULT_LAYOUT_UNIT * LAYOUT_FULL_HEIGHT_UNITS + CARD_GAP * (LAYOUT_FULL_HEIGHT_UNITS - 1);
18
+ var LAYOUT_MOBILE_BREAKPOINT_PX = CARD_WIDTH + CARD_GAP * 2;
19
+
20
+ // ../../node_modules/@rupertsworld/event-target/dist/index.js
21
+ var RESERVED_EVENT_KEYS = /* @__PURE__ */ new Set([
22
+ "target",
23
+ "currentTarget",
24
+ "eventPhase",
25
+ "defaultPrevented",
26
+ "isTrusted",
27
+ "timeStamp",
28
+ "srcElement",
29
+ "returnValue",
30
+ "cancelBubble",
31
+ "NONE",
32
+ "CAPTURING_PHASE",
33
+ "AT_TARGET",
34
+ "BUBBLING_PHASE",
35
+ "composedPath",
36
+ "stopPropagation",
37
+ "stopImmediatePropagation",
38
+ "preventDefault",
39
+ "initEvent"
40
+ ]);
41
+ function assertNoReservedPayloadKeys(props) {
42
+ for (const key of Object.keys(props)) {
43
+ if (RESERVED_EVENT_KEYS.has(key)) {
44
+ throw new Error(`Event payload key "${key}" is reserved; choose a different property name`);
45
+ }
46
+ }
47
+ }
48
+ function assignPayload(target, props) {
49
+ assertNoReservedPayloadKeys(props);
50
+ for (const key of Object.keys(props)) {
51
+ Object.defineProperty(target, key, {
52
+ value: props[key],
53
+ writable: true,
54
+ enumerable: true,
55
+ configurable: true
56
+ });
57
+ }
58
+ }
59
+ function defineEvent() {
60
+ class DefinedEvent extends Event {
61
+ constructor(type, init) {
62
+ const { type: initType, bubbles, cancelable, composed, ...payload } = init ?? {};
63
+ if (initType !== void 0) {
64
+ throw new Error(`Do not pass "type" in init; use the constructor argument instead`);
65
+ }
66
+ super(type, { bubbles, cancelable, composed });
67
+ assignPayload(this, payload);
68
+ }
69
+ }
70
+ return DefinedEvent;
71
+ }
72
+ var EventTarget = class extends globalThis.EventTarget {
73
+ };
74
+
75
+ // ../shared/src/events.ts
76
+ var ChangeEvent = defineEvent();
77
+ var ServerEventMessageEvent = defineEvent();
78
+ var LayoutMutationEvent = defineEvent();
79
+ var LayoutScrollEvent = defineEvent();
80
+ var ArtifactCreatedEvent = defineEvent();
81
+ var ArtifactUpdatedEvent = defineEvent();
82
+ var ArtifactRemovedEvent = defineEvent();
83
+ var ArtifactContentChangedEvent = defineEvent();
84
+ var ScreenCreatedEvent = defineEvent();
85
+ var ScreenUpdatedEvent = defineEvent();
86
+ var ScreenRemovedEvent = defineEvent();
87
+ var ScreenChangedEvent = defineEvent();
88
+ var ArtifactFocusEvent = defineEvent();
89
+ var ThemeChangedEvent = defineEvent();
90
+
91
+ // ../shared/src/errors.ts
92
+ function defineError(name) {
93
+ class DefinedError extends Error {
94
+ constructor(message, fields) {
95
+ super(message);
96
+ this.name = name;
97
+ if (fields) Object.assign(this, fields);
98
+ }
99
+ }
100
+ return DefinedError;
101
+ }
102
+ var RequestError = defineError("RequestError");
103
+ var ValidationError = defineError("ValidationError");
104
+ var NotFoundError = defineError("NotFoundError");
105
+ var InvalidRequestError = defineError("InvalidRequestError");
106
+ var ConflictError = defineError("ConflictError");
107
+
108
+ // ../shared/src/connect-url.ts
109
+ var TOKEN_QUERY_PARAM = "token";
110
+ function parseConnectURL(input) {
111
+ const url = new URL(input);
112
+ const token = url.searchParams.get(TOKEN_QUERY_PARAM);
113
+ return { serverURL: url.origin, token };
114
+ }
115
+
116
+ // src/connect-url.ts
117
+ var ConnectURLError = class extends Error {
118
+ constructor(message = "Enter a valid http or https URL") {
119
+ super(message);
120
+ this.name = "ConnectURLError";
121
+ }
122
+ };
123
+ function parseDesktopConnectURL(input) {
124
+ const trimmed = input.trim();
125
+ if (!trimmed) throw new ConnectURLError();
126
+ if (/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(trimmed) && !/^https?:\/\//i.test(trimmed)) {
127
+ throw new ConnectURLError();
128
+ }
129
+ const withScheme = /^https?:\/\//i.test(trimmed) ? trimmed : `http://${trimmed}`;
130
+ let url;
131
+ try {
132
+ url = new URL(withScheme);
133
+ } catch {
134
+ throw new ConnectURLError();
135
+ }
136
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
137
+ throw new ConnectURLError();
138
+ }
139
+ const parsed = parseConnectURL(url.toString());
140
+ return parsed;
141
+ }
142
+
11
143
  // src/connect-page.ts
12
144
  var MIN_CONNECT_MS = 500;
13
145
  function bindConnectPage(api, elements) {
@@ -48,10 +180,35 @@
48
180
  setBusy(false);
49
181
  }
50
182
  }
183
+ function applyConnectURL(input) {
184
+ let parsed;
185
+ try {
186
+ parsed = parseDesktopConnectURL(input);
187
+ } catch {
188
+ return;
189
+ }
190
+ elements.serverInput.value = parsed.serverURL;
191
+ if (parsed.token !== null) {
192
+ elements.tokenInput.value = parsed.token;
193
+ }
194
+ }
195
+ const onPasteServerURL = (event) => {
196
+ const text = event.clipboardData?.getData("text");
197
+ if (!text) return;
198
+ try {
199
+ parseDesktopConnectURL(text);
200
+ } catch {
201
+ return;
202
+ }
203
+ event.preventDefault();
204
+ applyConnectURL(text);
205
+ };
51
206
  const onSubmit = (event) => {
52
207
  event.preventDefault();
208
+ applyConnectURL(elements.serverInput.value);
53
209
  void runConnect(elements.serverInput.value, elements.tokenInput.value);
54
210
  };
211
+ elements.serverInput.addEventListener("paste", onPasteServerURL);
55
212
  elements.form.addEventListener("submit", onSubmit);
56
213
  void (async () => {
57
214
  const intent = await api.getConnectScreenIntent();
@@ -64,7 +221,10 @@
64
221
  await runConnect(saved.serverURL, saved.token);
65
222
  }
66
223
  })();
67
- return () => elements.form.removeEventListener("submit", onSubmit);
224
+ return () => {
225
+ elements.serverInput.removeEventListener("paste", onPasteServerURL);
226
+ elements.form.removeEventListener("submit", onSubmit);
227
+ };
68
228
  }
69
229
  function initConnectPage(api) {
70
230
  const form = document.getElementById("connect-form");
package/dist/electron.cjs CHANGED
@@ -36,6 +36,111 @@ module.exports = __toCommonJS(index_exports);
36
36
  var import_electron2 = require("electron");
37
37
  var import_node_path2 = __toESM(require("node:path"), 1);
38
38
 
39
+ // ../layout/src/constants.ts
40
+ var LAYOUT_FULL_WIDTH_UNITS = 4;
41
+ var LAYOUT_FULL_HEIGHT_UNITS = 6;
42
+ var DEFAULT_LAYOUT_UNIT = 128;
43
+ var CARD_GAP = 16;
44
+ var CARD_WIDTH = DEFAULT_LAYOUT_UNIT * LAYOUT_FULL_WIDTH_UNITS + CARD_GAP * (LAYOUT_FULL_WIDTH_UNITS - 1);
45
+ var CARD_HEIGHT = DEFAULT_LAYOUT_UNIT * LAYOUT_FULL_HEIGHT_UNITS + CARD_GAP * (LAYOUT_FULL_HEIGHT_UNITS - 1);
46
+ var LAYOUT_MOBILE_BREAKPOINT_PX = CARD_WIDTH + CARD_GAP * 2;
47
+
48
+ // ../../node_modules/@rupertsworld/event-target/dist/index.js
49
+ var RESERVED_EVENT_KEYS = /* @__PURE__ */ new Set([
50
+ "target",
51
+ "currentTarget",
52
+ "eventPhase",
53
+ "defaultPrevented",
54
+ "isTrusted",
55
+ "timeStamp",
56
+ "srcElement",
57
+ "returnValue",
58
+ "cancelBubble",
59
+ "NONE",
60
+ "CAPTURING_PHASE",
61
+ "AT_TARGET",
62
+ "BUBBLING_PHASE",
63
+ "composedPath",
64
+ "stopPropagation",
65
+ "stopImmediatePropagation",
66
+ "preventDefault",
67
+ "initEvent"
68
+ ]);
69
+ function assertNoReservedPayloadKeys(props) {
70
+ for (const key of Object.keys(props)) {
71
+ if (RESERVED_EVENT_KEYS.has(key)) {
72
+ throw new Error(`Event payload key "${key}" is reserved; choose a different property name`);
73
+ }
74
+ }
75
+ }
76
+ function assignPayload(target, props) {
77
+ assertNoReservedPayloadKeys(props);
78
+ for (const key of Object.keys(props)) {
79
+ Object.defineProperty(target, key, {
80
+ value: props[key],
81
+ writable: true,
82
+ enumerable: true,
83
+ configurable: true
84
+ });
85
+ }
86
+ }
87
+ function defineEvent() {
88
+ class DefinedEvent extends Event {
89
+ constructor(type, init) {
90
+ const { type: initType, bubbles, cancelable, composed, ...payload } = init ?? {};
91
+ if (initType !== void 0) {
92
+ throw new Error(`Do not pass "type" in init; use the constructor argument instead`);
93
+ }
94
+ super(type, { bubbles, cancelable, composed });
95
+ assignPayload(this, payload);
96
+ }
97
+ }
98
+ return DefinedEvent;
99
+ }
100
+ var EventTarget = class extends globalThis.EventTarget {
101
+ };
102
+
103
+ // ../shared/src/events.ts
104
+ var ChangeEvent = defineEvent();
105
+ var ServerEventMessageEvent = defineEvent();
106
+ var LayoutMutationEvent = defineEvent();
107
+ var LayoutScrollEvent = defineEvent();
108
+ var ArtifactCreatedEvent = defineEvent();
109
+ var ArtifactUpdatedEvent = defineEvent();
110
+ var ArtifactRemovedEvent = defineEvent();
111
+ var ArtifactContentChangedEvent = defineEvent();
112
+ var ScreenCreatedEvent = defineEvent();
113
+ var ScreenUpdatedEvent = defineEvent();
114
+ var ScreenRemovedEvent = defineEvent();
115
+ var ScreenChangedEvent = defineEvent();
116
+ var ArtifactFocusEvent = defineEvent();
117
+ var ThemeChangedEvent = defineEvent();
118
+
119
+ // ../shared/src/errors.ts
120
+ function defineError(name) {
121
+ class DefinedError extends Error {
122
+ constructor(message, fields) {
123
+ super(message);
124
+ this.name = name;
125
+ if (fields) Object.assign(this, fields);
126
+ }
127
+ }
128
+ return DefinedError;
129
+ }
130
+ var RequestError = defineError("RequestError");
131
+ var ValidationError = defineError("ValidationError");
132
+ var NotFoundError = defineError("NotFoundError");
133
+ var InvalidRequestError = defineError("InvalidRequestError");
134
+ var ConflictError = defineError("ConflictError");
135
+
136
+ // ../shared/src/connect-url.ts
137
+ var TOKEN_QUERY_PARAM = "token";
138
+ function parseConnectURL(input) {
139
+ const url = new URL(input);
140
+ const token = url.searchParams.get(TOKEN_QUERY_PARAM);
141
+ return { serverURL: url.origin, token };
142
+ }
143
+
39
144
  // src/connect-url.ts
40
145
  var ConnectURLError = class extends Error {
41
146
  constructor(message = "Enter a valid http or https URL") {
@@ -43,7 +148,7 @@ var ConnectURLError = class extends Error {
43
148
  this.name = "ConnectURLError";
44
149
  }
45
150
  };
46
- function normalizeConnectURL(input) {
151
+ function parseDesktopConnectURL(input) {
47
152
  const trimmed = input.trim();
48
153
  if (!trimmed) throw new ConnectURLError();
49
154
  if (/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(trimmed) && !/^https?:\/\//i.test(trimmed)) {
@@ -59,7 +164,8 @@ function normalizeConnectURL(input) {
59
164
  if (url.protocol !== "http:" && url.protocol !== "https:") {
60
165
  throw new ConnectURLError();
61
166
  }
62
- return url.toString().replace(/\/$/, "");
167
+ const parsed = parseConnectURL(url.toString());
168
+ return parsed;
63
169
  }
64
170
  function buildRemoteURL(serverURL, token) {
65
171
  const url = new URL(serverURL);
@@ -135,8 +241,8 @@ function loadConnection() {
135
241
  if (!(0, import_node_fs.existsSync)(file)) return null;
136
242
  try {
137
243
  const parsed = JSON.parse((0, import_node_fs.readFileSync)(file, "utf8"));
138
- if (typeof parsed.serverURL !== "string" || typeof parsed.token !== "string") return null;
139
- return { serverURL: parsed.serverURL, token: parsed.token };
244
+ if (typeof parsed.serverURL !== "string") return null;
245
+ return { serverURL: parsed.serverURL, token: typeof parsed.token === "string" ? parsed.token : "" };
140
246
  } catch {
141
247
  return null;
142
248
  }
@@ -245,21 +351,14 @@ var App = class {
245
351
  await this.window.loadFile(import_node_path2.default.join(__dirname, "connect.html"));
246
352
  }
247
353
  async tryConnect(raw) {
248
- const submitted = { serverURL: raw.serverURL, token: raw.token ?? "" };
249
- try {
250
- saveConnection(submitted);
251
- } catch (error) {
252
- const message = error instanceof Error ? error.message : "Failed to connect";
253
- return { ok: false, message };
254
- }
255
- let serverURL;
354
+ let connection;
256
355
  try {
257
- serverURL = normalizeConnectURL(raw.serverURL);
356
+ const parsed = parseDesktopConnectURL(raw.serverURL);
357
+ connection = { serverURL: parsed.serverURL, token: parsed.token ?? raw.token ?? "" };
258
358
  } catch (error) {
259
359
  const message = error instanceof ConnectURLError ? error.message : new ConnectURLError().message;
260
360
  return { ok: false, message };
261
361
  }
262
- const connection = { serverURL, token: submitted.token };
263
362
  try {
264
363
  const preflight = await preflightConnection({
265
364
  serverURL: connection.serverURL,
@@ -268,6 +367,7 @@ var App = class {
268
367
  if (!preflight.ok) {
269
368
  return { ok: false, message: preflight.message };
270
369
  }
370
+ saveConnection(connection);
271
371
  await this.loadRemote(connection);
272
372
  return { ok: true };
273
373
  } catch (error) {
@@ -22,6 +22,49 @@ if (process.contextIsolated) {
22
22
  } else {
23
23
  window.__televisionContentBridge = contentBridge;
24
24
  }
25
+ function startProxyContentPoll() {
26
+ const capturedURL = window.location.href;
27
+ const isTvArtifactURL = (url) => {
28
+ try {
29
+ const parsed = new URL(url);
30
+ return /^\/artifact\/[0-9A-Za-z]{26}(\/|$)/.test(parsed.pathname);
31
+ } catch {
32
+ return false;
33
+ }
34
+ };
35
+ if (!isTvArtifactURL(capturedURL) || typeof window.fetch !== "function") return;
36
+ const NORMAL_POLL_MS = 5e3;
37
+ const SLOW_POLL_MS = 15e3;
38
+ let baselineETag = null;
39
+ let delayMs = NORMAL_POLL_MS;
40
+ let stopped = false;
41
+ const poll = () => {
42
+ void (async () => {
43
+ try {
44
+ const response = await window.fetch(capturedURL, { method: "HEAD" });
45
+ if (!response.ok) throw new Error("Artifact poll failed");
46
+ const etag = response.headers.get("ETag");
47
+ if (!etag) throw new Error("Artifact poll missing ETag");
48
+ delayMs = NORMAL_POLL_MS;
49
+ if (baselineETag === null) {
50
+ baselineETag = etag;
51
+ } else if (etag !== baselineETag) {
52
+ baselineETag = etag;
53
+ import_electron.ipcRenderer.sendToHost(BRIDGE_CHANNEL, { type: "proxy-content-changed" });
54
+ }
55
+ } catch {
56
+ delayMs = Math.min(SLOW_POLL_MS, delayMs * 2);
57
+ } finally {
58
+ if (!stopped) window.setTimeout(poll, delayMs);
59
+ }
60
+ })();
61
+ };
62
+ window.addEventListener("pagehide", () => {
63
+ stopped = true;
64
+ });
65
+ poll();
66
+ }
67
+ startProxyContentPoll();
25
68
  function isTextEditingTarget(target) {
26
69
  if (!(target instanceof Element)) {
27
70
  return false;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@telepath-computer/television-desktop",
3
3
  "productName": "Television",
4
- "version": "0.1.160",
4
+ "version": "0.1.161",
5
5
  "type": "module",
6
6
  "main": "dist/electron.cjs",
7
7
  "bin": {
@@ -22,6 +22,7 @@
22
22
  "type-check": "tsc -p tsconfig.json --noEmit"
23
23
  },
24
24
  "dependencies": {
25
+ "@telepath-computer/television-shared": "*",
25
26
  "electron": "35.7.5"
26
27
  }
27
28
  }