UNPKG

strophe.js

Version:

Strophe.js is an XMPP library for JavaScript

345 lines 14.7 kB
import StreamManagement from './stream-management/engine'; /** How often the worker pings its ports (and sweeps for gone ones). */ export declare const PING_INTERVAL = 30000; /** * How long the primary may stay silent before the worker fails over to the * freshest other port. Pongs are posted from the page's message handler, * which runs even in background-throttled tabs, so silence this long means * the tab is frozen, crashed or gone — not merely backgrounded. */ export declare const PRIMARY_TIMEOUT = 70000; /** * How long any port may stay silent before it is dropped entirely. Generous: * a frozen tab posts nothing but keeps its MessagePort, and every broadcast * posted to it while frozen is delivered when it thaws — dropping it early * would forfeit that replay. A dropped port that speaks again is re-admitted. */ export declare const PORT_GC_TIMEOUT: number; /** How long the socket is kept open after the last port has gone away. */ export declare const SOCKET_GRACE = 10000; /** * How long the worker waits for the server to answer an ack probe (<r/>) * before presuming the socket dead. Probes are sent from the sweep when an * SM-enabled stream has been quiet for a full PING_INTERVAL, and when a * tab (re)joins the established session. Without them, a silently dead * socket (NAT timeout, sleep/wake) is only discovered by the OS's TCP * timeout, which can take many minutes. */ export declare const PROBE_TIMEOUT = 10000; type Role = 'primary' | 'secondary'; interface PortInfo { role: Role; lastSeen: number; } /** * Manages the shared websocket connection as well as the ports of the * connected tabs. */ declare class ConnectionManager { ports: Map<MessagePort, PortInfo>; jid: string; service: string; socket: WebSocket | null; /** * Where the shared stream is in its lifecycle. 'connecting' spans from * socket creation until the primary reports the bound JID (_bound) or a * worker-driven resumption succeeds; only in 'established' are join * requests answered — earlier ones wait in _pendingJoins. */ session: 'none' | 'connecting' | 'established'; /** * The worker-resident XEP-0198 engine (created lazily on the first SM * negotiation). The worker is the only party that sees all inbound * traffic (one socket) and all outbound traffic (every port's send * relays through here), so it is the only correct owner of the SM * counters and queue once more than one tab sends — a stanza sent from * a secondary tab survives resumption, and failover needs no reconnect. */ sm: StreamManagement | null; /** * Ports whose join arrived while the handshake was still in flight, * with the method they joined through: on failure, an '_attach' join is * answered with ATTACHFAIL (the attach() contract), while a '_connect' * join is told the socket closed, so its page fails the connect attempt * and the embedder's reconnect logic takes over. */ private _pendingJoins; private _sweepTimer; private _graceTimer; /** Pending death sentence for an unanswered ack probe (see _probe). */ private _probeTimer; /** When the socket last delivered anything (probes count inbound only). */ private _lastInbound; constructor(); /** * Register a newly connected port. It is admitted (and given a role) when * it first speaks. */ addPort(port: MessagePort): void; /** * Dispatch a message from a port. Any message counts as liveness; a port * that was dropped (e.g. a long-frozen tab) is re-admitted here. */ private _onPortMessage; /** * Connect, or (when a live socket for the same user already exists) * join the shared session as a secondary instead of reconnecting (§2.5). * The first joiner becomes primary and drives the connection. */ _connect(port: MessagePort, service: string, jid: string, version?: number): void; /** * Attach to the shared session, if there is one. * @param port * @param _service * @param version - The page's SHARED_WORKER_PROTOCOL_VERSION. */ _attach(port: MessagePort, _service?: string, version?: number): void; /** * The primary reports that the post-SASL stream features advertise * XEP-0198 (§2.3). This is the resume-vs-bind decision point: only the * worker knows whether resumable state exists, so it either sends * <resume/> itself (answering with _smResumed/_smFailed once the server * replies) or tells the requesting port to proceed with binding. * @param port */ _smFeatures(port: MessagePort): void; /** * The primary reports that the connect flow completed (resource bound, * or legacy auth/session establishment finished): adopt the bound JID as * the shared one so attaching secondaries and promotions hand out a * resource the server actually knows about, start a fresh SM session if * this stream's features advertised support (_smFeatures), and release * any joins parked on the handshake. The <enable/> is written before the * parked joins drain, so it precedes anything a newly attached tab sends. * @param _port * @param jid - The server-assigned full JID. */ _bound(_port: MessagePort, jid: string): void; /** * Lazily create the worker-resident engine. Its nonzas go straight to * the socket (outbound frames relay in receipt order, so nothing can be * overtaken), and its lifecycle events are forwarded to the tabs as the * _smEnabled/_smResumed/_smFailed messages that feed the page-side * mirrors and drive the primary's connect flow. */ private _ensureSM; /** * Liveness reply to the worker's _ping. The lastSeen update happens in * the dispatcher, so any message from a port counts. This method only * needs to exist. * @param _port */ _pong(_port: MessagePort): void; /** * The page announces it is about to be frozen (its CPU stops): hand the * primary role to the freshest other port right away instead of making * the worker discover the freeze through missed pongs. * @param port */ _relinquish(port: MessagePort): void; /** * Graceful removal (sent from pagehide). If the primary leaves, the next * live port is promoted on the same socket. * @param port */ _bye(port: MessagePort): void; /** * Relay an outbound frame to the socket, feeding it to the XEP-0198 * engine and reflecting messages/presences to the other tabs. * @param port * @param data */ send(port: MessagePort, data: string): void; /** * Reflect an outbound message/presence to every tab except the sender, * so all tabs can render what any one of them sent. Delivered as its own * `_onStanzaSent` page message — never as `_onMessage` — because a sent * stanza must not enter the receiving tabs' inbound pipeline (stanza * handlers, SM counting, xmlInput). IQs are not reflected: they are * request/response traffic private to the sending tab. * @param sender - The port the stanza came from (gets no reflection). * @param name - The frame's tag name (from the peeker). * @param data - The raw outbound frame. */ private _reflect; /** * Send the stream-closing frame before disconnecting. * @param port * @param data */ close(port: MessagePort, data: string): void; /** * Explicitly close the shared socket (a deliberate disconnect/logout — * a mere tab close sends `_bye` instead). Joins parked on the handshake * are answered with ATTACHFAIL: the session they were waiting for is * not going to happen. * @param _port */ _closeSocket(_port?: MessagePort): void; /** * @param e */ _onClose(e: CloseEvent): void; /** * Deliver an inbound frame to the tabs, feeding it to the XEP-0198 * engine first: counting and <r/> → <a/> replies happen exactly once. * Lifecycle transitions (<enabled/>/<resumed/>/<failed/>) surface * through the engine's event hooks (see _ensureSM). * * Until the session is established, frames go only to the primary, * since only it is responsible for SASL negotiation and session setup. * Once established, frames are broadcast to all tabs. * @param message */ _onMessage(message: MessageEvent): void; /** * @param error */ _onError(error: Event): void; /** * Verify that the page and this worker were built against the same * page↔worker protocol. On mismatch the port is refused (and told the * socket is closed, so the page fails loudly rather than misbehaving). * @param port * @param version - The version the page sent along with _connect/_attach. * @returns true when the page speaks this worker's protocol. */ private _checkVersion; /** * @returns true when the socket exists and is not closing/closed. A * CONNECTING socket counts: joins arriving during the handshake must * be parked, not refused. */ private _socketLive; /** * @returns true when the socket is OPEN, i.e. ready to carry frames. */ private _socketReady; /** * @returns true if any live port currently holds the primary role. */ private _hasPrimary; /** * @returns The port currently holding the primary role, or null. */ private _primaryPort; /** * Join a port onto the shared session: it becomes secondary (or primary, * if no primary is left) and is attached. Joins that arrive while the * handshake is still in flight are parked. * @param port * @param origin - The method the join arrived through (see _pendingJoins). */ private _joinShared; /** * The stream reached its usable state (resource bound or resumed): * record it and answer the joins that were parked on the handshake. */ private _sessionEstablished; /** * Fail parked joins. The session they were waiting for died before it * was established. '_attach' joins get ATTACHFAIL; '_connect' joins are * told the socket closed, which fails the page's connect attempt so the * embedder's reconnect logic can retry. */ private _failPendingJoins; /** * The primary is gone (bye, GC or freeze). If the session is established * the freshest remaining port takes over on the same socket. If the * handshake was still in flight, nobody else can finish it (only the * primary holds credentials): kill the socket so the remaining tabs * reconnect and elect a new primary. */ private _primaryLost; /** * Promote the freshest port to primary — same socket, no reconnect. * @param exclude - A port that must not be picked (e.g. the one * relinquishing the role). */ private _promoteNext; /** * Hand the primary role from `port` to the freshest other port, if any. * @param port - The current primary. * @param minLastSeen - Only consider candidates seen at or after this * timestamp (so a failover never picks an equally-dead port). * @returns true if a failover happened. */ private _failover; /** * @param exclude - A port to skip. * @param minLastSeen - Minimum lastSeen for a port to qualify. * @returns The most recently seen qualifying port, or null. */ private _freshestPort; /** * A frame was sent into a dead socket. If an SM session was active, feed * a stanza to the engine anyway: it lands in the unacked queue and is * replayed when the session is resumed over the next socket, so the * message the user just sent isn't lost to the bad timing. A <close/> * still ends the session: the final <a/> can't be delivered anymore * (the engine's sendRaw is null-guarded), but the persisted state is * cleared so the deliberately-ended session isn't resumed later. * @param port - The port the frame came from. * @param data - The raw outbound frame. */ private _salvageFrame; /** * Report a frame the regex peeker (see parse.ts) could not parse while an * SM session was active. Such a frame passes through uncounted and * untracked, so the XEP-0198 handled-stanza counters drift and stanzas * get duplicated (inbound) or lost (outbound) on the next resume — * exactly the silent corruption SM exists to prevent, so make it loud. * * Only frames that look like an element open are reported: the stream * prolog, whitespace keepalives and empty frames legitimately don't peek * and are never countable stanzas. * * The opening tag is included so the peeker can actually be fixed — it is * where the parse failed (peekElement only ever inspects the opening tag) * and is the whole diagnostic. The report stops at the first '>' (capped), * which keeps the stanza payload — message bodies and the like — out of * the logs. * @param direction * @param frame */ private _warnUnpeekable; /** * Ping every port and act on prolonged silence. A live page answers * pings from its message handler even when its timers are throttled, so: * a primary silent past PRIMARY_TIMEOUT is failed over (but keeps * receiving — it may merely be frozen and will learn of its demotion on * thaw), and a port silent past PORT_GC_TIMEOUT is dropped for good. */ private _sweep; /** * Ask the server for an ack and treat a missing reply as socket death. * The <r/> doubles as a keepalive on quiet connections. */ private _probe; private _cancelProbe; /** * The probe went unanswered: the socket is transmitting into the void. * Close it and tell the tabs, so they reconnect. The engine's state is * untouched, so the next stream resumes and replays the unacked queue. */ private _onSocketDead; /** * Close the socket if no port shows up within the grace period. */ private _startGrace; private _cancelGrace; /** * Send a message to a single port (worker→page traffic is port-targeted; * only genuine broadcasts use _broadcast). * @param port * @param msg */ private _post; /** * Send a message to all live ports. * @param msg */ private _broadcast; } export default ConnectionManager; //# sourceMappingURL=shared-connection-worker.d.ts.map