CI / Formatting (push) Successful in 5s
CI / Linting (push) Successful in 4s
CI / Tests (Python 3.12) (push) Successful in 2m54s
CI / Tests (Python 3.13) (push) Successful in 2m54s
CI / Tests (Python 3.14) (push) Successful in 2m52s
CI / Type Checking (push) Successful in 9s
CI / Spelling (push) Successful in 5s
76 lines
2.1 KiB
JavaScript
76 lines
2.1 KiB
JavaScript
// Copyright 2026 Logan Fick
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
function isRecord(value) {
|
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
}
|
|
|
|
function parseEventData(event) {
|
|
try {
|
|
return JSON.parse(event.data);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function isKeepalivePayload(value) {
|
|
return isRecord(value) && Number.isFinite(value.time_remaining);
|
|
}
|
|
|
|
export function isPollEndedPayload(value) {
|
|
return (
|
|
isRecord(value) &&
|
|
typeof value.results_url === "string" &&
|
|
value.results_url.length > 0
|
|
);
|
|
}
|
|
|
|
export function isTallyPayload(value, optionCount) {
|
|
return (
|
|
isRecord(value) &&
|
|
Array.isArray(value.counts) &&
|
|
value.counts.length === optionCount &&
|
|
value.counts.every((count) => Number.isInteger(count) && count >= 0)
|
|
);
|
|
}
|
|
|
|
export class PollEventStream {
|
|
constructor(url, { onClosed } = {}) {
|
|
this.source = new EventSource(url);
|
|
this.onClosed = onClosed;
|
|
|
|
this.source.addEventListener("error", () => {
|
|
if (this.source.readyState === EventSource.CLOSED) {
|
|
this.close();
|
|
if (this.onClosed) {
|
|
this.onClosed();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
on(eventName, handler, validator = () => true) {
|
|
this.source.addEventListener(eventName, (event) => {
|
|
const data = parseEventData(event);
|
|
if (validator(data)) {
|
|
handler(data);
|
|
}
|
|
});
|
|
}
|
|
|
|
close() {
|
|
this.source.close();
|
|
}
|
|
}
|