Compare commits

..

4 Commits

Author SHA1 Message Date
6b70af2943 big upgrade 2026-02-15 09:09:43 -08:00
09f77f099f yup 2026-02-15 09:05:04 -08:00
d1caf3fbf4 0.0.8 2026-02-15 09:03:45 -08:00
8fc226ce09 need new api 2026-02-15 08:46:23 -08:00
4 changed files with 56 additions and 28 deletions

View File

@ -7,7 +7,7 @@
"dependencies": {
"@because/forge": "^0.0.1",
"@because/hype": "^0.0.2",
"@because/toes": "^0.0.6",
"@because/toes": "^0.0.8",
"croner": "^9.1.0",
},
"devDependencies": {
@ -20,7 +20,9 @@
"@because/hype": ["@because/hype@0.0.2", "https://npm.nose.space/@because/hype/-/hype-0.0.2.tgz", { "dependencies": { "hono": "^4.10.4", "kleur": "^4.1.5" }, "peerDependencies": { "typescript": "^5" } }, "sha512-fdKeII6USGC1loVVj+tPz086cKz+Bm+XozNee3NOnK4VP+q4yNPP2Fq1Yujw5xeDYE+ZvJn40gKwlngRvmX2hA=="],
"@because/toes": ["@because/toes@0.0.6", "https://npm.nose.space/@because/toes/-/toes-0.0.6.tgz", { "dependencies": { "@because/forge": "^0.0.1", "@because/hype": "^0.0.2", "commander": "^14.0.2", "diff": "^8.0.3", "kleur": "^4.1.5" }, "peerDependencies": { "typescript": "^5.9.2" }, "bin": { "toes": "src/cli/index.ts" } }, "sha512-37x3E1qtG6yhF6Cm9DtevCb02t3KbPTBvG/ysonVgA8RGQqV/vagOF4/CNLiVXioEweM5RWtrqOtYtTMqOjufA=="],
"@because/sneaker": ["@because/sneaker@0.0.1", "https://npm.nose.space/@because/sneaker/-/sneaker-0.0.1.tgz", { "dependencies": { "hono": "^4.9.8", "unique-names-generator": "^4.7.1" }, "peerDependencies": { "typescript": "^5" } }, "sha512-rN9hc13ofap+7SvfShJkTJQYBcViCiElyfb8FBMzP1SKIe8B71csZeLh+Ujye/5538ojWfM/5hRRPJ+Aa/0A+g=="],
"@because/toes": ["@because/toes@0.0.8", "https://npm.nose.space/@because/toes/-/toes-0.0.8.tgz", { "dependencies": { "@because/forge": "^0.0.1", "@because/hype": "^0.0.2", "@because/sneaker": "^0.0.1", "commander": "^14.0.3", "diff": "^8.0.3", "kleur": "^4.1.5" }, "peerDependencies": { "typescript": "^5.9.3" }, "bin": { "toes": "src/cli/index.ts" } }, "sha512-ei4X+yX97dCRqAHSfsVnE4vAIAMkhG9v1WKW3whlo+BMm3TNdKuEv1o2PQpVfIChSGzO/05Y/YFbd/XdI7p/Kg=="],
"@types/bun": ["@types/bun@1.3.8", "https://npm.nose.space/@types/bun/-/bun-1.3.8.tgz", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="],
@ -41,5 +43,7 @@
"typescript": ["typescript@5.9.3", "https://npm.nose.space/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"undici-types": ["undici-types@7.16.0", "https://npm.nose.space/undici-types/-/undici-types-7.16.0.tgz", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"unique-names-generator": ["unique-names-generator@4.7.1", "https://npm.nose.space/unique-names-generator/-/unique-names-generator-4.7.1.tgz", {}, "sha512-lMx9dX+KRmG8sq6gulYYpKWZc9RlGsgBR6aoO8Qsm3qvkSJ+3rAymr+TnV8EDMrIrwuFJ4kruzMWM/OpYzPoow=="],
}
}

View File

@ -13,7 +13,7 @@
"dependencies": {
"@because/forge": "^0.0.1",
"@because/hype": "^0.0.2",
"@because/toes": "^0.0.6",
"@because/toes": "^0.0.8",
"croner": "^9.1.0"
},
"devDependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "@because/toes",
"version": "0.0.7",
"version": "0.0.8",
"description": "personal web appliance - turn it on and forget about the cloud",
"module": "src/index.ts",
"type": "module",

View File

@ -11,25 +11,32 @@ interface Listener {
const _listeners = new Set<Listener>()
let _es: EventSource | undefined
let _abort: AbortController | undefined
let _connected = false
function ensureConnection() {
if (_es && _es.readyState !== EventSource.CLOSED) return
if (_es) _es.close()
if (_connected) return
_connected = true
const url = `${process.env.TOES_URL}/api/events/stream`
_es = new EventSource(url)
_abort = new AbortController()
_es.onerror = () => {
if (_es?.readyState === EventSource.CLOSED) {
console.warn('[toes] Event stream closed, reconnecting...')
_es = undefined
if (_listeners.size > 0) ensureConnection()
}
}
fetch(url, { signal: _abort.signal })
.then(async (res) => {
const reader = res.body!.getReader()
const decoder = new TextDecoder()
let buf = ''
_es.onmessage = (msg) => {
while (true) {
const { done, value } = await reader.read()
if (done) break
buf += decoder.decode(value, { stream: true })
const lines = buf.split('\n')
buf = lines.pop()!
for (const line of lines) {
if (!line.startsWith('data: ')) continue
try {
const event: ToesEvent = JSON.parse(msg.data)
const event: ToesEvent = JSON.parse(line.slice(6))
_listeners.forEach(l => {
if (l.types.includes(event.type)) l.callback(event)
})
@ -38,6 +45,26 @@ function ensureConnection() {
}
}
}
})
.catch((e) => {
if (e.name === 'AbortError') return
console.warn('[toes] Event stream error, reconnecting...', e.message)
})
.finally(() => {
_connected = false
if (_listeners.size > 0) {
setTimeout(ensureConnection, 1000)
}
})
}
function closeConnection() {
if (_abort) {
_abort.abort()
_abort = undefined
}
_connected = false
}
export function on(type: ToesEventType | ToesEventType[], callback: EventCallback): () => void {
const listener: Listener = {
@ -49,9 +76,6 @@ export function on(type: ToesEventType | ToesEventType[], callback: EventCallbac
return () => {
_listeners.delete(listener)
if (_listeners.size === 0 && _es) {
_es.close()
_es = undefined
}
if (_listeners.size === 0) closeConnection()
}
}