@instantdb/core
Version:
Instant's core local abstraction
189 lines • 5.42 kB
JavaScript
let _connId = 0;
export class WSConnection {
type = 'ws';
conn;
id;
onopen;
onmessage;
onclose;
onerror;
constructor(url) {
this.id = `${this.type}_${_connId++}`;
this.conn = new WebSocket(url);
this.conn.onopen = (_e) => {
if (this.onopen) {
this.onopen({ target: this });
}
};
this.conn.onmessage = (e) => {
if (this.onmessage) {
this.onmessage({
target: this,
message: JSON.parse(e.data.toString()),
});
}
};
this.conn.onclose = (_e) => {
if (this.onclose) {
this.onclose({ target: this });
}
};
this.conn.onerror = (_e) => {
if (this.onerror) {
this.onerror({ target: this });
}
};
}
close() {
this.conn.close();
}
isOpen() {
return this.conn.readyState === (WebSocket.OPEN ?? 1);
}
isConnecting() {
return this.conn.readyState === (WebSocket.CONNECTING ?? 0);
}
send(msg) {
return this.conn.send(JSON.stringify(msg));
}
}
export class SSEConnection {
type = 'sse';
initParams = null;
sendQueue = [];
sendPromise;
closeFired = false;
sseInitTimeout = undefined;
ES;
messageUrl;
conn;
url;
id;
onopen;
onmessage;
onclose;
onerror;
constructor(ES, url, messageUrl) {
this.id = `${this.type}_${_connId++}`;
this.url = url;
this.messageUrl = messageUrl || this.url;
this.ES = ES;
this.conn = new ES(url);
// Close the connection if we didn't get an init within 10 seconds
this.sseInitTimeout = setTimeout(() => {
if (!this.initParams) {
this.handleError();
}
}, 10000);
this.conn.onmessage = (e) => {
const message = JSON.parse(e.data);
if (Array.isArray(message)) {
for (const msg of message) {
this.handleMessage(msg);
}
}
else {
this.handleMessage(message);
}
};
this.conn.onerror = (e) => {
this.handleError();
};
}
handleMessage(msg) {
if (msg.op === 'sse-init') {
this.initParams = {
machineId: msg['machine-id'],
sessionId: msg['session-id'],
sseToken: msg['sse-token'],
};
if (this.onopen) {
this.onopen({ target: this });
}
clearTimeout(this.sseInitTimeout);
return;
}
if (this.onmessage) {
this.onmessage({
target: this,
message: msg,
});
}
}
// Runs the onerror and closes the connection
handleError() {
try {
if (this.onerror) {
this.onerror({ target: this });
}
}
finally {
this.handleClose();
}
}
handleClose() {
this.conn.close();
if (this.onclose && !this.closeFired) {
this.closeFired = true;
this.onclose({ target: this });
}
}
async postMessages(messages) {
// TODO(dww): Create a connection with chunked encoding so we can
// send multiple messages over one request
try {
const resp = await fetch(this.messageUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
machine_id: this.initParams?.machineId,
session_id: this.initParams?.sessionId,
sse_token: this.initParams?.sseToken,
messages,
}),
});
if (!resp.ok) {
this.handleError();
}
}
catch (e) {
this.handleError();
}
}
async flushQueue() {
if (this.sendPromise || !this.sendQueue.length)
return;
const messages = this.sendQueue;
this.sendQueue = [];
const sendPromise = this.postMessages(messages);
this.sendPromise = sendPromise;
sendPromise.then(() => {
this.sendPromise = null;
this.flushQueue();
});
}
send(msg) {
if (!this.isOpen() || !this.initParams) {
if (this.isConnecting()) {
throw new Error(`Failed to execute 'send' on 'EventSource': Still in CONNECTING state.`);
}
if (this.conn.readyState === this.ES.CLOSED) {
throw new Error(`EventSource is already in CLOSING or CLOSED state.`);
}
throw new Error(`EventSource is in invalid state.`);
}
this.sendQueue.push(msg);
this.flushQueue();
}
isOpen() {
return this.conn.readyState === this.ES.OPEN && this.initParams !== null;
}
isConnecting() {
return (this.conn.readyState === this.ES.CONNECTING ||
(this.conn.readyState === this.ES.OPEN && this.initParams === null));
}
close() {
this.handleClose();
}
}
//# sourceMappingURL=Connection.js.map