@wtfalch/desktop 0.1.1 → 0.1.2

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
@@ -97,15 +97,18 @@ Cloudflare Access answers a request without a `CF_Authorization` cookie with a
97
97
  redirect to `*.cloudflareaccess.com`. The cookie lives in the company partition,
98
98
  so:
99
99
 
100
- 1. Every request the shell makes to the origin after the window exists (redeem,
101
- logout, the Access probe) goes through that partition's `session.fetch` with
102
- `credentials: 'include'` (`partitionFetcher`). Global `fetch` is never used
103
- for them; `DesktopLogin` has no default fetcher.
100
+ 1. Every request the shell makes to the origin after the window exists goes
101
+ through that partition. The redeem and logout use its `session.fetch` with
102
+ `credentials: 'include'` (`partitionFetcher`). The Access probe uses
103
+ `net.request` on the same session with its cookies (`manualRedirectFetcher`),
104
+ because `session.fetch` rejects a `redirect: 'manual'` request that meets a
105
+ 3xx with "Redirect was cancelled" (Electron 44.4.5), which would read Access's
106
+ own 302 as offline. Global `fetch` is never used for them; `DesktopLogin` has
107
+ no default fetcher.
104
108
  2. With `accessProbePath: '/api/health'`, before the system-browser sign-in starts (and
105
109
  before a stored company loads) the shell asks the origin through the
106
110
  partition with `redirect: 'manual'`, deciding from status and `Location`
107
- only (Electron's `session.fetch` reports a wrong `response.url`, so it is never
108
- read). A non-3xx answer, or a 3xx back to the origin itself, is a pass; a 3xx
111
+ only (the `redirect` event names the target before anything is followed). A non-3xx answer, or a 3xx back to the origin itself, is a pass; a 3xx
109
112
  to any other host (Access, or an identity provider) is not. A network failure
110
113
  is not an Access answer: the window shows the "Could not reach" screen with
111
114
  Try again. When Access wants a login, the window shows the origin so the person passes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wtfalch/desktop",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "The shared Electron shell for wtfalch apps: sign-in handoff, welcome screen, locked company window, auto-update.",
package/src/access.mjs CHANGED
@@ -51,6 +51,40 @@ export function accessNavigation(value, origin) {
51
51
  export const partitionFetcher = (partition) => (url, init) =>
52
52
  partition.fetch(url, { ...init, credentials: 'include' });
53
53
 
54
+ // The probe's fetcher. Electron's session.fetch rejects a `redirect: 'manual'`
55
+ // request that meets a 3xx with "Redirect was cancelled" (Electron 44.4.5), so
56
+ // Access's own 302 would read as offline. net.request on the same partition
57
+ // session names the target in its 'redirect' event instead, before anything is
58
+ // followed. `request` is net.request bound to the partition, injected so this
59
+ // module stays plain Node.
60
+ export const manualRedirectFetcher =
61
+ (request) =>
62
+ (url, init = {}) =>
63
+ new Promise((resolve, reject) => {
64
+ const req = request({
65
+ url,
66
+ method: init.method ?? 'GET',
67
+ redirect: 'manual',
68
+ useSessionCookies: true,
69
+ });
70
+ const answer = (status, location) => {
71
+ req.abort();
72
+ resolve({ status, headers: new Headers(location ? { location } : {}) });
73
+ };
74
+ req.on('redirect', (status, _method, location) => answer(status, location));
75
+ req.on('response', (response) => answer(response.statusCode));
76
+ req.on('error', reject);
77
+ init.signal?.addEventListener(
78
+ 'abort',
79
+ () => {
80
+ req.abort();
81
+ reject(init.signal.reason);
82
+ },
83
+ { once: true },
84
+ );
85
+ req.end();
86
+ });
87
+
54
88
  // Asks the origin, through the partition, whether Access lets this person in:
55
89
  // 'passed', 'access' (still being sent to log in) or 'offline' (the request
56
90
  // itself failed, so Access cannot be judged).
package/src/shell.mjs CHANGED
@@ -4,6 +4,7 @@ import { pathToFileURL } from 'node:url';
4
4
  // Main process only; loaded by startShell() so that importing the package
5
5
  // outside Electron (tests, check scripts) never imports 'electron'.
6
6
  import {
7
+ net,
7
8
  BrowserWindow,
8
9
  Menu,
9
10
  WebContentsView,
@@ -16,7 +17,13 @@ import {
16
17
  shell,
17
18
  } from 'electron';
18
19
  import updaterPkg from 'electron-updater';
19
- import { accessNavigation, partitionFetcher, probeAccess, waitForAccess } from './access.mjs';
20
+ import {
21
+ accessNavigation,
22
+ manualRedirectFetcher,
23
+ partitionFetcher,
24
+ probeAccess,
25
+ waitForAccess,
26
+ } from './access.mjs';
20
27
  import {
21
28
  forgetCompany,
22
29
  loadCompany,
@@ -228,8 +235,11 @@ export function run(config) {
228
235
  // polls through the partition until the origin answers for itself.
229
236
  const passAccess = async (found) => {
230
237
  if (config.accessProbePath === null) return 'passed';
231
- const { partition, fetcher } = context(found.origin);
232
- const probe = () => probeAccess(fetcher, found.origin, config.accessProbePath);
238
+ const { partition } = context(found.origin);
239
+ const probeFetcher = manualRedirectFetcher((options) =>
240
+ net.request({ ...options, session: partition }),
241
+ );
242
+ const probe = () => probeAccess(probeFetcher, found.origin, config.accessProbePath);
233
243
  // Set before the first await so a second request cannot open a second gate.
234
244
  const mine = { cancelled: false };
235
245
  gate = mine;