summaryrefslogtreecommitdiff
path: root/public/assets/actioncable.esm-555679e44f119be7a7e54121fca0beb0a5832f2933c1a1edcd0515bbafad22e0.js
diff options
context:
space:
mode:
authorerdgeist <erdgeist@erdgeist.org>2026-06-26 13:40:55 +0200
committererdgeist <erdgeist@erdgeist.org>2026-06-26 13:40:55 +0200
commit811bb04649365b0faaa00b1e0810bb101a4d19b1 (patch)
tree581d52faa6e1bf21a1cdfd4da7127a0f95786bad /public/assets/actioncable.esm-555679e44f119be7a7e54121fca0beb0a5832f2933c1a1edcd0515bbafad22e0.js
parenta1ddc25da0d2aa79a4d9216ef7792f26233bd38e (diff)
Stage 5 click-testing fixes
- Fix link_to :method → button_to for all PUT/DELETE actions - Add button_to CSS reset to admin.css for visual consistency - Fix admin layout: replace broken jquery/jquery_ujs pipeline refs with admin_bundle via sprockets; add sprockets-rails, jquery-ui-rails gems - Add app/assets/javascripts/admin_bundle.js pipeline manifest - Fix event_information helper: use safe_join to avoid double-escaping - Fix nodes_helper: to_s(:db) → to_fs(:db) for event times - Fix revisions view: eliminate nested forms; diff button uses vanilla JS to collect radio button values before POST - Fix config/environments/development.rb and test.rb: cache_classes → enable_reloading - Add routing_filter_rails71_patch.rb version guard - Move LockedByAnotherUser to own file for Zeitwerk autoloading - Fix Globalize fallbacks via config.i18n.fallbacks in application.rb
Diffstat (limited to 'public/assets/actioncable.esm-555679e44f119be7a7e54121fca0beb0a5832f2933c1a1edcd0515bbafad22e0.js')
-rw-r--r--public/assets/actioncable.esm-555679e44f119be7a7e54121fca0beb0a5832f2933c1a1edcd0515bbafad22e0.js512
1 files changed, 512 insertions, 0 deletions
diff --git a/public/assets/actioncable.esm-555679e44f119be7a7e54121fca0beb0a5832f2933c1a1edcd0515bbafad22e0.js b/public/assets/actioncable.esm-555679e44f119be7a7e54121fca0beb0a5832f2933c1a1edcd0515bbafad22e0.js
new file mode 100644
index 00000000..18320091
--- /dev/null
+++ b/public/assets/actioncable.esm-555679e44f119be7a7e54121fca0beb0a5832f2933c1a1edcd0515bbafad22e0.js
@@ -0,0 +1,512 @@
1var adapters = {
2 logger: typeof console !== "undefined" ? console : undefined,
3 WebSocket: typeof WebSocket !== "undefined" ? WebSocket : undefined
4};
5
6var logger = {
7 log(...messages) {
8 if (this.enabled) {
9 messages.push(Date.now());
10 adapters.logger.log("[ActionCable]", ...messages);
11 }
12 }
13};
14
15const now = () => (new Date).getTime();
16
17const secondsSince = time => (now() - time) / 1e3;
18
19class ConnectionMonitor {
20 constructor(connection) {
21 this.visibilityDidChange = this.visibilityDidChange.bind(this);
22 this.connection = connection;
23 this.reconnectAttempts = 0;
24 }
25 start() {
26 if (!this.isRunning()) {
27 this.startedAt = now();
28 delete this.stoppedAt;
29 this.startPolling();
30 addEventListener("visibilitychange", this.visibilityDidChange);
31 logger.log(`ConnectionMonitor started. stale threshold = ${this.constructor.staleThreshold} s`);
32 }
33 }
34 stop() {
35 if (this.isRunning()) {
36 this.stoppedAt = now();
37 this.stopPolling();
38 removeEventListener("visibilitychange", this.visibilityDidChange);
39 logger.log("ConnectionMonitor stopped");
40 }
41 }
42 isRunning() {
43 return this.startedAt && !this.stoppedAt;
44 }
45 recordMessage() {
46 this.pingedAt = now();
47 }
48 recordConnect() {
49 this.reconnectAttempts = 0;
50 delete this.disconnectedAt;
51 logger.log("ConnectionMonitor recorded connect");
52 }
53 recordDisconnect() {
54 this.disconnectedAt = now();
55 logger.log("ConnectionMonitor recorded disconnect");
56 }
57 startPolling() {
58 this.stopPolling();
59 this.poll();
60 }
61 stopPolling() {
62 clearTimeout(this.pollTimeout);
63 }
64 poll() {
65 this.pollTimeout = setTimeout((() => {
66 this.reconnectIfStale();
67 this.poll();
68 }), this.getPollInterval());
69 }
70 getPollInterval() {
71 const {staleThreshold: staleThreshold, reconnectionBackoffRate: reconnectionBackoffRate} = this.constructor;
72 const backoff = Math.pow(1 + reconnectionBackoffRate, Math.min(this.reconnectAttempts, 10));
73 const jitterMax = this.reconnectAttempts === 0 ? 1 : reconnectionBackoffRate;
74 const jitter = jitterMax * Math.random();
75 return staleThreshold * 1e3 * backoff * (1 + jitter);
76 }
77 reconnectIfStale() {
78 if (this.connectionIsStale()) {
79 logger.log(`ConnectionMonitor detected stale connection. reconnectAttempts = ${this.reconnectAttempts}, time stale = ${secondsSince(this.refreshedAt)} s, stale threshold = ${this.constructor.staleThreshold} s`);
80 this.reconnectAttempts++;
81 if (this.disconnectedRecently()) {
82 logger.log(`ConnectionMonitor skipping reopening recent disconnect. time disconnected = ${secondsSince(this.disconnectedAt)} s`);
83 } else {
84 logger.log("ConnectionMonitor reopening");
85 this.connection.reopen();
86 }
87 }
88 }
89 get refreshedAt() {
90 return this.pingedAt ? this.pingedAt : this.startedAt;
91 }
92 connectionIsStale() {
93 return secondsSince(this.refreshedAt) > this.constructor.staleThreshold;
94 }
95 disconnectedRecently() {
96 return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold;
97 }
98 visibilityDidChange() {
99 if (document.visibilityState === "visible") {
100 setTimeout((() => {
101 if (this.connectionIsStale() || !this.connection.isOpen()) {
102 logger.log(`ConnectionMonitor reopening stale connection on visibilitychange. visibilityState = ${document.visibilityState}`);
103 this.connection.reopen();
104 }
105 }), 200);
106 }
107 }
108}
109
110ConnectionMonitor.staleThreshold = 6;
111
112ConnectionMonitor.reconnectionBackoffRate = .15;
113
114var INTERNAL = {
115 message_types: {
116 welcome: "welcome",
117 disconnect: "disconnect",
118 ping: "ping",
119 confirmation: "confirm_subscription",
120 rejection: "reject_subscription"
121 },
122 disconnect_reasons: {
123 unauthorized: "unauthorized",
124 invalid_request: "invalid_request",
125 server_restart: "server_restart",
126 remote: "remote"
127 },
128 default_mount_path: "/cable",
129 protocols: [ "actioncable-v1-json", "actioncable-unsupported" ]
130};
131
132const {message_types: message_types, protocols: protocols} = INTERNAL;
133
134const supportedProtocols = protocols.slice(0, protocols.length - 1);
135
136const indexOf = [].indexOf;
137
138class Connection {
139 constructor(consumer) {
140 this.open = this.open.bind(this);
141 this.consumer = consumer;
142 this.subscriptions = this.consumer.subscriptions;
143 this.monitor = new ConnectionMonitor(this);
144 this.disconnected = true;
145 }
146 send(data) {
147 if (this.isOpen()) {
148 this.webSocket.send(JSON.stringify(data));
149 return true;
150 } else {
151 return false;
152 }
153 }
154 open() {
155 if (this.isActive()) {
156 logger.log(`Attempted to open WebSocket, but existing socket is ${this.getState()}`);
157 return false;
158 } else {
159 const socketProtocols = [ ...protocols, ...this.consumer.subprotocols || [] ];
160 logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${socketProtocols}`);
161 if (this.webSocket) {
162 this.uninstallEventHandlers();
163 }
164 this.webSocket = new adapters.WebSocket(this.consumer.url, socketProtocols);
165 this.installEventHandlers();
166 this.monitor.start();
167 return true;
168 }
169 }
170 close({allowReconnect: allowReconnect} = {
171 allowReconnect: true
172 }) {
173 if (!allowReconnect) {
174 this.monitor.stop();
175 }
176 if (this.isOpen()) {
177 return this.webSocket.close();
178 }
179 }
180 reopen() {
181 logger.log(`Reopening WebSocket, current state is ${this.getState()}`);
182 if (this.isActive()) {
183 try {
184 return this.close();
185 } catch (error) {
186 logger.log("Failed to reopen WebSocket", error);
187 } finally {
188 logger.log(`Reopening WebSocket in ${this.constructor.reopenDelay}ms`);
189 setTimeout(this.open, this.constructor.reopenDelay);
190 }
191 } else {
192 return this.open();
193 }
194 }
195 getProtocol() {
196 if (this.webSocket) {
197 return this.webSocket.protocol;
198 }
199 }
200 isOpen() {
201 return this.isState("open");
202 }
203 isActive() {
204 return this.isState("open", "connecting");
205 }
206 triedToReconnect() {
207 return this.monitor.reconnectAttempts > 0;
208 }
209 isProtocolSupported() {
210 return indexOf.call(supportedProtocols, this.getProtocol()) >= 0;
211 }
212 isState(...states) {
213 return indexOf.call(states, this.getState()) >= 0;
214 }
215 getState() {
216 if (this.webSocket) {
217 for (let state in adapters.WebSocket) {
218 if (adapters.WebSocket[state] === this.webSocket.readyState) {
219 return state.toLowerCase();
220 }
221 }
222 }
223 return null;
224 }
225 installEventHandlers() {
226 for (let eventName in this.events) {
227 const handler = this.events[eventName].bind(this);
228 this.webSocket[`on${eventName}`] = handler;
229 }
230 }
231 uninstallEventHandlers() {
232 for (let eventName in this.events) {
233 this.webSocket[`on${eventName}`] = function() {};
234 }
235 }
236}
237
238Connection.reopenDelay = 500;
239
240Connection.prototype.events = {
241 message(event) {
242 if (!this.isProtocolSupported()) {
243 return;
244 }
245 const {identifier: identifier, message: message, reason: reason, reconnect: reconnect, type: type} = JSON.parse(event.data);
246 this.monitor.recordMessage();
247 switch (type) {
248 case message_types.welcome:
249 if (this.triedToReconnect()) {
250 this.reconnectAttempted = true;
251 }
252 this.monitor.recordConnect();
253 return this.subscriptions.reload();
254
255 case message_types.disconnect:
256 logger.log(`Disconnecting. Reason: ${reason}`);
257 return this.close({
258 allowReconnect: reconnect
259 });
260
261 case message_types.ping:
262 return null;
263
264 case message_types.confirmation:
265 this.subscriptions.confirmSubscription(identifier);
266 if (this.reconnectAttempted) {
267 this.reconnectAttempted = false;
268 return this.subscriptions.notify(identifier, "connected", {
269 reconnected: true
270 });
271 } else {
272 return this.subscriptions.notify(identifier, "connected", {
273 reconnected: false
274 });
275 }
276
277 case message_types.rejection:
278 return this.subscriptions.reject(identifier);
279
280 default:
281 return this.subscriptions.notify(identifier, "received", message);
282 }
283 },
284 open() {
285 logger.log(`WebSocket onopen event, using '${this.getProtocol()}' subprotocol`);
286 this.disconnected = false;
287 if (!this.isProtocolSupported()) {
288 logger.log("Protocol is unsupported. Stopping monitor and disconnecting.");
289 return this.close({
290 allowReconnect: false
291 });
292 }
293 },
294 close(event) {
295 logger.log("WebSocket onclose event");
296 if (this.disconnected) {
297 return;
298 }
299 this.disconnected = true;
300 this.monitor.recordDisconnect();
301 return this.subscriptions.notifyAll("disconnected", {
302 willAttemptReconnect: this.monitor.isRunning()
303 });
304 },
305 error() {
306 logger.log("WebSocket onerror event");
307 }
308};
309
310const extend = function(object, properties) {
311 if (properties != null) {
312 for (let key in properties) {
313 const value = properties[key];
314 object[key] = value;
315 }
316 }
317 return object;
318};
319
320class Subscription {
321 constructor(consumer, params = {}, mixin) {
322 this.consumer = consumer;
323 this.identifier = JSON.stringify(params);
324 extend(this, mixin);
325 }
326 perform(action, data = {}) {
327 data.action = action;
328 return this.send(data);
329 }
330 send(data) {
331 return this.consumer.send({
332 command: "message",
333 identifier: this.identifier,
334 data: JSON.stringify(data)
335 });
336 }
337 unsubscribe() {
338 return this.consumer.subscriptions.remove(this);
339 }
340}
341
342class SubscriptionGuarantor {
343 constructor(subscriptions) {
344 this.subscriptions = subscriptions;
345 this.pendingSubscriptions = [];
346 }
347 guarantee(subscription) {
348 if (this.pendingSubscriptions.indexOf(subscription) == -1) {
349 logger.log(`SubscriptionGuarantor guaranteeing ${subscription.identifier}`);
350 this.pendingSubscriptions.push(subscription);
351 } else {
352 logger.log(`SubscriptionGuarantor already guaranteeing ${subscription.identifier}`);
353 }
354 this.startGuaranteeing();
355 }
356 forget(subscription) {
357 logger.log(`SubscriptionGuarantor forgetting ${subscription.identifier}`);
358 this.pendingSubscriptions = this.pendingSubscriptions.filter((s => s !== subscription));
359 }
360 startGuaranteeing() {
361 this.stopGuaranteeing();
362 this.retrySubscribing();
363 }
364 stopGuaranteeing() {
365 clearTimeout(this.retryTimeout);
366 }
367 retrySubscribing() {
368 this.retryTimeout = setTimeout((() => {
369 if (this.subscriptions && typeof this.subscriptions.subscribe === "function") {
370 this.pendingSubscriptions.map((subscription => {
371 logger.log(`SubscriptionGuarantor resubscribing ${subscription.identifier}`);
372 this.subscriptions.subscribe(subscription);
373 }));
374 }
375 }), 500);
376 }
377}
378
379class Subscriptions {
380 constructor(consumer) {
381 this.consumer = consumer;
382 this.guarantor = new SubscriptionGuarantor(this);
383 this.subscriptions = [];
384 }
385 create(channelName, mixin) {
386 const channel = channelName;
387 const params = typeof channel === "object" ? channel : {
388 channel: channel
389 };
390 const subscription = new Subscription(this.consumer, params, mixin);
391 return this.add(subscription);
392 }
393 add(subscription) {
394 this.subscriptions.push(subscription);
395 this.consumer.ensureActiveConnection();
396 this.notify(subscription, "initialized");
397 this.subscribe(subscription);
398 return subscription;
399 }
400 remove(subscription) {
401 this.forget(subscription);
402 if (!this.findAll(subscription.identifier).length) {
403 this.sendCommand(subscription, "unsubscribe");
404 }
405 return subscription;
406 }
407 reject(identifier) {
408 return this.findAll(identifier).map((subscription => {
409 this.forget(subscription);
410 this.notify(subscription, "rejected");
411 return subscription;
412 }));
413 }
414 forget(subscription) {
415 this.guarantor.forget(subscription);
416 this.subscriptions = this.subscriptions.filter((s => s !== subscription));
417 return subscription;
418 }
419 findAll(identifier) {
420 return this.subscriptions.filter((s => s.identifier === identifier));
421 }
422 reload() {
423 return this.subscriptions.map((subscription => this.subscribe(subscription)));
424 }
425 notifyAll(callbackName, ...args) {
426 return this.subscriptions.map((subscription => this.notify(subscription, callbackName, ...args)));
427 }
428 notify(subscription, callbackName, ...args) {
429 let subscriptions;
430 if (typeof subscription === "string") {
431 subscriptions = this.findAll(subscription);
432 } else {
433 subscriptions = [ subscription ];
434 }
435 return subscriptions.map((subscription => typeof subscription[callbackName] === "function" ? subscription[callbackName](...args) : undefined));
436 }
437 subscribe(subscription) {
438 if (this.sendCommand(subscription, "subscribe")) {
439 this.guarantor.guarantee(subscription);
440 }
441 }
442 confirmSubscription(identifier) {
443 logger.log(`Subscription confirmed ${identifier}`);
444 this.findAll(identifier).map((subscription => this.guarantor.forget(subscription)));
445 }
446 sendCommand(subscription, command) {
447 const {identifier: identifier} = subscription;
448 return this.consumer.send({
449 command: command,
450 identifier: identifier
451 });
452 }
453}
454
455class Consumer {
456 constructor(url) {
457 this._url = url;
458 this.subscriptions = new Subscriptions(this);
459 this.connection = new Connection(this);
460 this.subprotocols = [];
461 }
462 get url() {
463 return createWebSocketURL(this._url);
464 }
465 send(data) {
466 return this.connection.send(data);
467 }
468 connect() {
469 return this.connection.open();
470 }
471 disconnect() {
472 return this.connection.close({
473 allowReconnect: false
474 });
475 }
476 ensureActiveConnection() {
477 if (!this.connection.isActive()) {
478 return this.connection.open();
479 }
480 }
481 addSubProtocol(subprotocol) {
482 this.subprotocols = [ ...this.subprotocols, subprotocol ];
483 }
484}
485
486function createWebSocketURL(url) {
487 if (typeof url === "function") {
488 url = url();
489 }
490 if (url && !/^wss?:/i.test(url)) {
491 const a = document.createElement("a");
492 a.href = url;
493 a.href = a.href;
494 a.protocol = a.protocol.replace("http", "ws");
495 return a.href;
496 } else {
497 return url;
498 }
499}
500
501function createConsumer(url = getConfig("url") || INTERNAL.default_mount_path) {
502 return new Consumer(url);
503}
504
505function getConfig(name) {
506 const element = document.head.querySelector(`meta[name='action-cable-${name}']`);
507 if (element) {
508 return element.getAttribute("content");
509 }
510}
511
512export { Connection, ConnectionMonitor, Consumer, INTERNAL, Subscription, SubscriptionGuarantor, Subscriptions, adapters, createConsumer, createWebSocketURL, getConfig, logger };