where_is_waldo 0.1.14 → 0.1.16

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 (5) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +89 -0
  3. data/README.md +65 -0
  4. data/VERSION +1 -1
  5. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 55b6e1ced7f3e93a1e415bc0e28d10b15a212ad134e3108f585bea84220a1f10
4
- data.tar.gz: 6c068bbd05c86ea7c6ba9b2136295f5ef17c02bd19bf9211f8b4ee59a83b17f1
3
+ metadata.gz: c5bbdc6480c178670602c5f3749370dced0ef932e99d49c5a753eb3ceef3ed9e
4
+ data.tar.gz: e8b3d2db9b9179ae9828c7637b772f9f9b9dde5b82cfc1a756c524b8d701694f
5
5
  SHA512:
6
- metadata.gz: 9d96b99856186d169d3e6c97f4a032f298158ecb9e8532238c40d98a0fc7388d2a3a4eaa195417fe21064d1babc389b1c083bf85dbdfc20a33f5fb50d223d076
7
- data.tar.gz: 4f0a4167858defcc5403deb7fafeac5abdb7556af91017fc1e679c952bfb39e067609ca09320353d99663106dba8f0afc9c7740c28642aff3e231057681c6b5f
6
+ metadata.gz: d92709f8debef9199e51190a7a1952f132af7657faba25d1df174c58e7b841da9fdbd76ad5ef2c50cb922866f49c14306abbe78bfd86cff350564cf819a57715
7
+ data.tar.gz: 771c824d4eef5c3d02883389dc51ae8197342bb24196d18d00cc07bcba1ba971c654a98a7a285d5d81d574e78d550c28f8a9350212f3da65b38260c2510d7f80
data/CHANGELOG.md CHANGED
@@ -5,6 +5,95 @@ Notable changes to where_is_waldo. Format loosely follows
5
5
 
6
6
  ## Unreleased
7
7
 
8
+ ## 0.1.16 - 2026-11-11
9
+
10
+ ### Fixed
11
+
12
+ - **A client could stop reconnecting FOREVER, and nothing in ActionCable would
13
+ turn it back on.** ActionCable's `ConnectionMonitor` is the only thing that
14
+ reconnects a dropped socket, and it can be switched off permanently:
15
+
16
+ ```js
17
+ close({allowReconnect}) { if (!allowReconnect) { this.monitor.stop() } }
18
+ stop() { this.stopPolling(); removeEventListener("visibilitychange", ...) }
19
+ ```
20
+
21
+ `stop()` unregisters the very listener that would notice the tab being
22
+ focused again, and nothing re-registers it. The far end can trigger this
23
+ from outside the browser: Rails sends a disconnect with `reconnect: false`
24
+ for an unauthorized or remotely-disconnected connection.
25
+
26
+ Observed in production: a socket closed and the tab then ran for SEVEN AND A
27
+ HALF HOURS -- visible, focused, making ordinary HTTP requests -- without a
28
+ single further WebSocket attempt. Presence stayed empty the whole time and
29
+ anything broadcast to online subscribers missed it. Nothing was refused;
30
+ nothing was attempted.
31
+
32
+ There is now a watchdog: while a connection is wanted, waldo notices a
33
+ monitor that has stopped and starts it again, on a 10-second interval and on
34
+ its own `visibilitychange` listener. It deliberately does NOTHING while the
35
+ monitor is running, so it never opens a competing socket or interferes with
36
+ ActionCable's backoff, and it never undoes a deliberate `disconnectCable()`
37
+ or a teardown from `configureCable()`.
38
+
39
+ ### Added
40
+
41
+ - `checkConnection()` is exported. It performs one watchdog pass and returns
42
+ what it did (`open`, `monitor-running`, `monitor-restarted`, `opened`,
43
+ `not-wanted`, `no-consumer`, `error`), which makes the recovery path
44
+ testable and gives an application a way to force a check -- after a resume
45
+ from sleep, say.
46
+
47
+ ### Changed
48
+
49
+ - Every read of ActionCable's internals is guarded and returns `error` rather
50
+ than throwing, so a future rename inside ActionCable degrades the watchdog
51
+ instead of breaking a consumer's render or heartbeat.
52
+
53
+ ## 0.1.15 - 2026-11-10
54
+
55
+ ### Fixed
56
+
57
+ - **A reconnecting client kept sending its ORIGINAL token, forever.** The auth
58
+ token was read once and baked into the consumer's URL string at first
59
+ connect. ActionCable's `Consumer` declares `get url()`, which re-runs
60
+ `createWebSocketURL` on every connection attempt -- so passing a string meant
61
+ each reconnect after a sleep, a network blip or a server restart reused the
62
+ token from page load.
63
+
64
+ With a short-lived access token that is a silent, permanent failure. The
65
+ first reconnect after expiry is rejected, every retry sends the same dead
66
+ token, and the client sits disconnected while looking merely idle. Presence
67
+ empties and anything broadcast to "online" subscribers reaches nobody. In
68
+ one case a tab opened a socket and the server closed it 74ms later, rejected,
69
+ while the application kept working and reported nothing.
70
+
71
+ `getConsumer` now hands `createConsumer` a FUNCTION, so the token is resolved
72
+ per attempt and a refreshed one is picked up by the next retry with no
73
+ intervention.
74
+
75
+ ### Added
76
+
77
+ - **`getToken` may now return a promise.** A token provider that has to refresh
78
+ before it can answer could not be expressed before, because the URL callback
79
+ is invoked synchronously while opening a socket. An async provider is now
80
+ resolved in the background and its result cached for the next attempt, which
81
+ costs at most one refused attempt given ActionCable's backoff. Returning a
82
+ plain value still works exactly as before and stays synchronous.
83
+
84
+ - A throwing `getToken` no longer takes the socket down with it. A storage
85
+ accessor can raise outright with cookies blocked or in a private window; the
86
+ connection now proceeds without a token and lets the server decide.
87
+
88
+ ### Changed
89
+
90
+ - A **synchronous** `getToken` returning `null` is now taken at face value: the
91
+ socket connects with no token and the server decides. Previously the last
92
+ cached token was reused, so after a sign-out a client kept reconnecting with
93
+ the OLD token rather than being refused properly. A rejected or throwing
94
+ provider still falls back to the last known token, since those mean "could
95
+ not answer" rather than "there is no token".
96
+
8
97
  ## 0.1.14 - 2026-11-06
