gubes mirror. how does this work
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

improved autoreconnect

leah e7dab98c 37e9fb08

+172 -85
+50 -33
core/connection.ts
··· 14 14 export interface ConnectionConfig { 15 15 /** 16 16 * a unique identifier for the connection. 17 - * 17 + * 18 18 * if not specified, a random id will be generated. 19 19 */ 20 20 id?: string, ··· 29 29 label?: string, 30 30 /** 31 31 * the URL of the IRC server to connect to. 32 - * 32 + * 33 33 * the protocol used varies depending on the type of connection, but 34 - * it's usually either `ws[s]://` for websocket connections or 34 + * it's usually either `ws[s]://` for websocket connections or 35 35 * `irc[s]://` for tcp connections. 36 36 */ 37 37 url: string | URL, ··· 40 40 */ 41 41 nickname: string, 42 42 /** 43 - * the user's username (or ident). 43 + * the user's username (or ident). 44 44 * defaults to the same value as {@link nickname}. 45 45 */ 46 46 username?: string, 47 47 /** 48 - * the user's "real name" or "GECOS" field. contains general information 48 + * the user's "real name" or "GECOS" field. contains general information 49 49 * about the user. 50 50 */ 51 51 realname: string, 52 52 /** 53 - * automatically connect when this connection is constructed. 53 + * automatically connect when this connection is constructed. 54 54 * @default false 55 55 */ 56 56 autoconnect?: boolean, ··· 92 92 */ 93 93 Connected, 94 94 /** 95 - * something went horribly wrong and the connection was severed. 95 + * something went horribly wrong and the connection was severed. 96 96 * the reason is typically stored in {@link Connection.$error} 97 97 */ 98 98 Failed, ··· 131 131 nickname: string; 132 132 username!: string; 133 133 realname!: string; 134 - adapter_id?: string; 134 + adapter_id?: string; 135 135 hostname?: string; 136 136 motd: string | null = null; 137 137 system_msgs = <string[]>[] ··· 143 143 capabilities: Map<string, string | null> = new Map(); 144 144 /** 145 145 * Map of every capability the server advertised to us. 146 - * 147 - * These may or may not have been negotaited, check {@link capabilities} 146 + * 147 + * These may or may not have been negotaited, check {@link capabilities} 148 148 * if you want to know what capabilities the client can actually use. 149 149 */ 150 150 available_capabilities: Map<string, string | null> = new Map(); ··· 154 154 $error: Signal<[code: ConnectionErrorCode, reason?: IrcMessage] | null> = signal(null); 155 155 156 156 abstract connect(): Promise<void>; 157 + 157 158 abstract disconnect(state?: ConnectionState): Promise<void>; 159 + 158 160 abstract send_raw(message: string): void; 159 161 160 162 on_connect?: (conn: Connection) => void; ··· 170 172 expect(...params: Parameters<TaskQueue["expect"]>) { 171 173 return this.queue.expect(...params); 172 174 } 175 + 173 176 collect(...params: Parameters<TaskQueue["collect"]>) { 174 177 return this.queue.collect(...params); 175 178 } 179 + 176 180 collect_batch(...params: Parameters<TaskQueue["collect_batch"]>) { 177 181 return this.queue.collect_batch(...params); 178 182 } ··· 198 202 199 203 parsed.timestamp = new Date(server_time ?? Date.now()); 200 204 201 - if (this.debug) console.debug(`${this.id} → RECEIVED: ${message}`, parsed); 205 + if (this.debug) console.debug(`${this.id} → RECEIVED: ${message}`, parsed); 202 206 203 207 // resolve pending tasks 204 208 this.queue.resolve_tasks(parsed, { batch }); ··· 210 214 211 215 server_does_the_pinging = false; 212 216 #ping_token = "tubes"; 217 + 213 218 /** 214 219 * begin pinging the server every {@link ping_interval} milliseconds. 215 - * 220 + * 216 221 * this is only used if the server doesn't bother pinging the client itself. 217 222 */ 218 223 protected start_pinging() { 219 224 const token = crypto.randomUUID(); 220 225 this.#ping_token = token; 221 - 226 + 222 227 setTimeout(() => { 223 228 if (this.server_does_the_pinging) { 224 229 return; ··· 244 249 * Dynamically update the connection's config whilst it's running. 245 250 */ 246 251 #apply_config(config: ConnectionConfig) { 247 - this.id = config.id ?? nanoid(); 252 + this.id = config.id ?? nanoid(); 248 253 this.adapter_id = config.adapter_id; 249 254 this.url = config.url instanceof URL ? config.url : new URL(config.url); 250 255 this.label = config.label ?? this.url.hostname; ··· 279 284 * @param new_nick The new nickname 280 285 * @returns A promise that resolves when the change is acknowledged by the 281 286 * server 282 - * @throws If the change is rejected by the server for whatever reason 287 + * @throws If the change is rejected by the server for whatever reason 283 288 * (e.g., the nickname is already in use). 284 289 */ 285 290 async update_nick(new_nick: string) { ··· 298 303 return this.nickname; 299 304 } 300 305 301 - retry_interval = 0; 306 + $retry_interval = signal(0); 302 307 retry_count = 0; 308 + interval_incr_amount = 1000; 303 309 $recovering = signal(false); 304 310 305 311 protected async recover_connection() { 306 312 console.log("Recovering connection"); 307 313 this.$recovering.value = true; 314 + if (this.$state.value == ConnectionState.Connected) { 315 + return; 316 + } 317 + 308 318 try { 309 - await this.connect().catch(x => {}); 310 - } catch {} 311 - 312 - if (this.retry_count > 5 319 + await this.connect().catch(() => { 320 + }); 321 + } catch { 322 + } 323 + 324 + if (this.retry_count > 5 325 + // @ts-ignore 313 326 || this.$state.value == ConnectionState.Connected 314 327 ) { 315 328 this.$recovering.value = false; 316 329 this.retry_count = 0; 317 - this.retry_interval = 0; 330 + this.$retry_interval.value = 0; 331 + this.interval_incr_amount = 1000; 318 332 } else { 319 - this.retry_interval += 10000; 333 + this.$retry_interval.value += this.interval_incr_amount; 334 + this.interval_incr_amount *= 2; 320 335 this.retry_count += 1; 321 - 322 - console.log(`Reconnection failed, trying again in ${this.retry_interval}ms`) 323 - 324 - setTimeout(() => this.recover_connection(), this.retry_interval); 336 + 337 + console.log(`Reconnection failed, trying again in ${this.$retry_interval.value}ms`) 338 + 339 + setTimeout(() => this.recover_connection(), this.$retry_interval.value); 325 340 } 326 341 } 327 342 328 343 /** 329 344 * join an IRC Channel 330 - * 345 + * 331 346 * if the channel is already in the list of joined channels, this will return it. 332 - * 333 - * @param channel The name of the channel. 334 - * @throws if the channel name doesn't match the network's supported channel prefixes 335 - * (see {@link ISupport.CHANTYPES}), is too long (see {@link ISupport.CHANNELLEN}), 347 + * 348 + * @param channel The name of the channel. 349 + * @throws if the channel name doesn't match the network's supported channel prefixes 350 + * (see {@link ISupport.CHANTYPES}), is too long (see {@link ISupport.CHANNELLEN}), 336 351 * or the server forbids it (e.g., if you're banned). 337 352 * @returns A promise that resolves when the channel been joined. 338 353 */ ··· 370 385 371 386 /** 372 387 * retrieve a channel from the connection's buffer list. 373 - * 388 + * 374 389 * @param name the name of the channel 375 390 * @throws if the channel is actually a direct message 376 391 * @returns the channel, or nothing ··· 401 416 * @param target The channel or nick to retrieve history from. 402 417 * @param range The range to retrieve history from. Can be an object with a 403 418 * 'after' key or a 'before' key, or the string "latest". 404 - * @param limit The maximum number of messages to retrieve. 419 + * @param limit The maximum number of messages to retrieve. 405 420 */ 406 421 fetch_history?(...params: FetchHistoryParams): Promise<IrcMessage[]>; 407 422 ··· 415 430 sasl: () => this.capabilities.has("sasl"), 416 431 batches: () => this.capabilities.has("batch"), 417 432 } 433 + 434 + __debug_explode: (() => void) | undefined; 418 435 }
+4 -1
core/ws/connection.ts
··· 100 100 } 101 101 102 102 103 - 104 103 // do this /after/ getting the motd to avoid it being missing 105 104 // when the connection opens. 106 105 // this feels incorrect, but who gives a shit ··· 120 119 this.$state.value = ConnectionState.Disconnected; 121 120 } 122 121 } 122 + 123 + __debug_explode = () => { 124 + this.socket_error() 125 + }; 123 126 124 127 private socket_error() { 125 128 console.log("Socket Error Moment!!!!")
+4 -1
neo/src/bits/buttons.tsx
··· 2 2 import { FunctionalComponent } from "preact"; 3 3 import { HTMLProps } from "preact/compat"; 4 4 5 - type Button = FunctionalComponent<HTMLProps<HTMLButtonElement>>; 5 + type Button = FunctionalComponent<HTMLProps<HTMLButtonElement> 6 + // webstorm doesn't like it if you don't make this explicit. 7 + // don't ask me why 8 + & { type?: "submit" | "reset" | "button" | undefined }>; 6 9 7 10 export const IconButton: Button = ({ children, ...rest }) => 8 11 <button {...rest} class={`icon-button ${rest["class"] ?? ""}`}>
+28 -3
neo/src/bits/sidebar/network-section.tsx
··· 32 32 import JoinIcon from "~icons/ph/plus"; 33 33 import LoginIcon from "~icons/ph/sign-in"; 34 34 import { ChatBuffer } from "tubes_core/buffer"; 35 + import { useEffect } from "preact/hooks"; 35 36 36 37 export const NetworkSection: FunctionalComponent<{ conn: Connection; }> = ({ conn }) => { 37 38 const errored = conn.$state.value == ConnectionState.Failed; 38 39 const disconnected = conn.$state.value == ConnectionState.Disconnected; 40 + const recovering = conn.$recovering.value; 41 + const recover_countdown = useSignal(0); 42 + 43 + useEffect(() => { 44 + const interval = conn.$retry_interval.value; 45 + recover_countdown.value = Math.round(interval / 1000); 46 + 47 + console.log(interval, recover_countdown.value); 48 + 49 + const decr = () => { 50 + console.log(recover_countdown.value); 51 + if (recover_countdown.value != 0) setTimeout(decr, 1000); 52 + recover_countdown.value -= 1; 53 + } 54 + 55 + decr(); 56 + }, [conn.$retry_interval.value]); 39 57 40 58 const [, set_location] = useLocation(); 41 59 ··· 187 205 <menu.Popover/> 188 206 </header> 189 207 190 - {/* error message if there is one */} 191 - {errored && <div class="connection-error"><ConnErrorMessage conn={conn}/></div>} 208 + {recovering && conn.$state.value != ConnectionState.Connected 209 + && <p class="reconnecting"> 210 + {recover_countdown.value <= 0 211 + ? "Reconnecting..." 212 + : `Reconnecting in ${recover_countdown.value}s` 213 + } 214 + </p> 215 + } 192 216 193 - {conn.$recovering.value && "Recovering..."} 217 + {/* error message if there is one */} 218 + {errored && !recovering && <div class="connection-error"><ConnErrorMessage conn={conn}/></div>} 194 219 195 220 {/* list of buffers */} 196 221 <ul class="sidebar-list">
+14 -45
neo/src/css/debug.css
··· 1 1 article.debug { 2 2 padding-bottom: 2rem; 3 3 4 - hgroup { 5 - display: flex; 6 - flex-direction: column; 7 - align-items: center; 8 - margin: 6vw auto; 9 - margin-bottom: 8vw; 10 - width: max-content; 11 - 12 - h1 { 13 - font-size: 12vw; 14 - font-weight: 100; 15 - font-style: italic; 16 - margin: 0; 17 - text-align: center; 18 - line-height: .7; 19 - user-select: none; 20 - } 21 - 22 - b { 23 - z-index: 1; 24 - font-weight: 900; 25 - font-variation-settings: 'GRAD' 100; 26 - } 27 - } 28 4 29 5 h2 { 30 6 font-size: 6vw; ··· 38 14 z-index: -1; 39 15 } 40 16 41 - table { 42 - margin: auto; 43 - position: relative; 44 - z-index: 1; 45 - text-align: left; 46 - border-radius: 4px; 47 - border: 1px solid var(--colour-grey-200); 48 - border-spacing: 0; 49 - font-size: .9rem; 17 + div.sep { 18 + display: flex; 19 + white-space: nowrap; 20 + align-items: center; 21 + gap: 1rem; 22 + letter-spacing: .5rem; 23 + user-select: none; 50 24 51 - th, td { 52 - padding: .5rem; 53 - } 54 - 55 - th { 56 - font-weight: 500; 57 - font-variation-settings: 'GRAD' 100; 25 + &::before, &::after { 26 + content: ''; 27 + width: 100%; 28 + height: 1px; 29 + background-color: var(--colour-grey-200); 58 30 } 59 31 60 - thead { 61 - background-color: var(--colour-grey-100); 62 - border-bottom: 1px solid var(--colour-grey-200); 63 - font-style: italic; 64 - } 32 + grid-column: 1 / span 3; 65 33 } 34 + 66 35 }
+61
neo/src/css/settings.css
··· 14 14 grid-column: 1 / span 3; 15 15 } 16 16 17 + hgroup { 18 + grid-column: 1 / span 3; 19 + 20 + display: flex; 21 + flex-direction: column; 22 + align-items: center; 23 + margin: 6vw auto; 24 + margin-bottom: 8vw; 25 + width: max-content; 26 + 27 + h1 { 28 + font-size: 12vw; 29 + font-weight: 100; 30 + font-style: italic; 31 + margin: 0; 32 + text-align: center; 33 + line-height: .7; 34 + user-select: none; 35 + } 36 + 37 + b { 38 + z-index: 1; 39 + font-weight: 900; 40 + font-variation-settings: 'GRAD' 100; 41 + } 42 + } 43 + 17 44 h2 { 18 45 font-size: 6vw; 19 46 font-weight: 200; ··· 70 97 grid-column: 1 / -1; 71 98 margin: 0; 72 99 color: var(--colour-grey-700); 100 + } 101 + } 102 + 103 + section { 104 + grid-column: 1 / span 3; 105 + display: grid; 106 + grid-template-columns: subgrid; 107 + } 108 + 109 + table { 110 + grid-column: 2; 111 + 112 + margin: auto; 113 + position: relative; 114 + z-index: 1; 115 + text-align: left; 116 + border-radius: 4px; 117 + border: 1px solid var(--colour-grey-200); 118 + border-spacing: 0; 119 + font-size: .9rem; 120 + 121 + th, td { 122 + padding: .5rem; 123 + } 124 + 125 + th { 126 + font-weight: 500; 127 + font-variation-settings: 'GRAD' 100; 128 + } 129 + 130 + thead { 131 + background-color: var(--colour-grey-100); 132 + border-bottom: 1px solid var(--colour-grey-200); 133 + font-style: italic; 73 134 } 74 135 } 75 136
+11 -2
neo/src/debug/index.tsx
··· 1 - import "@css/debug.css"; 1 + import "@css/settings.css"; 2 2 import { FunctionalComponent } from "preact"; 3 3 import { Connection } from "tubes_core"; 4 + import { PrimaryButton } from "@src/bits/buttons.tsx"; 4 5 5 - const DebugView: FunctionalComponent<{ conn: Connection }> = ({ conn }) => <article class="debug"> 6 + const DebugView: FunctionalComponent<{ conn: Connection }> = ({ conn }) => <article class="settings"> 6 7 <hgroup> 7 8 <b>{conn.label}</b> 8 9 <h1 class="title">debugzone</h1> 9 10 </hgroup> 10 11 <div class="sep" aria-hidden>🪲🐛🐞</div> 12 + <section> 13 + <h2>buttons</h2> 14 + <div class="panel"> 15 + <PrimaryButton onClick={() => conn.__debug_explode?.()}> 16 + Simulate Disconnect 17 + </PrimaryButton> 18 + </div> 19 + </section> 11 20 <section> 12 21 <h2>negotiated</h2> 13 22 <table>