@telepath-computer/television-desktop 0.1.112 → 0.1.142

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 CHANGED
@@ -14,8 +14,9 @@ npm i -g @telepath-computer/television-desktop
14
14
  tv-desktop
15
15
  ```
16
16
 
17
- A connect window opens; point it at a running Television server (URL +
18
- token, or a `--public` server with an empty token).
17
+ A connect window opens; point it at a running Television server. Enter the
18
+ server URL plus token when the server runs with `--auth`; leave the token blank
19
+ for no-auth servers.
19
20
 
20
21
  ## macOS notes
21
22
 
package/assets/icon.icns CHANGED
Binary file
package/assets/icon.png CHANGED
Binary file
package/dist/connect.html CHANGED
@@ -92,7 +92,7 @@
92
92
  <input id="serverURL" type="url" placeholder="https://example.com" required autofocus />
93
93
  </label>
94
94
  <label>
95
- Token <span style="text-transform: none; letter-spacing: 0; color: #555;">(leave blank for --public servers)</span>
95
+ Token <span style="text-transform: none; letter-spacing: 0; color: #555;">(leave blank for no-auth servers)</span>
96
96
  <input id="token" type="password" />
97
97
  </label>
98
98
  <div class="error" id="error"></div>
package/dist/electron.cjs CHANGED
@@ -64,7 +64,11 @@ var WINDOW_WIDTH = 1400;
64
64
  var WINDOW_HEIGHT = 1e3;
65
65
  var TRAFFIC_LIGHT_POSITION = { x: 12, y: 12 };
66
66
  var SET_CONNECTION_CHANNEL = "television:set-connection";
67
+ var TEST_EXTERNAL_OPEN_CHANNEL = "television:test:external-open";
67
68
  var TEST_FIXTURE_FLAG = "--test-fixture";
69
+ if (process.env.TV_ELECTRON_CHROMIUM_LOGS !== "1") {
70
+ import_electron2.app.commandLine?.appendSwitch("log-level", "3");
71
+ }
68
72
  function buildRemoteURL({ serverURL, token }) {
69
73
  const url = new URL(serverURL);
70
74
  url.searchParams.set("mode", "electron");
@@ -76,6 +80,17 @@ function readTestFixtureURL(argv) {
76
80
  if (idx === -1 || idx === argv.length - 1) return null;
77
81
  return argv[idx + 1] ?? null;
78
82
  }
83
+ function isExternalOpenURL(url) {
84
+ return url.startsWith("http://") || url.startsWith("https://");
85
+ }
86
+ function recordExternalOpenForTest(url) {
87
+ const globalState = globalThis;
88
+ const log = globalState.__televisionExternalOpenLog ??= [];
89
+ log.push(url);
90
+ for (const window of import_electron2.BrowserWindow.getAllWindows()) {
91
+ window.webContents.send(TEST_EXTERNAL_OPEN_CHANNEL, url);
92
+ }
93
+ }
79
94
  var App = class {
80
95
  window = null;
81
96
  async start() {
@@ -84,6 +99,7 @@ var App = class {
84
99
  import_electron2.app.dock.setIcon(import_node_path2.default.join(__dirname, "..", "assets", "icon.png"));
85
100
  }
86
101
  this.installMenu();
102
+ this.installWindowOpenHandler();
87
103
  import_electron2.ipcMain.handle(SET_CONNECTION_CHANNEL, async (_event, connection) => {
88
104
  saveConnection(connection);
89
105
  await this.loadRemote(connection);
@@ -114,6 +130,7 @@ var App = class {
114
130
  });
115
131
  this.window.webContents.on("will-attach-webview", (_event, webPreferences) => {
116
132
  webPreferences.preload = import_node_path2.default.join(__dirname, "webview-bridge-preload.cjs");
133
+ webPreferences.contextIsolation = false;
117
134
  });
118
135
  this.window.once("ready-to-show", () => this.window?.show());
119
136
  this.window.on("closed", () => {
@@ -139,6 +156,20 @@ var App = class {
139
156
  if (!this.window) return;
140
157
  await this.window.loadURL(buildRemoteURL(connection));
141
158
  }
159
+ installWindowOpenHandler() {
160
+ import_electron2.app.on("web-contents-created", (_event, contents) => {
161
+ contents.setWindowOpenHandler(({ url }) => {
162
+ if (isExternalOpenURL(url)) {
163
+ if (process.env.TV_TEST_MODE === "true") {
164
+ recordExternalOpenForTest(url);
165
+ } else {
166
+ void import_electron2.shell.openExternal(url);
167
+ }
168
+ }
169
+ return { action: "deny" };
170
+ });
171
+ });
172
+ }
142
173
  installMenu() {
143
174
  const isMac = process.platform === "darwin";
144
175
  const connectItem = {
@@ -3,55 +3,76 @@
3
3
  // src/webview-bridge-preload.ts
4
4
  var import_electron = require("electron");
5
5
  var BRIDGE_CHANNEL = "television-artifact-bridge";
6
- var lastWheelClientX = 0;
7
- var lastWheelClientY = 0;
6
+ var contentBridge = {
7
+ postToHost(message) {
8
+ import_electron.ipcRenderer.sendToHost(BRIDGE_CHANNEL, message);
9
+ },
10
+ onHostMessage(callback) {
11
+ const listener = (_event, message) => {
12
+ callback(message);
13
+ };
14
+ import_electron.ipcRenderer.on(BRIDGE_CHANNEL, listener);
15
+ return () => {
16
+ import_electron.ipcRenderer.removeListener(BRIDGE_CHANNEL, listener);
17
+ };
18
+ }
19
+ };
20
+ if (process.contextIsolated) {
21
+ import_electron.contextBridge.exposeInMainWorld("__televisionContentBridge", contentBridge);
22
+ } else {
23
+ window.__televisionContentBridge = contentBridge;
24
+ }
25
+ function isTextEditingTarget(target) {
26
+ if (!(target instanceof Element)) {
27
+ return false;
28
+ }
29
+ const editable = target.closest(
30
+ "input, textarea, select, [contenteditable], [role='textbox'], [role='searchbox'], [role='combobox'], [role='spinbutton'], [role='slider']"
31
+ );
32
+ if (!editable) {
33
+ return false;
34
+ }
35
+ if (editable.hasAttribute("contenteditable")) {
36
+ return editable.getAttribute("contenteditable")?.toLowerCase() !== "false";
37
+ }
38
+ return true;
39
+ }
40
+ window.addEventListener(
41
+ "keydown",
42
+ (event) => {
43
+ if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") {
44
+ return;
45
+ }
46
+ if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) {
47
+ return;
48
+ }
49
+ if (isTextEditingTarget(event.target)) {
50
+ return;
51
+ }
52
+ event.preventDefault();
53
+ import_electron.ipcRenderer.sendToHost(BRIDGE_CHANNEL, {
54
+ type: "filmstrip-key",
55
+ direction: event.key === "ArrowRight" ? 1 : -1
56
+ });
57
+ },
58
+ { capture: true }
59
+ );
8
60
  window.addEventListener(
9
61
  "wheel",
10
62
  (event) => {
11
- lastWheelClientX = event.clientX;
12
- lastWheelClientY = event.clientY;
63
+ const usingShiftFallback = event.shiftKey && event.deltaX === 0 && event.deltaY !== 0;
64
+ const horizontalDelta = usingShiftFallback ? event.deltaY : event.deltaX;
65
+ const horizontalMagnitude = Math.abs(horizontalDelta);
66
+ const verticalMagnitude = usingShiftFallback ? 0 : Math.abs(event.deltaY);
67
+ if (horizontalMagnitude <= verticalMagnitude) {
68
+ return;
69
+ }
13
70
  const payload = {
14
71
  type: "wheel",
15
- deltaX: event.deltaX,
16
- deltaY: event.deltaY,
72
+ deltaX: horizontalDelta,
17
73
  shiftKey: event.shiftKey
18
74
  };
19
75
  import_electron.ipcRenderer.sendToHost(BRIDGE_CHANNEL, payload);
20
- event.preventDefault();
21
76
  },
22
77
  { passive: false }
23
78
  );
24
- import_electron.ipcRenderer.on(BRIDGE_CHANNEL, (_event, payload) => {
25
- if (payload?.type !== "scroll") return;
26
- const deltaX = payload.deltaX ?? 0;
27
- const deltaY = payload.deltaY ?? 0;
28
- const target = resolveScrollTarget(lastWheelClientX, lastWheelClientY, deltaX, deltaY);
29
- target.scrollBy(deltaX, deltaY);
30
- });
31
- function resolveScrollTarget(x, y, deltaX, deltaY) {
32
- const fallback = document.scrollingElement ?? document.documentElement;
33
- if (typeof document.elementFromPoint !== "function") return fallback;
34
- let node = document.elementFromPoint(x, y);
35
- while (node && node !== document.documentElement) {
36
- if (canScroll(node, deltaX, deltaY)) return node;
37
- node = node.parentElement;
38
- }
39
- return fallback;
40
- }
41
- function canScroll(node, deltaX, deltaY) {
42
- const style = getComputedStyle(node);
43
- if (deltaY !== 0) {
44
- if (isScrollableOverflow(style.overflowY) && node.scrollHeight > node.clientHeight) {
45
- return true;
46
- }
47
- }
48
- if (deltaX !== 0) {
49
- if (isScrollableOverflow(style.overflowX) && node.scrollWidth > node.clientWidth) {
50
- return true;
51
- }
52
- }
53
- return false;
54
- }
55
- function isScrollableOverflow(value) {
56
- return value === "auto" || value === "scroll" || value === "overlay";
57
- }
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.112",
4
+ "version": "0.1.142",
5
5
  "type": "module",
6
6
  "main": "dist/electron.cjs",
7
7
  "bin": {
@@ -19,7 +19,9 @@
19
19
  "scripts": {
20
20
  "build": "node build.mjs",
21
21
  "prepublishOnly": "node build.mjs",
22
- "type-check": "tsc -p tsconfig.json --noEmit"
22
+ "type-check": "tsc -p tsconfig.json --noEmit",
23
+ "test": "vitest run --config vitest.config.ts",
24
+ "test:e2e": "node ../../scripts/run-electron-e2e.mjs"
23
25
  },
24
26
  "dependencies": {
25
27
  "electron": "35.7.5"