9
98
 
10
99
  ### Fixed
data/README.md CHANGED
@@ -81,6 +81,71 @@ configureCable({
81
81
  </PresenceProvider>
82
82
  ```
83
83
 
84
+ ### Reconnection
85
+
86
+ ActionCable reconnects dropped sockets through its own `ConnectionMonitor`, and
87
+ that is still what does the work here. But the monitor can be switched off
88
+ permanently -- `close({allowReconnect: false})` calls `monitor.stop()`, which
89
+ unregisters its `visibilitychange` listener, and nothing in ActionCable
90
+ re-registers it. Rails sends a disconnect with `reconnect: false` for an
91
+ unauthorized or remotely-disconnected connection, so the far end can put a
92
+ browser tab permanently offline.
93
+
94
+ Since 0.1.16 waldo watches for that: while a connection is wanted it notices a
95
+ stopped monitor and starts it again, on a 10-second interval and on its own
96
+ visibility listener. It does nothing while the monitor is running, so it never
97
+ opens a competing socket, and it never undoes a deliberate `disconnectCable()`.
98
+
99
+ `checkConnection()` runs one pass on demand and returns what it did, which is
100
+ useful after a resume from sleep or any point an app knows more than a timer
101
+ does:
102
+
103
+ ```jsx
104
+ import { checkConnection } from '@byscott-io/where-is-waldo';
105
+
106
+ // e.g. after regaining network
107
+ window.addEventListener('online', () => checkConnection());
108
+ ```
109
+
110
+ ### `getToken` and short-lived tokens
111
+
112
+ `getToken` is called **once per connection attempt**, not once at startup. That
113
+ matters because ActionCable reconnects on its own -- after a sleep, a network
114
+ blip, or a server restart -- and each attempt asks again. So returning the
115
+ current token keeps a long-lived tab connected, while capturing one in a
116
+ closure does not:
117
+
118
+ ```jsx
119
+ // Good: read at call time, so a rotated token is picked up on reconnect.
120
+ getToken: () => localStorage.getItem('token')
121
+
122
+ // Broken: captured once; every reconnect resends the original token, and once
123
+ // it expires the connection is refused for the life of the page.
124
+ const token = localStorage.getItem('token');
125
+ getToken: () => token
126
+ ```
127
+
128
+ **It may also return a promise**, for a provider that has to refresh before it
129
+ can answer:
130
+
131
+ ```jsx
132
+ getToken: async () => (await auth.freshToken()) || null
133
+ ```
134
+
135
+ The URL callback cannot await -- ActionCable invokes it synchronously while
136
+ opening a socket -- so an async provider is resolved in the background and its
137
+ result used from the next attempt. Given ActionCable's backoff that costs at
138
+ most one refused attempt. The provider is not invoked again while a resolve is
139
+ outstanding, so a reconnect storm will not fire a refresh request per attempt.
140
+
141
+ Two smaller guarantees:
142
+
143
+ - Returning `null` from a **synchronous** provider is taken at face value: the
144
+ socket connects with no token and the server decides. Use that on sign-out.
145
+ - A provider that **throws** does not take the connection down. The last known
146
+ token is used instead, since a storage accessor can raise outright with
147
+ cookies blocked or in a private window.
148
+
84
149
  ```jsx
85
150
  // any component — subscribe to an event type and filter the payload yourself.
86
151
  // Auto-unsubscribes on unmount; no central handler config.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.14
1
+ 0.1.16
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: where_is_waldo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.14
4
+ version: 0.1.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Gibson