Format with prettier. Use screen mode for invitation import - dialog mode is broken.

This commit is contained in:
2026-03-23 10:15:48 +00:00
parent 7fd89c5663
commit b475b23beb
47 changed files with 1718 additions and 1098 deletions

View File

@@ -1,11 +1,19 @@
import { ExponentialBackoff } from './exponential-backoff.js';
import { ExponentialBackoff } from "./exponential-backoff.js";
// Type declarations for browser environment (not available in Node.js)
declare const document: {
visibilityState: 'visible' | 'hidden';
addEventListener: (event: string, handler: (event: Event) => void) => void;
removeEventListener: (event: string, handler: (event: Event) => void) => void;
} | undefined;
declare const document:
| {
visibilityState: "visible" | "hidden";
addEventListener: (
event: string,
handler: (event: Event) => void,
) => void;
removeEventListener: (
event: string,
handler: (event: Event) => void,
) => void;
}
| undefined;
/**
* A Server-Sent Events client implementation using fetch API.
@@ -51,14 +59,14 @@ export class SSESession {
this.options = {
// Use default fetch function.
fetch: (...args) => fetch(...args),
method: 'GET',
method: "GET",
headers: {
Accept: 'text/event-stream',
'Cache-Control': 'no-cache',
Accept: "text/event-stream",
"Cache-Control": "no-cache",
},
onConnected: () => {},
onMessage: () => {},
onError: (error) => console.error('SSESession error:', error),
onError: (error) => console.error("SSESession error:", error),
onDisconnected: () => {},
onReconnect: (options) => Promise.resolve(options),
@@ -71,10 +79,10 @@ export class SSESession {
this.controller = new AbortController();
// Set up visibility change handling if in mobile browser environment
if (typeof document !== 'undefined') {
if (typeof document !== "undefined") {
this.visibilityChangeHandler = this.handleVisibilityChange.bind(this);
document.addEventListener(
'visibilitychange',
"visibilitychange",
this.visibilityChangeHandler,
);
}
@@ -85,16 +93,16 @@ export class SSESession {
*/
private async handleVisibilityChange(): Promise<void> {
// Guard for Node.js environment where document is undefined
if (typeof document === 'undefined') return;
if (typeof document === "undefined") return;
// When going to background, close the current connection cleanly
// This allows us to reconnect mobile devices when they come back after leaving the tab or browser app.
if (document.visibilityState === 'hidden') {
if (document.visibilityState === "hidden") {
this.controller.abort();
}
// When coming back to foreground, attempt to reconnect if not connected
if (document.visibilityState === 'visible' && !this.connected) {
if (document.visibilityState === "visible" && !this.connected) {
await this.connect();
}
}
@@ -115,7 +123,7 @@ export class SSESession {
headers: headers || {},
body: body || null,
signal: this.controller.signal,
cache: 'no-store',
cache: "no-store",
};
const exponentialBackoff = ExponentialBackoff.from({
@@ -144,7 +152,7 @@ export class SSESession {
}
if (!res.body) {
throw new Error('Response body is null');
throw new Error("Response body is null");
}
return res.body.getReader();
@@ -228,10 +236,10 @@ export class SSESession {
const line = lines[i];
// Empty line signals the end of an event
if (line === '') {
if (line === "") {
if (currentEvent.data) {
// Remove trailing newline if present
currentEvent.data = currentEvent.data.replace(/\n$/, '');
currentEvent.data = currentEvent.data.replace(/\n$/, "");
events.push(currentEvent as SSEvent);
currentEvent = {};
completeEventCount = i + 1;
@@ -242,24 +250,24 @@ export class SSESession {
if (!line) continue;
// Parse field: value format
const colonIndex = line.indexOf(':');
const colonIndex = line.indexOf(":");
if (colonIndex === -1) continue;
const field = line.slice(0, colonIndex);
// Skip initial space after colon if present
const valueStartIndex =
colonIndex + 1 + (line[colonIndex + 1] === ' ' ? 1 : 0);
colonIndex + 1 + (line[colonIndex + 1] === " " ? 1 : 0);
const value = line.slice(valueStartIndex);
if (field === 'data') {
if (field === "data") {
currentEvent.data = currentEvent.data
? currentEvent.data + '\n' + value
? currentEvent.data + "\n" + value
: value;
} else if (field === 'event') {
} else if (field === "event") {
currentEvent.event = value;
} else if (field === 'id') {
} else if (field === "id") {
currentEvent.id = value;
} else if (field === 'retry') {
} else if (field === "retry") {
const retryMs = parseInt(value, 10);
if (!isNaN(retryMs)) {
currentEvent.retry = retryMs;
@@ -268,7 +276,7 @@ export class SSESession {
}
// Store the remainder of the buffer for the next chunk
const remainder = lines.slice(completeEventCount).join('\n');
const remainder = lines.slice(completeEventCount).join("\n");
this.messageBuffer = this.textEncoder.encode(remainder);
return events;
@@ -291,9 +299,9 @@ export class SSESession {
this.controller.abort();
// Remove the visibility handler (This is only required on browsers)
if (this.visibilityChangeHandler && typeof document !== 'undefined') {
if (this.visibilityChangeHandler && typeof document !== "undefined") {
document.removeEventListener(
'visibilitychange',
"visibilitychange",
this.visibilityChangeHandler,
);
this.visibilityChangeHandler = null;
@@ -348,7 +356,7 @@ export interface SSESessionOptions {
/**
* HTTP method to use (GET or POST).
*/
method: 'GET' | 'POST';
method: "GET" | "POST";
/**
* HTTP headers to send with the request.