| … | … | @@ -0,0 +1 @@ |
| 1 | node_modules/ |
| … | … | @@ -0,0 +1,73 @@ |
| 1 | import { Bridge } from './lib/bridge.js' | |
| 2 | ||
| 3 | const args = parseArgs(typeof Pear !== 'undefined' ? Pear.config.args : process.argv.slice(2)) | |
| 4 | ||
| 5 | if (args.help) { | |
| 6 | console.log(` | |
| 7 | bridge-discord — Discord ↔ Keet bidirectional bridge | |
| 8 | ||
| 9 | Usage: | |
| 10 | bridge-discord --token <bot-token> --channel <channelId> --keet-room <roomId> | |
| 11 | ||
| 12 | Options: | |
| 13 | --token <token> Discord bot token (required) | |
| 14 | --channel <id> Discord channel ID to bridge (required) | |
| 15 | --guild <id> Discord guild/server ID (optional) | |
| 16 | --keet-room <roomId> Keet room ID to bridge (required) | |
| 17 | --keet-port <port> Keet daemon port (default: 3117) | |
| 18 | --help Show this help | |
| 19 | ||
| 20 | Environment: | |
| 21 | DISCORD_TOKEN Alternative to --token flag | |
| 22 | ||
| 23 | Setup: | |
| 24 | 1. Create a Discord bot at https://discord.com/developers/applications | |
| 25 | 2. Enable MESSAGE CONTENT intent in Bot settings | |
| 26 | 3. Invite bot to your server with permissions: Send Messages, Read Message History | |
| 27 | 4. Get channel ID (right-click channel → Copy Channel ID with Developer Mode on) | |
| 28 | `) | |
| 29 | process.exit(0) | |
| 30 | } | |
| 31 | ||
| 32 | const token = args.token || process.env.DISCORD_TOKEN | |
| 33 | if (!token || !args.channel || !args['keet-room']) { | |
| 34 | console.error('Error: --token (or DISCORD_TOKEN env), --channel, and --keet-room are required') | |
| 35 | console.error('Run with --help for usage') | |
| 36 | process.exit(1) | |
| 37 | } | |
| 38 | ||
| 39 | const bridge = new Bridge({ | |
| 40 | discordToken: token, | |
| 41 | discordChannel: args.channel, | |
| 42 | discordGuild: args.guild, | |
| 43 | keetRoom: args['keet-room'], | |
| 44 | keetPort: parseInt(args['keet-port'] || '3117') | |
| 45 | }) | |
| 46 | ||
| 47 | bridge.start().catch((err) => { | |
| 48 | console.error('Bridge failed to start:', err.message) | |
| 49 | process.exit(1) | |
| 50 | }) | |
| 51 | ||
| 52 | process.on('SIGINT', () => bridge.stop().then(() => process.exit(0))) | |
| 53 | process.on('SIGTERM', () => bridge.stop().then(() => process.exit(0))) | |
| 54 | ||
| 55 | function parseArgs (argv) { | |
| 56 | const result = {} | |
| 57 | for (let i = 0; i < argv.length; i++) { | |
| 58 | const arg = argv[i] | |
| 59 | if (arg === '--help') { | |
| 60 | result.help = true | |
| 61 | } else if (arg.startsWith('--')) { | |
| 62 | const key = arg.slice(2) | |
| 63 | const next = argv[i + 1] | |
| 64 | if (next && !next.startsWith('--')) { | |
| 65 | result[key] = next | |
| 66 | i++ | |
| 67 | } else { | |
| 68 | result[key] = true | |
| 69 | } | |
| 70 | } | |
| 71 | } | |
| 72 | return result | |
| 73 | } |
| … | … | @@ -0,0 +1,99 @@ |
| 1 | import { createEnvelope, TYPES, PLATFORMS } from 'bridge-envelope' | |
| 2 | ||
| 3 | function discordToEnvelope (message) { | |
| 4 | // Skip bot messages | |
| 5 | if (message.author.bot) return null | |
| 6 | ||
| 7 | const author = message.author.tag || message.author.username | |
| 8 | const channel = message.channel.name || message.channelId | |
| 9 | const content = message.content || '' | |
| 10 | ||
| 11 | // Detect message type | |
| 12 | let type = TYPES.TEXT | |
| 13 | let mediaUrl = '' | |
| 14 | let mediaType = '' | |
| 15 | let replyTo = '' | |
| 16 | ||
| 17 | // Attachments | |
| 18 | const attachment = message.attachments?.first?.() | |
| 19 | if (attachment) { | |
| 20 | type = TYPES.MEDIA | |
| 21 | mediaUrl = attachment.url | |
| 22 | mediaType = attachment.contentType || '' | |
| 23 | } | |
| 24 | ||
| 25 | // Reply | |
| 26 | if (message.reference?.messageId) { | |
| 27 | replyTo = message.reference.messageId | |
| 28 | if (type === TYPES.TEXT) type = TYPES.REPLY | |
| 29 | } | |
| 30 | ||
| 31 | return createEnvelope({ | |
| 32 | type, | |
| 33 | platform: PLATFORMS.DISCORD, | |
| 34 | author, | |
| 35 | content, | |
| 36 | channel, | |
| 37 | replyTo, | |
| 38 | mediaUrl, | |
| 39 | mediaType | |
| 40 | }) | |
| 41 | } | |
| 42 | ||
| 43 | function keetToDiscord (keetMsg, bridgeIdentity) { | |
| 44 | if (keetMsg.memberId === bridgeIdentity) return null | |
| 45 | if (keetMsg.system) return null | |
| 46 | ||
| 47 | const name = keetMsg.name || 'anonymous' | |
| 48 | const text = keetMsg.text || '' | |
| 49 | if (!text.trim()) return null | |
| 50 | ||
| 51 | // Format for Discord — bold name prefix | |
| 52 | return { | |
| 53 | name, | |
| 54 | text: `**${name}** (Keet): ${text}` | |
| 55 | } | |
| 56 | } | |
| 57 | ||
| 58 | function envelopeToDiscordContent (envelope) { | |
| 59 | const platform = platformName(envelope.platform) | |
| 60 | const prefix = platform !== 'discord' ? `[${platform}] ` : '' | |
| 61 | const author = envelope.author || 'anonymous' | |
| 62 | ||
| 63 | switch (envelope.type) { | |
| 64 | case TYPES.TEXT: | |
| 65 | case TYPES.REPLY: | |
| 66 | return `${prefix}**${author}**: ${envelope.content}` | |
| 67 | case TYPES.MEDIA: | |
| 68 | return `${prefix}**${author}**: ${envelope.content || ''}\n${envelope.mediaUrl || ''}` | |
| 69 | case TYPES.REACTION: | |
| 70 | return `${prefix}*${author} reacted: ${envelope.content}*` | |
| 71 | case TYPES.SYSTEM: | |
| 72 | return `*${prefix}${envelope.content}*` | |
| 73 | default: | |
| 74 | return `${prefix}**${author}**: ${envelope.content}` | |
| 75 | } | |
| 76 | } | |
| 77 | ||
| 78 | function discordReactionToEnvelope (reaction, user) { | |
| 79 | return createEnvelope({ | |
| 80 | type: TYPES.REACTION, | |
| 81 | platform: PLATFORMS.DISCORD, | |
| 82 | author: user.tag || user.username, | |
| 83 | content: reaction.emoji.name || reaction.emoji.toString(), | |
| 84 | channel: reaction.message.channel.name || reaction.message.channelId, | |
| 85 | replyTo: reaction.message.id | |
| 86 | }) | |
| 87 | } | |
| 88 | ||
| 89 | function platformName (id) { | |
| 90 | const names = ['keet', 'irc', 'discord', 'matrix', 'mastodon', 'email', 'android'] | |
| 91 | return names[id] || 'unknown' | |
| 92 | } | |
| 93 | ||
| 94 | export { | |
| 95 | discordToEnvelope, | |
| 96 | keetToDiscord, | |
| 97 | envelopeToDiscordContent, | |
| 98 | discordReactionToEnvelope | |
| 99 | } |
| … | … | @@ -0,0 +1,146 @@ |
| 1 | import { DiscordClient } from './discord-client.js' | |
| 2 | import { KeetClient } from './keet-client.js' | |
| 3 | import { Router } from './router.js' | |
| 4 | import { discordToEnvelope, keetToDiscord } from './adapter.js' | |
| 5 | ||
| 6 | class Bridge { | |
| 7 | constructor (opts) { | |
| 8 | this.discordToken = opts.discordToken | |
| 9 | this.discordChannel = opts.discordChannel | |
| 10 | this.discordGuild = opts.discordGuild || null | |
| 11 | ||
| 12 | this.keetRoom = opts.keetRoom | |
| 13 | this.keetHost = opts.keetHost || 'localhost' | |
| 14 | this.keetPort = opts.keetPort || 3117 | |
| 15 | ||
| 16 | this._discord = null | |
| 17 | this._keet = null | |
| 18 | this._router = null | |
| 19 | this._running = false | |
| 20 | this._bridgeIdentity = null | |
| 21 | ||
| 22 | this.onLog = opts.onLog || console.log | |
| 23 | } | |
| 24 | ||
| 25 | async start () { | |
| 26 | this.onLog('[bridge] Starting Discord ↔ Keet bridge') | |
| 27 | this.onLog(`[bridge] Discord channel: ${this.discordChannel}`) | |
| 28 | this.onLog(`[bridge] Keet room: ${this.keetRoom}`) | |
| 29 | ||
| 30 | // Initialize Keet client | |
| 31 | this._keet = new KeetClient({ | |
| 32 | host: this.keetHost, | |
| 33 | port: this.keetPort | |
| 34 | }) | |
| 35 | ||
| 36 | try { | |
| 37 | const identity = await this._keet.identity() | |
| 38 | this._bridgeIdentity = identity.memberId || identity.id | |
| 39 | this.onLog(`[bridge] Keet identity: ${identity.displayName || identity.username || identity.name || 'unknown'}`) | |
| 40 | } catch (err) { | |
| 41 | this.onLog(`[bridge] Warning: could not get Keet identity: ${err.message}`) | |
| 42 | } | |
| 43 | ||
| 44 | // Initialize router | |
| 45 | this._router = new Router({ | |
| 46 | onIrcMessage: (name, text) => { | |
| 47 | // "IrcMessage" is a misnomer inherited from shared router — sends to Discord | |
| 48 | this._discord.send(`**${name}** (Keet): ${text}`).catch((err) => { | |
| 49 | this.onLog(`[bridge] Discord send error: ${err.message}`) | |
| 50 | }) | |
| 51 | }, | |
| 52 | onKeetMessage: (nick, text) => { | |
| 53 | this._keet.send(this.keetRoom, `[Discord] <${nick}> ${text}`).catch((err) => { | |
| 54 | this.onLog(`[bridge] Keet send error: ${err.message}`) | |
| 55 | }) | |
| 56 | } | |
| 57 | }) | |
| 58 | ||
| 59 | // Initialize Discord client | |
| 60 | this._discord = new DiscordClient({ | |
| 61 | token: this.discordToken, | |
| 62 | channelId: this.discordChannel, | |
| 63 | guildId: this.discordGuild, | |
| 64 | onMessage: (message) => { | |
| 65 | const author = message.author.tag || message.author.username | |
| 66 | const text = message.content | |
| 67 | ||
| 68 | // Skip bridged messages | |
| 69 | if (text.startsWith('**') && text.includes('(Keet):')) return | |
| 70 | ||
| 71 | this.onLog(`[discord→keet] <${author}> ${text}`) | |
| 72 | ||
| 73 | // Handle attachments | |
| 74 | let fullText = text | |
| 75 | for (const [, att] of message.attachments) { | |
| 76 | fullText += (fullText ? '\n' : '') + att.url | |
| 77 | } | |
| 78 | ||
| 79 | this._router.routeToKeet(author, fullText) | |
| 80 | }, | |
| 81 | onReaction: (reaction, user) => { | |
| 82 | const emoji = reaction.emoji.name || '?' | |
| 83 | const author = user.tag || user.username | |
| 84 | this.onLog(`[discord→keet] ${author} reacted ${emoji}`) | |
| 85 | }, | |
| 86 | onError: (err) => { | |
| 87 | this.onLog(`[bridge] Discord error: ${err.message}`) | |
| 88 | } | |
| 89 | }) | |
| 90 | ||
| 91 | // Connect to Discord | |
| 92 | await this._discord.connect() | |
| 93 | this.onLog(`[bridge] Connected to Discord as ${this._discord.botUser?.tag || 'bot'}`) | |
| 94 | ||
| 95 | // Verify Keet daemon | |
| 96 | try { | |
| 97 | const status = await this._keet.status() | |
| 98 | this.onLog(`[bridge] Keet daemon online (${status.rooms || '?'} rooms)`) | |
| 99 | } catch (err) { | |
| 100 | this.onLog(`[bridge] Warning: Keet daemon unreachable: ${err.message}`) | |
| 101 | } | |
| 102 | ||
| 103 | // Start watching Keet room | |
| 104 | this._startKeetWatch() | |
| 105 | ||
| 106 | this._running = true | |
| 107 | this.onLog('[bridge] Bridge active — messages flowing bidirectionally') | |
| 108 | } | |
| 109 | ||
| 110 | _startKeetWatch () { | |
| 111 | this._keet.watch(this.keetRoom, (msg) => { | |
| 112 | const relayed = keetToDiscord(msg, this._bridgeIdentity) | |
| 113 | if (!relayed) return | |
| 114 | ||
| 115 | // Skip bridged messages | |
| 116 | if (relayed.text.includes('[Discord]')) return | |
| 117 | ||
| 118 | this.onLog(`[keet→discord] ${relayed.text}`) | |
| 119 | this._router.routeToIrc(relayed.name, msg.text) | |
| 120 | }) | |
| 121 | } | |
| 122 | ||
| 123 | async stop () { | |
| 124 | this.onLog('[bridge] Shutting down...') | |
| 125 | this._running = false | |
| 126 | ||
| 127 | if (this._keet) this._keet.stopWatch() | |
| 128 | if (this._discord) this._discord.disconnect() | |
| 129 | ||
| 130 | this.onLog('[bridge] Stopped') | |
| 131 | } | |
| 132 | ||
| 133 | get running () { | |
| 134 | return this._running | |
| 135 | } | |
| 136 | ||
| 137 | get stats () { | |
| 138 | return { | |
| 139 | running: this._running, | |
| 140 | discordConnected: this._discord?.connected || false, | |
| 141 | deduped: this._router?.seenCount || 0 | |
| 142 | } | |
| 143 | } | |
| 144 | } | |
| 145 | ||
| 146 | export { Bridge } |
| … | … | @@ -0,0 +1,98 @@ |
| 1 | import { Client, GatewayIntentBits, Events } from 'discord.js' | |
| 2 | ||
| 3 | class DiscordClient { | |
| 4 | constructor (opts) { | |
| 5 | this.token = opts.token | |
| 6 | this.channelId = opts.channelId | |
| 7 | this.guildId = opts.guildId || null | |
| 8 | ||
| 9 | this._client = null | |
| 10 | this._channel = null | |
| 11 | this._connected = false | |
| 12 | this._onMessage = opts.onMessage || null | |
| 13 | this._onReaction = opts.onReaction || null | |
| 14 | this._onError = opts.onError || null | |
| 15 | } | |
| 16 | ||
| 17 | connect () { | |
| 18 | return new Promise((resolve, reject) => { | |
| 19 | this._client = new Client({ | |
| 20 | intents: [ | |
| 21 | GatewayIntentBits.Guilds, | |
| 22 | GatewayIntentBits.GuildMessages, | |
| 23 | GatewayIntentBits.MessageContent, | |
| 24 | GatewayIntentBits.GuildMessageReactions | |
| 25 | ] | |
| 26 | }) | |
| 27 | ||
| 28 | this._client.once(Events.ClientReady, async () => { | |
| 29 | this._connected = true | |
| 30 | ||
| 31 | // Find channel | |
| 32 | this._channel = this._client.channels.cache.get(this.channelId) | |
| 33 | if (!this._channel) { | |
| 34 | try { | |
| 35 | this._channel = await this._client.channels.fetch(this.channelId) | |
| 36 | } catch (err) { | |
| 37 | return reject(new Error(`Channel ${this.channelId} not found: ${err.message}`)) | |
| 38 | } | |
| 39 | } | |
| 40 | ||
| 41 | resolve() | |
| 42 | }) | |
| 43 | ||
| 44 | this._client.on(Events.MessageCreate, (message) => { | |
| 45 | if (message.channelId !== this.channelId) return | |
| 46 | if (message.author.id === this._client.user.id) return // skip self | |
| 47 | if (this._onMessage) this._onMessage(message) | |
| 48 | }) | |
| 49 | ||
| 50 | this._client.on(Events.MessageReactionAdd, (reaction, user) => { | |
| 51 | if (reaction.message.channelId !== this.channelId) return | |
| 52 | if (user.id === this._client.user.id) return | |
| 53 | if (this._onReaction) this._onReaction(reaction, user) | |
| 54 | }) | |
| 55 | ||
| 56 | this._client.on(Events.Error, (err) => { | |
| 57 | if (this._onError) this._onError(err) | |
| 58 | }) | |
| 59 | ||
| 60 | this._client.login(this.token).catch(reject) | |
| 61 | }) | |
| 62 | } | |
| 63 | ||
| 64 | async send (text) { | |
| 65 | if (!this._connected || !this._channel) return | |
| 66 | await this._channel.send(text) | |
| 67 | } | |
| 68 | ||
| 69 | async sendEmbed (opts) { | |
| 70 | if (!this._connected || !this._channel) return | |
| 71 | await this._channel.send({ embeds: [opts] }) | |
| 72 | } | |
| 73 | ||
| 74 | async sendFile (url, text) { | |
| 75 | if (!this._connected || !this._channel) return | |
| 76 | await this._channel.send({ | |
| 77 | content: text || '', | |
| 78 | files: [url] | |
| 79 | }) | |
| 80 | } | |
| 81 | ||
| 82 | disconnect () { | |
| 83 | if (this._client) { | |
| 84 | this._client.destroy() | |
| 85 | this._connected = false | |
| 86 | } | |
| 87 | } | |
| 88 | ||
| 89 | get connected () { | |
| 90 | return this._connected | |
| 91 | } | |
| 92 | ||
| 93 | get botUser () { | |
| 94 | return this._client?.user || null | |
| 95 | } | |
| 96 | } | |
| 97 | ||
| 98 | export { DiscordClient } |
| … | … | @@ -0,0 +1,166 @@ |
| 1 | import http from 'http' | |
| 2 | import net from 'net' | |
| 3 | ||
| 4 | class KeetClient { | |
| 5 | constructor (opts = {}) { | |
| 6 | this.host = opts.host || 'localhost' | |
| 7 | this.port = opts.port || 3117 | |
| 8 | this._watching = false | |
| 9 | this._watchAbort = null | |
| 10 | } | |
| 11 | ||
| 12 | // Raw TCP request — bypasses Node's strict HTTP parser | |
| 13 | // Needed because keet-daemon sends duplicate Content-Length headers | |
| 14 | _rawRequest (method, path, body) { | |
| 15 | return new Promise((resolve, reject) => { | |
| 16 | const socket = net.connect(this.port, this.host) | |
| 17 | let payload = '' | |
| 18 | ||
| 19 | if (body) { | |
| 20 | payload = JSON.stringify(body) | |
| 21 | } | |
| 22 | ||
| 23 | socket.on('connect', () => { | |
| 24 | let req = `${method} ${path} HTTP/1.1\r\n` | |
| 25 | req += `Host: ${this.host}:${this.port}\r\n` | |
| 26 | if (payload) { | |
| 27 | req += `Content-Type: application/json\r\n` | |
| 28 | req += `Content-Length: ${Buffer.byteLength(payload)}\r\n` | |
| 29 | } | |
| 30 | req += `Connection: close\r\n` | |
| 31 | req += `\r\n` | |
| 32 | if (payload) req += payload | |
| 33 | socket.write(req) | |
| 34 | }) | |
| 35 | ||
| 36 | let data = '' | |
| 37 | socket.on('data', (chunk) => { data += chunk.toString() }) | |
| 38 | ||
| 39 | socket.on('end', () => { | |
| 40 | // Split headers from body | |
| 41 | const idx = data.indexOf('\r\n\r\n') | |
| 42 | if (idx === -1) return reject(new Error('No HTTP body')) | |
| 43 | const jsonBody = data.slice(idx + 4) | |
| 44 | ||
| 45 | try { | |
| 46 | const parsed = JSON.parse(jsonBody) | |
| 47 | if (!parsed.ok) reject(new Error(parsed.error || 'RPC error')) | |
| 48 | else resolve(parsed.data) | |
| 49 | } catch { | |
| 50 | reject(new Error('Invalid JSON: ' + jsonBody.slice(0, 200))) | |
| 51 | } | |
| 52 | }) | |
| 53 | ||
| 54 | socket.on('error', reject) | |
| 55 | socket.setTimeout(10000, () => { | |
| 56 | socket.destroy(new Error('Request timeout')) | |
| 57 | }) | |
| 58 | }) | |
| 59 | } | |
| 60 | ||
| 61 | rpc (method, params = {}) { | |
| 62 | return this._rawRequest('POST', '/rpc', { method, params }) | |
| 63 | } | |
| 64 | ||
| 65 | async send (roomId, text) { | |
| 66 | return this.rpc('send', { roomId, text }) | |
| 67 | } | |
| 68 | ||
| 69 | async messages (roomId, limit = 20) { | |
| 70 | return this._rawRequest('GET', `/rpc/messages/${roomId}?limit=${limit}`) | |
| 71 | } | |
| 72 | ||
| 73 | async rooms () { | |
| 74 | return this._rawRequest('GET', '/rpc/rooms') | |
| 75 | } | |
| 76 | ||
| 77 | async identity () { | |
| 78 | return this._rawRequest('GET', '/rpc/identity') | |
| 79 | } | |
| 80 | ||
| 81 | async status () { | |
| 82 | return this._rawRequest('GET', '/rpc/status') | |
| 83 | } | |
| 84 | ||
| 85 | async members (roomId) { | |
| 86 | return this._rawRequest('GET', `/rpc/members/${roomId}`) | |
| 87 | } | |
| 88 | ||
| 89 | watch (roomId, onMessage) { | |
| 90 | if (this._watching) return | |
| 91 | ||
| 92 | this._watching = true | |
| 93 | this._doWatch(roomId, onMessage) | |
| 94 | } | |
| 95 | ||
| 96 | _doWatch (roomId, onMessage) { | |
| 97 | if (!this._watching) return | |
| 98 | ||
| 99 | const req = http.request({ | |
| 100 | hostname: this.host, | |
| 101 | port: this.port, | |
| 102 | path: `/rpc/watch/${roomId}`, | |
| 103 | method: 'GET', | |
| 104 | headers: { Accept: 'text/event-stream' } | |
| 105 | }, (res) => { | |
| 106 | let buffer = '' | |
| 107 | ||
| 108 | res.on('data', (chunk) => { | |
| 109 | buffer += chunk.toString() | |
| 110 | const events = buffer.split('\n\n') | |
| 111 | buffer = events.pop() // keep incomplete tail | |
| 112 | ||
| 113 | for (const event of events) { | |
| 114 | if (!event.trim()) continue | |
| 115 | const lines = event.split('\n') | |
| 116 | let type = 'message' | |
| 117 | let data = null | |
| 118 | ||
| 119 | for (const line of lines) { | |
| 120 | if (line.startsWith('event: ')) type = line.slice(7) | |
| 121 | if (line.startsWith('data: ')) { | |
| 122 | try { | |
| 123 | data = JSON.parse(line.slice(6)) | |
| 124 | } catch {} | |
| 125 | } | |
| 126 | } | |
| 127 | ||
| 128 | if (type === 'message' && data) { | |
| 129 | onMessage(data) | |
| 130 | } | |
| 131 | } | |
| 132 | }) | |
| 133 | ||
| 134 | res.on('end', () => { | |
| 135 | if (this._watching) { | |
| 136 | setTimeout(() => this._doWatch(roomId, onMessage), 2000) | |
| 137 | } | |
| 138 | }) | |
| 139 | ||
| 140 | res.on('error', () => { | |
| 141 | if (this._watching) { | |
| 142 | setTimeout(() => this._doWatch(roomId, onMessage), 5000) | |
| 143 | } | |
| 144 | }) | |
| 145 | }) | |
| 146 | ||
| 147 | req.on('error', () => { | |
| 148 | if (this._watching) { | |
| 149 | setTimeout(() => this._doWatch(roomId, onMessage), 5000) | |
| 150 | } | |
| 151 | }) | |
| 152 | ||
| 153 | this._watchAbort = req | |
| 154 | req.end() | |
| 155 | } | |
| 156 | ||
| 157 | stopWatch () { | |
| 158 | this._watching = false | |
| 159 | if (this._watchAbort) { | |
| 160 | this._watchAbort.destroy() | |
| 161 | this._watchAbort = null | |
| 162 | } | |
| 163 | } | |
| 164 | } | |
| 165 | ||
| 166 | export { KeetClient } |
| … | … | @@ -0,0 +1,70 @@ |
| 1 | import { createHash } from 'crypto' | |
| 2 | ||
| 3 | class Router { | |
| 4 | constructor (opts = {}) { | |
| 5 | this._seen = new Map() | |
| 6 | this._maxSeen = opts.maxSeen || 10000 | |
| 7 | this._ttl = opts.ttl || 60000 // 1 minute dedup window | |
| 8 | this._onIrcMessage = opts.onIrcMessage || null | |
| 9 | this._onKeetMessage = opts.onKeetMessage || null | |
| 10 | } | |
| 11 | ||
| 12 | _hash (text, author) { | |
| 13 | return createHash('sha256') | |
| 14 | .update(author || '') | |
| 15 | .update(text || '') | |
| 16 | .digest('hex') | |
| 17 | .slice(0, 16) | |
| 18 | } | |
| 19 | ||
| 20 | _isDuplicate (hash) { | |
| 21 | const now = Date.now() | |
| 22 | ||
| 23 | // Cleanup old entries | |
| 24 | if (this._seen.size > this._maxSeen) { | |
| 25 | for (const [k, ts] of this._seen) { | |
| 26 | if (now - ts > this._ttl) this._seen.delete(k) | |
| 27 | } | |
| 28 | } | |
| 29 | ||
| 30 | if (this._seen.has(hash)) { | |
| 31 | const ts = this._seen.get(hash) | |
| 32 | if (now - ts < this._ttl) return true | |
| 33 | } | |
| 34 | ||
| 35 | this._seen.set(hash, now) | |
| 36 | return false | |
| 37 | } | |
| 38 | ||
| 39 | // Called when IRC receives a message — relay to Keet | |
| 40 | routeToKeet (nick, text) { | |
| 41 | const hash = this._hash(text, nick) | |
| 42 | if (this._isDuplicate(hash)) return false | |
| 43 | ||
| 44 | if (this._onKeetMessage) { | |
| 45 | this._onKeetMessage(nick, text) | |
| 46 | } | |
| 47 | return true | |
| 48 | } | |
| 49 | ||
| 50 | // Called when Keet receives a message — relay to IRC | |
| 51 | routeToIrc (name, text) { | |
| 52 | const hash = this._hash(text, name) | |
| 53 | if (this._isDuplicate(hash)) return false | |
| 54 | ||
| 55 | if (this._onIrcMessage) { | |
| 56 | this._onIrcMessage(name, text) | |
| 57 | } | |
| 58 | return true | |
| 59 | } | |
| 60 | ||
| 61 | get seenCount () { | |
| 62 | return this._seen.size | |
| 63 | } | |
| 64 | ||
| 65 | clearSeen () { | |
| 66 | this._seen.clear() | |
| 67 | } | |
| 68 | } | |
| 69 | ||
| 70 | export { Router } |
| … | … | @@ -0,0 +1,1175 @@ |
| 1 | { | |
| 2 | "name": "bridge-discord", | |
| 3 | "version": "1.0.0", | |
| 4 | "lockfileVersion": 3, | |
| 5 | "requires": true, | |
| 6 | "packages": { | |
| 7 | "": { | |
| 8 | "name": "bridge-discord", | |
| 9 | "version": "1.0.0", | |
| 10 | "license": "MIT", | |
| 11 | "dependencies": { | |
| 12 | "b4a": "^1.0.0", | |
| 13 | "bridge-envelope": "file:../bridge-envelope", | |
| 14 | "discord.js": "^14.16.3" | |
| 15 | }, | |
| 16 | "devDependencies": { | |
| 17 | "brittle": "^3.0.0" | |
| 18 | } | |
| 19 | }, | |
| 20 | "../bridge-envelope": { | |
| 21 | "version": "1.0.0", | |
| 22 | "license": "MIT", | |
| 23 | "dependencies": { | |
| 24 | "b4a": "^1.0.0", | |
| 25 | "compact-encoding": "^2.0.0" | |
| 26 | }, | |
| 27 | "devDependencies": { | |
| 28 | "brittle": "^3.0.0" | |
| 29 | } | |
| 30 | }, | |
| 31 | "node_modules/@discordjs/builders": { | |
| 32 | "version": "1.13.1", | |
| 33 | "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.13.1.tgz", | |
| 34 | "integrity": "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w==", | |
| 35 | "license": "Apache-2.0", | |
| 36 | "dependencies": { | |
| 37 | "@discordjs/formatters": "^0.6.2", | |
| 38 | "@discordjs/util": "^1.2.0", | |
| 39 | "@sapphire/shapeshift": "^4.0.0", | |
| 40 | "discord-api-types": "^0.38.33", | |
| 41 | "fast-deep-equal": "^3.1.3", | |
| 42 | "ts-mixer": "^6.0.4", | |
| 43 | "tslib": "^2.6.3" | |
| 44 | }, | |
| 45 | "engines": { | |
| 46 | "node": ">=16.11.0" | |
| 47 | }, | |
| 48 | "funding": { | |
| 49 | "url": "https://github.com/discordjs/discord.js?sponsor" | |
| 50 | } | |
| 51 | }, | |
| 52 | "node_modules/@discordjs/collection": { | |
| 53 | "version": "1.5.3", | |
| 54 | "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", | |
| 55 | "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", | |
| 56 | "license": "Apache-2.0", | |
| 57 | "engines": { | |
| 58 | "node": ">=16.11.0" | |
| 59 | } | |
| 60 | }, | |
| 61 | "node_modules/@discordjs/formatters": { | |
| 62 | "version": "0.6.2", | |
| 63 | "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.2.tgz", | |
| 64 | "integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==", | |
| 65 | "license": "Apache-2.0", | |
| 66 | "dependencies": { | |
| 67 | "discord-api-types": "^0.38.33" | |
| 68 | }, | |
| 69 | "engines": { | |
| 70 | "node": ">=16.11.0" | |
| 71 | }, | |
| 72 | "funding": { | |
| 73 | "url": "https://github.com/discordjs/discord.js?sponsor" | |
| 74 | } | |
| 75 | }, | |
| 76 | "node_modules/@discordjs/rest": { | |
| 77 | "version": "2.6.0", | |
| 78 | "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.0.tgz", | |
| 79 | "integrity": "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w==", | |
| 80 | "license": "Apache-2.0", | |
| 81 | "dependencies": { | |
| 82 | "@discordjs/collection": "^2.1.1", | |
| 83 | "@discordjs/util": "^1.1.1", | |
| 84 | "@sapphire/async-queue": "^1.5.3", | |
| 85 | "@sapphire/snowflake": "^3.5.3", | |
| 86 | "@vladfrangu/async_event_emitter": "^2.4.6", | |
| 87 | "discord-api-types": "^0.38.16", | |
| 88 | "magic-bytes.js": "^1.10.0", | |
| 89 | "tslib": "^2.6.3", | |
| 90 | "undici": "6.21.3" | |
| 91 | }, | |
| 92 | "engines": { | |
| 93 | "node": ">=18" | |
| 94 | }, | |
| 95 | "funding": { | |
| 96 | "url": "https://github.com/discordjs/discord.js?sponsor" | |
| 97 | } | |
| 98 | }, | |
| 99 | "node_modules/@discordjs/rest/node_modules/@discordjs/collection": { | |
| 100 | "version": "2.1.1", | |
| 101 | "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", | |
| 102 | "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", | |
| 103 | "license": "Apache-2.0", | |
| 104 | "engines": { | |
| 105 | "node": ">=18" | |
| 106 | }, | |
| 107 | "funding": { | |
| 108 | "url": "https://github.com/discordjs/discord.js?sponsor" | |
| 109 | } | |
| 110 | }, | |
| 111 | "node_modules/@discordjs/util": { | |
| 112 | "version": "1.2.0", | |
| 113 | "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz", | |
| 114 | "integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==", | |
| 115 | "license": "Apache-2.0", | |
| 116 | "dependencies": { | |
| 117 | "discord-api-types": "^0.38.33" | |
| 118 | }, | |
| 119 | "engines": { | |
| 120 | "node": ">=18" | |
| 121 | }, | |
| 122 | "funding": { | |
| 123 | "url": "https://github.com/discordjs/discord.js?sponsor" | |
| 124 | } | |
| 125 | }, | |
| 126 | "node_modules/@discordjs/ws": { | |
| 127 | "version": "1.2.3", | |
| 128 | "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz", | |
| 129 | "integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==", | |
| 130 | "license": "Apache-2.0", | |
| 131 | "dependencies": { | |
| 132 | "@discordjs/collection": "^2.1.0", | |
| 133 | "@discordjs/rest": "^2.5.1", | |
| 134 | "@discordjs/util": "^1.1.0", | |
| 135 | "@sapphire/async-queue": "^1.5.2", | |
| 136 | "@types/ws": "^8.5.10", | |
| 137 | "@vladfrangu/async_event_emitter": "^2.2.4", | |
| 138 | "discord-api-types": "^0.38.1", | |
| 139 | "tslib": "^2.6.2", | |
| 140 | "ws": "^8.17.0" | |
| 141 | }, | |
| 142 | "engines": { | |
| 143 | "node": ">=16.11.0" | |
| 144 | }, | |
| 145 | "funding": { | |
| 146 | "url": "https://github.com/discordjs/discord.js?sponsor" | |
| 147 | } | |
| 148 | }, | |
| 149 | "node_modules/@discordjs/ws/node_modules/@discordjs/collection": { | |
| 150 | "version": "2.1.1", | |
| 151 | "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", | |
| 152 | "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", | |
| 153 | "license": "Apache-2.0", | |
| 154 | "engines": { | |
| 155 | "node": ">=18" | |
| 156 | }, | |
| 157 | "funding": { | |
| 158 | "url": "https://github.com/discordjs/discord.js?sponsor" | |
| 159 | } | |
| 160 | }, | |
| 161 | "node_modules/@jridgewell/resolve-uri": { | |
| 162 | "version": "3.1.2", | |
| 163 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", | |
| 164 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", | |
| 165 | "dev": true, | |
| 166 | "license": "MIT", | |
| 167 | "engines": { | |
| 168 | "node": ">=6.0.0" | |
| 169 | } | |
| 170 | }, | |
| 171 | "node_modules/@jridgewell/sourcemap-codec": { | |
| 172 | "version": "1.5.5", | |
| 173 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", | |
| 174 | "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", | |
| 175 | "dev": true, | |
| 176 | "license": "MIT" | |
| 177 | }, | |
| 178 | "node_modules/@jridgewell/trace-mapping": { | |
| 179 | "version": "0.3.31", | |
| 180 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", | |
| 181 | "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", | |
| 182 | "dev": true, | |
| 183 | "license": "MIT", | |
| 184 | "dependencies": { | |
| 185 | "@jridgewell/resolve-uri": "^3.1.0", | |
| 186 | "@jridgewell/sourcemap-codec": "^1.4.14" | |
| 187 | } | |
| 188 | }, | |
| 189 | "node_modules/@sapphire/async-queue": { | |
| 190 | "version": "1.5.5", | |
| 191 | "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz", | |
| 192 | "integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==", | |
| 193 | "license": "MIT", | |
| 194 | "engines": { | |
| 195 | "node": ">=v14.0.0", | |
| 196 | "npm": ">=7.0.0" | |
| 197 | } | |
| 198 | }, | |
| 199 | "node_modules/@sapphire/shapeshift": { | |
| 200 | "version": "4.0.0", | |
| 201 | "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz", | |
| 202 | "integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==", | |
| 203 | "license": "MIT", | |
| 204 | "dependencies": { | |
| 205 | "fast-deep-equal": "^3.1.3", | |
| 206 | "lodash": "^4.17.21" | |
| 207 | }, | |
| 208 | "engines": { | |
| 209 | "node": ">=v16" | |
| 210 | } | |
| 211 | }, | |
| 212 | "node_modules/@sapphire/snowflake": { | |
| 213 | "version": "3.5.3", | |
| 214 | "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", | |
| 215 | "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", | |
| 216 | "license": "MIT", | |
| 217 | "engines": { | |
| 218 | "node": ">=v14.0.0", | |
| 219 | "npm": ">=7.0.0" | |
| 220 | } | |
| 221 | }, | |
| 222 | "node_modules/@types/istanbul-lib-coverage": { | |
| 223 | "version": "2.0.6", | |
| 224 | "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", | |
| 225 | "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", | |
| 226 | "dev": true, | |
| 227 | "license": "MIT" | |
| 228 | }, | |
| 229 | "node_modules/@types/node": { | |
| 230 | "version": "25.2.3", | |
| 231 | "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz", | |
| 232 | "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==", | |
| 233 | "license": "MIT", | |
| 234 | "dependencies": { | |
| 235 | "undici-types": "~7.16.0" | |
| 236 | } | |
| 237 | }, | |
| 238 | "node_modules/@types/ws": { | |
| 239 | "version": "8.18.1", | |
| 240 | "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", | |
| 241 | "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", | |
| 242 | "license": "MIT", | |
| 243 | "dependencies": { | |
| 244 | "@types/node": "*" | |
| 245 | } | |
| 246 | }, | |
| 247 | "node_modules/@vladfrangu/async_event_emitter": { | |
| 248 | "version": "2.4.7", | |
| 249 | "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz", | |
| 250 | "integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==", | |
| 251 | "license": "MIT", | |
| 252 | "engines": { | |
| 253 | "node": ">=v14.0.0", | |
| 254 | "npm": ">=7.0.0" | |
| 255 | } | |
| 256 | }, | |
| 257 | "node_modules/b4a": { | |
| 258 | "version": "1.7.4", | |
| 259 | "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.4.tgz", | |
| 260 | "integrity": "sha512-u20zJLDaSWpxaZ+zaAkEIB2dZZ1o+DF4T/MRbmsvGp9nletHOyiai19OzX1fF8xUBYsO1bPXxODvcd0978pnug==", | |
| 261 | "license": "Apache-2.0", | |
| 262 | "peerDependencies": { | |
| 263 | "react-native-b4a": "*" | |
| 264 | }, | |
| 265 | "peerDependenciesMeta": { | |
| 266 | "react-native-b4a": { | |
| 267 | "optional": true | |
| 268 | } | |
| 269 | } | |
| 270 | }, | |
| 271 | "node_modules/bare-addon-resolve": { | |
| 272 | "version": "1.10.0", | |
| 273 | "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.0.tgz", | |
| 274 | "integrity": "sha512-sSd0jieRJlDaODOzj0oe0RjFVC1QI0ZIjGIdPkbrTXsdVVtENg14c+lHHAhHwmWCZ2nQlMhy8jA3Y5LYPc/isA==", | |
| 275 | "dev": true, | |
| 276 | "license": "Apache-2.0", | |
| 277 | "dependencies": { | |
| 278 | "bare-module-resolve": "^1.10.0", | |
| 279 | "bare-semver": "^1.0.0" | |
| 280 | }, | |
| 281 | "peerDependencies": { | |
| 282 | "bare-url": "*" | |
| 283 | }, | |
| 284 | "peerDependenciesMeta": { | |
| 285 | "bare-url": { | |
| 286 | "optional": true | |
| 287 | } | |
| 288 | } | |
| 289 | }, | |
| 290 | "node_modules/bare-ansi-escapes": { | |
| 291 | "version": "2.2.3", | |
| 292 | "resolved": "https://registry.npmjs.org/bare-ansi-escapes/-/bare-ansi-escapes-2.2.3.tgz", | |
| 293 | "integrity": "sha512-02ES4/E2RbrtZSnHJ9LntBhYkLA6lPpSEeP8iqS3MccBIVhVBlEmruF1I7HZqx5Q8aiTeYfQVeqmrU9YO2yYoQ==", | |
| 294 | "dev": true, | |
| 295 | "license": "Apache-2.0", | |
| 296 | "dependencies": { | |
| 297 | "bare-stream": "^2.6.5" | |
| 298 | }, | |
| 299 | "peerDependencies": { | |
| 300 | "bare-buffer": "*" | |
| 301 | }, | |
| 302 | "peerDependenciesMeta": { | |
| 303 | "bare-buffer": { | |
| 304 | "optional": true | |
| 305 | } | |
| 306 | } | |
| 307 | }, | |
| 308 | "node_modules/bare-assert": { | |
| 309 | "version": "1.2.0", | |
| 310 | "resolved": "https://registry.npmjs.org/bare-assert/-/bare-assert-1.2.0.tgz", | |
| 311 | "integrity": "sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==", | |
| 312 | "dev": true, | |
| 313 | "license": "Apache-2.0", | |
| 314 | "dependencies": { | |
| 315 | "bare-inspect": "^3.1.2" | |
| 316 | } | |
| 317 | }, | |
| 318 | "node_modules/bare-bundle": { | |
| 319 | "version": "1.10.0", | |
| 320 | "resolved": "https://registry.npmjs.org/bare-bundle/-/bare-bundle-1.10.0.tgz", | |
| 321 | "integrity": "sha512-4LVlnJAHr00Hh6Vu6ZUJS38rcEtJT3b3vChXSsBsJ2mk1TN0lQ+gzd+Dw5L0aV7uqDZv84smuwW+O02X7PfDlw==", | |
| 322 | "dev": true, | |
| 323 | "license": "Apache-2.0", | |
| 324 | "peerDependencies": { | |
| 325 | "bare-buffer": "*", | |
| 326 | "bare-url": "*" | |
| 327 | }, | |
| 328 | "peerDependenciesMeta": { | |
| 329 | "bare-buffer": { | |
| 330 | "optional": true | |
| 331 | }, | |
| 332 | "bare-url": { | |
| 333 | "optional": true | |
| 334 | } | |
| 335 | } | |
| 336 | }, | |
| 337 | "node_modules/bare-cov": { | |
| 338 | "version": "1.2.1", | |
| 339 | "resolved": "https://registry.npmjs.org/bare-cov/-/bare-cov-1.2.1.tgz", | |
| 340 | "integrity": "sha512-SHNqb/WG9udUEj8m5qGSehoUQ9o0Xzv8nrf4HIdmG+KNU/XdhPLhTP5VPrHnSAMAXhAzEB4Q+2IYZ0x9qPzN1g==", | |
| 341 | "dev": true, | |
| 342 | "license": "Apache-2.0", | |
| 343 | "dependencies": { | |
| 344 | "bare-fs": "^4.1.2", | |
| 345 | "bare-inspector": "^4.0.1", | |
| 346 | "bare-path": "^3.0.0", | |
| 347 | "bare-process": "^4.2.1", | |
| 348 | "bare-url": "^2.1.5", | |
| 349 | "bare-v8-to-istanbul": "^1.0.2", | |
| 350 | "picomatch": "^4.0.2", | |
| 351 | "which-runtime": "^1.2.1" | |
| 352 | } | |
| 353 | }, | |
| 354 | "node_modules/bare-crypto": { | |
| 355 | "version": "1.13.0", | |
| 356 | "resolved": "https://registry.npmjs.org/bare-crypto/-/bare-crypto-1.13.0.tgz", | |
| 357 | "integrity": "sha512-RQl13yD+YTACWUZHMMck0C6LNjXgGgRyhVC557ag0xIrkxXf4IQwpnc8WOB58f4k5kSzKhIjy05oW3HUBrFpSQ==", | |
| 358 | "dev": true, | |
| 359 | "license": "Apache-2.0", | |
| 360 | "dependencies": { | |
| 361 | "bare-stream": "^2.6.3" | |
| 362 | }, | |
| 363 | "peerDependencies": { | |
| 364 | "bare-buffer": "*" | |
| 365 | }, | |
| 366 | "peerDependenciesMeta": { | |
| 367 | "bare-buffer": { | |
| 368 | "optional": true | |
| 369 | } | |
| 370 | } | |
| 371 | }, | |
| 372 | "node_modules/bare-debug-log": { | |
| 373 | "version": "2.0.0", | |
| 374 | "resolved": "https://registry.npmjs.org/bare-debug-log/-/bare-debug-log-2.0.0.tgz", | |
| 375 | "integrity": "sha512-Vi42PkMQsNV9PUpx2Gl1hikshx5O9FzMJ6o9Nnopseg7qLBBK7Nl31d0RHcfwLEAfmcPApytpc0ZFfq68u22FQ==", | |
| 376 | "dev": true, | |
| 377 | "license": "Apache-2.0", | |
| 378 | "dependencies": { | |
| 379 | "bare-os": "^3.0.1" | |
| 380 | } | |
| 381 | }, | |
| 382 | "node_modules/bare-dns": { | |
| 383 | "version": "2.1.4", | |
| 384 | "resolved": "https://registry.npmjs.org/bare-dns/-/bare-dns-2.1.4.tgz", | |
| 385 | "integrity": "sha512-abwjHmpWqSRNB7V5615QxPH92L71AVzFm/kKTs8VYiNTAi2xVdonpv0BjJ0hwXLwomoW+xsSOPjW6PZPO14asg==", | |
| 386 | "dev": true, | |
| 387 | "license": "Apache-2.0", | |
| 388 | "engines": { | |
| 389 | "bare": ">=1.7.0" | |
| 390 | } | |
| 391 | }, | |
| 392 | "node_modules/bare-encoding": { | |
| 393 | "version": "1.0.3", | |
| 394 | "resolved": "https://registry.npmjs.org/bare-encoding/-/bare-encoding-1.0.3.tgz", | |
| 395 | "integrity": "sha512-Kqf+t/azs13lUeyK4Tb7ha4wdLRXKWCXQ8w1rVmt7KtoPCPdHD/Xwt7LBIsCSwwGglrcmblo5VOLa5avkJqULA==", | |
| 396 | "dev": true, | |
| 397 | "license": "Apache-2.0", | |
| 398 | "peerDependencies": { | |
| 399 | "bare-buffer": "*" | |
| 400 | }, | |
| 401 | "peerDependenciesMeta": { | |
| 402 | "bare-buffer": { | |
| 403 | "optional": true | |
| 404 | } | |
| 405 | } | |
| 406 | }, | |
| 407 | "node_modules/bare-env": { | |
| 408 | "version": "3.0.0", | |
| 409 | "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.0.tgz", | |
| 410 | "integrity": "sha512-0u964P5ZLAxTi+lW4Kjp7YRJQ5gZr9ycYOtjLxsSrupgMz3sn5Z9n4SH/JIifHwvadsf1brA2JAjP+9IOWwTiw==", | |
| 411 | "dev": true, | |
| 412 | "license": "Apache-2.0", | |
| 413 | "dependencies": { | |
| 414 | "bare-os": "^3.0.1" | |
| 415 | } | |
| 416 | }, | |
| 417 | "node_modules/bare-events": { | |
| 418 | "version": "2.8.2", | |
| 419 | "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", | |
| 420 | "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", | |
| 421 | "dev": true, | |
| 422 | "license": "Apache-2.0", | |
| 423 | "peerDependencies": { | |
| 424 | "bare-abort-controller": "*" | |
| 425 | }, | |
| 426 | "peerDependenciesMeta": { | |
| 427 | "bare-abort-controller": { | |
| 428 | "optional": true | |
| 429 | } | |
| 430 | } | |
| 431 | }, | |
| 432 | "node_modules/bare-format": { | |
| 433 | "version": "1.0.1", | |
| 434 | "resolved": "https://registry.npmjs.org/bare-format/-/bare-format-1.0.1.tgz", | |
| 435 | "integrity": "sha512-1oS+LZrWK6tnYnvNSHDGljc2MPunRxwhpFriuCgzNF+oklrnwmBKD91tS0yt+jpl2j3UgcSDzBIMiVTvLs9A8w==", | |
| 436 | "dev": true, | |
| 437 | "license": "Apache-2.0", | |
| 438 | "dependencies": { | |
| 439 | "bare-inspect": "^3.0.0" | |
| 440 | } | |
| 441 | }, | |
| 442 | "node_modules/bare-fs": { | |
| 443 | "version": "4.5.4", | |
| 444 | "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.4.tgz", | |
| 445 | "integrity": "sha512-POK4oplfA7P7gqvetNmCs4CNtm9fNsx+IAh7jH7GgU0OJdge2rso0R20TNWVq6VoWcCvsTdlNDaleLHGaKx8CA==", | |
| 446 | "dev": true, | |
| 447 | "license": "Apache-2.0", | |
| 448 | "dependencies": { | |
| 449 | "bare-events": "^2.5.4", | |
| 450 | "bare-path": "^3.0.0", | |
| 451 | "bare-stream": "^2.6.4", | |
| 452 | "bare-url": "^2.2.2", | |
| 453 | "fast-fifo": "^1.3.2" | |
| 454 | }, | |
| 455 | "engines": { | |
| 456 | "bare": ">=1.16.0" | |
| 457 | }, | |
| 458 | "peerDependencies": { | |
| 459 | "bare-buffer": "*" | |
| 460 | }, | |
| 461 | "peerDependenciesMeta": { | |
| 462 | "bare-buffer": { | |
| 463 | "optional": true | |
| 464 | } | |
| 465 | } | |
| 466 | }, | |
| 467 | "node_modules/bare-hrtime": { | |
| 468 | "version": "2.1.1", | |
| 469 | "resolved": "https://registry.npmjs.org/bare-hrtime/-/bare-hrtime-2.1.1.tgz", | |
| 470 | "integrity": "sha512-VMb3tHo05gsnbu3OXTmkDiwTjMlOsbQmKoysKqKEyR09m77TuDrYFbj3Q5GGk10dAKsUHrnXmwCaeJqzVpB5ZA==", | |
| 471 | "dev": true, | |
| 472 | "license": "Apache-2.0" | |
| 473 | }, | |
| 474 | "node_modules/bare-http-parser": { | |
| 475 | "version": "1.0.1", | |
| 476 | "resolved": "https://registry.npmjs.org/bare-http-parser/-/bare-http-parser-1.0.1.tgz", | |
| 477 | "integrity": "sha512-A3LTDTcELcmNJ3g5liIaS038v/BQxOhA9cjhBESn7eoV7QCuMoIRBKLDadDe08flxyLbxI2f+1l2MZ/5+HnKPA==", | |
| 478 | "dev": true, | |
| 479 | "license": "Apache-2.0" | |
| 480 | }, | |
| 481 | "node_modules/bare-http1": { | |
| 482 | "version": "4.2.3", | |
| 483 | "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.2.3.tgz", | |
| 484 | "integrity": "sha512-zyvHF3xzvBJNpuw0QydE9etTn2AyC/TRVDWTo8Ed3LQEEGaFOkv6KHhaJPlf4eWEeTekk41Fowie+fTsXoNxkA==", | |
| 485 | "dev": true, | |
| 486 | "license": "Apache-2.0", | |
| 487 | "dependencies": { | |
| 488 | "bare-events": "^2.6.0", | |
| 489 | "bare-http-parser": "^1.0.0", | |
| 490 | "bare-stream": "^2.3.0", | |
| 491 | "bare-tcp": "^2.2.0" | |
| 492 | }, | |
| 493 | "peerDependencies": { | |
| 494 | "bare-buffer": "*", | |
| 495 | "bare-url": "*" | |
| 496 | }, | |
| 497 | "peerDependenciesMeta": { | |
| 498 | "bare-buffer": { | |
| 499 | "optional": true | |
| 500 | }, | |
| 501 | "bare-url": { | |
| 502 | "optional": true | |
| 503 | } | |
| 504 | } | |
| 505 | }, | |
| 506 | "node_modules/bare-https": { | |
| 507 | "version": "2.1.2", | |
| 508 | "resolved": "https://registry.npmjs.org/bare-https/-/bare-https-2.1.2.tgz", | |
| 509 | "integrity": "sha512-Q+TTydUDsuKQJvh8dX2dvOXCR9fM3xR5TBmKaFrs5p7Lj7XbKX7v4vIUJ36H0SXg2xCOQxXKIbjwrLg5tfJNYg==", | |
| 510 | "dev": true, | |
| 511 | "license": "Apache-2.0", | |
| 512 | "dependencies": { | |
| 513 | "bare-http1": "^4.0.0", | |
| 514 | "bare-tcp": "^2.2.0", | |
| 515 | "bare-tls": "^2.0.0" | |
| 516 | } | |
| 517 | }, | |
| 518 | "node_modules/bare-inspect": { | |
| 519 | "version": "3.1.4", | |
| 520 | "resolved": "https://registry.npmjs.org/bare-inspect/-/bare-inspect-3.1.4.tgz", | |
| 521 | "integrity": "sha512-jfW5KRA84o3REpI6Vr4nbvMn+hqVAw8GU1mMdRwUsY5yJovQamxYeKGVKGqdzs+8ZbG4jRzGUXP/3Ji/DnqfPg==", | |
| 522 | "dev": true, | |
| 523 | "license": "Apache-2.0", | |
| 524 | "dependencies": { | |
| 525 | "bare-ansi-escapes": "^2.1.0", | |
| 526 | "bare-type": "^1.0.0" | |
| 527 | }, | |
| 528 | "engines": { | |
| 529 | "bare": ">=1.18.0" | |
| 530 | } | |
| 531 | }, | |
| 532 | "node_modules/bare-inspector": { | |
| 533 | "version": "4.0.3", | |
| 534 | "resolved": "https://registry.npmjs.org/bare-inspector/-/bare-inspector-4.0.3.tgz", | |
| 535 | "integrity": "sha512-yYk6+7tTVKJKHOja5+QBn989ETv8oVsmwoH0nCM+QYC6w5MC03GC4aQNK+lipAZSz1EaAj42WZ8Wewjpy0L9TA==", | |
| 536 | "dev": true, | |
| 537 | "license": "Apache-2.0", | |
| 538 | "dependencies": { | |
| 539 | "bare-events": "^2.1.0", | |
| 540 | "bare-http1": "^4.0.0", | |
| 541 | "bare-stream": "^2.0.0", | |
| 542 | "bare-url": "^2.0.0", | |
| 543 | "bare-ws": "^2.0.0" | |
| 544 | }, | |
| 545 | "engines": { | |
| 546 | "bare": ">=1.2.0" | |
| 547 | }, | |
| 548 | "peerDependencies": { | |
| 549 | "bare-tcp": "*" | |
| 550 | }, | |
| 551 | "peerDependenciesMeta": { | |
| 552 | "bare-tcp": { | |
| 553 | "optional": true | |
| 554 | } | |
| 555 | } | |
| 556 | }, | |
| 557 | "node_modules/bare-module": { | |
| 558 | "version": "6.1.3", | |
| 559 | "resolved": "https://registry.npmjs.org/bare-module/-/bare-module-6.1.3.tgz", | |
| 560 | "integrity": "sha512-5XWsVHsvtWMH4tK4DQWgpNTV0t/sg3ZrAaQLIxrwjrS5+u8Q9vEgc/zQ4QaDPWDse/y/5h+d+YG1Q0JfSMt0zA==", | |
| 561 | "dev": true, | |
| 562 | "license": "Apache-2.0", | |
| 563 | "dependencies": { | |
| 564 | "bare-bundle": "^1.3.0", | |
| 565 | "bare-module-lexer": "^1.0.0", | |
| 566 | "bare-module-resolve": "^1.8.0", | |
| 567 | "bare-path": "^3.0.0", | |
| 568 | "bare-url": "^2.0.1" | |
| 569 | }, | |
| 570 | "engines": { | |
| 571 | "bare": ">=1.23.0" | |
| 572 | }, | |
| 573 | "peerDependencies": { | |
| 574 | "bare-buffer": "*" | |
| 575 | }, | |
| 576 | "peerDependenciesMeta": { | |
| 577 | "bare-buffer": { | |
| 578 | "optional": true | |
| 579 | } | |
| 580 | } | |
| 581 | }, | |
| 582 | "node_modules/bare-module-lexer": { | |
| 583 | "version": "1.4.7", | |
| 584 | "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.4.7.tgz", | |
| 585 | "integrity": "sha512-0klU4eMsjh/wcxi8FdHmNom2j2F4kmkXOhyJFL9qTaSFp2lE3m6BtbKgMHY8R5miqC9r8/IfA8wzXnC5Os14WA==", | |
| 586 | "dev": true, | |
| 587 | "license": "Apache-2.0", | |
| 588 | "dependencies": { | |
| 589 | "require-addon": "^1.0.2" | |
| 590 | }, | |
| 591 | "peerDependencies": { | |
| 592 | "bare-buffer": "*" | |
| 593 | }, | |
| 594 | "peerDependenciesMeta": { | |
| 595 | "bare-buffer": { | |
| 596 | "optional": true | |
| 597 | } | |
| 598 | } | |
| 599 | }, | |
| 600 | "node_modules/bare-module-resolve": { | |
| 601 | "version": "1.12.1", | |
| 602 | "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.1.tgz", | |
| 603 | "integrity": "sha512-hbmAPyFpEq8FoZMd5sFO3u6MC5feluWoGE8YKlA8fCrl6mNtx68Wjg4DTiDJcqRJaovTvOYKfYngoBUnbaT7eg==", | |
| 604 | "dev": true, | |
| 605 | "license": "Apache-2.0", | |
| 606 | "dependencies": { | |
| 607 | "bare-semver": "^1.0.0" | |
| 608 | }, | |
| 609 | "peerDependencies": { | |
| 610 | "bare-url": "*" | |
| 611 | }, | |
| 612 | "peerDependenciesMeta": { | |
| 613 | "bare-url": { | |
| 614 | "optional": true | |
| 615 | } | |
| 616 | } | |
| 617 | }, | |
| 618 | "node_modules/bare-net": { | |
| 619 | "version": "2.2.0", | |
| 620 | "resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.2.0.tgz", | |
| 621 | "integrity": "sha512-UF7cAbHsGE+H6uEqWF5IULBow1x58chZz4g3ALgHtv7wZsFcCbRDt0JKWEumf5Oma3QWS1Q6aLi0Rpll8RElMg==", | |
| 622 | "dev": true, | |
| 623 | "license": "Apache-2.0", | |
| 624 | "dependencies": { | |
| 625 | "bare-events": "^2.2.2", | |
| 626 | "bare-pipe": "^4.0.0", | |
| 627 | "bare-stream": "^2.0.0", | |
| 628 | "bare-tcp": "^2.0.0" | |
| 629 | } | |
| 630 | }, | |
| 631 | "node_modules/bare-os": { | |
| 632 | "version": "3.6.2", | |
| 633 | "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", | |
| 634 | "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", | |
| 635 | "dev": true, | |
| 636 | "license": "Apache-2.0", | |
| 637 | "engines": { | |
| 638 | "bare": ">=1.14.0" | |
| 639 | } | |
| 640 | }, | |
| 641 | "node_modules/bare-path": { | |
| 642 | "version": "3.0.0", | |
| 643 | "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", | |
| 644 | "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", | |
| 645 | "dev": true, | |
| 646 | "license": "Apache-2.0", | |
| 647 | "dependencies": { | |
| 648 | "bare-os": "^3.0.1" | |
| 649 | } | |
| 650 | }, | |
| 651 | "node_modules/bare-pipe": { | |
| 652 | "version": "4.1.3", | |
| 653 | "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.1.3.tgz", | |
| 654 | "integrity": "sha512-DqQsx93rAzre6yJ9T6l/Vgh+X+bntkVMB1X5ggtXjXtqtMmF2Y2RVlCzxxy/09R6yeR9FSWBEUIaMYJL1/5VDA==", | |
| 655 | "dev": true, | |
| 656 | "license": "Apache-2.0", | |
| 657 | "dependencies": { | |
| 658 | "bare-events": "^2.0.0", | |
| 659 | "bare-stream": "^2.0.0" | |
| 660 | }, | |
| 661 | "engines": { | |
| 662 | "bare": ">=1.16.0" | |
| 663 | } | |
| 664 | }, | |
| 665 | "node_modules/bare-process": { | |
| 666 | "version": "4.2.2", | |
| 667 | "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.2.2.tgz", | |
| 668 | "integrity": "sha512-ikzEw+HGLB+2lS/WLVEsqi8BXBwzleG3n7DYI0v6/YNklvNabOIGlLd1dof+7Jw5ob4jsBRPtyflweVahY7rFA==", | |
| 669 | "dev": true, | |
| 670 | "license": "Apache-2.0", | |
| 671 | "dependencies": { | |
| 672 | "bare-env": "^3.0.0", | |
| 673 | "bare-events": "^2.3.1", | |
| 674 | "bare-hrtime": "^2.0.0", | |
| 675 | "bare-os": "^3.5.0", | |
| 676 | "bare-pipe": "^4.0.0", | |
| 677 | "bare-signals": "^4.0.0", | |
| 678 | "bare-tty": "^5.0.0" | |
| 679 | } | |
| 680 | }, | |
| 681 | "node_modules/bare-semver": { | |
| 682 | "version": "1.0.2", | |
| 683 | "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.0.2.tgz", | |
| 684 | "integrity": "sha512-ESVaN2nzWhcI5tf3Zzcq9aqCZ676VWzqw07eEZ0qxAcEOAFYBa0pWq8sK34OQeHLY3JsfKXZS9mDyzyxGjeLzA==", | |
| 685 | "dev": true, | |
| 686 | "license": "Apache-2.0" | |
| 687 | }, | |
| 688 | "node_modules/bare-signals": { | |
| 689 | "version": "4.2.0", | |
| 690 | "resolved": "https://registry.npmjs.org/bare-signals/-/bare-signals-4.2.0.tgz", | |
| 691 | "integrity": "sha512-fNHMOdQIlYuTvMB3Oh9Apk99hLKn351+Ir8vz+khiPTcOqIyGG4uWWjdLTzxWdYGsA0eT+We3y0K74hjj2nq7A==", | |
| 692 | "dev": true, | |
| 693 | "license": "Apache-2.0", | |
| 694 | "dependencies": { | |
| 695 | "bare-events": "^2.5.3", | |
| 696 | "bare-os": "^3.3.1" | |
| 697 | }, | |
| 698 | "engines": { | |
| 699 | "bare": ">=1.7.0" | |
| 700 | } | |
| 701 | }, | |
| 702 | "node_modules/bare-stream": { | |
| 703 | "version": "2.7.0", | |
| 704 | "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", | |
| 705 | "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", | |
| 706 | "dev": true, | |
| 707 | "license": "Apache-2.0", | |
| 708 | "dependencies": { | |
| 709 | "streamx": "^2.21.0" | |
| 710 | }, | |
| 711 | "peerDependencies": { | |
| 712 | "bare-buffer": "*", | |
| 713 | "bare-events": "*" | |
| 714 | }, | |
| 715 | "peerDependenciesMeta": { | |
| 716 | "bare-buffer": { | |
| 717 | "optional": true | |
| 718 | }, | |
| 719 | "bare-events": { | |
| 720 | "optional": true | |
| 721 | } | |
| 722 | } | |
| 723 | }, | |
| 724 | "node_modules/bare-subprocess": { | |
| 725 | "version": "5.2.2", | |
| 726 | "resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.2.tgz", | |
| 727 | "integrity": "sha512-L6oXgQ1aWs25RtG5Ky0bDD06p3RAcVVrDDMWb1DfXpHtyEWxamcyIWUbSykxMTWrpLlmSj6ytbb6yoKehGFfmw==", | |
| 728 | "dev": true, | |
| 729 | "license": "Apache-2.0", | |
| 730 | "dependencies": { | |
| 731 | "bare-env": "^3.0.0", | |
| 732 | "bare-events": "^2.5.4", | |
| 733 | "bare-os": "^3.0.1", | |
| 734 | "bare-pipe": "^4.0.0", | |
| 735 | "bare-url": "^2.2.2" | |
| 736 | }, | |
| 737 | "engines": { | |
| 738 | "bare": ">=1.7.0" | |
| 739 | }, | |
| 740 | "peerDependencies": { | |
| 741 | "bare-buffer": "*" | |
| 742 | }, | |
| 743 | "peerDependenciesMeta": { | |
| 744 | "bare-buffer": { | |
| 745 | "optional": true | |
| 746 | } | |
| 747 | } | |
| 748 | }, | |
| 749 | "node_modules/bare-tcp": { | |
| 750 | "version": "2.2.4", | |
| 751 | "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.2.4.tgz", | |
| 752 | "integrity": "sha512-vGjfMkyhS1lxp+Y4+gXRDR1ijZMK2cKx0RBwtYKUuZbD90ARCoM6bAiTa1HBztumCIC+NXb8eXdMFXyuYwlPyw==", | |
| 753 | "dev": true, | |
| 754 | "license": "Apache-2.0", | |
| 755 | "dependencies": { | |
| 756 | "bare-dns": "^2.0.4", | |
| 757 | "bare-events": "^2.5.4", | |
| 758 | "bare-stream": "^2.6.4" | |
| 759 | }, | |
| 760 | "engines": { | |
| 761 | "bare": ">=1.16.0" | |
| 762 | } | |
| 763 | }, | |
| 764 | "node_modules/bare-tls": { | |
| 765 | "version": "2.1.7", | |
| 766 | "resolved": "https://registry.npmjs.org/bare-tls/-/bare-tls-2.1.7.tgz", | |
| 767 | "integrity": "sha512-h6wcNXQdBeTX7fed9tjPp0/9cA/QfcBTv3ItgjnbUk4rWAU8bEFalZCZnUDdCK/t9zrNfJ+yvcPx4D/1Y6biyA==", | |
| 768 | "dev": true, | |
| 769 | "license": "Apache-2.0", | |
| 770 | "dependencies": { | |
| 771 | "bare-net": "^2.0.1", | |
| 772 | "bare-stream": "^2.6.4" | |
| 773 | }, | |
| 774 | "engines": { | |
| 775 | "bare": ">=1.7.0" | |
| 776 | } | |
| 777 | }, | |
| 778 | "node_modules/bare-tty": { | |
| 779 | "version": "5.0.3", | |
| 780 | "resolved": "https://registry.npmjs.org/bare-tty/-/bare-tty-5.0.3.tgz", | |
| 781 | "integrity": "sha512-jW24RBWRZOMHuSEWC9mh8wUKO5WUNl0UWUTNPn3yZ8qkOqwa8vaV2dR3a+BvblONPk7V35wuvK9P8508+judAg==", | |
| 782 | "dev": true, | |
| 783 | "license": "Apache-2.0", | |
| 784 | "dependencies": { | |
| 785 | "bare-events": "^2.2.0", | |
| 786 | "bare-signals": "^4.0.0", | |
| 787 | "bare-stream": "^2.0.0" | |
| 788 | }, | |
| 789 | "engines": { | |
| 790 | "bare": ">=1.16.0" | |
| 791 | } | |
| 792 | }, | |
| 793 | "node_modules/bare-type": { | |
| 794 | "version": "1.1.0", | |
| 795 | "resolved": "https://registry.npmjs.org/bare-type/-/bare-type-1.1.0.tgz", | |
| 796 | "integrity": "sha512-LdtnnEEYldOc87Dr4GpsKnStStZk3zfgoEMXy8yvEZkXrcCv9RtYDrUYWFsBQHtaB0s1EUWmcvS6XmEZYIj3Bw==", | |
| 797 | "dev": true, | |
| 798 | "license": "Apache-2.0", | |
| 799 | "engines": { | |
| 800 | "bare": ">=1.2.0" | |
| 801 | } | |
| 802 | }, | |
| 803 | "node_modules/bare-url": { | |
| 804 | "version": "2.3.2", | |
| 805 | "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", | |
| 806 | "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", | |
| 807 | "dev": true, | |
| 808 | "license": "Apache-2.0", | |
| 809 | "peer": true, | |
| 810 | "dependencies": { | |
| 811 | "bare-path": "^3.0.0" | |
| 812 | } | |
| 813 | }, | |
| 814 | "node_modules/bare-utils": { | |
| 815 | "version": "1.5.1", | |
| 816 | "resolved": "https://registry.npmjs.org/bare-utils/-/bare-utils-1.5.1.tgz", | |
| 817 | "integrity": "sha512-mxCkFvmDU3mlD/mb+pT64kKXOsx2KMsWLQbngN1LB+NOXfhfnRnyvpy3VZc6m7gzQxe57Bsi+aTCBqA4/S3elQ==", | |
| 818 | "dev": true, | |
| 819 | "license": "Apache-2.0", | |
| 820 | "dependencies": { | |
| 821 | "bare-debug-log": "^2.0.0", | |
| 822 | "bare-encoding": "^1.0.0", | |
| 823 | "bare-format": "^1.0.0", | |
| 824 | "bare-inspect": "^3.0.0", | |
| 825 | "bare-type": "^1.0.6" | |
| 826 | } | |
| 827 | }, | |
| 828 | "node_modules/bare-v8-to-istanbul": { | |
| 829 | "version": "1.0.2", | |
| 830 | "resolved": "https://registry.npmjs.org/bare-v8-to-istanbul/-/bare-v8-to-istanbul-1.0.2.tgz", | |
| 831 | "integrity": "sha512-CM0wPgyQtXTQ7h4q8NjbTZ/FH5he0Jkiwgpq0lFZZA5tkVZzmQ/9ul4R7E+cNlGZJIZlQZ9v4CwbgVaFG3zOAA==", | |
| 832 | "dev": true, | |
| 833 | "license": "Apache-2.0", | |
| 834 | "dependencies": { | |
| 835 | "bare-assert": "^1.0.2", | |
| 836 | "bare-fs": "^4.1.2", | |
| 837 | "bare-module": "^6.1.2", | |
| 838 | "bare-path": "^3.0.0", | |
| 839 | "bare-process": "^4.2.0", | |
| 840 | "bare-url": "^2.1.5", | |
| 841 | "bare-utils": "^1.2.0", | |
| 842 | "v8-to-istanbul": "^9.3.0", | |
| 843 | "which-runtime": "^1.2.1" | |
| 844 | } | |
| 845 | }, | |
| 846 | "node_modules/bare-ws": { | |
| 847 | "version": "2.0.4", | |
| 848 | "resolved": "https://registry.npmjs.org/bare-ws/-/bare-ws-2.0.4.tgz", | |
| 849 | "integrity": "sha512-SQMXzBYna9dRj57Dz1/ag+VWHCRXbfCjMHgyfM2F2lhkVLzMjnVSZP72aVeFWPFqe494Rd70Kzhe2JElGwFlJQ==", | |
| 850 | "dev": true, | |
| 851 | "license": "Apache-2.0", | |
| 852 | "dependencies": { | |
| 853 | "bare-crypto": "^1.2.0", | |
| 854 | "bare-events": "^2.3.1", | |
| 855 | "bare-http1": "^4.0.0", | |
| 856 | "bare-https": "^2.0.0", | |
| 857 | "bare-stream": "^2.1.2" | |
| 858 | }, | |
| 859 | "peerDependencies": { | |
| 860 | "bare-buffer": "*", | |
| 861 | "bare-url": "*" | |
| 862 | }, | |
| 863 | "peerDependenciesMeta": { | |
| 864 | "bare-buffer": { | |
| 865 | "optional": true | |
| 866 | }, | |
| 867 | "bare-url": { | |
| 868 | "optional": true | |
| 869 | } | |
| 870 | } | |
| 871 | }, | |
| 872 | "node_modules/bridge-envelope": { | |
| 873 | "resolved": "../bridge-envelope", | |
| 874 | "link": true | |
| 875 | }, | |
| 876 | "node_modules/brittle": { | |
| 877 | "version": "3.19.1", | |
| 878 | "resolved": "https://registry.npmjs.org/brittle/-/brittle-3.19.1.tgz", | |
| 879 | "integrity": "sha512-4Ted1Mt9o9B6oIA6ImJJCtB/Fv++ZO3IDNQgRcHOXWSYGRUidgxchaGrUBaRn+4mlneXrgInPkCPzi0jO8qKbA==", | |
| 880 | "dev": true, | |
| 881 | "license": "Apache-2.0", | |
| 882 | "dependencies": { | |
| 883 | "b4a": "^1.6.0", | |
| 884 | "bare-assert": "^1.0.2", | |
| 885 | "bare-cov": "^1.1.0", | |
| 886 | "bare-fs": "^4.1.6", | |
| 887 | "bare-os": "^3.6.1", | |
| 888 | "bare-path": "^3.0.0", | |
| 889 | "bare-process": "^4.2.1", | |
| 890 | "bare-subprocess": "^5.0.0", | |
| 891 | "bare-url": "^2.1.6", | |
| 892 | "error-stack-parser": "^2.1.4", | |
| 893 | "globbie": "^1.0.2", | |
| 894 | "paparam": "^1.6.2", | |
| 895 | "same-object": "^1.0.2", | |
| 896 | "test-tmp": "^1.4.0", | |
| 897 | "tmatch": "^5.0.0" | |
| 898 | }, | |
| 899 | "bin": { | |
| 900 | "brittle": "bin/node.js", | |
| 901 | "brittle-bare": "bin/bare.js", | |
| 902 | "brittle-node": "bin/node.js", | |
| 903 | "brittle-pear": "bin/pear.js" | |
| 904 | } | |
| 905 | }, | |
| 906 | "node_modules/convert-source-map": { | |
| 907 | "version": "2.0.0", | |
| 908 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", | |
| 909 | "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", | |
| 910 | "dev": true, | |
| 911 | "license": "MIT" | |
| 912 | }, | |
| 913 | "node_modules/discord-api-types": { | |
| 914 | "version": "0.38.39", | |
| 915 | "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.39.tgz", | |
| 916 | "integrity": "sha512-XRdDQvZvID1XvcFftjSmd4dcmMi/RL/jSy5sduBDAvCGFcNFHThdIQXCEBDZFe52lCNEzuIL0QJoKYAmRmxLUA==", | |
| 917 | "license": "MIT", | |
| 918 | "workspaces": [ | |
| 919 | "scripts/actions/documentation" | |
| 920 | ] | |
| 921 | }, | |
| 922 | "node_modules/discord.js": { | |
| 923 | "version": "14.25.1", | |
| 924 | "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.25.1.tgz", | |
| 925 | "integrity": "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g==", | |
| 926 | "license": "Apache-2.0", | |
| 927 | "dependencies": { | |
| 928 | "@discordjs/builders": "^1.13.0", | |
| 929 | "@discordjs/collection": "1.5.3", | |
| 930 | "@discordjs/formatters": "^0.6.2", | |
| 931 | "@discordjs/rest": "^2.6.0", | |
| 932 | "@discordjs/util": "^1.2.0", | |
| 933 | "@discordjs/ws": "^1.2.3", | |
| 934 | "@sapphire/snowflake": "3.5.3", | |
| 935 | "discord-api-types": "^0.38.33", | |
| 936 | "fast-deep-equal": "3.1.3", | |
| 937 | "lodash.snakecase": "4.1.1", | |
| 938 | "magic-bytes.js": "^1.10.0", | |
| 939 | "tslib": "^2.6.3", | |
| 940 | "undici": "6.21.3" | |
| 941 | }, | |
| 942 | "engines": { | |
| 943 | "node": ">=18" | |
| 944 | }, | |
| 945 | "funding": { | |
| 946 | "url": "https://github.com/discordjs/discord.js?sponsor" | |
| 947 | } | |
| 948 | }, | |
| 949 | "node_modules/error-stack-parser": { | |
| 950 | "version": "2.1.4", | |
| 951 | "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", | |
| 952 | "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", | |
| 953 | "dev": true, | |
| 954 | "license": "MIT", | |
| 955 | "dependencies": { | |
| 956 | "stackframe": "^1.3.4" | |
| 957 | } | |
| 958 | }, | |
| 959 | "node_modules/events-universal": { | |
| 960 | "version": "1.0.1", | |
| 961 | "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", | |
| 962 | "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", | |
| 963 | "dev": true, | |
| 964 | "license": "Apache-2.0", | |
| 965 | "dependencies": { | |
| 966 | "bare-events": "^2.7.0" | |
| 967 | } | |
| 968 | }, | |
| 969 | "node_modules/fast-deep-equal": { | |
| 970 | "version": "3.1.3", | |
| 971 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", | |
| 972 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", | |
| 973 | "license": "MIT" | |
| 974 | }, | |
| 975 | "node_modules/fast-fifo": { | |
| 976 | "version": "1.3.2", | |
| 977 | "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", | |
| 978 | "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", | |
| 979 | "dev": true, | |
| 980 | "license": "MIT" | |
| 981 | }, | |
| 982 | "node_modules/globbie": { | |
| 983 | "version": "1.0.3", | |
| 984 | "resolved": "https://registry.npmjs.org/globbie/-/globbie-1.0.3.tgz", | |
| 985 | "integrity": "sha512-hcryJmKcftf82xfBWTWxuUITWIIoYO3ec104V17SMHGFl6VLbm1d1Ju9LX9L+jyJTwICpemX/dmPI/HGYoKr1A==", | |
| 986 | "dev": true, | |
| 987 | "license": "Apache-2.0", | |
| 988 | "dependencies": { | |
| 989 | "bare-fs": "^4.1.2", | |
| 990 | "bare-path": "^3.0.0", | |
| 991 | "bare-process": "^4.2.1", | |
| 992 | "picomatch": "^4.0.2" | |
| 993 | } | |
| 994 | }, | |
| 995 | "node_modules/lodash": { | |
| 996 | "version": "4.17.23", | |
| 997 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", | |
| 998 | "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", | |
| 999 | "license": "MIT" | |
| 1000 | }, | |
| 1001 | "node_modules/lodash.snakecase": { | |
| 1002 | "version": "4.1.1", | |
| 1003 | "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", | |
| 1004 | "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", | |
| 1005 | "license": "MIT" | |
| 1006 | }, | |
| 1007 | "node_modules/magic-bytes.js": { | |
| 1008 | "version": "1.13.0", | |
| 1009 | "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.13.0.tgz", | |
| 1010 | "integrity": "sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg==", | |
| 1011 | "license": "MIT" | |
| 1012 | }, | |
| 1013 | "node_modules/paparam": { | |
| 1014 | "version": "1.10.0", | |
| 1015 | "resolved": "https://registry.npmjs.org/paparam/-/paparam-1.10.0.tgz", | |
| 1016 | "integrity": "sha512-p36QQrwU3X6fj5d2JN20B4lcZI/O3fxMVUHc1xD7MNe5bcOf3jIgE0CIK5Zv/s5YAUGlobTJ/kKXrh1FJVrs3w==", | |
| 1017 | "dev": true, | |
| 1018 | "license": "Apache-2.0" | |
| 1019 | }, | |
| 1020 | "node_modules/picomatch": { | |
| 1021 | "version": "4.0.3", | |
| 1022 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", | |
| 1023 | "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", | |
| 1024 | "dev": true, | |
| 1025 | "license": "MIT", | |
| 1026 | "engines": { | |
| 1027 | "node": ">=12" | |
| 1028 | }, | |
| 1029 | "funding": { | |
| 1030 | "url": "https://github.com/sponsors/jonschlinkert" | |
| 1031 | } | |
| 1032 | }, | |
| 1033 | "node_modules/require-addon": { | |
| 1034 | "version": "1.2.0", | |
| 1035 | "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz", | |
| 1036 | "integrity": "sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA==", | |
| 1037 | "dev": true, | |
| 1038 | "license": "Apache-2.0", | |
| 1039 | "dependencies": { | |
| 1040 | "bare-addon-resolve": "^1.3.0" | |
| 1041 | }, | |
| 1042 | "engines": { | |
| 1043 | "bare": ">=1.10.0" | |
| 1044 | } | |
| 1045 | }, | |
| 1046 | "node_modules/same-object": { | |
| 1047 | "version": "1.0.2", | |
| 1048 | "resolved": "https://registry.npmjs.org/same-object/-/same-object-1.0.2.tgz", | |
| 1049 | "integrity": "sha512-csHWhvUsLbIOHDM/nP+KHWM+BLPsIzWkFa8HbzaI0G7BqKXgx+7FJpKTGgLXyz5amfdY2OVBcmXTqYOMEk04og==", | |
| 1050 | "dev": true, | |
| 1051 | "license": "MIT" | |
| 1052 | }, | |
| 1053 | "node_modules/stackframe": { | |
| 1054 | "version": "1.3.4", | |
| 1055 | "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", | |
| 1056 | "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", | |
| 1057 | "dev": true, | |
| 1058 | "license": "MIT" | |
| 1059 | }, | |
| 1060 | "node_modules/streamx": { | |
| 1061 | "version": "2.23.0", | |
| 1062 | "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", | |
| 1063 | "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", | |
| 1064 | "dev": true, | |
| 1065 | "license": "MIT", | |
| 1066 | "dependencies": { | |
| 1067 | "events-universal": "^1.0.0", | |
| 1068 | "fast-fifo": "^1.3.2", | |
| 1069 | "text-decoder": "^1.1.0" | |
| 1070 | } | |
| 1071 | }, | |
| 1072 | "node_modules/test-tmp": { | |
| 1073 | "version": "1.4.0", | |
| 1074 | "resolved": "https://registry.npmjs.org/test-tmp/-/test-tmp-1.4.0.tgz", | |
| 1075 | "integrity": "sha512-GVggxGg+jXqP2Wbju50JVLo+9E+nIOPPyWqgr63EbOnNItIKu1cEbJpTWAJeflnyGqXOtcMI7ijHRp88GUkfDA==", | |
| 1076 | "dev": true, | |
| 1077 | "license": "MIT", | |
| 1078 | "dependencies": { | |
| 1079 | "bare-fs": "^4.0.1", | |
| 1080 | "bare-os": "^3.3.0", | |
| 1081 | "bare-path": "^3.0.0" | |
| 1082 | } | |
| 1083 | }, | |
| 1084 | "node_modules/text-decoder": { | |
| 1085 | "version": "1.2.6", | |
| 1086 | "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.6.tgz", | |
| 1087 | "integrity": "sha512-27FeW5GQFDfw0FpwMQhMagB7BztOOlmjcSRi97t2oplhKVTZtp0DZbSegSaXS5IIC6mxMvBG4AR1Sgc6BX3CQg==", | |
| 1088 | "dev": true, | |
| 1089 | "license": "Apache-2.0", | |
| 1090 | "dependencies": { | |
| 1091 | "b4a": "^1.6.4" | |
| 1092 | } | |
| 1093 | }, | |
| 1094 | "node_modules/tmatch": { | |
| 1095 | "version": "5.0.0", | |
| 1096 | "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-5.0.0.tgz", | |
| 1097 | "integrity": "sha512-Ib9OtBkpHn07tXP04SlN1SYRxFgTk6wSM2EBmjjxug4u5RXPRVLkdFJSS1PmrQidaSB8Lru9nRtViQBsbxzE5Q==", | |
| 1098 | "dev": true, | |
| 1099 | "license": "ISC", | |
| 1100 | "engines": { | |
| 1101 | "node": ">=8" | |
| 1102 | } | |
| 1103 | }, | |
| 1104 | "node_modules/ts-mixer": { | |
| 1105 | "version": "6.0.4", | |
| 1106 | "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", | |
| 1107 | "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", | |
| 1108 | "license": "MIT" | |
| 1109 | }, | |
| 1110 | "node_modules/tslib": { | |
| 1111 | "version": "2.8.1", | |
| 1112 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", | |
| 1113 | "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", | |
| 1114 | "license": "0BSD" | |
| 1115 | }, | |
| 1116 | "node_modules/undici": { | |
| 1117 | "version": "6.21.3", | |
| 1118 | "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", | |
| 1119 | "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", | |
| 1120 | "license": "MIT", | |
| 1121 | "engines": { | |
| 1122 | "node": ">=18.17" | |
| 1123 | } | |
| 1124 | }, | |
| 1125 | "node_modules/undici-types": { | |
| 1126 | "version": "7.16.0", | |
| 1127 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", | |
| 1128 | "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", | |
| 1129 | "license": "MIT" | |
| 1130 | }, | |
| 1131 | "node_modules/v8-to-istanbul": { | |
| 1132 | "version": "9.3.0", | |
| 1133 | "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", | |
| 1134 | "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", | |
| 1135 | "dev": true, | |
| 1136 | "license": "ISC", | |
| 1137 | "dependencies": { | |
| 1138 | "@jridgewell/trace-mapping": "^0.3.12", | |
| 1139 | "@types/istanbul-lib-coverage": "^2.0.1", | |
| 1140 | "convert-source-map": "^2.0.0" | |
| 1141 | }, | |
| 1142 | "engines": { | |
| 1143 | "node": ">=10.12.0" | |
| 1144 | } | |
| 1145 | }, | |
| 1146 | "node_modules/which-runtime": { | |
| 1147 | "version": "1.3.2", | |
| 1148 | "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.3.2.tgz", | |
| 1149 | "integrity": "sha512-5kwCfWml7+b2NO7KrLMhYihjRx0teKkd3yGp1Xk5Vaf2JGdSh+rgVhEALAD9c/59dP+YwJHXoEO7e8QPy7gOkw==", | |
| 1150 | "dev": true, | |
| 1151 | "license": "Apache-2.0" | |
| 1152 | }, | |
| 1153 | "node_modules/ws": { | |
| 1154 | "version": "8.19.0", | |
| 1155 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", | |
| 1156 | "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", | |
| 1157 | "license": "MIT", | |
| 1158 | "engines": { | |
| 1159 | "node": ">=10.0.0" | |
| 1160 | }, | |
| 1161 | "peerDependencies": { | |
| 1162 | "bufferutil": "^4.0.1", | |
| 1163 | "utf-8-validate": ">=5.0.2" | |
| 1164 | }, | |
| 1165 | "peerDependenciesMeta": { | |
| 1166 | "bufferutil": { | |
| 1167 | "optional": true | |
| 1168 | }, | |
| 1169 | "utf-8-validate": { | |
| 1170 | "optional": true | |
| 1171 | } | |
| 1172 | } | |
| 1173 | } | |
| 1174 | } | |
| 1175 | } |
| … | … | @@ -0,0 +1,23 @@ |
| 1 | { | |
| 2 | "name": "bridge-discord", | |
| 3 | "version": "1.0.0", | |
| 4 | "description": "Discord ↔ Keet bidirectional bridge", | |
| 5 | "type": "module", | |
| 6 | "main": "index.js", | |
| 7 | "pear": { | |
| 8 | "name": "bridge-discord", | |
| 9 | "type": "terminal" | |
| 10 | }, | |
| 11 | "scripts": { | |
| 12 | "test": "brittle test/*.test.js" | |
| 13 | }, | |
| 14 | "dependencies": { | |
| 15 | "discord.js": "^14.16.3", | |
| 16 | "bridge-envelope": "file:../bridge-envelope", | |
| 17 | "b4a": "^1.0.0" | |
| 18 | }, | |
| 19 | "devDependencies": { | |
| 20 | "brittle": "^3.0.0" | |
| 21 | }, | |
| 22 | "license": "MIT" | |
| 23 | } |
| … | … | @@ -0,0 +1,212 @@ |
| 1 | import test from 'brittle' | |
| 2 | import { discordToEnvelope, keetToDiscord, envelopeToDiscordContent, discordReactionToEnvelope } from '../lib/adapter.js' | |
| 3 | import { TYPES, PLATFORMS, createEnvelope } from 'bridge-envelope' | |
| 4 | ||
| 5 | // Mock Discord message objects | |
| 6 | function mockMessage (overrides = {}) { | |
| 7 | return { | |
| 8 | author: { tag: 'user#1234', username: 'user', bot: false, id: '123' }, | |
| 9 | channel: { name: 'general', id: '456' }, | |
| 10 | channelId: '456', | |
| 11 | content: 'Hello from Discord!', | |
| 12 | attachments: { first: () => null, [Symbol.iterator]: function * () {} }, | |
| 13 | reference: null, | |
| 14 | ...overrides | |
| 15 | } | |
| 16 | } | |
| 17 | ||
| 18 | test('discordToEnvelope - basic text message', (t) => { | |
| 19 | const msg = mockMessage() | |
| 20 | const env = discordToEnvelope(msg) | |
| 21 | ||
| 22 | t.is(env.type, TYPES.TEXT) | |
| 23 | t.is(env.platform, PLATFORMS.DISCORD) | |
| 24 | t.is(env.author, 'user#1234') | |
| 25 | t.is(env.content, 'Hello from Discord!') | |
| 26 | t.is(env.channel, 'general') | |
| 27 | t.ok(env.id) | |
| 28 | }) | |
| 29 | ||
| 30 | test('discordToEnvelope - skips bot messages', (t) => { | |
| 31 | const msg = mockMessage({ author: { tag: 'bot#0000', bot: true, id: '999' } }) | |
| 32 | const env = discordToEnvelope(msg) | |
| 33 | t.is(env, null) | |
| 34 | }) | |
| 35 | ||
| 36 | test('discordToEnvelope - with attachment', (t) => { | |
| 37 | const msg = mockMessage({ | |
| 38 | attachments: { | |
| 39 | first: () => ({ url: 'https://cdn.discord.com/img.png', contentType: 'image/png' }), | |
| 40 | [Symbol.iterator]: function * () { yield ['1', { url: 'https://cdn.discord.com/img.png' }] } | |
| 41 | } | |
| 42 | }) | |
| 43 | ||
| 44 | const env = discordToEnvelope(msg) | |
| 45 | t.is(env.type, TYPES.MEDIA) | |
| 46 | t.is(env.mediaUrl, 'https://cdn.discord.com/img.png') | |
| 47 | t.is(env.mediaType, 'image/png') | |
| 48 | }) | |
| 49 | ||
| 50 | test('discordToEnvelope - reply message', (t) => { | |
| 51 | const msg = mockMessage({ | |
| 52 | reference: { messageId: 'orig-msg-123' } | |
| 53 | }) | |
| 54 | ||
| 55 | const env = discordToEnvelope(msg) | |
| 56 | t.is(env.type, TYPES.REPLY) | |
| 57 | t.is(env.replyTo, 'orig-msg-123') | |
| 58 | }) | |
| 59 | ||
| 60 | test('discordToEnvelope - reply with attachment', (t) => { | |
| 61 | const msg = mockMessage({ | |
| 62 | reference: { messageId: 'orig-456' }, | |
| 63 | attachments: { | |
| 64 | first: () => ({ url: 'https://cdn.discord.com/file.pdf', contentType: 'application/pdf' }), | |
| 65 | [Symbol.iterator]: function * () {} | |
| 66 | } | |
| 67 | }) | |
| 68 | ||
| 69 | const env = discordToEnvelope(msg) | |
| 70 | t.is(env.type, TYPES.MEDIA) | |
| 71 | t.is(env.replyTo, 'orig-456') | |
| 72 | t.is(env.mediaUrl, 'https://cdn.discord.com/file.pdf') | |
| 73 | }) | |
| 74 | ||
| 75 | test('discordToEnvelope - empty content', (t) => { | |
| 76 | const msg = mockMessage({ content: '' }) | |
| 77 | const env = discordToEnvelope(msg) | |
| 78 | t.is(env.content, '') | |
| 79 | }) | |
| 80 | ||
| 81 | test('discordToEnvelope - unicode content', (t) => { | |
| 82 | const msg = mockMessage({ content: '🎉 Hello 世界!' }) | |
| 83 | const env = discordToEnvelope(msg) | |
| 84 | t.is(env.content, '🎉 Hello 世界!') | |
| 85 | }) | |
| 86 | ||
| 87 | test('keetToDiscord - basic message', (t) => { | |
| 88 | const msg = { | |
| 89 | memberId: 'member-123', | |
| 90 | name: 'Alice', | |
| 91 | text: 'Hello from Keet!', | |
| 92 | timestamp: Date.now() | |
| 93 | } | |
| 94 | ||
| 95 | const result = keetToDiscord(msg, 'bridge-identity') | |
| 96 | t.ok(result) | |
| 97 | t.is(result.name, 'Alice') | |
| 98 | t.ok(result.text.includes('Alice')) | |
| 99 | t.ok(result.text.includes('Keet')) | |
| 100 | t.ok(result.text.includes('Hello from Keet!')) | |
| 101 | }) | |
| 102 | ||
| 103 | test('keetToDiscord - skips own messages', (t) => { | |
| 104 | const msg = { memberId: 'bridge-id', name: 'Bridge', text: 'echo' } | |
| 105 | t.is(keetToDiscord(msg, 'bridge-id'), null) | |
| 106 | }) | |
| 107 | ||
| 108 | test('keetToDiscord - skips system messages', (t) => { | |
| 109 | const msg = { memberId: 'other', name: 'Sys', text: 'joined', system: { type: 'join' } } | |
| 110 | t.is(keetToDiscord(msg, 'bridge-id'), null) | |
| 111 | }) | |
| 112 | ||
| 113 | test('keetToDiscord - skips empty text', (t) => { | |
| 114 | const msg = { memberId: 'other', name: 'User', text: ' ' } | |
| 115 | t.is(keetToDiscord(msg, 'bridge-id'), null) | |
| 116 | }) | |
| 117 | ||
| 118 | test('keetToDiscord - anonymous fallback', (t) => { | |
| 119 | const msg = { memberId: 'other', text: 'hello' } | |
| 120 | const result = keetToDiscord(msg, 'bridge-id') | |
| 121 | t.is(result.name, 'anonymous') | |
| 122 | }) | |
| 123 | ||
| 124 | test('envelopeToDiscordContent - keet text', (t) => { | |
| 125 | const env = createEnvelope({ | |
| 126 | type: TYPES.TEXT, | |
| 127 | platform: PLATFORMS.KEET, | |
| 128 | author: 'Alice', | |
| 129 | content: 'Hello!' | |
| 130 | }) | |
| 131 | const text = envelopeToDiscordContent(env) | |
| 132 | t.ok(text.includes('[keet]')) | |
| 133 | t.ok(text.includes('**Alice**')) | |
| 134 | t.ok(text.includes('Hello!')) | |
| 135 | }) | |
| 136 | ||
| 137 | test('envelopeToDiscordContent - discord message no prefix', (t) => { | |
| 138 | const env = createEnvelope({ | |
| 139 | type: TYPES.TEXT, | |
| 140 | platform: PLATFORMS.DISCORD, | |
| 141 | author: 'bob#1234', | |
| 142 | content: 'native discord' | |
| 143 | }) | |
| 144 | const text = envelopeToDiscordContent(env) | |
| 145 | t.absent(text.includes('[discord]')) | |
| 146 | t.ok(text.includes('bob#1234')) | |
| 147 | }) | |
| 148 | ||
| 149 | test('envelopeToDiscordContent - IRC message', (t) => { | |
| 150 | const env = createEnvelope({ | |
| 151 | type: TYPES.TEXT, | |
| 152 | platform: PLATFORMS.IRC, | |
| 153 | author: 'ircuser', | |
| 154 | content: 'from IRC' | |
| 155 | }) | |
| 156 | const text = envelopeToDiscordContent(env) | |
| 157 | t.ok(text.includes('[irc]')) | |
| 158 | t.ok(text.includes('**ircuser**')) | |
| 159 | }) | |
| 160 | ||
| 161 | test('envelopeToDiscordContent - media', (t) => { | |
| 162 | const env = createEnvelope({ | |
| 163 | type: TYPES.MEDIA, | |
| 164 | platform: PLATFORMS.KEET, | |
| 165 | author: 'Alice', | |
| 166 | content: 'Check this', | |
| 167 | mediaUrl: 'https://example.com/img.png' | |
| 168 | }) | |
| 169 | const text = envelopeToDiscordContent(env) | |
| 170 | t.ok(text.includes('https://example.com/img.png')) | |
| 171 | }) | |
| 172 | ||
| 173 | test('envelopeToDiscordContent - reaction', (t) => { | |
| 174 | const env = createEnvelope({ | |
| 175 | type: TYPES.REACTION, | |
| 176 | platform: PLATFORMS.KEET, | |
| 177 | author: 'Bob', | |
| 178 | content: '👍' | |
| 179 | }) | |
| 180 | const text = envelopeToDiscordContent(env) | |
| 181 | t.ok(text.includes('reacted')) | |
| 182 | t.ok(text.includes('👍')) | |
| 183 | }) | |
| 184 | ||
| 185 | test('envelopeToDiscordContent - system', (t) => { | |
| 186 | const env = createEnvelope({ | |
| 187 | type: TYPES.SYSTEM, | |
| 188 | platform: PLATFORMS.KEET, | |
| 189 | content: 'User joined' | |
| 190 | }) | |
| 191 | const text = envelopeToDiscordContent(env) | |
| 192 | t.ok(text.includes('User joined')) | |
| 193 | }) | |
| 194 | ||
| 195 | test('discordReactionToEnvelope', (t) => { | |
| 196 | const reaction = { | |
| 197 | emoji: { name: '👍', toString: () => '👍' }, | |
| 198 | message: { | |
| 199 | id: 'msg-789', | |
| 200 | channel: { name: 'general' }, | |
| 201 | channelId: '456' | |
| 202 | } | |
| 203 | } | |
| 204 | const user = { tag: 'alice#1234', username: 'alice' } | |
| 205 | ||
| 206 | const env = discordReactionToEnvelope(reaction, user) | |
| 207 | t.is(env.type, TYPES.REACTION) | |
| 208 | t.is(env.platform, PLATFORMS.DISCORD) | |
| 209 | t.is(env.author, 'alice#1234') | |
| 210 | t.is(env.content, '👍') | |
| 211 | t.is(env.replyTo, 'msg-789') | |
| 212 | }) |
| … | … | @@ -0,0 +1,86 @@ |
| 1 | import { EmailBridge } from './lib/bridge.js' | |
| 2 | import { ImapClient, parseEmail } from './lib/imap-client.js' | |
| 3 | import { SmtpClient } from './lib/smtp-client.js' | |
| 4 | import { KeetClient } from './lib/keet-client.js' | |
| 5 | import { Router } from './lib/router.js' | |
| 6 | import { emailToEnvelope, keetToEmail, emailToKeetText, keetToEmailText, extractEmail, extractName } from './lib/adapter.js' | |
| 7 | ||
| 8 | export { | |
| 9 | EmailBridge, | |
| 10 | ImapClient, | |
| 11 | SmtpClient, | |
| 12 | KeetClient, | |
| 13 | Router, | |
| 14 | parseEmail, | |
| 15 | emailToEnvelope, | |
| 16 | keetToEmail, | |
| 17 | emailToKeetText, | |
| 18 | keetToEmailText, | |
| 19 | extractEmail, | |
| 20 | extractName | |
| 21 | } | |
| 22 | ||
| 23 | // CLI entry point | |
| 24 | const args = process.argv.slice(2) | |
| 25 | if (args.includes('--help') || args.includes('-h')) { | |
| 26 | console.log(`bridge-email — Email ↔ Keet bridge | |
| 27 | ||
| 28 | Usage: | |
| 29 | bridge-email --imap-host <host> --smtp-host <host> --user <email> --pass <password> --keet-room <roomId> [options] | |
| 30 | ||
| 31 | Options: | |
| 32 | --imap-host <host> IMAP server hostname | |
| 33 | --imap-port <port> IMAP port (default: 993) | |
| 34 | --smtp-host <host> SMTP server hostname | |
| 35 | --smtp-port <port> SMTP port (default: 465) | |
| 36 | --user <email> Email username/address | |
| 37 | --pass <password> Email password (or EMAIL_PASS env) | |
| 38 | --forward-to <email> Address to relay Keet messages to | |
| 39 | --keet-room <roomId> Keet room ID to bridge | |
| 40 | --keet-port <port> Keet daemon port (default: 3117) | |
| 41 | --poll-interval <ms> IMAP poll interval in ms (default: 30000) | |
| 42 | --help Show this help`) | |
| 43 | process.exit(0) | |
| 44 | } | |
| 45 | ||
| 46 | function getArg (flag) { | |
| 47 | const idx = args.indexOf(flag) | |
| 48 | return idx !== -1 ? args[idx + 1] : null | |
| 49 | } | |
| 50 | ||
| 51 | const imapHost = getArg('--imap-host') | |
| 52 | const smtpHost = getArg('--smtp-host') | |
| 53 | const user = getArg('--user') | |
| 54 | const pass = getArg('--pass') || process.env.EMAIL_PASS | |
| 55 | const roomId = getArg('--keet-room') | |
| 56 | const keetPort = parseInt(getArg('--keet-port') || '3117', 10) | |
| 57 | const forwardTo = getArg('--forward-to') | |
| 58 | const pollInterval = parseInt(getArg('--poll-interval') || '30000', 10) | |
| 59 | const imapPort = parseInt(getArg('--imap-port') || '993', 10) | |
| 60 | const smtpPort = parseInt(getArg('--smtp-port') || '465', 10) | |
| 61 | ||
| 62 | if (imapHost && smtpHost && user && pass && roomId) { | |
| 63 | const bridge = new EmailBridge({ | |
| 64 | imapHost, | |
| 65 | imapPort, | |
| 66 | smtpHost, | |
| 67 | smtpPort, | |
| 68 | user, | |
| 69 | pass, | |
| 70 | roomId, | |
| 71 | forwardTo, | |
| 72 | keetPort, | |
| 73 | pollInterval, | |
| 74 | onLog: (msg) => console.log(`[email-bridge] ${msg}`) | |
| 75 | }) | |
| 76 | ||
| 77 | bridge.start().catch(err => { | |
| 78 | console.error('Bridge failed to start:', err.message) | |
| 79 | process.exit(1) | |
| 80 | }) | |
| 81 | ||
| 82 | process.on('SIGINT', () => { | |
| 83 | bridge.stop() | |
| 84 | process.exit(0) | |
| 85 | }) | |
| 86 | } |
| … | … | @@ -0,0 +1,112 @@ |
| 1 | import { createEnvelope, TYPES, PLATFORMS } from 'bridge-envelope' | |
| 2 | ||
| 3 | // Extract email address from "Name <addr>" format | |
| 4 | function extractEmail (from) { | |
| 5 | const match = from.match(/<([^>]+)>/) | |
| 6 | return match ? match[1] : from.trim() | |
| 7 | } | |
| 8 | ||
| 9 | // Extract display name from "Name <addr>" format | |
| 10 | function extractName (from) { | |
| 11 | const match = from.match(/^"?([^"<]+)"?\s*</) | |
| 12 | return match ? match[1].trim() : extractEmail(from) | |
| 13 | } | |
| 14 | ||
| 15 | // Convert parsed email to bridge envelope | |
| 16 | function emailToEnvelope (email) { | |
| 17 | if (!email) return null | |
| 18 | ||
| 19 | const from = email.from || '' | |
| 20 | const author = extractName(from) | |
| 21 | const subject = email.subject || '' | |
| 22 | const body = email.body || '' | |
| 23 | ||
| 24 | if (!body.trim() && !subject.trim()) return null | |
| 25 | ||
| 26 | // Skip bridge-generated emails | |
| 27 | if (email.body?.includes('[email]') || email.body?.includes('[Email]')) return null | |
| 28 | ||
| 29 | const content = subject ? `[${subject}] ${body}` : body | |
| 30 | ||
| 31 | const envelope = createEnvelope({ | |
| 32 | type: TYPES.TEXT, | |
| 33 | platform: PLATFORMS.EMAIL, | |
| 34 | author, | |
| 35 | content, | |
| 36 | channel: extractEmail(from) | |
| 37 | }) | |
| 38 | ||
| 39 | // Preserve email metadata for threading | |
| 40 | envelope._messageId = email.messageId || '' | |
| 41 | envelope._subject = subject | |
| 42 | envelope._from = from | |
| 43 | envelope._to = email.to || '' | |
| 44 | envelope._date = email.date || '' | |
| 45 | ||
| 46 | return envelope | |
| 47 | } | |
| 48 | ||
| 49 | // Convert Keet message to email-ready format | |
| 50 | function keetToEmail (keetMsg, bridgeIdentity) { | |
| 51 | if (!keetMsg) return null | |
| 52 | ||
| 53 | // Skip own messages | |
| 54 | if (keetMsg.memberId === bridgeIdentity) return null | |
| 55 | ||
| 56 | // Skip system messages | |
| 57 | if (keetMsg.type === 'system') return null | |
| 58 | ||
| 59 | const text = keetMsg.text || '' | |
| 60 | if (!text.trim()) return null | |
| 61 | ||
| 62 | // Skip already bridged from email | |
| 63 | if (text.includes('[email]') || text.includes('[Email]')) return null | |
| 64 | ||
| 65 | const name = keetMsg.name || keetMsg.username || 'someone' | |
| 66 | ||
| 67 | return { | |
| 68 | author: name, | |
| 69 | text, | |
| 70 | subject: `[Keet] Message from ${name}`, | |
| 71 | body: `${name} wrote:\n\n${text}\n\n---\nRelayed via bridge-email from Keet P2P chat` | |
| 72 | } | |
| 73 | } | |
| 74 | ||
| 75 | // Format email envelope for display in Keet | |
| 76 | function emailToKeetText (envelope) { | |
| 77 | if (!envelope) return '' | |
| 78 | ||
| 79 | let text = `[email] ${envelope.author}` | |
| 80 | ||
| 81 | if (envelope._subject) { | |
| 82 | text += ` — ${envelope._subject}` | |
| 83 | } | |
| 84 | ||
| 85 | // Truncate long emails for chat display | |
| 86 | const body = envelope.content.replace(`[${envelope._subject}] `, '') | |
| 87 | const maxLen = 500 | |
| 88 | if (body.length > maxLen) { | |
| 89 | text += `: ${body.slice(0, maxLen)}...` | |
| 90 | } else { | |
| 91 | text += `: ${body}` | |
| 92 | } | |
| 93 | ||
| 94 | return text | |
| 95 | } | |
| 96 | ||
| 97 | // Format Keet message as email subject/body | |
| 98 | function keetToEmailText (author, text) { | |
| 99 | return { | |
| 100 | subject: `[Keet] Message from ${author}`, | |
| 101 | body: `${author} wrote:\n\n${text}\n\n---\nRelayed via bridge-email from Keet P2P chat` | |
| 102 | } | |
| 103 | } | |
| 104 | ||
| 105 | export { | |
| 106 | emailToEnvelope, | |
| 107 | keetToEmail, | |
| 108 | emailToKeetText, | |
| 109 | keetToEmailText, | |
| 110 | extractEmail, | |
| 111 | extractName | |
| 112 | } |
| … | … | @@ -0,0 +1,142 @@ |
| 1 | import { ImapClient } from './imap-client.js' | |
| 2 | import { SmtpClient } from './smtp-client.js' | |
| 3 | import { KeetClient } from './keet-client.js' | |
| 4 | import { Router } from './router.js' | |
| 5 | import { emailToEnvelope, keetToEmail, emailToKeetText } from './adapter.js' | |
| 6 | ||
| 7 | class EmailBridge { | |
| 8 | constructor (opts = {}) { | |
| 9 | this.imap = new ImapClient({ | |
| 10 | host: opts.imapHost, | |
| 11 | port: opts.imapPort, | |
| 12 | user: opts.user, | |
| 13 | pass: opts.pass | |
| 14 | }) | |
| 15 | this.smtp = new SmtpClient({ | |
| 16 | host: opts.smtpHost, | |
| 17 | port: opts.smtpPort, | |
| 18 | user: opts.user, | |
| 19 | pass: opts.pass, | |
| 20 | from: opts.from || opts.user | |
| 21 | }) | |
| 22 | this.keet = new KeetClient({ | |
| 23 | host: opts.keetHost, | |
| 24 | port: opts.keetPort | |
| 25 | }) | |
| 26 | this.roomId = opts.roomId | |
| 27 | this.forwardTo = opts.forwardTo // email address to relay Keet messages to | |
| 28 | this.pollInterval = opts.pollInterval || 30000 // 30s | |
| 29 | this.bridgeIdentity = opts.bridgeIdentity || null | |
| 30 | this.onLog = opts.onLog || (() => {}) | |
| 31 | ||
| 32 | this._polling = false | |
| 33 | this._pollTimer = null | |
| 34 | ||
| 35 | this.router = new Router({ | |
| 36 | onKeetMessage: (author, text) => { | |
| 37 | this.keet.send(this.roomId, text).catch(err => { | |
| 38 | this.onLog(`Keet send error: ${err.message}`) | |
| 39 | }) | |
| 40 | }, | |
| 41 | onExternalMessage: (author, text) => { | |
| 42 | if (!this.forwardTo) return | |
| 43 | this._sendEmail(author, text).catch(err => { | |
| 44 | this.onLog(`Email send error: ${err.message}`) | |
| 45 | }) | |
| 46 | } | |
| 47 | }) | |
| 48 | } | |
| 49 | ||
| 50 | async _sendEmail (author, text) { | |
| 51 | const smtp = new SmtpClient({ | |
| 52 | host: this.smtp.host, | |
| 53 | port: this.smtp.port, | |
| 54 | user: this.smtp.user, | |
| 55 | pass: this.smtp.pass, | |
| 56 | from: this.smtp.from | |
| 57 | }) | |
| 58 | ||
| 59 | await smtp.connect() | |
| 60 | await smtp.ehlo() | |
| 61 | await smtp.auth() | |
| 62 | await smtp.sendMail( | |
| 63 | this.forwardTo, | |
| 64 | `[Keet] Message from ${author}`, | |
| 65 | `${author} wrote:\n\n${text}\n\n---\nRelayed via bridge-email from Keet P2P chat` | |
| 66 | ) | |
| 67 | await smtp.quit() | |
| 68 | } | |
| 69 | ||
| 70 | async start () { | |
| 71 | // Get bridge identity | |
| 72 | if (!this.bridgeIdentity) { | |
| 73 | try { | |
| 74 | const identity = await this.keet.identity() | |
| 75 | this.bridgeIdentity = identity.memberId || identity.id || '' | |
| 76 | this.onLog(`Bridge identity: ${this.bridgeIdentity}`) | |
| 77 | } catch (err) { | |
| 78 | this.onLog(`Warning: Could not get identity: ${err.message}`) | |
| 79 | } | |
| 80 | } | |
| 81 | ||
| 82 | // Connect IMAP | |
| 83 | await this.imap.connect() | |
| 84 | await this.imap.login() | |
| 85 | await this.imap.select('INBOX') | |
| 86 | this.onLog(`IMAP: connected to ${this.imap.host}`) | |
| 87 | ||
| 88 | // Start polling IMAP for new messages | |
| 89 | this._polling = true | |
| 90 | this._poll() | |
| 91 | ||
| 92 | // Watch Keet → Email | |
| 93 | this.keet.watch(this.roomId, (msg) => { | |
| 94 | const result = keetToEmail(msg, this.bridgeIdentity) | |
| 95 | if (!result) return | |
| 96 | ||
| 97 | this.router.routeToExternal(result.author, result.text) | |
| 98 | }) | |
| 99 | ||
| 100 | this.onLog(`Bridge started: Email (${this.imap.host}) ↔ Keet`) | |
| 101 | } | |
| 102 | ||
| 103 | async _poll () { | |
| 104 | if (!this._polling) return | |
| 105 | ||
| 106 | try { | |
| 107 | const unseen = await this.imap.searchUnseen() | |
| 108 | ||
| 109 | for (const id of unseen) { | |
| 110 | try { | |
| 111 | const email = await this.imap.fetch(id) | |
| 112 | const envelope = emailToEnvelope(email) | |
| 113 | ||
| 114 | if (envelope) { | |
| 115 | const text = emailToKeetText(envelope) | |
| 116 | this.router.routeToKeet(envelope.author, text) | |
| 117 | await this.imap.markSeen(id) | |
| 118 | } | |
| 119 | } catch (err) { | |
| 120 | this.onLog(`Fetch error for message ${id}: ${err.message}`) | |
| 121 | } | |
| 122 | } | |
| 123 | } catch (err) { | |
| 124 | this.onLog(`Poll error: ${err.message}`) | |
| 125 | } | |
| 126 | ||
| 127 | this._pollTimer = setTimeout(() => this._poll(), this.pollInterval) | |
| 128 | } | |
| 129 | ||
| 130 | stop () { | |
| 131 | this._polling = false | |
| 132 | if (this._pollTimer) { | |
| 133 | clearTimeout(this._pollTimer) | |
| 134 | this._pollTimer = null | |
| 135 | } | |
| 136 | this.keet.stopWatch() | |
| 137 | this.imap.close() | |
| 138 | this.onLog('Bridge stopped') | |
| 139 | } | |
| 140 | } | |
| 141 | ||
| 142 | export { EmailBridge } |
| … | … | @@ -0,0 +1,223 @@ |
| 1 | import tls from 'tls' | |
| 2 | ||
| 3 | class ImapClient { | |
| 4 | constructor (opts = {}) { | |
| 5 | this.host = opts.host | |
| 6 | this.port = opts.port || 993 | |
| 7 | this.user = opts.user | |
| 8 | this.pass = opts.pass | |
| 9 | this._socket = null | |
| 10 | this._tag = 0 | |
| 11 | this._buffer = '' | |
| 12 | this._handlers = new Map() | |
| 13 | this._connected = false | |
| 14 | } | |
| 15 | ||
| 16 | _nextTag () { | |
| 17 | return `A${++this._tag}` | |
| 18 | } | |
| 19 | ||
| 20 | connect () { | |
| 21 | return new Promise((resolve, reject) => { | |
| 22 | this._socket = tls.connect({ | |
| 23 | host: this.host, | |
| 24 | port: this.port, | |
| 25 | rejectUnauthorized: true | |
| 26 | }) | |
| 27 | ||
| 28 | this._socket.setEncoding('utf8') | |
| 29 | ||
| 30 | let greeting = false | |
| 31 | this._socket.on('data', (data) => { | |
| 32 | this._buffer += data | |
| 33 | ||
| 34 | if (!greeting && this._buffer.includes('\r\n')) { | |
| 35 | greeting = true | |
| 36 | this._buffer = '' | |
| 37 | this._connected = true | |
| 38 | resolve() | |
| 39 | } | |
| 40 | ||
| 41 | this._processBuffer() | |
| 42 | }) | |
| 43 | ||
| 44 | this._socket.on('error', (err) => { | |
| 45 | if (!greeting) reject(err) | |
| 46 | }) | |
| 47 | ||
| 48 | this._socket.setTimeout(15000, () => { | |
| 49 | if (!greeting) reject(new Error('IMAP connection timeout')) | |
| 50 | }) | |
| 51 | }) | |
| 52 | } | |
| 53 | ||
| 54 | _processBuffer () { | |
| 55 | const lines = this._buffer.split('\r\n') | |
| 56 | this._buffer = lines.pop() // keep incomplete | |
| 57 | ||
| 58 | for (const line of lines) { | |
| 59 | if (!line) continue | |
| 60 | ||
| 61 | // Check if it's a tagged response | |
| 62 | for (const [tag, handler] of this._handlers) { | |
| 63 | if (line.startsWith(tag + ' ')) { | |
| 64 | const rest = line.slice(tag.length + 1) | |
| 65 | handler.lines.push(line) | |
| 66 | if (rest.startsWith('OK')) { | |
| 67 | handler.resolve(handler.lines) | |
| 68 | } else { | |
| 69 | handler.reject(new Error(`IMAP error: ${line}`)) | |
| 70 | } | |
| 71 | this._handlers.delete(tag) | |
| 72 | break | |
| 73 | } | |
| 74 | } | |
| 75 | ||
| 76 | // Untagged response — add to current handler | |
| 77 | if (line.startsWith('* ')) { | |
| 78 | for (const handler of this._handlers.values()) { | |
| 79 | handler.lines.push(line) | |
| 80 | } | |
| 81 | } | |
| 82 | } | |
| 83 | } | |
| 84 | ||
| 85 | _command (cmd) { | |
| 86 | return new Promise((resolve, reject) => { | |
| 87 | const tag = this._nextTag() | |
| 88 | this._handlers.set(tag, { resolve, reject, lines: [] }) | |
| 89 | this._socket.write(`${tag} ${cmd}\r\n`) | |
| 90 | ||
| 91 | // Timeout | |
| 92 | setTimeout(() => { | |
| 93 | if (this._handlers.has(tag)) { | |
| 94 | this._handlers.delete(tag) | |
| 95 | reject(new Error(`IMAP timeout: ${cmd}`)) | |
| 96 | } | |
| 97 | }, 15000) | |
| 98 | }) | |
| 99 | } | |
| 100 | ||
| 101 | async login () { | |
| 102 | // Escape password with quotes | |
| 103 | const pass = this.pass.replace(/\\/g, '\\\\').replace(/"/g, '\\"') | |
| 104 | return this._command(`LOGIN "${this.user}" "${pass}"`) | |
| 105 | } | |
| 106 | ||
| 107 | async select (mailbox = 'INBOX') { | |
| 108 | return this._command(`SELECT "${mailbox}"`) | |
| 109 | } | |
| 110 | ||
| 111 | async searchUnseen () { | |
| 112 | const lines = await this._command('SEARCH UNSEEN') | |
| 113 | const searchLine = lines.find(l => l.startsWith('* SEARCH')) | |
| 114 | if (!searchLine) return [] | |
| 115 | ||
| 116 | const ids = searchLine.slice(9).trim().split(/\s+/).filter(Boolean) | |
| 117 | return ids.map(Number).filter(n => !isNaN(n)) | |
| 118 | } | |
| 119 | ||
| 120 | async fetch (id) { | |
| 121 | const lines = await this._command(`FETCH ${id} (RFC822)`) | |
| 122 | // Extract the raw email body from FETCH response | |
| 123 | const content = lines | |
| 124 | .filter(l => !l.startsWith(`A${this._tag}`) && !l.match(/^\* \d+ FETCH/)) | |
| 125 | .join('\r\n') | |
| 126 | ||
| 127 | return parseEmail(content) | |
| 128 | } | |
| 129 | ||
| 130 | async markSeen (id) { | |
| 131 | return this._command(`STORE ${id} +FLAGS (\\Seen)`) | |
| 132 | } | |
| 133 | ||
| 134 | async logout () { | |
| 135 | try { | |
| 136 | await this._command('LOGOUT') | |
| 137 | } catch {} | |
| 138 | this.close() | |
| 139 | } | |
| 140 | ||
| 141 | close () { | |
| 142 | this._connected = false | |
| 143 | if (this._socket) { | |
| 144 | this._socket.destroy() | |
| 145 | this._socket = null | |
| 146 | } | |
| 147 | this._handlers.clear() | |
| 148 | } | |
| 149 | } | |
| 150 | ||
| 151 | // Minimal email parser — extracts headers and text body | |
| 152 | function parseEmail (raw) { | |
| 153 | const headerEnd = raw.indexOf('\r\n\r\n') | |
| 154 | if (headerEnd === -1) { | |
| 155 | return { from: '', to: '', subject: '', body: raw, date: '', messageId: '' } | |
| 156 | } | |
| 157 | ||
| 158 | const headerSection = raw.slice(0, headerEnd) | |
| 159 | const body = raw.slice(headerEnd + 4) | |
| 160 | ||
| 161 | function getHeader (name) { | |
| 162 | const regex = new RegExp(`^${name}:\\s*(.+?)$`, 'mi') | |
| 163 | const match = headerSection.match(regex) | |
| 164 | return match ? match[1].trim() : '' | |
| 165 | } | |
| 166 | ||
| 167 | // Extract plain text from multipart if needed | |
| 168 | let textBody = body | |
| 169 | const contentType = getHeader('Content-Type') | |
| 170 | ||
| 171 | if (contentType.includes('multipart')) { | |
| 172 | const boundaryMatch = contentType.match(/boundary="?([^";\s]+)"?/) | |
| 173 | if (boundaryMatch) { | |
| 174 | const boundary = boundaryMatch[1] | |
| 175 | const parts = body.split('--' + boundary) | |
| 176 | ||
| 177 | for (const part of parts) { | |
| 178 | if (part.includes('text/plain')) { | |
| 179 | const partBody = part.split('\r\n\r\n').slice(1).join('\r\n\r\n') | |
| 180 | textBody = partBody.replace(/--\s*$/, '').trim() | |
| 181 | break | |
| 182 | } | |
| 183 | } | |
| 184 | } | |
| 185 | } | |
| 186 | ||
| 187 | // Decode quoted-printable if needed | |
| 188 | if (raw.includes('Content-Transfer-Encoding: quoted-printable')) { | |
| 189 | textBody = textBody.replace(/=\r?\n/g, '') // soft line breaks | |
| 190 | // Collect bytes, then decode as UTF-8 | |
| 191 | const bytes = [] | |
| 192 | for (let i = 0; i < textBody.length; i++) { | |
| 193 | if (textBody[i] === '=' && i + 2 < textBody.length) { | |
| 194 | const hex = textBody.slice(i + 1, i + 3) | |
| 195 | if (/^[0-9A-Fa-f]{2}$/.test(hex)) { | |
| 196 | bytes.push(parseInt(hex, 16)) | |
| 197 | i += 2 | |
| 198 | continue | |
| 199 | } | |
| 200 | } | |
| 201 | bytes.push(textBody.charCodeAt(i)) | |
| 202 | } | |
| 203 | textBody = Buffer.from(bytes).toString('utf8') | |
| 204 | } | |
| 205 | ||
| 206 | // Decode base64 if needed | |
| 207 | if (raw.includes('Content-Transfer-Encoding: base64')) { | |
| 208 | try { | |
| 209 | textBody = Buffer.from(textBody.replace(/\s/g, ''), 'base64').toString('utf8') | |
| 210 | } catch {} | |
| 211 | } | |
| 212 | ||
| 213 | return { | |
| 214 | from: getHeader('From'), | |
| 215 | to: getHeader('To'), | |
| 216 | subject: getHeader('Subject'), | |
| 217 | body: textBody.trim(), | |
| 218 | date: getHeader('Date'), | |
| 219 | messageId: getHeader('Message-ID') || getHeader('Message-Id') | |
| 220 | } | |
| 221 | } | |
| 222 | ||
| 223 | export { ImapClient, parseEmail } |
| … | … | @@ -0,0 +1,156 @@ |
| 1 | import http from 'http' | |
| 2 | import net from 'net' | |
| 3 | ||
| 4 | class KeetClient { | |
| 5 | constructor (opts = {}) { | |
| 6 | this.host = opts.host || 'localhost' | |
| 7 | this.port = opts.port || 3117 | |
| 8 | this._watching = false | |
| 9 | this._watchAbort = null | |
| 10 | } | |
| 11 | ||
| 12 | _rawRequest (method, path, body) { | |
| 13 | return new Promise((resolve, reject) => { | |
| 14 | const socket = net.connect(this.port, this.host) | |
| 15 | let payload = '' | |
| 16 | ||
| 17 | if (body) { | |
| 18 | payload = JSON.stringify(body) | |
| 19 | } | |
| 20 | ||
| 21 | socket.on('connect', () => { | |
| 22 | let req = `${method} ${path} HTTP/1.1\r\n` | |
| 23 | req += `Host: ${this.host}:${this.port}\r\n` | |
| 24 | if (payload) { | |
| 25 | req += `Content-Type: application/json\r\n` | |
| 26 | req += `Content-Length: ${Buffer.byteLength(payload)}\r\n` | |
| 27 | } | |
| 28 | req += `Connection: close\r\n` | |
| 29 | req += `\r\n` | |
| 30 | if (payload) req += payload | |
| 31 | socket.write(req) | |
| 32 | }) | |
| 33 | ||
| 34 | let data = '' | |
| 35 | socket.on('data', (chunk) => { data += chunk.toString() }) | |
| 36 | ||
| 37 | socket.on('end', () => { | |
| 38 | const idx = data.indexOf('\r\n\r\n') | |
| 39 | if (idx === -1) return reject(new Error('No HTTP body')) | |
| 40 | const jsonBody = data.slice(idx + 4) | |
| 41 | ||
| 42 | try { | |
| 43 | const parsed = JSON.parse(jsonBody) | |
| 44 | if (!parsed.ok) reject(new Error(parsed.error || 'RPC error')) | |
| 45 | else resolve(parsed.data) | |
| 46 | } catch { | |
| 47 | reject(new Error('Invalid JSON: ' + jsonBody.slice(0, 200))) | |
| 48 | } | |
| 49 | }) | |
| 50 | ||
| 51 | socket.on('error', reject) | |
| 52 | socket.setTimeout(10000, () => { | |
| 53 | socket.destroy(new Error('Request timeout')) | |
| 54 | }) | |
| 55 | }) | |
| 56 | } | |
| 57 | ||
| 58 | rpc (method, params = {}) { | |
| 59 | return this._rawRequest('POST', '/rpc', { method, params }) | |
| 60 | } | |
| 61 | ||
| 62 | async send (roomId, text) { | |
| 63 | return this.rpc('send', { roomId, text }) | |
| 64 | } | |
| 65 | ||
| 66 | async messages (roomId, limit = 20) { | |
| 67 | return this._rawRequest('GET', `/rpc/messages/${roomId}?limit=${limit}`) | |
| 68 | } | |
| 69 | ||
| 70 | async rooms () { | |
| 71 | return this._rawRequest('GET', '/rpc/rooms') | |
| 72 | } | |
| 73 | ||
| 74 | async identity () { | |
| 75 | return this._rawRequest('GET', '/rpc/identity') | |
| 76 | } | |
| 77 | ||
| 78 | async status () { | |
| 79 | return this._rawRequest('GET', '/rpc/status') | |
| 80 | } | |
| 81 | ||
| 82 | watch (roomId, onMessage) { | |
| 83 | if (this._watching) return | |
| 84 | this._watching = true | |
| 85 | this._doWatch(roomId, onMessage) | |
| 86 | } | |
| 87 | ||
| 88 | _doWatch (roomId, onMessage) { | |
| 89 | if (!this._watching) return | |
| 90 | ||
| 91 | const req = http.request({ | |
| 92 | hostname: this.host, | |
| 93 | port: this.port, | |
| 94 | path: `/rpc/watch/${roomId}`, | |
| 95 | method: 'GET', | |
| 96 | headers: { Accept: 'text/event-stream' } | |
| 97 | }, (res) => { | |
| 98 | let buffer = '' | |
| 99 | ||
| 100 | res.on('data', (chunk) => { | |
| 101 | buffer += chunk.toString() | |
| 102 | const events = buffer.split('\n\n') | |
| 103 | buffer = events.pop() | |
| 104 | ||
| 105 | for (const event of events) { | |
| 106 | if (!event.trim()) continue | |
| 107 | const lines = event.split('\n') | |
| 108 | let type = 'message' | |
| 109 | let data = null | |
| 110 | ||
| 111 | for (const line of lines) { | |
| 112 | if (line.startsWith('event: ')) type = line.slice(7) | |
| 113 | if (line.startsWith('data: ')) { | |
| 114 | try { data = JSON.parse(line.slice(6)) } catch {} | |
| 115 | } | |
| 116 | } | |
| 117 | ||
| 118 | if (type === 'message' && data) { | |
| 119 | onMessage(data) | |
| 120 | } | |
| 121 | } | |
| 122 | }) | |
| 123 | ||
| 124 | res.on('end', () => { | |
| 125 | if (this._watching) { | |
| 126 | setTimeout(() => this._doWatch(roomId, onMessage), 2000) | |
| 127 | } | |
| 128 | }) | |
| 129 | ||
| 130 | res.on('error', () => { | |
| 131 | if (this._watching) { | |
| 132 | setTimeout(() => this._doWatch(roomId, onMessage), 5000) | |
| 133 | } | |
| 134 | }) | |
| 135 | }) | |
| 136 | ||
| 137 | req.on('error', () => { | |
| 138 | if (this._watching) { | |
| 139 | setTimeout(() => this._doWatch(roomId, onMessage), 5000) | |
| 140 | } | |
| 141 | }) | |
| 142 | ||
| 143 | this._watchAbort = req | |
| 144 | req.end() | |
| 145 | } | |
| 146 | ||
| 147 | stopWatch () { | |
| 148 | this._watching = false | |
| 149 | if (this._watchAbort) { | |
| 150 | this._watchAbort.destroy() | |
| 151 | this._watchAbort = null | |
| 152 | } | |
| 153 | } | |
| 154 | } | |
| 155 | ||
| 156 | export { KeetClient } |
| … | … | @@ -0,0 +1,67 @@ |
| 1 | import { createHash } from 'crypto' | |
| 2 | ||
| 3 | class Router { | |
| 4 | constructor (opts = {}) { | |
| 5 | this._seen = new Map() | |
| 6 | this._maxSeen = opts.maxSeen || 10000 | |
| 7 | this._ttl = opts.ttl || 60000 | |
| 8 | this._onExternalMessage = opts.onExternalMessage || null | |
| 9 | this._onKeetMessage = opts.onKeetMessage || null | |
| 10 | } | |
| 11 | ||
| 12 | _hash (text, author) { | |
| 13 | return createHash('sha256') | |
| 14 | .update(author || '') | |
| 15 | .update(text || '') | |
| 16 | .digest('hex') | |
| 17 | .slice(0, 16) | |
| 18 | } | |
| 19 | ||
| 20 | _isDuplicate (hash) { | |
| 21 | const now = Date.now() | |
| 22 | ||
| 23 | if (this._seen.size > this._maxSeen) { | |
| 24 | for (const [k, ts] of this._seen) { | |
| 25 | if (now - ts > this._ttl) this._seen.delete(k) | |
| 26 | } | |
| 27 | } | |
| 28 | ||
| 29 | if (this._seen.has(hash)) { | |
| 30 | const ts = this._seen.get(hash) | |
| 31 | if (now - ts < this._ttl) return true | |
| 32 | } | |
| 33 | ||
| 34 | this._seen.set(hash, now) | |
| 35 | return false | |
| 36 | } | |
| 37 | ||
| 38 | routeToKeet (author, text) { | |
| 39 | const hash = this._hash(text, author) | |
| 40 | if (this._isDuplicate(hash)) return false | |
| 41 | ||
| 42 | if (this._onKeetMessage) { | |
| 43 | this._onKeetMessage(author, text) | |
| 44 | } | |
| 45 | return true | |
| 46 | } | |
| 47 | ||
| 48 | routeToExternal (name, text) { | |
| 49 | const hash = this._hash(text, name) | |
| 50 | if (this._isDuplicate(hash)) return false | |
| 51 | ||
| 52 | if (this._onExternalMessage) { | |
| 53 | this._onExternalMessage(name, text) | |
| 54 | } | |
| 55 | return true | |
| 56 | } | |
| 57 | ||
| 58 | get seenCount () { | |
| 59 | return this._seen.size | |
| 60 | } | |
| 61 | ||
| 62 | clearSeen () { | |
| 63 | this._seen.clear() | |
| 64 | } | |
| 65 | } | |
| 66 | ||
| 67 | export { Router } |
| … | … | @@ -0,0 +1,111 @@ |
| 1 | import tls from 'tls' | |
| 2 | import net from 'net' | |
| 3 | ||
| 4 | class SmtpClient { | |
| 5 | constructor (opts = {}) { | |
| 6 | this.host = opts.host | |
| 7 | this.port = opts.port || 465 | |
| 8 | this.user = opts.user | |
| 9 | this.pass = opts.pass | |
| 10 | this.from = opts.from || opts.user | |
| 11 | this.secure = opts.secure !== false // TLS by default | |
| 12 | this._socket = null | |
| 13 | } | |
| 14 | ||
| 15 | connect () { | |
| 16 | return new Promise((resolve, reject) => { | |
| 17 | const onConnect = () => { | |
| 18 | this._readLine().then(() => resolve()).catch(reject) | |
| 19 | } | |
| 20 | ||
| 21 | if (this.secure) { | |
| 22 | this._socket = tls.connect({ | |
| 23 | host: this.host, | |
| 24 | port: this.port, | |
| 25 | rejectUnauthorized: true | |
| 26 | }, onConnect) | |
| 27 | } else { | |
| 28 | this._socket = net.connect(this.port, this.host, onConnect) | |
| 29 | } | |
| 30 | ||
| 31 | this._socket.setEncoding('utf8') | |
| 32 | this._socket.on('error', reject) | |
| 33 | this._socket.setTimeout(15000, () => reject(new Error('SMTP connection timeout'))) | |
| 34 | }) | |
| 35 | } | |
| 36 | ||
| 37 | _readLine () { | |
| 38 | return new Promise((resolve, reject) => { | |
| 39 | let buf = '' | |
| 40 | const onData = (chunk) => { | |
| 41 | buf += chunk | |
| 42 | if (buf.includes('\r\n')) { | |
| 43 | this._socket.removeListener('data', onData) | |
| 44 | const code = parseInt(buf.slice(0, 3), 10) | |
| 45 | if (code >= 400) reject(new Error(`SMTP ${code}: ${buf.trim()}`)) | |
| 46 | else resolve(buf.trim()) | |
| 47 | } | |
| 48 | } | |
| 49 | this._socket.on('data', onData) | |
| 50 | ||
| 51 | setTimeout(() => { | |
| 52 | this._socket.removeListener('data', onData) | |
| 53 | reject(new Error('SMTP read timeout')) | |
| 54 | }, 10000) | |
| 55 | }) | |
| 56 | } | |
| 57 | ||
| 58 | async _send (cmd) { | |
| 59 | this._socket.write(cmd + '\r\n') | |
| 60 | return this._readLine() | |
| 61 | } | |
| 62 | ||
| 63 | async ehlo () { | |
| 64 | return this._send('EHLO bridge-email') | |
| 65 | } | |
| 66 | ||
| 67 | async auth () { | |
| 68 | await this._send('AUTH LOGIN') | |
| 69 | await this._send(Buffer.from(this.user).toString('base64')) | |
| 70 | return this._send(Buffer.from(this.pass).toString('base64')) | |
| 71 | } | |
| 72 | ||
| 73 | async sendMail (to, subject, body, opts = {}) { | |
| 74 | await this._send(`MAIL FROM:<${this.from}>`) | |
| 75 | await this._send(`RCPT TO:<${to}>`) | |
| 76 | await this._send('DATA') | |
| 77 | ||
| 78 | const inReplyTo = opts.inReplyTo || '' | |
| 79 | const references = opts.references || '' | |
| 80 | ||
| 81 | let headers = `From: ${this.from}\r\n` | |
| 82 | headers += `To: ${to}\r\n` | |
| 83 | headers += `Subject: ${subject}\r\n` | |
| 84 | headers += `Date: ${new Date().toUTCString()}\r\n` | |
| 85 | headers += `Content-Type: text/plain; charset=utf-8\r\n` | |
| 86 | headers += `Content-Transfer-Encoding: 8bit\r\n` | |
| 87 | headers += `X-Bridge: bridge-email\r\n` | |
| 88 | ||
| 89 | if (inReplyTo) headers += `In-Reply-To: ${inReplyTo}\r\n` | |
| 90 | if (references) headers += `References: ${references}\r\n` | |
| 91 | ||
| 92 | const message = headers + '\r\n' + body + '\r\n.' | |
| 93 | return this._send(message) | |
| 94 | } | |
| 95 | ||
| 96 | async quit () { | |
| 97 | try { | |
| 98 | await this._send('QUIT') | |
| 99 | } catch {} | |
| 100 | this.close() | |
| 101 | } | |
| 102 | ||
| 103 | close () { | |
| 104 | if (this._socket) { | |
| 105 | this._socket.destroy() | |
| 106 | this._socket = null | |
| 107 | } | |
| 108 | } | |
| 109 | } | |
| 110 | ||
| 111 | export { SmtpClient } |
| … | … | @@ -0,0 +1,875 @@ |
| 1 | { | |
| 2 | "name": "bridge-email", | |
| 3 | "version": "1.0.0", | |
| 4 | "lockfileVersion": 3, | |
| 5 | "requires": true, | |
| 6 | "packages": { | |
| 7 | "": { | |
| 8 | "name": "bridge-email", | |
| 9 | "version": "1.0.0", | |
| 10 | "license": "MIT", | |
| 11 | "dependencies": { | |
| 12 | "b4a": "^1.0.0", | |
| 13 | "bridge-envelope": "file:../bridge-envelope" | |
| 14 | }, | |
| 15 | "devDependencies": { | |
| 16 | "brittle": "^3.0.0" | |
| 17 | } | |
| 18 | }, | |
| 19 | "../bridge-envelope": { | |
| 20 | "version": "1.0.0", | |
| 21 | "license": "MIT", | |
| 22 | "dependencies": { | |
| 23 | "b4a": "^1.0.0", | |
| 24 | "compact-encoding": "^2.0.0" | |
| 25 | }, | |
| 26 | "devDependencies": { | |
| 27 | "brittle": "^3.0.0" | |
| 28 | } | |
| 29 | }, | |
| 30 | "node_modules/@jridgewell/resolve-uri": { | |
| 31 | "version": "3.1.2", | |
| 32 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", | |
| 33 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", | |
| 34 | "dev": true, | |
| 35 | "license": "MIT", | |
| 36 | "engines": { | |
| 37 | "node": ">=6.0.0" | |
| 38 | } | |
| 39 | }, | |
| 40 | "node_modules/@jridgewell/sourcemap-codec": { | |
| 41 | "version": "1.5.5", | |
| 42 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", | |
| 43 | "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", | |
| 44 | "dev": true, | |
| 45 | "license": "MIT" | |
| 46 | }, | |
| 47 | "node_modules/@jridgewell/trace-mapping": { | |
| 48 | "version": "0.3.31", | |
| 49 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", | |
| 50 | "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", | |
| 51 | "dev": true, | |
| 52 | "license": "MIT", | |
| 53 | "dependencies": { | |
| 54 | "@jridgewell/resolve-uri": "^3.1.0", | |
| 55 | "@jridgewell/sourcemap-codec": "^1.4.14" | |
| 56 | } | |
| 57 | }, | |
| 58 | "node_modules/@types/istanbul-lib-coverage": { | |
| 59 | "version": "2.0.6", | |
| 60 | "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", | |
| 61 | "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", | |
| 62 | "dev": true, | |
| 63 | "license": "MIT" | |
| 64 | }, | |
| 65 | "node_modules/b4a": { | |
| 66 | "version": "1.7.4", | |
| 67 | "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.4.tgz", | |
| 68 | "integrity": "sha512-u20zJLDaSWpxaZ+zaAkEIB2dZZ1o+DF4T/MRbmsvGp9nletHOyiai19OzX1fF8xUBYsO1bPXxODvcd0978pnug==", | |
| 69 | "license": "Apache-2.0", | |
| 70 | "peerDependencies": { | |
| 71 | "react-native-b4a": "*" | |
| 72 | }, | |
| 73 | "peerDependenciesMeta": { | |
| 74 | "react-native-b4a": { | |
| 75 | "optional": true | |
| 76 | } | |
| 77 | } | |
| 78 | }, | |
| 79 | "node_modules/bare-addon-resolve": { | |
| 80 | "version": "1.10.0", | |
| 81 | "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.0.tgz", | |
| 82 | "integrity": "sha512-sSd0jieRJlDaODOzj0oe0RjFVC1QI0ZIjGIdPkbrTXsdVVtENg14c+lHHAhHwmWCZ2nQlMhy8jA3Y5LYPc/isA==", | |
| 83 | "dev": true, | |
| 84 | "license": "Apache-2.0", | |
| 85 | "dependencies": { | |
| 86 | "bare-module-resolve": "^1.10.0", | |
| 87 | "bare-semver": "^1.0.0" | |
| 88 | }, | |
| 89 | "peerDependencies": { | |
| 90 | "bare-url": "*" | |
| 91 | }, | |
| 92 | "peerDependenciesMeta": { | |
| 93 | "bare-url": { | |
| 94 | "optional": true | |
| 95 | } | |
| 96 | } | |
| 97 | }, | |
| 98 | "node_modules/bare-ansi-escapes": { | |
| 99 | "version": "2.2.3", | |
| 100 | "resolved": "https://registry.npmjs.org/bare-ansi-escapes/-/bare-ansi-escapes-2.2.3.tgz", | |
| 101 | "integrity": "sha512-02ES4/E2RbrtZSnHJ9LntBhYkLA6lPpSEeP8iqS3MccBIVhVBlEmruF1I7HZqx5Q8aiTeYfQVeqmrU9YO2yYoQ==", | |
| 102 | "dev": true, | |
| 103 | "license": "Apache-2.0", | |
| 104 | "dependencies": { | |
| 105 | "bare-stream": "^2.6.5" | |
| 106 | }, | |
| 107 | "peerDependencies": { | |
| 108 | "bare-buffer": "*" | |
| 109 | }, | |
| 110 | "peerDependenciesMeta": { | |
| 111 | "bare-buffer": { | |
| 112 | "optional": true | |
| 113 | } | |
| 114 | } | |
| 115 | }, | |
| 116 | "node_modules/bare-assert": { | |
| 117 | "version": "1.2.0", | |
| 118 | "resolved": "https://registry.npmjs.org/bare-assert/-/bare-assert-1.2.0.tgz", | |
| 119 | "integrity": "sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==", | |
| 120 | "dev": true, | |
| 121 | "license": "Apache-2.0", | |
| 122 | "dependencies": { | |
| 123 | "bare-inspect": "^3.1.2" | |
| 124 | } | |
| 125 | }, | |
| 126 | "node_modules/bare-bundle": { | |
| 127 | "version": "1.10.0", | |
| 128 | "resolved": "https://registry.npmjs.org/bare-bundle/-/bare-bundle-1.10.0.tgz", | |
| 129 | "integrity": "sha512-4LVlnJAHr00Hh6Vu6ZUJS38rcEtJT3b3vChXSsBsJ2mk1TN0lQ+gzd+Dw5L0aV7uqDZv84smuwW+O02X7PfDlw==", | |
| 130 | "dev": true, | |
| 131 | "license": "Apache-2.0", | |
| 132 | "peerDependencies": { | |
| 133 | "bare-buffer": "*", | |
| 134 | "bare-url": "*" | |
| 135 | }, | |
| 136 | "peerDependenciesMeta": { | |
| 137 | "bare-buffer": { | |
| 138 | "optional": true | |
| 139 | }, | |
| 140 | "bare-url": { | |
| 141 | "optional": true | |
| 142 | } | |
| 143 | } | |
| 144 | }, | |
| 145 | "node_modules/bare-cov": { | |
| 146 | "version": "1.2.1", | |
| 147 | "resolved": "https://registry.npmjs.org/bare-cov/-/bare-cov-1.2.1.tgz", | |
| 148 | "integrity": "sha512-SHNqb/WG9udUEj8m5qGSehoUQ9o0Xzv8nrf4HIdmG+KNU/XdhPLhTP5VPrHnSAMAXhAzEB4Q+2IYZ0x9qPzN1g==", | |
| 149 | "dev": true, | |
| 150 | "license": "Apache-2.0", | |
| 151 | "dependencies": { | |
| 152 | "bare-fs": "^4.1.2", | |
| 153 | "bare-inspector": "^4.0.1", | |
| 154 | "bare-path": "^3.0.0", | |
| 155 | "bare-process": "^4.2.1", | |
| 156 | "bare-url": "^2.1.5", | |
| 157 | "bare-v8-to-istanbul": "^1.0.2", | |
| 158 | "picomatch": "^4.0.2", | |
| 159 | "which-runtime": "^1.2.1" | |
| 160 | } | |
| 161 | }, | |
| 162 | "node_modules/bare-crypto": { | |
| 163 | "version": "1.13.0", | |
| 164 | "resolved": "https://registry.npmjs.org/bare-crypto/-/bare-crypto-1.13.0.tgz", | |
| 165 | "integrity": "sha512-RQl13yD+YTACWUZHMMck0C6LNjXgGgRyhVC557ag0xIrkxXf4IQwpnc8WOB58f4k5kSzKhIjy05oW3HUBrFpSQ==", | |
| 166 | "dev": true, | |
| 167 | "license": "Apache-2.0", | |
| 168 | "dependencies": { | |
| 169 | "bare-stream": "^2.6.3" | |
| 170 | }, | |
| 171 | "peerDependencies": { | |
| 172 | "bare-buffer": "*" | |
| 173 | }, | |
| 174 | "peerDependenciesMeta": { | |
| 175 | "bare-buffer": { | |
| 176 | "optional": true | |
| 177 | } | |
| 178 | } | |
| 179 | }, | |
| 180 | "node_modules/bare-debug-log": { | |
| 181 | "version": "2.0.0", | |
| 182 | "resolved": "https://registry.npmjs.org/bare-debug-log/-/bare-debug-log-2.0.0.tgz", | |
| 183 | "integrity": "sha512-Vi42PkMQsNV9PUpx2Gl1hikshx5O9FzMJ6o9Nnopseg7qLBBK7Nl31d0RHcfwLEAfmcPApytpc0ZFfq68u22FQ==", | |
| 184 | "dev": true, | |
| 185 | "license": "Apache-2.0", | |
| 186 | "dependencies": { | |
| 187 | "bare-os": "^3.0.1" | |
| 188 | } | |
| 189 | }, | |
| 190 | "node_modules/bare-dns": { | |
| 191 | "version": "2.1.4", | |
| 192 | "resolved": "https://registry.npmjs.org/bare-dns/-/bare-dns-2.1.4.tgz", | |
| 193 | "integrity": "sha512-abwjHmpWqSRNB7V5615QxPH92L71AVzFm/kKTs8VYiNTAi2xVdonpv0BjJ0hwXLwomoW+xsSOPjW6PZPO14asg==", | |
| 194 | "dev": true, | |
| 195 | "license": "Apache-2.0", | |
| 196 | "engines": { | |
| 197 | "bare": ">=1.7.0" | |
| 198 | } | |
| 199 | }, | |
| 200 | "node_modules/bare-encoding": { | |
| 201 | "version": "1.0.3", | |
| 202 | "resolved": "https://registry.npmjs.org/bare-encoding/-/bare-encoding-1.0.3.tgz", | |
| 203 | "integrity": "sha512-Kqf+t/azs13lUeyK4Tb7ha4wdLRXKWCXQ8w1rVmt7KtoPCPdHD/Xwt7LBIsCSwwGglrcmblo5VOLa5avkJqULA==", | |
| 204 | "dev": true, | |
| 205 | "license": "Apache-2.0", | |
| 206 | "peerDependencies": { | |
| 207 | "bare-buffer": "*" | |
| 208 | }, | |
| 209 | "peerDependenciesMeta": { | |
| 210 | "bare-buffer": { | |
| 211 | "optional": true | |
| 212 | } | |
| 213 | } | |
| 214 | }, | |
| 215 | "node_modules/bare-env": { | |
| 216 | "version": "3.0.0", | |
| 217 | "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.0.tgz", | |
| 218 | "integrity": "sha512-0u964P5ZLAxTi+lW4Kjp7YRJQ5gZr9ycYOtjLxsSrupgMz3sn5Z9n4SH/JIifHwvadsf1brA2JAjP+9IOWwTiw==", | |
| 219 | "dev": true, | |
| 220 | "license": "Apache-2.0", | |
| 221 | "dependencies": { | |
| 222 | "bare-os": "^3.0.1" | |
| 223 | } | |
| 224 | }, | |
| 225 | "node_modules/bare-events": { | |
| 226 | "version": "2.8.2", | |
| 227 | "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", | |
| 228 | "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", | |
| 229 | "dev": true, | |
| 230 | "license": "Apache-2.0", | |
| 231 | "peerDependencies": { | |
| 232 | "bare-abort-controller": "*" | |
| 233 | }, | |
| 234 | "peerDependenciesMeta": { | |
| 235 | "bare-abort-controller": { | |
| 236 | "optional": true | |
| 237 | } | |
| 238 | } | |
| 239 | }, | |
| 240 | "node_modules/bare-format": { | |
| 241 | "version": "1.0.1", | |
| 242 | "resolved": "https://registry.npmjs.org/bare-format/-/bare-format-1.0.1.tgz", | |
| 243 | "integrity": "sha512-1oS+LZrWK6tnYnvNSHDGljc2MPunRxwhpFriuCgzNF+oklrnwmBKD91tS0yt+jpl2j3UgcSDzBIMiVTvLs9A8w==", | |
| 244 | "dev": true, | |
| 245 | "license": "Apache-2.0", | |
| 246 | "dependencies": { | |
| 247 | "bare-inspect": "^3.0.0" | |
| 248 | } | |
| 249 | }, | |
| 250 | "node_modules/bare-fs": { | |
| 251 | "version": "4.5.4", | |
| 252 | "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.4.tgz", | |
| 253 | "integrity": "sha512-POK4oplfA7P7gqvetNmCs4CNtm9fNsx+IAh7jH7GgU0OJdge2rso0R20TNWVq6VoWcCvsTdlNDaleLHGaKx8CA==", | |
| 254 | "dev": true, | |
| 255 | "license": "Apache-2.0", | |
| 256 | "dependencies": { | |
| 257 | "bare-events": "^2.5.4", | |
| 258 | "bare-path": "^3.0.0", | |
| 259 | "bare-stream": "^2.6.4", | |
| 260 | "bare-url": "^2.2.2", | |
| 261 | "fast-fifo": "^1.3.2" | |
| 262 | }, | |
| 263 | "engines": { | |
| 264 | "bare": ">=1.16.0" | |
| 265 | }, | |
| 266 | "peerDependencies": { | |
| 267 | "bare-buffer": "*" | |
| 268 | }, | |
| 269 | "peerDependenciesMeta": { | |
| 270 | "bare-buffer": { | |
| 271 | "optional": true | |
| 272 | } | |
| 273 | } | |
| 274 | }, | |
| 275 | "node_modules/bare-hrtime": { | |
| 276 | "version": "2.1.1", | |
| 277 | "resolved": "https://registry.npmjs.org/bare-hrtime/-/bare-hrtime-2.1.1.tgz", | |
| 278 | "integrity": "sha512-VMb3tHo05gsnbu3OXTmkDiwTjMlOsbQmKoysKqKEyR09m77TuDrYFbj3Q5GGk10dAKsUHrnXmwCaeJqzVpB5ZA==", | |
| 279 | "dev": true, | |
| 280 | "license": "Apache-2.0" | |
| 281 | }, | |
| 282 | "node_modules/bare-http-parser": { | |
| 283 | "version": "1.0.1", | |
| 284 | "resolved": "https://registry.npmjs.org/bare-http-parser/-/bare-http-parser-1.0.1.tgz", | |
| 285 | "integrity": "sha512-A3LTDTcELcmNJ3g5liIaS038v/BQxOhA9cjhBESn7eoV7QCuMoIRBKLDadDe08flxyLbxI2f+1l2MZ/5+HnKPA==", | |
| 286 | "dev": true, | |
| 287 | "license": "Apache-2.0" | |
| 288 | }, | |
| 289 | "node_modules/bare-http1": { | |
| 290 | "version": "4.2.3", | |
| 291 | "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.2.3.tgz", | |
| 292 | "integrity": "sha512-zyvHF3xzvBJNpuw0QydE9etTn2AyC/TRVDWTo8Ed3LQEEGaFOkv6KHhaJPlf4eWEeTekk41Fowie+fTsXoNxkA==", | |
| 293 | "dev": true, | |
| 294 | "license": "Apache-2.0", | |
| 295 | "dependencies": { | |
| 296 | "bare-events": "^2.6.0", | |
| 297 | "bare-http-parser": "^1.0.0", | |
| 298 | "bare-stream": "^2.3.0", | |
| 299 | "bare-tcp": "^2.2.0" | |
| 300 | }, | |
| 301 | "peerDependencies": { | |
| 302 | "bare-buffer": "*", | |
| 303 | "bare-url": "*" | |
| 304 | }, | |
| 305 | "peerDependenciesMeta": { | |
| 306 | "bare-buffer": { | |
| 307 | "optional": true | |
| 308 | }, | |
| 309 | "bare-url": { | |
| 310 | "optional": true | |
| 311 | } | |
| 312 | } | |
| 313 | }, | |
| 314 | "node_modules/bare-https": { | |
| 315 | "version": "2.1.2", | |
| 316 | "resolved": "https://registry.npmjs.org/bare-https/-/bare-https-2.1.2.tgz", | |
| 317 | "integrity": "sha512-Q+TTydUDsuKQJvh8dX2dvOXCR9fM3xR5TBmKaFrs5p7Lj7XbKX7v4vIUJ36H0SXg2xCOQxXKIbjwrLg5tfJNYg==", | |
| 318 | "dev": true, | |
| 319 | "license": "Apache-2.0", | |
| 320 | "dependencies": { | |
| 321 | "bare-http1": "^4.0.0", | |
| 322 | "bare-tcp": "^2.2.0", | |
| 323 | "bare-tls": "^2.0.0" | |
| 324 | } | |
| 325 | }, | |
| 326 | "node_modules/bare-inspect": { | |
| 327 | "version": "3.1.4", | |
| 328 | "resolved": "https://registry.npmjs.org/bare-inspect/-/bare-inspect-3.1.4.tgz", | |
| 329 | "integrity": "sha512-jfW5KRA84o3REpI6Vr4nbvMn+hqVAw8GU1mMdRwUsY5yJovQamxYeKGVKGqdzs+8ZbG4jRzGUXP/3Ji/DnqfPg==", | |
| 330 | "dev": true, | |
| 331 | "license": "Apache-2.0", | |
| 332 | "dependencies": { | |
| 333 | "bare-ansi-escapes": "^2.1.0", | |
| 334 | "bare-type": "^1.0.0" | |
| 335 | }, | |
| 336 | "engines": { | |
| 337 | "bare": ">=1.18.0" | |
| 338 | } | |
| 339 | }, | |
| 340 | "node_modules/bare-inspector": { | |
| 341 | "version": "4.0.3", | |
| 342 | "resolved": "https://registry.npmjs.org/bare-inspector/-/bare-inspector-4.0.3.tgz", | |
| 343 | "integrity": "sha512-yYk6+7tTVKJKHOja5+QBn989ETv8oVsmwoH0nCM+QYC6w5MC03GC4aQNK+lipAZSz1EaAj42WZ8Wewjpy0L9TA==", | |
| 344 | "dev": true, | |
| 345 | "license": "Apache-2.0", | |
| 346 | "dependencies": { | |
| 347 | "bare-events": "^2.1.0", | |
| 348 | "bare-http1": "^4.0.0", | |
| 349 | "bare-stream": "^2.0.0", | |
| 350 | "bare-url": "^2.0.0", | |
| 351 | "bare-ws": "^2.0.0" | |
| 352 | }, | |
| 353 | "engines": { | |
| 354 | "bare": ">=1.2.0" | |
| 355 | }, | |
| 356 | "peerDependencies": { | |
| 357 | "bare-tcp": "*" | |
| 358 | }, | |
| 359 | "peerDependenciesMeta": { | |
| 360 | "bare-tcp": { | |
| 361 | "optional": true | |
| 362 | } | |
| 363 | } | |
| 364 | }, | |
| 365 | "node_modules/bare-module": { | |
| 366 | "version": "6.1.3", | |
| 367 | "resolved": "https://registry.npmjs.org/bare-module/-/bare-module-6.1.3.tgz", | |
| 368 | "integrity": "sha512-5XWsVHsvtWMH4tK4DQWgpNTV0t/sg3ZrAaQLIxrwjrS5+u8Q9vEgc/zQ4QaDPWDse/y/5h+d+YG1Q0JfSMt0zA==", | |
| 369 | "dev": true, | |
| 370 | "license": "Apache-2.0", | |
| 371 | "dependencies": { | |
| 372 | "bare-bundle": "^1.3.0", | |
| 373 | "bare-module-lexer": "^1.0.0", | |
| 374 | "bare-module-resolve": "^1.8.0", | |
| 375 | "bare-path": "^3.0.0", | |
| 376 | "bare-url": "^2.0.1" | |
| 377 | }, | |
| 378 | "engines": { | |
| 379 | "bare": ">=1.23.0" | |
| 380 | }, | |
| 381 | "peerDependencies": { | |
| 382 | "bare-buffer": "*" | |
| 383 | }, | |
| 384 | "peerDependenciesMeta": { | |
| 385 | "bare-buffer": { | |
| 386 | "optional": true | |
| 387 | } | |
| 388 | } | |
| 389 | }, | |
| 390 | "node_modules/bare-module-lexer": { | |
| 391 | "version": "1.4.7", | |
| 392 | "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.4.7.tgz", | |
| 393 | "integrity": "sha512-0klU4eMsjh/wcxi8FdHmNom2j2F4kmkXOhyJFL9qTaSFp2lE3m6BtbKgMHY8R5miqC9r8/IfA8wzXnC5Os14WA==", | |
| 394 | "dev": true, | |
| 395 | "license": "Apache-2.0", | |
| 396 | "dependencies": { | |
| 397 | "require-addon": "^1.0.2" | |
| 398 | }, | |
| 399 | "peerDependencies": { | |
| 400 | "bare-buffer": "*" | |
| 401 | }, | |
| 402 | "peerDependenciesMeta": { | |
| 403 | "bare-buffer": { | |
| 404 | "optional": true | |
| 405 | } | |
| 406 | } | |
| 407 | }, | |
| 408 | "node_modules/bare-module-resolve": { | |
| 409 | "version": "1.12.1", | |
| 410 | "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.1.tgz", | |
| 411 | "integrity": "sha512-hbmAPyFpEq8FoZMd5sFO3u6MC5feluWoGE8YKlA8fCrl6mNtx68Wjg4DTiDJcqRJaovTvOYKfYngoBUnbaT7eg==", | |
| 412 | "dev": true, | |
| 413 | "license": "Apache-2.0", | |
| 414 | "dependencies": { | |
| 415 | "bare-semver": "^1.0.0" | |
| 416 | }, | |
| 417 | "peerDependencies": { | |
| 418 | "bare-url": "*" | |
| 419 | }, | |
| 420 | "peerDependenciesMeta": { | |
| 421 | "bare-url": { | |
| 422 | "optional": true | |
| 423 | } | |
| 424 | } | |
| 425 | }, | |
| 426 | "node_modules/bare-net": { | |
| 427 | "version": "2.2.0", | |
| 428 | "resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.2.0.tgz", | |
| 429 | "integrity": "sha512-UF7cAbHsGE+H6uEqWF5IULBow1x58chZz4g3ALgHtv7wZsFcCbRDt0JKWEumf5Oma3QWS1Q6aLi0Rpll8RElMg==", | |
| 430 | "dev": true, | |
| 431 | "license": "Apache-2.0", | |
| 432 | "dependencies": { | |
| 433 | "bare-events": "^2.2.2", | |
| 434 | "bare-pipe": "^4.0.0", | |
| 435 | "bare-stream": "^2.0.0", | |
| 436 | "bare-tcp": "^2.0.0" | |
| 437 | } | |
| 438 | }, | |
| 439 | "node_modules/bare-os": { | |
| 440 | "version": "3.6.2", | |
| 441 | "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", | |
| 442 | "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", | |
| 443 | "dev": true, | |
| 444 | "license": "Apache-2.0", | |
| 445 | "engines": { | |
| 446 | "bare": ">=1.14.0" | |
| 447 | } | |
| 448 | }, | |
| 449 | "node_modules/bare-path": { | |
| 450 | "version": "3.0.0", | |
| 451 | "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", | |
| 452 | "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", | |
| 453 | "dev": true, | |
| 454 | "license": "Apache-2.0", | |
| 455 | "dependencies": { | |
| 456 | "bare-os": "^3.0.1" | |
| 457 | } | |
| 458 | }, | |
| 459 | "node_modules/bare-pipe": { | |
| 460 | "version": "4.1.3", | |
| 461 | "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.1.3.tgz", | |
| 462 | "integrity": "sha512-DqQsx93rAzre6yJ9T6l/Vgh+X+bntkVMB1X5ggtXjXtqtMmF2Y2RVlCzxxy/09R6yeR9FSWBEUIaMYJL1/5VDA==", | |
| 463 | "dev": true, | |
| 464 | "license": "Apache-2.0", | |
| 465 | "dependencies": { | |
| 466 | "bare-events": "^2.0.0", | |
| 467 | "bare-stream": "^2.0.0" | |
| 468 | }, | |
| 469 | "engines": { | |
| 470 | "bare": ">=1.16.0" | |
| 471 | } | |
| 472 | }, | |
| 473 | "node_modules/bare-process": { | |
| 474 | "version": "4.2.2", | |
| 475 | "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.2.2.tgz", | |
| 476 | "integrity": "sha512-ikzEw+HGLB+2lS/WLVEsqi8BXBwzleG3n7DYI0v6/YNklvNabOIGlLd1dof+7Jw5ob4jsBRPtyflweVahY7rFA==", | |
| 477 | "dev": true, | |
| 478 | "license": "Apache-2.0", | |
| 479 | "dependencies": { | |
| 480 | "bare-env": "^3.0.0", | |
| 481 | "bare-events": "^2.3.1", | |
| 482 | "bare-hrtime": "^2.0.0", | |
| 483 | "bare-os": "^3.5.0", | |
| 484 | "bare-pipe": "^4.0.0", | |
| 485 | "bare-signals": "^4.0.0", | |
| 486 | "bare-tty": "^5.0.0" | |
| 487 | } | |
| 488 | }, | |
| 489 | "node_modules/bare-semver": { | |
| 490 | "version": "1.0.2", | |
| 491 | "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.0.2.tgz", | |
| 492 | "integrity": "sha512-ESVaN2nzWhcI5tf3Zzcq9aqCZ676VWzqw07eEZ0qxAcEOAFYBa0pWq8sK34OQeHLY3JsfKXZS9mDyzyxGjeLzA==", | |
| 493 | "dev": true, | |
| 494 | "license": "Apache-2.0" | |
| 495 | }, | |
| 496 | "node_modules/bare-signals": { | |
| 497 | "version": "4.2.0", | |
| 498 | "resolved": "https://registry.npmjs.org/bare-signals/-/bare-signals-4.2.0.tgz", | |
| 499 | "integrity": "sha512-fNHMOdQIlYuTvMB3Oh9Apk99hLKn351+Ir8vz+khiPTcOqIyGG4uWWjdLTzxWdYGsA0eT+We3y0K74hjj2nq7A==", | |
| 500 | "dev": true, | |
| 501 | "license": "Apache-2.0", | |
| 502 | "dependencies": { | |
| 503 | "bare-events": "^2.5.3", | |
| 504 | "bare-os": "^3.3.1" | |
| 505 | }, | |
| 506 | "engines": { | |
| 507 | "bare": ">=1.7.0" | |
| 508 | } | |
| 509 | }, | |
| 510 | "node_modules/bare-stream": { | |
| 511 | "version": "2.7.0", | |
| 512 | "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", | |
| 513 | "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", | |
| 514 | "dev": true, | |
| 515 | "license": "Apache-2.0", | |
| 516 | "dependencies": { | |
| 517 | "streamx": "^2.21.0" | |
| 518 | }, | |
| 519 | "peerDependencies": { | |
| 520 | "bare-buffer": "*", | |
| 521 | "bare-events": "*" | |
| 522 | }, | |
| 523 | "peerDependenciesMeta": { | |
| 524 | "bare-buffer": { | |
| 525 | "optional": true | |
| 526 | }, | |
| 527 | "bare-events": { | |
| 528 | "optional": true | |
| 529 | } | |
| 530 | } | |
| 531 | }, | |
| 532 | "node_modules/bare-subprocess": { | |
| 533 | "version": "5.2.2", | |
| 534 | "resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.2.tgz", | |
| 535 | "integrity": "sha512-L6oXgQ1aWs25RtG5Ky0bDD06p3RAcVVrDDMWb1DfXpHtyEWxamcyIWUbSykxMTWrpLlmSj6ytbb6yoKehGFfmw==", | |
| 536 | "dev": true, | |
| 537 | "license": "Apache-2.0", | |
| 538 | "dependencies": { | |
| 539 | "bare-env": "^3.0.0", | |
| 540 | "bare-events": "^2.5.4", | |
| 541 | "bare-os": "^3.0.1", | |
| 542 | "bare-pipe": "^4.0.0", | |
| 543 | "bare-url": "^2.2.2" | |
| 544 | }, | |
| 545 | "engines": { | |
| 546 | "bare": ">=1.7.0" | |
| 547 | }, | |
| 548 | "peerDependencies": { | |
| 549 | "bare-buffer": "*" | |
| 550 | }, | |
| 551 | "peerDependenciesMeta": { | |
| 552 | "bare-buffer": { | |
| 553 | "optional": true | |
| 554 | } | |
| 555 | } | |
| 556 | }, | |
| 557 | "node_modules/bare-tcp": { | |
| 558 | "version": "2.2.4", | |
| 559 | "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.2.4.tgz", | |
| 560 | "integrity": "sha512-vGjfMkyhS1lxp+Y4+gXRDR1ijZMK2cKx0RBwtYKUuZbD90ARCoM6bAiTa1HBztumCIC+NXb8eXdMFXyuYwlPyw==", | |
| 561 | "dev": true, | |
| 562 | "license": "Apache-2.0", | |
| 563 | "dependencies": { | |
| 564 | "bare-dns": "^2.0.4", | |
| 565 | "bare-events": "^2.5.4", | |
| 566 | "bare-stream": "^2.6.4" | |
| 567 | }, | |
| 568 | "engines": { | |
| 569 | "bare": ">=1.16.0" | |
| 570 | } | |
| 571 | }, | |
| 572 | "node_modules/bare-tls": { | |
| 573 | "version": "2.1.7", | |
| 574 | "resolved": "https://registry.npmjs.org/bare-tls/-/bare-tls-2.1.7.tgz", | |
| 575 | "integrity": "sha512-h6wcNXQdBeTX7fed9tjPp0/9cA/QfcBTv3ItgjnbUk4rWAU8bEFalZCZnUDdCK/t9zrNfJ+yvcPx4D/1Y6biyA==", | |
| 576 | "dev": true, | |
| 577 | "license": "Apache-2.0", | |
| 578 | "dependencies": { | |
| 579 | "bare-net": "^2.0.1", | |
| 580 | "bare-stream": "^2.6.4" | |
| 581 | }, | |
| 582 | "engines": { | |
| 583 | "bare": ">=1.7.0" | |
| 584 | } | |
| 585 | }, | |
| 586 | "node_modules/bare-tty": { | |
| 587 | "version": "5.0.3", | |
| 588 | "resolved": "https://registry.npmjs.org/bare-tty/-/bare-tty-5.0.3.tgz", | |
| 589 | "integrity": "sha512-jW24RBWRZOMHuSEWC9mh8wUKO5WUNl0UWUTNPn3yZ8qkOqwa8vaV2dR3a+BvblONPk7V35wuvK9P8508+judAg==", | |
| 590 | "dev": true, | |
| 591 | "license": "Apache-2.0", | |
| 592 | "dependencies": { | |
| 593 | "bare-events": "^2.2.0", | |
| 594 | "bare-signals": "^4.0.0", | |
| 595 | "bare-stream": "^2.0.0" | |
| 596 | }, | |
| 597 | "engines": { | |
| 598 | "bare": ">=1.16.0" | |
| 599 | } | |
| 600 | }, | |
| 601 | "node_modules/bare-type": { | |
| 602 | "version": "1.1.0", | |
| 603 | "resolved": "https://registry.npmjs.org/bare-type/-/bare-type-1.1.0.tgz", | |
| 604 | "integrity": "sha512-LdtnnEEYldOc87Dr4GpsKnStStZk3zfgoEMXy8yvEZkXrcCv9RtYDrUYWFsBQHtaB0s1EUWmcvS6XmEZYIj3Bw==", | |
| 605 | "dev": true, | |
| 606 | "license": "Apache-2.0", | |
| 607 | "engines": { | |
| 608 | "bare": ">=1.2.0" | |
| 609 | } | |
| 610 | }, | |
| 611 | "node_modules/bare-url": { | |
| 612 | "version": "2.3.2", | |
| 613 | "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", | |
| 614 | "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", | |
| 615 | "dev": true, | |
| 616 | "license": "Apache-2.0", | |
| 617 | "peer": true, | |
| 618 | "dependencies": { | |
| 619 | "bare-path": "^3.0.0" | |
| 620 | } | |
| 621 | }, | |
| 622 | "node_modules/bare-utils": { | |
| 623 | "version": "1.5.1", | |
| 624 | "resolved": "https://registry.npmjs.org/bare-utils/-/bare-utils-1.5.1.tgz", | |
| 625 | "integrity": "sha512-mxCkFvmDU3mlD/mb+pT64kKXOsx2KMsWLQbngN1LB+NOXfhfnRnyvpy3VZc6m7gzQxe57Bsi+aTCBqA4/S3elQ==", | |
| 626 | "dev": true, | |
| 627 | "license": "Apache-2.0", | |
| 628 | "dependencies": { | |
| 629 | "bare-debug-log": "^2.0.0", | |
| 630 | "bare-encoding": "^1.0.0", | |
| 631 | "bare-format": "^1.0.0", | |
| 632 | "bare-inspect": "^3.0.0", | |
| 633 | "bare-type": "^1.0.6" | |
| 634 | } | |
| 635 | }, | |
| 636 | "node_modules/bare-v8-to-istanbul": { | |
| 637 | "version": "1.0.2", | |
| 638 | "resolved": "https://registry.npmjs.org/bare-v8-to-istanbul/-/bare-v8-to-istanbul-1.0.2.tgz", | |
| 639 | "integrity": "sha512-CM0wPgyQtXTQ7h4q8NjbTZ/FH5he0Jkiwgpq0lFZZA5tkVZzmQ/9ul4R7E+cNlGZJIZlQZ9v4CwbgVaFG3zOAA==", | |
| 640 | "dev": true, | |
| 641 | "license": "Apache-2.0", | |
| 642 | "dependencies": { | |
| 643 | "bare-assert": "^1.0.2", | |
| 644 | "bare-fs": "^4.1.2", | |
| 645 | "bare-module": "^6.1.2", | |
| 646 | "bare-path": "^3.0.0", | |
| 647 | "bare-process": "^4.2.0", | |
| 648 | "bare-url": "^2.1.5", | |
| 649 | "bare-utils": "^1.2.0", | |
| 650 | "v8-to-istanbul": "^9.3.0", | |
| 651 | "which-runtime": "^1.2.1" | |
| 652 | } | |
| 653 | }, | |
| 654 | "node_modules/bare-ws": { | |
| 655 | "version": "2.0.4", | |
| 656 | "resolved": "https://registry.npmjs.org/bare-ws/-/bare-ws-2.0.4.tgz", | |
| 657 | "integrity": "sha512-SQMXzBYna9dRj57Dz1/ag+VWHCRXbfCjMHgyfM2F2lhkVLzMjnVSZP72aVeFWPFqe494Rd70Kzhe2JElGwFlJQ==", | |
| 658 | "dev": true, | |
| 659 | "license": "Apache-2.0", | |
| 660 | "dependencies": { | |
| 661 | "bare-crypto": "^1.2.0", | |
| 662 | "bare-events": "^2.3.1", | |
| 663 | "bare-http1": "^4.0.0", | |
| 664 | "bare-https": "^2.0.0", | |
| 665 | "bare-stream": "^2.1.2" | |
| 666 | }, | |
| 667 | "peerDependencies": { | |
| 668 | "bare-buffer": "*", | |
| 669 | "bare-url": "*" | |
| 670 | }, | |
| 671 | "peerDependenciesMeta": { | |
| 672 | "bare-buffer": { | |
| 673 | "optional": true | |
| 674 | }, | |
| 675 | "bare-url": { | |
| 676 | "optional": true | |
| 677 | } | |
| 678 | } | |
| 679 | }, | |
| 680 | "node_modules/bridge-envelope": { | |
| 681 | "resolved": "../bridge-envelope", | |
| 682 | "link": true | |
| 683 | }, | |
| 684 | "node_modules/brittle": { | |
| 685 | "version": "3.19.1", | |
| 686 | "resolved": "https://registry.npmjs.org/brittle/-/brittle-3.19.1.tgz", | |
| 687 | "integrity": "sha512-4Ted1Mt9o9B6oIA6ImJJCtB/Fv++ZO3IDNQgRcHOXWSYGRUidgxchaGrUBaRn+4mlneXrgInPkCPzi0jO8qKbA==", | |
| 688 | "dev": true, | |
| 689 | "license": "Apache-2.0", | |
| 690 | "dependencies": { | |
| 691 | "b4a": "^1.6.0", | |
| 692 | "bare-assert": "^1.0.2", | |
| 693 | "bare-cov": "^1.1.0", | |
| 694 | "bare-fs": "^4.1.6", | |
| 695 | "bare-os": "^3.6.1", | |
| 696 | "bare-path": "^3.0.0", | |
| 697 | "bare-process": "^4.2.1", | |
| 698 | "bare-subprocess": "^5.0.0", | |
| 699 | "bare-url": "^2.1.6", | |
| 700 | "error-stack-parser": "^2.1.4", | |
| 701 | "globbie": "^1.0.2", | |
| 702 | "paparam": "^1.6.2", | |
| 703 | "same-object": "^1.0.2", | |
| 704 | "test-tmp": "^1.4.0", | |
| 705 | "tmatch": "^5.0.0" | |
| 706 | }, | |
| 707 | "bin": { | |
| 708 | "brittle": "bin/node.js", | |
| 709 | "brittle-bare": "bin/bare.js", | |
| 710 | "brittle-node": "bin/node.js", | |
| 711 | "brittle-pear": "bin/pear.js" | |
| 712 | } | |
| 713 | }, | |
| 714 | "node_modules/convert-source-map": { | |
| 715 | "version": "2.0.0", | |
| 716 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", | |
| 717 | "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", | |
| 718 | "dev": true, | |
| 719 | "license": "MIT" | |
| 720 | }, | |
| 721 | "node_modules/error-stack-parser": { | |
| 722 | "version": "2.1.4", | |
| 723 | "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", | |
| 724 | "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", | |
| 725 | "dev": true, | |
| 726 | "license": "MIT", | |
| 727 | "dependencies": { | |
| 728 | "stackframe": "^1.3.4" | |
| 729 | } | |
| 730 | }, | |
| 731 | "node_modules/events-universal": { | |
| 732 | "version": "1.0.1", | |
| 733 | "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", | |
| 734 | "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", | |
| 735 | "dev": true, | |
| 736 | "license": "Apache-2.0", | |
| 737 | "dependencies": { | |
| 738 | "bare-events": "^2.7.0" | |
| 739 | } | |
| 740 | }, | |
| 741 | "node_modules/fast-fifo": { | |
| 742 | "version": "1.3.2", | |
| 743 | "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", | |
| 744 | "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", | |
| 745 | "dev": true, | |
| 746 | "license": "MIT" | |
| 747 | }, | |
| 748 | "node_modules/globbie": { | |
| 749 | "version": "1.0.3", | |
| 750 | "resolved": "https://registry.npmjs.org/globbie/-/globbie-1.0.3.tgz", | |
| 751 | "integrity": "sha512-hcryJmKcftf82xfBWTWxuUITWIIoYO3ec104V17SMHGFl6VLbm1d1Ju9LX9L+jyJTwICpemX/dmPI/HGYoKr1A==", | |
| 752 | "dev": true, | |
| 753 | "license": "Apache-2.0", | |
| 754 | "dependencies": { | |
| 755 | "bare-fs": "^4.1.2", | |
| 756 | "bare-path": "^3.0.0", | |
| 757 | "bare-process": "^4.2.1", | |
| 758 | "picomatch": "^4.0.2" | |
| 759 | } | |
| 760 | }, | |
| 761 | "node_modules/paparam": { | |
| 762 | "version": "1.10.0", | |
| 763 | "resolved": "https://registry.npmjs.org/paparam/-/paparam-1.10.0.tgz", | |
| 764 | "integrity": "sha512-p36QQrwU3X6fj5d2JN20B4lcZI/O3fxMVUHc1xD7MNe5bcOf3jIgE0CIK5Zv/s5YAUGlobTJ/kKXrh1FJVrs3w==", | |
| 765 | "dev": true, | |
| 766 | "license": "Apache-2.0" | |
| 767 | }, | |
| 768 | "node_modules/picomatch": { | |
| 769 | "version": "4.0.3", | |
| 770 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", | |
| 771 | "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", | |
| 772 | "dev": true, | |
| 773 | "license": "MIT", | |
| 774 | "engines": { | |
| 775 | "node": ">=12" | |
| 776 | }, | |
| 777 | "funding": { | |
| 778 | "url": "https://github.com/sponsors/jonschlinkert" | |
| 779 | } | |
| 780 | }, | |
| 781 | "node_modules/require-addon": { | |
| 782 | "version": "1.2.0", | |
| 783 | "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz", | |
| 784 | "integrity": "sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA==", | |
| 785 | "dev": true, | |
| 786 | "license": "Apache-2.0", | |
| 787 | "dependencies": { | |
| 788 | "bare-addon-resolve": "^1.3.0" | |
| 789 | }, | |
| 790 | "engines": { | |
| 791 | "bare": ">=1.10.0" | |
| 792 | } | |
| 793 | }, | |
| 794 | "node_modules/same-object": { | |
| 795 | "version": "1.0.2", | |
| 796 | "resolved": "https://registry.npmjs.org/same-object/-/same-object-1.0.2.tgz", | |
| 797 | "integrity": "sha512-csHWhvUsLbIOHDM/nP+KHWM+BLPsIzWkFa8HbzaI0G7BqKXgx+7FJpKTGgLXyz5amfdY2OVBcmXTqYOMEk04og==", | |
| 798 | "dev": true, | |
| 799 | "license": "MIT" | |
| 800 | }, | |
| 801 | "node_modules/stackframe": { | |
| 802 | "version": "1.3.4", | |
| 803 | "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", | |
| 804 | "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", | |
| 805 | "dev": true, | |
| 806 | "license": "MIT" | |
| 807 | }, | |
| 808 | "node_modules/streamx": { | |
| 809 | "version": "2.23.0", | |
| 810 | "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", | |
| 811 | "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", | |
| 812 | "dev": true, | |
| 813 | "license": "MIT", | |
| 814 | "dependencies": { | |
| 815 | "events-universal": "^1.0.0", | |
| 816 | "fast-fifo": "^1.3.2", | |
| 817 | "text-decoder": "^1.1.0" | |
| 818 | } | |
| 819 | }, | |
| 820 | "node_modules/test-tmp": { | |
| 821 | "version": "1.4.0", | |
| 822 | "resolved": "https://registry.npmjs.org/test-tmp/-/test-tmp-1.4.0.tgz", | |
| 823 | "integrity": "sha512-GVggxGg+jXqP2Wbju50JVLo+9E+nIOPPyWqgr63EbOnNItIKu1cEbJpTWAJeflnyGqXOtcMI7ijHRp88GUkfDA==", | |
| 824 | "dev": true, | |
| 825 | "license": "MIT", | |
| 826 | "dependencies": { | |
| 827 | "bare-fs": "^4.0.1", | |
| 828 | "bare-os": "^3.3.0", | |
| 829 | "bare-path": "^3.0.0" | |
| 830 | } | |
| 831 | }, | |
| 832 | "node_modules/text-decoder": { | |
| 833 | "version": "1.2.6", | |
| 834 | "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.6.tgz", | |
| 835 | "integrity": "sha512-27FeW5GQFDfw0FpwMQhMagB7BztOOlmjcSRi97t2oplhKVTZtp0DZbSegSaXS5IIC6mxMvBG4AR1Sgc6BX3CQg==", | |
| 836 | "dev": true, | |
| 837 | "license": "Apache-2.0", | |
| 838 | "dependencies": { | |
| 839 | "b4a": "^1.6.4" | |
| 840 | } | |
| 841 | }, | |
| 842 | "node_modules/tmatch": { | |
| 843 | "version": "5.0.0", | |
| 844 | "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-5.0.0.tgz", | |
| 845 | "integrity": "sha512-Ib9OtBkpHn07tXP04SlN1SYRxFgTk6wSM2EBmjjxug4u5RXPRVLkdFJSS1PmrQidaSB8Lru9nRtViQBsbxzE5Q==", | |
| 846 | "dev": true, | |
| 847 | "license": "ISC", | |
| 848 | "engines": { | |
| 849 | "node": ">=8" | |
| 850 | } | |
| 851 | }, | |
| 852 | "node_modules/v8-to-istanbul": { | |
| 853 | "version": "9.3.0", | |
| 854 | "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", | |
| 855 | "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", | |
| 856 | "dev": true, | |
| 857 | "license": "ISC", | |
| 858 | "dependencies": { | |
| 859 | "@jridgewell/trace-mapping": "^0.3.12", | |
| 860 | "@types/istanbul-lib-coverage": "^2.0.1", | |
| 861 | "convert-source-map": "^2.0.0" | |
| 862 | }, | |
| 863 | "engines": { | |
| 864 | "node": ">=10.12.0" | |
| 865 | } | |
| 866 | }, | |
| 867 | "node_modules/which-runtime": { | |
| 868 | "version": "1.3.2", | |
| 869 | "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.3.2.tgz", | |
| 870 | "integrity": "sha512-5kwCfWml7+b2NO7KrLMhYihjRx0teKkd3yGp1Xk5Vaf2JGdSh+rgVhEALAD9c/59dP+YwJHXoEO7e8QPy7gOkw==", | |
| 871 | "dev": true, | |
| 872 | "license": "Apache-2.0" | |
| 873 | } | |
| 874 | } | |
| 875 | } |
| … | … | @@ -0,0 +1,22 @@ |
| 1 | { | |
| 2 | "name": "bridge-email", | |
| 3 | "version": "1.0.0", | |
| 4 | "description": "Email ↔ Keet bidirectional bridge (IMAP/SMTP)", | |
| 5 | "type": "module", | |
| 6 | "main": "index.js", | |
| 7 | "pear": { | |
| 8 | "name": "bridge-email", | |
| 9 | "type": "terminal" | |
| 10 | }, | |
| 11 | "scripts": { | |
| 12 | "test": "brittle test/*.test.js" | |
| 13 | }, | |
| 14 | "dependencies": { | |
| 15 | "bridge-envelope": "file:../bridge-envelope", | |
| 16 | "b4a": "^1.0.0" | |
| 17 | }, | |
| 18 | "devDependencies": { | |
| 19 | "brittle": "^3.0.0" | |
| 20 | }, | |
| 21 | "license": "MIT" | |
| 22 | } |
| … | … | @@ -0,0 +1,139 @@ |
| 1 | import test from 'brittle' | |
| 2 | import { emailToEnvelope, keetToEmail, emailToKeetText, keetToEmailText, extractEmail, extractName } from '../lib/adapter.js' | |
| 3 | import { TYPES, PLATFORMS } from 'bridge-envelope' | |
| 4 | ||
| 5 | test('extractEmail - angle bracket format', (t) => { | |
| 6 | t.is(extractEmail('Alice <alice@example.com>'), 'alice@example.com') | |
| 7 | t.is(extractEmail('"Bob Smith" <bob@test.org>'), 'bob@test.org') | |
| 8 | }) | |
| 9 | ||
| 10 | test('extractEmail - bare address', (t) => { | |
| 11 | t.is(extractEmail('alice@example.com'), 'alice@example.com') | |
| 12 | }) | |
| 13 | ||
| 14 | test('extractName - display name', (t) => { | |
| 15 | t.is(extractName('Alice <alice@example.com>'), 'Alice') | |
| 16 | t.is(extractName('"Bob Smith" <bob@test.org>'), 'Bob Smith') | |
| 17 | }) | |
| 18 | ||
| 19 | test('extractName - bare address falls back', (t) => { | |
| 20 | t.is(extractName('alice@example.com'), 'alice@example.com') | |
| 21 | }) | |
| 22 | ||
| 23 | test('emailToEnvelope - basic email', (t) => { | |
| 24 | const email = { | |
| 25 | from: 'Alice <alice@example.com>', | |
| 26 | to: 'bridge@example.com', | |
| 27 | subject: 'Hello', | |
| 28 | body: 'This is a test message', | |
| 29 | date: 'Fri, 14 Feb 2026 10:00:00 +0000', | |
| 30 | messageId: '<abc123@example.com>' | |
| 31 | } | |
| 32 | ||
| 33 | const env = emailToEnvelope(email) | |
| 34 | t.ok(env) | |
| 35 | t.is(env.type, TYPES.TEXT) | |
| 36 | t.is(env.platform, PLATFORMS.EMAIL) | |
| 37 | t.is(env.author, 'Alice') | |
| 38 | t.ok(env.content.includes('Hello')) | |
| 39 | t.ok(env.content.includes('test message')) | |
| 40 | t.is(env._messageId, '<abc123@example.com>') | |
| 41 | t.is(env._subject, 'Hello') | |
| 42 | }) | |
| 43 | ||
| 44 | test('emailToEnvelope - no subject', (t) => { | |
| 45 | const email = { | |
| 46 | from: 'bob@test.org', | |
| 47 | subject: '', | |
| 48 | body: 'Just a body', | |
| 49 | messageId: '' | |
| 50 | } | |
| 51 | ||
| 52 | const env = emailToEnvelope(email) | |
| 53 | t.ok(env) | |
| 54 | t.is(env.content, 'Just a body') | |
| 55 | }) | |
| 56 | ||
| 57 | test('emailToEnvelope - empty body and subject', (t) => { | |
| 58 | const email = { from: 'x@y.z', subject: '', body: '', messageId: '' } | |
| 59 | t.is(emailToEnvelope(email), null) | |
| 60 | }) | |
| 61 | ||
| 62 | test('emailToEnvelope - skip bridged emails', (t) => { | |
| 63 | const email = { | |
| 64 | from: 'bridge@test.com', | |
| 65 | subject: '[Keet] Message', | |
| 66 | body: '[email] Alice: hello', | |
| 67 | messageId: '' | |
| 68 | } | |
| 69 | t.is(emailToEnvelope(email), null) | |
| 70 | }) | |
| 71 | ||
| 72 | test('emailToEnvelope - null input', (t) => { | |
| 73 | t.is(emailToEnvelope(null), null) | |
| 74 | }) | |
| 75 | ||
| 76 | test('keetToEmail - normal message', (t) => { | |
| 77 | const msg = { name: 'fleeky', text: 'Hello!', memberId: 'abc' } | |
| 78 | const result = keetToEmail(msg, 'bridge-id') | |
| 79 | t.ok(result) | |
| 80 | t.is(result.author, 'fleeky') | |
| 81 | t.is(result.text, 'Hello!') | |
| 82 | t.ok(result.subject.includes('fleeky')) | |
| 83 | t.ok(result.body.includes('Hello!')) | |
| 84 | }) | |
| 85 | ||
| 86 | test('keetToEmail - skip bridge identity', (t) => { | |
| 87 | const msg = { name: 'clode', text: 'Hello', memberId: 'bridge-id' } | |
| 88 | t.is(keetToEmail(msg, 'bridge-id'), null) | |
| 89 | }) | |
| 90 | ||
| 91 | test('keetToEmail - skip system messages', (t) => { | |
| 92 | const msg = { name: 'system', text: 'joined', type: 'system' } | |
| 93 | t.is(keetToEmail(msg, 'bridge-id'), null) | |
| 94 | }) | |
| 95 | ||
| 96 | test('keetToEmail - skip already bridged', (t) => { | |
| 97 | const msg = { name: 'x', text: '[email] Alice: hello', memberId: 'y' } | |
| 98 | t.is(keetToEmail(msg, 'bridge-id'), null) | |
| 99 | }) | |
| 100 | ||
| 101 | test('keetToEmail - skip empty', (t) => { | |
| 102 | t.is(keetToEmail({ name: 'x', text: '', memberId: 'y' }, 'b'), null) | |
| 103 | t.is(keetToEmail(null, 'b'), null) | |
| 104 | }) | |
| 105 | ||
| 106 | test('emailToKeetText - basic', (t) => { | |
| 107 | const email = { | |
| 108 | from: 'Alice <alice@example.com>', | |
| 109 | subject: 'Hello', | |
| 110 | body: 'Test message', | |
| 111 | messageId: '' | |
| 112 | } | |
| 113 | const env = emailToEnvelope(email) | |
| 114 | const text = emailToKeetText(env) | |
| 115 | t.ok(text.includes('[email]')) | |
| 116 | t.ok(text.includes('Alice')) | |
| 117 | t.ok(text.includes('Hello')) | |
| 118 | }) | |
| 119 | ||
| 120 | test('emailToKeetText - long email truncated', (t) => { | |
| 121 | const email = { | |
| 122 | from: 'bob@test.org', | |
| 123 | subject: 'Long', | |
| 124 | body: 'x'.repeat(1000), | |
| 125 | messageId: '' | |
| 126 | } | |
| 127 | const env = emailToEnvelope(email) | |
| 128 | const text = emailToKeetText(env) | |
| 129 | t.ok(text.includes('...')) | |
| 130 | t.ok(text.length < 600) | |
| 131 | }) | |
| 132 | ||
| 133 | test('keetToEmailText - format', (t) => { | |
| 134 | const result = keetToEmailText('fleeky', 'Hello world') | |
| 135 | t.is(result.subject, '[Keet] Message from fleeky') | |
| 136 | t.ok(result.body.includes('fleeky wrote')) | |
| 137 | t.ok(result.body.includes('Hello world')) | |
| 138 | t.ok(result.body.includes('bridge-email')) | |
| 139 | }) |
| … | … | @@ -0,0 +1,96 @@ |
| 1 | import test from 'brittle' | |
| 2 | import { parseEmail } from '../lib/imap-client.js' | |
| 3 | ||
| 4 | test('parseEmail - simple email', (t) => { | |
| 5 | const raw = [ | |
| 6 | 'From: Alice <alice@example.com>', | |
| 7 | 'To: bob@example.com', | |
| 8 | 'Subject: Test', | |
| 9 | 'Date: Fri, 14 Feb 2026 10:00:00 +0000', | |
| 10 | 'Message-ID: <abc123@example.com>', | |
| 11 | 'Content-Type: text/plain; charset=utf-8', | |
| 12 | '', | |
| 13 | 'Hello, this is a test email.' | |
| 14 | ].join('\r\n') | |
| 15 | ||
| 16 | const email = parseEmail(raw) | |
| 17 | t.is(email.from, 'Alice <alice@example.com>') | |
| 18 | t.is(email.to, 'bob@example.com') | |
| 19 | t.is(email.subject, 'Test') | |
| 20 | t.is(email.body, 'Hello, this is a test email.') | |
| 21 | t.is(email.messageId, '<abc123@example.com>') | |
| 22 | }) | |
| 23 | ||
| 24 | test('parseEmail - multiline body', (t) => { | |
| 25 | const raw = [ | |
| 26 | 'From: sender@test.com', | |
| 27 | 'Subject: Multi', | |
| 28 | '', | |
| 29 | 'Line 1', | |
| 30 | 'Line 2', | |
| 31 | 'Line 3' | |
| 32 | ].join('\r\n') | |
| 33 | ||
| 34 | const email = parseEmail(raw) | |
| 35 | t.ok(email.body.includes('Line 1')) | |
| 36 | t.ok(email.body.includes('Line 2')) | |
| 37 | t.ok(email.body.includes('Line 3')) | |
| 38 | }) | |
| 39 | ||
| 40 | test('parseEmail - multipart text/plain extraction', (t) => { | |
| 41 | const raw = [ | |
| 42 | 'From: sender@test.com', | |
| 43 | 'Subject: Multipart', | |
| 44 | 'Content-Type: multipart/alternative; boundary="boundary123"', | |
| 45 | '', | |
| 46 | '--boundary123', | |
| 47 | 'Content-Type: text/plain; charset=utf-8', | |
| 48 | '', | |
| 49 | 'Plain text body here', | |
| 50 | '--boundary123', | |
| 51 | 'Content-Type: text/html; charset=utf-8', | |
| 52 | '', | |
| 53 | '<html><body>HTML body</body></html>', | |
| 54 | '--boundary123--' | |
| 55 | ].join('\r\n') | |
| 56 | ||
| 57 | const email = parseEmail(raw) | |
| 58 | t.ok(email.body.includes('Plain text body')) | |
| 59 | t.absent(email.body.includes('HTML body')) | |
| 60 | }) | |
| 61 | ||
| 62 | test('parseEmail - quoted-printable decoding', (t) => { | |
| 63 | const raw = [ | |
| 64 | 'From: sender@test.com', | |
| 65 | 'Subject: QP', | |
| 66 | 'Content-Transfer-Encoding: quoted-printable', | |
| 67 | '', | |
| 68 | 'Hello =C3=A9l=C3=A8ve' | |
| 69 | ].join('\r\n') | |
| 70 | ||
| 71 | const email = parseEmail(raw) | |
| 72 | t.ok(email.body.includes('Hello')) | |
| 73 | // UTF-8 decoded é and è | |
| 74 | t.ok(email.body.includes('élève')) | |
| 75 | }) | |
| 76 | ||
| 77 | test('parseEmail - no headers', (t) => { | |
| 78 | const raw = 'Just a body with no headers at all' | |
| 79 | const email = parseEmail(raw) | |
| 80 | t.is(email.from, '') | |
| 81 | t.is(email.subject, '') | |
| 82 | t.ok(email.body.includes('Just a body')) | |
| 83 | }) | |
| 84 | ||
| 85 | test('parseEmail - base64 encoding', (t) => { | |
| 86 | const raw = [ | |
| 87 | 'From: sender@test.com', | |
| 88 | 'Subject: B64', | |
| 89 | 'Content-Transfer-Encoding: base64', | |
| 90 | '', | |
| 91 | Buffer.from('Decoded base64 content').toString('base64') | |
| 92 | ].join('\r\n') | |
| 93 | ||
| 94 | const email = parseEmail(raw) | |
| 95 | t.is(email.body, 'Decoded base64 content') | |
| 96 | }) |
| … | … | @@ -0,0 +1,11 @@ |
| 1 | import { envelopeCodec } from './lib/codec.js' | |
| 2 | import { createEnvelope, TYPES, PLATFORMS } from './lib/envelope.js' | |
| 3 | ||
| 4 | export { | |
| 5 | envelopeCodec, | |
| 6 | createEnvelope, | |
| 7 | TYPES, | |
| 8 | PLATFORMS | |
| 9 | } | |
| 10 | ||
| 11 | export { encode, decode } from './lib/codec.js' |
| … | … | @@ -0,0 +1,66 @@ |
| 1 | import cenc from 'compact-encoding' | |
| 2 | ||
| 3 | // Compact-encoding codec for bridge envelopes | |
| 4 | // Fields: type(uint), platform(uint), author(string), content(string), | |
| 5 | // timestamp(uint), channel(string), replyTo(string), | |
| 6 | // mediaUrl(string), mediaType(string), editOf(string), | |
| 7 | // deleteOf(string), id(string) | |
| 8 | ||
| 9 | const envelopeCodec = { | |
| 10 | preencode (state, envelope) { | |
| 11 | cenc.uint.preencode(state, envelope.type) | |
| 12 | cenc.uint.preencode(state, envelope.platform) | |
| 13 | cenc.string.preencode(state, envelope.author) | |
| 14 | cenc.string.preencode(state, envelope.content) | |
| 15 | cenc.uint.preencode(state, envelope.timestamp) | |
| 16 | cenc.string.preencode(state, envelope.channel) | |
| 17 | cenc.string.preencode(state, envelope.replyTo || '') | |
| 18 | cenc.string.preencode(state, envelope.mediaUrl || '') | |
| 19 | cenc.string.preencode(state, envelope.mediaType || '') | |
| 20 | cenc.string.preencode(state, envelope.editOf || '') | |
| 21 | cenc.string.preencode(state, envelope.deleteOf || '') | |
| 22 | cenc.string.preencode(state, envelope.id) | |
| 23 | }, | |
| 24 | ||
| 25 | encode (state, envelope) { | |
| 26 | cenc.uint.encode(state, envelope.type) | |
| 27 | cenc.uint.encode(state, envelope.platform) | |
| 28 | cenc.string.encode(state, envelope.author) | |
| 29 | cenc.string.encode(state, envelope.content) | |
| 30 | cenc.uint.encode(state, envelope.timestamp) | |
| 31 | cenc.string.encode(state, envelope.channel) | |
| 32 | cenc.string.encode(state, envelope.replyTo || '') | |
| 33 | cenc.string.encode(state, envelope.mediaUrl || '') | |
| 34 | cenc.string.encode(state, envelope.mediaType || '') | |
| 35 | cenc.string.encode(state, envelope.editOf || '') | |
| 36 | cenc.string.encode(state, envelope.deleteOf || '') | |
| 37 | cenc.string.encode(state, envelope.id) | |
| 38 | }, | |
| 39 | ||
| 40 | decode (state) { | |
| 41 | return { | |
| 42 | type: cenc.uint.decode(state), | |
| 43 | platform: cenc.uint.decode(state), | |
| 44 | author: cenc.string.decode(state), | |
| 45 | content: cenc.string.decode(state), | |
| 46 | timestamp: cenc.uint.decode(state), | |
| 47 | channel: cenc.string.decode(state), | |
| 48 | replyTo: cenc.string.decode(state), | |
| 49 | mediaUrl: cenc.string.decode(state), | |
| 50 | mediaType: cenc.string.decode(state), | |
| 51 | editOf: cenc.string.decode(state), | |
| 52 | deleteOf: cenc.string.decode(state), | |
| 53 | id: cenc.string.decode(state) | |
| 54 | } | |
| 55 | } | |
| 56 | } | |
| 57 | ||
| 58 | function encode (envelope) { | |
| 59 | return cenc.encode(envelopeCodec, envelope) | |
| 60 | } | |
| 61 | ||
| 62 | function decode (buf) { | |
| 63 | return cenc.decode(envelopeCodec, buf) | |
| 64 | } | |
| 65 | ||
| 66 | export { envelopeCodec, encode, decode } |
| … | … | @@ -0,0 +1,79 @@ |
| 1 | import b4a from 'b4a' | |
| 2 | import { createHash } from 'crypto' | |
| 3 | ||
| 4 | // Message types | |
| 5 | const TYPES = { | |
| 6 | TEXT: 0, | |
| 7 | MEDIA: 1, | |
| 8 | REACTION: 2, | |
| 9 | EDIT: 3, | |
| 10 | DELETE: 4, | |
| 11 | SYSTEM: 5, | |
| 12 | REPLY: 6 | |
| 13 | } | |
| 14 | ||
| 15 | // Platform identifiers | |
| 16 | const PLATFORMS = { | |
| 17 | KEET: 0, | |
| 18 | IRC: 1, | |
| 19 | DISCORD: 2, | |
| 20 | MATRIX: 3, | |
| 21 | MASTODON: 4, | |
| 22 | EMAIL: 5, | |
| 23 | ANDROID: 6 | |
| 24 | } | |
| 25 | ||
| 26 | // Reverse maps for display | |
| 27 | const TYPE_NAMES = Object.fromEntries(Object.entries(TYPES).map(([k, v]) => [v, k.toLowerCase()])) | |
| 28 | const PLATFORM_NAMES = Object.fromEntries(Object.entries(PLATFORMS).map(([k, v]) => [v, k.toLowerCase()])) | |
| 29 | ||
| 30 | function contentHash (envelope) { | |
| 31 | const h = createHash('sha256') | |
| 32 | h.update(String(envelope.platform)) | |
| 33 | h.update(envelope.author) | |
| 34 | h.update(envelope.content) | |
| 35 | h.update(String(envelope.timestamp)) | |
| 36 | return h.digest('hex').slice(0, 16) | |
| 37 | } | |
| 38 | ||
| 39 | function createEnvelope (opts) { | |
| 40 | const type = opts.type ?? TYPES.TEXT | |
| 41 | const platform = opts.platform ?? PLATFORMS.KEET | |
| 42 | const author = opts.author || '' | |
| 43 | const content = opts.content || '' | |
| 44 | const timestamp = opts.timestamp || Date.now() | |
| 45 | const channel = opts.channel || '' | |
| 46 | const replyTo = opts.replyTo || '' | |
| 47 | const mediaUrl = opts.mediaUrl || '' | |
| 48 | const mediaType = opts.mediaType || '' | |
| 49 | const editOf = opts.editOf || '' | |
| 50 | const deleteOf = opts.deleteOf || '' | |
| 51 | ||
| 52 | const envelope = { | |
| 53 | type, | |
| 54 | platform, | |
| 55 | author, | |
| 56 | content, | |
| 57 | timestamp, | |
| 58 | channel, | |
| 59 | replyTo, | |
| 60 | mediaUrl, | |
| 61 | mediaType, | |
| 62 | editOf, | |
| 63 | deleteOf, | |
| 64 | id: '' | |
| 65 | } | |
| 66 | ||
| 67 | envelope.id = opts.id || contentHash(envelope) | |
| 68 | ||
| 69 | return envelope | |
| 70 | } | |
| 71 | ||
| 72 | export { | |
| 73 | TYPES, | |
| 74 | PLATFORMS, | |
| 75 | TYPE_NAMES, | |
| 76 | PLATFORM_NAMES, | |
| 77 | createEnvelope, | |
| 78 | contentHash | |
| 79 | } |
| … | … | @@ -0,0 +1,869 @@ |
| 1 | { | |
| 2 | "name": "bridge-envelope", | |
| 3 | "version": "1.0.0", | |
| 4 | "lockfileVersion": 3, | |
| 5 | "requires": true, | |
| 6 | "packages": { | |
| 7 | "": { | |
| 8 | "name": "bridge-envelope", | |
| 9 | "version": "1.0.0", | |
| 10 | "license": "MIT", | |
| 11 | "dependencies": { | |
| 12 | "b4a": "^1.0.0", | |
| 13 | "compact-encoding": "^2.0.0" | |
| 14 | }, | |
| 15 | "devDependencies": { | |
| 16 | "brittle": "^3.0.0" | |
| 17 | } | |
| 18 | }, | |
| 19 | "node_modules/@jridgewell/resolve-uri": { | |
| 20 | "version": "3.1.2", | |
| 21 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", | |
| 22 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", | |
| 23 | "dev": true, | |
| 24 | "license": "MIT", | |
| 25 | "engines": { | |
| 26 | "node": ">=6.0.0" | |
| 27 | } | |
| 28 | }, | |
| 29 | "node_modules/@jridgewell/sourcemap-codec": { | |
| 30 | "version": "1.5.5", | |
| 31 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", | |
| 32 | "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", | |
| 33 | "dev": true, | |
| 34 | "license": "MIT" | |
| 35 | }, | |
| 36 | "node_modules/@jridgewell/trace-mapping": { | |
| 37 | "version": "0.3.31", | |
| 38 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", | |
| 39 | "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", | |
| 40 | "dev": true, | |
| 41 | "license": "MIT", | |
| 42 | "dependencies": { | |
| 43 | "@jridgewell/resolve-uri": "^3.1.0", | |
| 44 | "@jridgewell/sourcemap-codec": "^1.4.14" | |
| 45 | } | |
| 46 | }, | |
| 47 | "node_modules/@types/istanbul-lib-coverage": { | |
| 48 | "version": "2.0.6", | |
| 49 | "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", | |
| 50 | "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", | |
| 51 | "dev": true, | |
| 52 | "license": "MIT" | |
| 53 | }, | |
| 54 | "node_modules/b4a": { | |
| 55 | "version": "1.7.4", | |
| 56 | "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.4.tgz", | |
| 57 | "integrity": "sha512-u20zJLDaSWpxaZ+zaAkEIB2dZZ1o+DF4T/MRbmsvGp9nletHOyiai19OzX1fF8xUBYsO1bPXxODvcd0978pnug==", | |
| 58 | "license": "Apache-2.0", | |
| 59 | "peerDependencies": { | |
| 60 | "react-native-b4a": "*" | |
| 61 | }, | |
| 62 | "peerDependenciesMeta": { | |
| 63 | "react-native-b4a": { | |
| 64 | "optional": true | |
| 65 | } | |
| 66 | } | |
| 67 | }, | |
| 68 | "node_modules/bare-addon-resolve": { | |
| 69 | "version": "1.10.0", | |
| 70 | "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.0.tgz", | |
| 71 | "integrity": "sha512-sSd0jieRJlDaODOzj0oe0RjFVC1QI0ZIjGIdPkbrTXsdVVtENg14c+lHHAhHwmWCZ2nQlMhy8jA3Y5LYPc/isA==", | |
| 72 | "dev": true, | |
| 73 | "license": "Apache-2.0", | |
| 74 | "dependencies": { | |
| 75 | "bare-module-resolve": "^1.10.0", | |
| 76 | "bare-semver": "^1.0.0" | |
| 77 | }, | |
| 78 | "peerDependencies": { | |
| 79 | "bare-url": "*" | |
| 80 | }, | |
| 81 | "peerDependenciesMeta": { | |
| 82 | "bare-url": { | |
| 83 | "optional": true | |
| 84 | } | |
| 85 | } | |
| 86 | }, | |
| 87 | "node_modules/bare-ansi-escapes": { | |
| 88 | "version": "2.2.3", | |
| 89 | "resolved": "https://registry.npmjs.org/bare-ansi-escapes/-/bare-ansi-escapes-2.2.3.tgz", | |
| 90 | "integrity": "sha512-02ES4/E2RbrtZSnHJ9LntBhYkLA6lPpSEeP8iqS3MccBIVhVBlEmruF1I7HZqx5Q8aiTeYfQVeqmrU9YO2yYoQ==", | |
| 91 | "dev": true, | |
| 92 | "license": "Apache-2.0", | |
| 93 | "dependencies": { | |
| 94 | "bare-stream": "^2.6.5" | |
| 95 | }, | |
| 96 | "peerDependencies": { | |
| 97 | "bare-buffer": "*" | |
| 98 | }, | |
| 99 | "peerDependenciesMeta": { | |
| 100 | "bare-buffer": { | |
| 101 | "optional": true | |
| 102 | } | |
| 103 | } | |
| 104 | }, | |
| 105 | "node_modules/bare-assert": { | |
| 106 | "version": "1.2.0", | |
| 107 | "resolved": "https://registry.npmjs.org/bare-assert/-/bare-assert-1.2.0.tgz", | |
| 108 | "integrity": "sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==", | |
| 109 | "dev": true, | |
| 110 | "license": "Apache-2.0", | |
| 111 | "dependencies": { | |
| 112 | "bare-inspect": "^3.1.2" | |
| 113 | } | |
| 114 | }, | |
| 115 | "node_modules/bare-bundle": { | |
| 116 | "version": "1.10.0", | |
| 117 | "resolved": "https://registry.npmjs.org/bare-bundle/-/bare-bundle-1.10.0.tgz", | |
| 118 | "integrity": "sha512-4LVlnJAHr00Hh6Vu6ZUJS38rcEtJT3b3vChXSsBsJ2mk1TN0lQ+gzd+Dw5L0aV7uqDZv84smuwW+O02X7PfDlw==", | |
| 119 | "dev": true, | |
| 120 | "license": "Apache-2.0", | |
| 121 | "peerDependencies": { | |
| 122 | "bare-buffer": "*", | |
| 123 | "bare-url": "*" | |
| 124 | }, | |
| 125 | "peerDependenciesMeta": { | |
| 126 | "bare-buffer": { | |
| 127 | "optional": true | |
| 128 | }, | |
| 129 | "bare-url": { | |
| 130 | "optional": true | |
| 131 | } | |
| 132 | } | |
| 133 | }, | |
| 134 | "node_modules/bare-cov": { | |
| 135 | "version": "1.2.1", | |
| 136 | "resolved": "https://registry.npmjs.org/bare-cov/-/bare-cov-1.2.1.tgz", | |
| 137 | "integrity": "sha512-SHNqb/WG9udUEj8m5qGSehoUQ9o0Xzv8nrf4HIdmG+KNU/XdhPLhTP5VPrHnSAMAXhAzEB4Q+2IYZ0x9qPzN1g==", | |
| 138 | "dev": true, | |
| 139 | "license": "Apache-2.0", | |
| 140 | "dependencies": { | |
| 141 | "bare-fs": "^4.1.2", | |
| 142 | "bare-inspector": "^4.0.1", | |
| 143 | "bare-path": "^3.0.0", | |
| 144 | "bare-process": "^4.2.1", | |
| 145 | "bare-url": "^2.1.5", | |
| 146 | "bare-v8-to-istanbul": "^1.0.2", | |
| 147 | "picomatch": "^4.0.2", | |
| 148 | "which-runtime": "^1.2.1" | |
| 149 | } | |
| 150 | }, | |
| 151 | "node_modules/bare-crypto": { | |
| 152 | "version": "1.13.0", | |
| 153 | "resolved": "https://registry.npmjs.org/bare-crypto/-/bare-crypto-1.13.0.tgz", | |
| 154 | "integrity": "sha512-RQl13yD+YTACWUZHMMck0C6LNjXgGgRyhVC557ag0xIrkxXf4IQwpnc8WOB58f4k5kSzKhIjy05oW3HUBrFpSQ==", | |
| 155 | "dev": true, | |
| 156 | "license": "Apache-2.0", | |
| 157 | "dependencies": { | |
| 158 | "bare-stream": "^2.6.3" | |
| 159 | }, | |
| 160 | "peerDependencies": { | |
| 161 | "bare-buffer": "*" | |
| 162 | }, | |
| 163 | "peerDependenciesMeta": { | |
| 164 | "bare-buffer": { | |
| 165 | "optional": true | |
| 166 | } | |
| 167 | } | |
| 168 | }, | |
| 169 | "node_modules/bare-debug-log": { | |
| 170 | "version": "2.0.0", | |
| 171 | "resolved": "https://registry.npmjs.org/bare-debug-log/-/bare-debug-log-2.0.0.tgz", | |
| 172 | "integrity": "sha512-Vi42PkMQsNV9PUpx2Gl1hikshx5O9FzMJ6o9Nnopseg7qLBBK7Nl31d0RHcfwLEAfmcPApytpc0ZFfq68u22FQ==", | |
| 173 | "dev": true, | |
| 174 | "license": "Apache-2.0", | |
| 175 | "dependencies": { | |
| 176 | "bare-os": "^3.0.1" | |
| 177 | } | |
| 178 | }, | |
| 179 | "node_modules/bare-dns": { | |
| 180 | "version": "2.1.4", | |
| 181 | "resolved": "https://registry.npmjs.org/bare-dns/-/bare-dns-2.1.4.tgz", | |
| 182 | "integrity": "sha512-abwjHmpWqSRNB7V5615QxPH92L71AVzFm/kKTs8VYiNTAi2xVdonpv0BjJ0hwXLwomoW+xsSOPjW6PZPO14asg==", | |
| 183 | "dev": true, | |
| 184 | "license": "Apache-2.0", | |
| 185 | "engines": { | |
| 186 | "bare": ">=1.7.0" | |
| 187 | } | |
| 188 | }, | |
| 189 | "node_modules/bare-encoding": { | |
| 190 | "version": "1.0.3", | |
| 191 | "resolved": "https://registry.npmjs.org/bare-encoding/-/bare-encoding-1.0.3.tgz", | |
| 192 | "integrity": "sha512-Kqf+t/azs13lUeyK4Tb7ha4wdLRXKWCXQ8w1rVmt7KtoPCPdHD/Xwt7LBIsCSwwGglrcmblo5VOLa5avkJqULA==", | |
| 193 | "dev": true, | |
| 194 | "license": "Apache-2.0", | |
| 195 | "peerDependencies": { | |
| 196 | "bare-buffer": "*" | |
| 197 | }, | |
| 198 | "peerDependenciesMeta": { | |
| 199 | "bare-buffer": { | |
| 200 | "optional": true | |
| 201 | } | |
| 202 | } | |
| 203 | }, | |
| 204 | "node_modules/bare-env": { | |
| 205 | "version": "3.0.0", | |
| 206 | "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.0.tgz", | |
| 207 | "integrity": "sha512-0u964P5ZLAxTi+lW4Kjp7YRJQ5gZr9ycYOtjLxsSrupgMz3sn5Z9n4SH/JIifHwvadsf1brA2JAjP+9IOWwTiw==", | |
| 208 | "dev": true, | |
| 209 | "license": "Apache-2.0", | |
| 210 | "dependencies": { | |
| 211 | "bare-os": "^3.0.1" | |
| 212 | } | |
| 213 | }, | |
| 214 | "node_modules/bare-events": { | |
| 215 | "version": "2.8.2", | |
| 216 | "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", | |
| 217 | "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", | |
| 218 | "dev": true, | |
| 219 | "license": "Apache-2.0", | |
| 220 | "peerDependencies": { | |
| 221 | "bare-abort-controller": "*" | |
| 222 | }, | |
| 223 | "peerDependenciesMeta": { | |
| 224 | "bare-abort-controller": { | |
| 225 | "optional": true | |
| 226 | } | |
| 227 | } | |
| 228 | }, | |
| 229 | "node_modules/bare-format": { | |
| 230 | "version": "1.0.1", | |
| 231 | "resolved": "https://registry.npmjs.org/bare-format/-/bare-format-1.0.1.tgz", | |
| 232 | "integrity": "sha512-1oS+LZrWK6tnYnvNSHDGljc2MPunRxwhpFriuCgzNF+oklrnwmBKD91tS0yt+jpl2j3UgcSDzBIMiVTvLs9A8w==", | |
| 233 | "dev": true, | |
| 234 | "license": "Apache-2.0", | |
| 235 | "dependencies": { | |
| 236 | "bare-inspect": "^3.0.0" | |
| 237 | } | |
| 238 | }, | |
| 239 | "node_modules/bare-fs": { | |
| 240 | "version": "4.5.4", | |
| 241 | "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.4.tgz", | |
| 242 | "integrity": "sha512-POK4oplfA7P7gqvetNmCs4CNtm9fNsx+IAh7jH7GgU0OJdge2rso0R20TNWVq6VoWcCvsTdlNDaleLHGaKx8CA==", | |
| 243 | "dev": true, | |
| 244 | "license": "Apache-2.0", | |
| 245 | "dependencies": { | |
| 246 | "bare-events": "^2.5.4", | |
| 247 | "bare-path": "^3.0.0", | |
| 248 | "bare-stream": "^2.6.4", | |
| 249 | "bare-url": "^2.2.2", | |
| 250 | "fast-fifo": "^1.3.2" | |
| 251 | }, | |
| 252 | "engines": { | |
| 253 | "bare": ">=1.16.0" | |
| 254 | }, | |
| 255 | "peerDependencies": { | |
| 256 | "bare-buffer": "*" | |
| 257 | }, | |
| 258 | "peerDependenciesMeta": { | |
| 259 | "bare-buffer": { | |
| 260 | "optional": true | |
| 261 | } | |
| 262 | } | |
| 263 | }, | |
| 264 | "node_modules/bare-hrtime": { | |
| 265 | "version": "2.1.1", | |
| 266 | "resolved": "https://registry.npmjs.org/bare-hrtime/-/bare-hrtime-2.1.1.tgz", | |
| 267 | "integrity": "sha512-VMb3tHo05gsnbu3OXTmkDiwTjMlOsbQmKoysKqKEyR09m77TuDrYFbj3Q5GGk10dAKsUHrnXmwCaeJqzVpB5ZA==", | |
| 268 | "dev": true, | |
| 269 | "license": "Apache-2.0" | |
| 270 | }, | |
| 271 | "node_modules/bare-http-parser": { | |
| 272 | "version": "1.0.1", | |
| 273 | "resolved": "https://registry.npmjs.org/bare-http-parser/-/bare-http-parser-1.0.1.tgz", | |
| 274 | "integrity": "sha512-A3LTDTcELcmNJ3g5liIaS038v/BQxOhA9cjhBESn7eoV7QCuMoIRBKLDadDe08flxyLbxI2f+1l2MZ/5+HnKPA==", | |
| 275 | "dev": true, | |
| 276 | "license": "Apache-2.0" | |
| 277 | }, | |
| 278 | "node_modules/bare-http1": { | |
| 279 | "version": "4.2.3", | |
| 280 | "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.2.3.tgz", | |
| 281 | "integrity": "sha512-zyvHF3xzvBJNpuw0QydE9etTn2AyC/TRVDWTo8Ed3LQEEGaFOkv6KHhaJPlf4eWEeTekk41Fowie+fTsXoNxkA==", | |
| 282 | "dev": true, | |
| 283 | "license": "Apache-2.0", | |
| 284 | "dependencies": { | |
| 285 | "bare-events": "^2.6.0", | |
| 286 | "bare-http-parser": "^1.0.0", | |
| 287 | "bare-stream": "^2.3.0", | |
| 288 | "bare-tcp": "^2.2.0" | |
| 289 | }, | |
| 290 | "peerDependencies": { | |
| 291 | "bare-buffer": "*", | |
| 292 | "bare-url": "*" | |
| 293 | }, | |
| 294 | "peerDependenciesMeta": { | |
| 295 | "bare-buffer": { | |
| 296 | "optional": true | |
| 297 | }, | |
| 298 | "bare-url": { | |
| 299 | "optional": true | |
| 300 | } | |
| 301 | } | |
| 302 | }, | |
| 303 | "node_modules/bare-https": { | |
| 304 | "version": "2.1.2", | |
| 305 | "resolved": "https://registry.npmjs.org/bare-https/-/bare-https-2.1.2.tgz", | |
| 306 | "integrity": "sha512-Q+TTydUDsuKQJvh8dX2dvOXCR9fM3xR5TBmKaFrs5p7Lj7XbKX7v4vIUJ36H0SXg2xCOQxXKIbjwrLg5tfJNYg==", | |
| 307 | "dev": true, | |
| 308 | "license": "Apache-2.0", | |
| 309 | "dependencies": { | |
| 310 | "bare-http1": "^4.0.0", | |
| 311 | "bare-tcp": "^2.2.0", | |
| 312 | "bare-tls": "^2.0.0" | |
| 313 | } | |
| 314 | }, | |
| 315 | "node_modules/bare-inspect": { | |
| 316 | "version": "3.1.4", | |
| 317 | "resolved": "https://registry.npmjs.org/bare-inspect/-/bare-inspect-3.1.4.tgz", | |
| 318 | "integrity": "sha512-jfW5KRA84o3REpI6Vr4nbvMn+hqVAw8GU1mMdRwUsY5yJovQamxYeKGVKGqdzs+8ZbG4jRzGUXP/3Ji/DnqfPg==", | |
| 319 | "dev": true, | |
| 320 | "license": "Apache-2.0", | |
| 321 | "dependencies": { | |
| 322 | "bare-ansi-escapes": "^2.1.0", | |
| 323 | "bare-type": "^1.0.0" | |
| 324 | }, | |
| 325 | "engines": { | |
| 326 | "bare": ">=1.18.0" | |
| 327 | } | |
| 328 | }, | |
| 329 | "node_modules/bare-inspector": { | |
| 330 | "version": "4.0.3", | |
| 331 | "resolved": "https://registry.npmjs.org/bare-inspector/-/bare-inspector-4.0.3.tgz", | |
| 332 | "integrity": "sha512-yYk6+7tTVKJKHOja5+QBn989ETv8oVsmwoH0nCM+QYC6w5MC03GC4aQNK+lipAZSz1EaAj42WZ8Wewjpy0L9TA==", | |
| 333 | "dev": true, | |
| 334 | "license": "Apache-2.0", | |
| 335 | "dependencies": { | |
| 336 | "bare-events": "^2.1.0", | |
| 337 | "bare-http1": "^4.0.0", | |
| 338 | "bare-stream": "^2.0.0", | |
| 339 | "bare-url": "^2.0.0", | |
| 340 | "bare-ws": "^2.0.0" | |
| 341 | }, | |
| 342 | "engines": { | |
| 343 | "bare": ">=1.2.0" | |
| 344 | }, | |
| 345 | "peerDependencies": { | |
| 346 | "bare-tcp": "*" | |
| 347 | }, | |
| 348 | "peerDependenciesMeta": { | |
| 349 | "bare-tcp": { | |
| 350 | "optional": true | |
| 351 | } | |
| 352 | } | |
| 353 | }, | |
| 354 | "node_modules/bare-module": { | |
| 355 | "version": "6.1.3", | |
| 356 | "resolved": "https://registry.npmjs.org/bare-module/-/bare-module-6.1.3.tgz", | |
| 357 | "integrity": "sha512-5XWsVHsvtWMH4tK4DQWgpNTV0t/sg3ZrAaQLIxrwjrS5+u8Q9vEgc/zQ4QaDPWDse/y/5h+d+YG1Q0JfSMt0zA==", | |
| 358 | "dev": true, | |
| 359 | "license": "Apache-2.0", | |
| 360 | "dependencies": { | |
| 361 | "bare-bundle": "^1.3.0", | |
| 362 | "bare-module-lexer": "^1.0.0", | |
| 363 | "bare-module-resolve": "^1.8.0", | |
| 364 | "bare-path": "^3.0.0", | |
| 365 | "bare-url": "^2.0.1" | |
| 366 | }, | |
| 367 | "engines": { | |
| 368 | "bare": ">=1.23.0" | |
| 369 | }, | |
| 370 | "peerDependencies": { | |
| 371 | "bare-buffer": "*" | |
| 372 | }, | |
| 373 | "peerDependenciesMeta": { | |
| 374 | "bare-buffer": { | |
| 375 | "optional": true | |
| 376 | } | |
| 377 | } | |
| 378 | }, | |
| 379 | "node_modules/bare-module-lexer": { | |
| 380 | "version": "1.4.7", | |
| 381 | "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.4.7.tgz", | |
| 382 | "integrity": "sha512-0klU4eMsjh/wcxi8FdHmNom2j2F4kmkXOhyJFL9qTaSFp2lE3m6BtbKgMHY8R5miqC9r8/IfA8wzXnC5Os14WA==", | |
| 383 | "dev": true, | |
| 384 | "license": "Apache-2.0", | |
| 385 | "dependencies": { | |
| 386 | "require-addon": "^1.0.2" | |
| 387 | }, | |
| 388 | "peerDependencies": { | |
| 389 | "bare-buffer": "*" | |
| 390 | }, | |
| 391 | "peerDependenciesMeta": { | |
| 392 | "bare-buffer": { | |
| 393 | "optional": true | |
| 394 | } | |
| 395 | } | |
| 396 | }, | |
| 397 | "node_modules/bare-module-resolve": { | |
| 398 | "version": "1.12.1", | |
| 399 | "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.1.tgz", | |
| 400 | "integrity": "sha512-hbmAPyFpEq8FoZMd5sFO3u6MC5feluWoGE8YKlA8fCrl6mNtx68Wjg4DTiDJcqRJaovTvOYKfYngoBUnbaT7eg==", | |
| 401 | "dev": true, | |
| 402 | "license": "Apache-2.0", | |
| 403 | "dependencies": { | |
| 404 | "bare-semver": "^1.0.0" | |
| 405 | }, | |
| 406 | "peerDependencies": { | |
| 407 | "bare-url": "*" | |
| 408 | }, | |
| 409 | "peerDependenciesMeta": { | |
| 410 | "bare-url": { | |
| 411 | "optional": true | |
| 412 | } | |
| 413 | } | |
| 414 | }, | |
| 415 | "node_modules/bare-net": { | |
| 416 | "version": "2.2.0", | |
| 417 | "resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.2.0.tgz", | |
| 418 | "integrity": "sha512-UF7cAbHsGE+H6uEqWF5IULBow1x58chZz4g3ALgHtv7wZsFcCbRDt0JKWEumf5Oma3QWS1Q6aLi0Rpll8RElMg==", | |
| 419 | "dev": true, | |
| 420 | "license": "Apache-2.0", | |
| 421 | "dependencies": { | |
| 422 | "bare-events": "^2.2.2", | |
| 423 | "bare-pipe": "^4.0.0", | |
| 424 | "bare-stream": "^2.0.0", | |
| 425 | "bare-tcp": "^2.0.0" | |
| 426 | } | |
| 427 | }, | |
| 428 | "node_modules/bare-os": { | |
| 429 | "version": "3.6.2", | |
| 430 | "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", | |
| 431 | "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", | |
| 432 | "dev": true, | |
| 433 | "license": "Apache-2.0", | |
| 434 | "engines": { | |
| 435 | "bare": ">=1.14.0" | |
| 436 | } | |
| 437 | }, | |
| 438 | "node_modules/bare-path": { | |
| 439 | "version": "3.0.0", | |
| 440 | "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", | |
| 441 | "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", | |
| 442 | "dev": true, | |
| 443 | "license": "Apache-2.0", | |
| 444 | "dependencies": { | |
| 445 | "bare-os": "^3.0.1" | |
| 446 | } | |
| 447 | }, | |
| 448 | "node_modules/bare-pipe": { | |
| 449 | "version": "4.1.3", | |
| 450 | "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.1.3.tgz", | |
| 451 | "integrity": "sha512-DqQsx93rAzre6yJ9T6l/Vgh+X+bntkVMB1X5ggtXjXtqtMmF2Y2RVlCzxxy/09R6yeR9FSWBEUIaMYJL1/5VDA==", | |
| 452 | "dev": true, | |
| 453 | "license": "Apache-2.0", | |
| 454 | "dependencies": { | |
| 455 | "bare-events": "^2.0.0", | |
| 456 | "bare-stream": "^2.0.0" | |
| 457 | }, | |
| 458 | "engines": { | |
| 459 | "bare": ">=1.16.0" | |
| 460 | } | |
| 461 | }, | |
| 462 | "node_modules/bare-process": { | |
| 463 | "version": "4.2.2", | |
| 464 | "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.2.2.tgz", | |
| 465 | "integrity": "sha512-ikzEw+HGLB+2lS/WLVEsqi8BXBwzleG3n7DYI0v6/YNklvNabOIGlLd1dof+7Jw5ob4jsBRPtyflweVahY7rFA==", | |
| 466 | "dev": true, | |
| 467 | "license": "Apache-2.0", | |
| 468 | "dependencies": { | |
| 469 | "bare-env": "^3.0.0", | |
| 470 | "bare-events": "^2.3.1", | |
| 471 | "bare-hrtime": "^2.0.0", | |
| 472 | "bare-os": "^3.5.0", | |
| 473 | "bare-pipe": "^4.0.0", | |
| 474 | "bare-signals": "^4.0.0", | |
| 475 | "bare-tty": "^5.0.0" | |
| 476 | } | |
| 477 | }, | |
| 478 | "node_modules/bare-semver": { | |
| 479 | "version": "1.0.2", | |
| 480 | "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.0.2.tgz", | |
| 481 | "integrity": "sha512-ESVaN2nzWhcI5tf3Zzcq9aqCZ676VWzqw07eEZ0qxAcEOAFYBa0pWq8sK34OQeHLY3JsfKXZS9mDyzyxGjeLzA==", | |
| 482 | "dev": true, | |
| 483 | "license": "Apache-2.0" | |
| 484 | }, | |
| 485 | "node_modules/bare-signals": { | |
| 486 | "version": "4.2.0", | |
| 487 | "resolved": "https://registry.npmjs.org/bare-signals/-/bare-signals-4.2.0.tgz", | |
| 488 | "integrity": "sha512-fNHMOdQIlYuTvMB3Oh9Apk99hLKn351+Ir8vz+khiPTcOqIyGG4uWWjdLTzxWdYGsA0eT+We3y0K74hjj2nq7A==", | |
| 489 | "dev": true, | |
| 490 | "license": "Apache-2.0", | |
| 491 | "dependencies": { | |
| 492 | "bare-events": "^2.5.3", | |
| 493 | "bare-os": "^3.3.1" | |
| 494 | }, | |
| 495 | "engines": { | |
| 496 | "bare": ">=1.7.0" | |
| 497 | } | |
| 498 | }, | |
| 499 | "node_modules/bare-stream": { | |
| 500 | "version": "2.7.0", | |
| 501 | "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", | |
| 502 | "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", | |
| 503 | "dev": true, | |
| 504 | "license": "Apache-2.0", | |
| 505 | "dependencies": { | |
| 506 | "streamx": "^2.21.0" | |
| 507 | }, | |
| 508 | "peerDependencies": { | |
| 509 | "bare-buffer": "*", | |
| 510 | "bare-events": "*" | |
| 511 | }, | |
| 512 | "peerDependenciesMeta": { | |
| 513 | "bare-buffer": { | |
| 514 | "optional": true | |
| 515 | }, | |
| 516 | "bare-events": { | |
| 517 | "optional": true | |
| 518 | } | |
| 519 | } | |
| 520 | }, | |
| 521 | "node_modules/bare-subprocess": { | |
| 522 | "version": "5.2.2", | |
| 523 | "resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.2.tgz", | |
| 524 | "integrity": "sha512-L6oXgQ1aWs25RtG5Ky0bDD06p3RAcVVrDDMWb1DfXpHtyEWxamcyIWUbSykxMTWrpLlmSj6ytbb6yoKehGFfmw==", | |
| 525 | "dev": true, | |
| 526 | "license": "Apache-2.0", | |
| 527 | "dependencies": { | |
| 528 | "bare-env": "^3.0.0", | |
| 529 | "bare-events": "^2.5.4", | |
| 530 | "bare-os": "^3.0.1", | |
| 531 | "bare-pipe": "^4.0.0", | |
| 532 | "bare-url": "^2.2.2" | |
| 533 | }, | |
| 534 | "engines": { | |
| 535 | "bare": ">=1.7.0" | |
| 536 | }, | |
| 537 | "peerDependencies": { | |
| 538 | "bare-buffer": "*" | |
| 539 | }, | |
| 540 | "peerDependenciesMeta": { | |
| 541 | "bare-buffer": { | |
| 542 | "optional": true | |
| 543 | } | |
| 544 | } | |
| 545 | }, | |
| 546 | "node_modules/bare-tcp": { | |
| 547 | "version": "2.2.4", | |
| 548 | "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.2.4.tgz", | |
| 549 | "integrity": "sha512-vGjfMkyhS1lxp+Y4+gXRDR1ijZMK2cKx0RBwtYKUuZbD90ARCoM6bAiTa1HBztumCIC+NXb8eXdMFXyuYwlPyw==", | |
| 550 | "dev": true, | |
| 551 | "license": "Apache-2.0", | |
| 552 | "dependencies": { | |
| 553 | "bare-dns": "^2.0.4", | |
| 554 | "bare-events": "^2.5.4", | |
| 555 | "bare-stream": "^2.6.4" | |
| 556 | }, | |
| 557 | "engines": { | |
| 558 | "bare": ">=1.16.0" | |
| 559 | } | |
| 560 | }, | |
| 561 | "node_modules/bare-tls": { | |
| 562 | "version": "2.1.7", | |
| 563 | "resolved": "https://registry.npmjs.org/bare-tls/-/bare-tls-2.1.7.tgz", | |
| 564 | "integrity": "sha512-h6wcNXQdBeTX7fed9tjPp0/9cA/QfcBTv3ItgjnbUk4rWAU8bEFalZCZnUDdCK/t9zrNfJ+yvcPx4D/1Y6biyA==", | |
| 565 | "dev": true, | |
| 566 | "license": "Apache-2.0", | |
| 567 | "dependencies": { | |
| 568 | "bare-net": "^2.0.1", | |
| 569 | "bare-stream": "^2.6.4" | |
| 570 | }, | |
| 571 | "engines": { | |
| 572 | "bare": ">=1.7.0" | |
| 573 | } | |
| 574 | }, | |
| 575 | "node_modules/bare-tty": { | |
| 576 | "version": "5.0.3", | |
| 577 | "resolved": "https://registry.npmjs.org/bare-tty/-/bare-tty-5.0.3.tgz", | |
| 578 | "integrity": "sha512-jW24RBWRZOMHuSEWC9mh8wUKO5WUNl0UWUTNPn3yZ8qkOqwa8vaV2dR3a+BvblONPk7V35wuvK9P8508+judAg==", | |
| 579 | "dev": true, | |
| 580 | "license": "Apache-2.0", | |
| 581 | "dependencies": { | |
| 582 | "bare-events": "^2.2.0", | |
| 583 | "bare-signals": "^4.0.0", | |
| 584 | "bare-stream": "^2.0.0" | |
| 585 | }, | |
| 586 | "engines": { | |
| 587 | "bare": ">=1.16.0" | |
| 588 | } | |
| 589 | }, | |
| 590 | "node_modules/bare-type": { | |
| 591 | "version": "1.1.0", | |
| 592 | "resolved": "https://registry.npmjs.org/bare-type/-/bare-type-1.1.0.tgz", | |
| 593 | "integrity": "sha512-LdtnnEEYldOc87Dr4GpsKnStStZk3zfgoEMXy8yvEZkXrcCv9RtYDrUYWFsBQHtaB0s1EUWmcvS6XmEZYIj3Bw==", | |
| 594 | "dev": true, | |
| 595 | "license": "Apache-2.0", | |
| 596 | "engines": { | |
| 597 | "bare": ">=1.2.0" | |
| 598 | } | |
| 599 | }, | |
| 600 | "node_modules/bare-url": { | |
| 601 | "version": "2.3.2", | |
| 602 | "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", | |
| 603 | "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", | |
| 604 | "dev": true, | |
| 605 | "license": "Apache-2.0", | |
| 606 | "peer": true, | |
| 607 | "dependencies": { | |
| 608 | "bare-path": "^3.0.0" | |
| 609 | } | |
| 610 | }, | |
| 611 | "node_modules/bare-utils": { | |
| 612 | "version": "1.5.1", | |
| 613 | "resolved": "https://registry.npmjs.org/bare-utils/-/bare-utils-1.5.1.tgz", | |
| 614 | "integrity": "sha512-mxCkFvmDU3mlD/mb+pT64kKXOsx2KMsWLQbngN1LB+NOXfhfnRnyvpy3VZc6m7gzQxe57Bsi+aTCBqA4/S3elQ==", | |
| 615 | "dev": true, | |
| 616 | "license": "Apache-2.0", | |
| 617 | "dependencies": { | |
| 618 | "bare-debug-log": "^2.0.0", | |
| 619 | "bare-encoding": "^1.0.0", | |
| 620 | "bare-format": "^1.0.0", | |
| 621 | "bare-inspect": "^3.0.0", | |
| 622 | "bare-type": "^1.0.6" | |
| 623 | } | |
| 624 | }, | |
| 625 | "node_modules/bare-v8-to-istanbul": { | |
| 626 | "version": "1.0.2", | |
| 627 | "resolved": "https://registry.npmjs.org/bare-v8-to-istanbul/-/bare-v8-to-istanbul-1.0.2.tgz", | |
| 628 | "integrity": "sha512-CM0wPgyQtXTQ7h4q8NjbTZ/FH5he0Jkiwgpq0lFZZA5tkVZzmQ/9ul4R7E+cNlGZJIZlQZ9v4CwbgVaFG3zOAA==", | |
| 629 | "dev": true, | |
| 630 | "license": "Apache-2.0", | |
| 631 | "dependencies": { | |
| 632 | "bare-assert": "^1.0.2", | |
| 633 | "bare-fs": "^4.1.2", | |
| 634 | "bare-module": "^6.1.2", | |
| 635 | "bare-path": "^3.0.0", | |
| 636 | "bare-process": "^4.2.0", | |
| 637 | "bare-url": "^2.1.5", | |
| 638 | "bare-utils": "^1.2.0", | |
| 639 | "v8-to-istanbul": "^9.3.0", | |
| 640 | "which-runtime": "^1.2.1" | |
| 641 | } | |
| 642 | }, | |
| 643 | "node_modules/bare-ws": { | |
| 644 | "version": "2.0.4", | |
| 645 | "resolved": "https://registry.npmjs.org/bare-ws/-/bare-ws-2.0.4.tgz", | |
| 646 | "integrity": "sha512-SQMXzBYna9dRj57Dz1/ag+VWHCRXbfCjMHgyfM2F2lhkVLzMjnVSZP72aVeFWPFqe494Rd70Kzhe2JElGwFlJQ==", | |
| 647 | "dev": true, | |
| 648 | "license": "Apache-2.0", | |
| 649 | "dependencies": { | |
| 650 | "bare-crypto": "^1.2.0", | |
| 651 | "bare-events": "^2.3.1", | |
| 652 | "bare-http1": "^4.0.0", | |
| 653 | "bare-https": "^2.0.0", | |
| 654 | "bare-stream": "^2.1.2" | |
| 655 | }, | |
| 656 | "peerDependencies": { | |
| 657 | "bare-buffer": "*", | |
| 658 | "bare-url": "*" | |
| 659 | }, | |
| 660 | "peerDependenciesMeta": { | |
| 661 | "bare-buffer": { | |
| 662 | "optional": true | |
| 663 | }, | |
| 664 | "bare-url": { | |
| 665 | "optional": true | |
| 666 | } | |
| 667 | } | |
| 668 | }, | |
| 669 | "node_modules/brittle": { | |
| 670 | "version": "3.19.1", | |
| 671 | "resolved": "https://registry.npmjs.org/brittle/-/brittle-3.19.1.tgz", | |
| 672 | "integrity": "sha512-4Ted1Mt9o9B6oIA6ImJJCtB/Fv++ZO3IDNQgRcHOXWSYGRUidgxchaGrUBaRn+4mlneXrgInPkCPzi0jO8qKbA==", | |
| 673 | "dev": true, | |
| 674 | "license": "Apache-2.0", | |
| 675 | "dependencies": { | |
| 676 | "b4a": "^1.6.0", | |
| 677 | "bare-assert": "^1.0.2", | |
| 678 | "bare-cov": "^1.1.0", | |
| 679 | "bare-fs": "^4.1.6", | |
| 680 | "bare-os": "^3.6.1", | |
| 681 | "bare-path": "^3.0.0", | |
| 682 | "bare-process": "^4.2.1", | |
| 683 | "bare-subprocess": "^5.0.0", | |
| 684 | "bare-url": "^2.1.6", | |
| 685 | "error-stack-parser": "^2.1.4", | |
| 686 | "globbie": "^1.0.2", | |
| 687 | "paparam": "^1.6.2", | |
| 688 | "same-object": "^1.0.2", | |
| 689 | "test-tmp": "^1.4.0", | |
| 690 | "tmatch": "^5.0.0" | |
| 691 | }, | |
| 692 | "bin": { | |
| 693 | "brittle": "bin/node.js", | |
| 694 | "brittle-bare": "bin/bare.js", | |
| 695 | "brittle-node": "bin/node.js", | |
| 696 | "brittle-pear": "bin/pear.js" | |
| 697 | } | |
| 698 | }, | |
| 699 | "node_modules/compact-encoding": { | |
| 700 | "version": "2.18.0", | |
| 701 | "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-2.18.0.tgz", | |
| 702 | "integrity": "sha512-goACAOlhMI2xo5jGOMUDfOLnGdRE1jGfyZ+zie8N5114nHrbPIqf6GLUtzbLof6DSyrERlYRm3EcBplte5LcQw==", | |
| 703 | "license": "Apache-2.0", | |
| 704 | "dependencies": { | |
| 705 | "b4a": "^1.3.0" | |
| 706 | } | |
| 707 | }, | |
| 708 | "node_modules/convert-source-map": { | |
| 709 | "version": "2.0.0", | |
| 710 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", | |
| 711 | "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", | |
| 712 | "dev": true, | |
| 713 | "license": "MIT" | |
| 714 | }, | |
| 715 | "node_modules/error-stack-parser": { | |
| 716 | "version": "2.1.4", | |
| 717 | "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", | |
| 718 | "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", | |
| 719 | "dev": true, | |
| 720 | "license": "MIT", | |
| 721 | "dependencies": { | |
| 722 | "stackframe": "^1.3.4" | |
| 723 | } | |
| 724 | }, | |
| 725 | "node_modules/events-universal": { | |
| 726 | "version": "1.0.1", | |
| 727 | "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", | |
| 728 | "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", | |
| 729 | "dev": true, | |
| 730 | "license": "Apache-2.0", | |
| 731 | "dependencies": { | |
| 732 | "bare-events": "^2.7.0" | |
| 733 | } | |
| 734 | }, | |
| 735 | "node_modules/fast-fifo": { | |
| 736 | "version": "1.3.2", | |
| 737 | "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", | |
| 738 | "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", | |
| 739 | "dev": true, | |
| 740 | "license": "MIT" | |
| 741 | }, | |
| 742 | "node_modules/globbie": { | |
| 743 | "version": "1.0.3", | |
| 744 | "resolved": "https://registry.npmjs.org/globbie/-/globbie-1.0.3.tgz", | |
| 745 | "integrity": "sha512-hcryJmKcftf82xfBWTWxuUITWIIoYO3ec104V17SMHGFl6VLbm1d1Ju9LX9L+jyJTwICpemX/dmPI/HGYoKr1A==", | |
| 746 | "dev": true, | |
| 747 | "license": "Apache-2.0", | |
| 748 | "dependencies": { | |
| 749 | "bare-fs": "^4.1.2", | |
| 750 | "bare-path": "^3.0.0", | |
| 751 | "bare-process": "^4.2.1", | |
| 752 | "picomatch": "^4.0.2" | |
| 753 | } | |
| 754 | }, | |
| 755 | "node_modules/paparam": { | |
| 756 | "version": "1.10.0", | |
| 757 | "resolved": "https://registry.npmjs.org/paparam/-/paparam-1.10.0.tgz", | |
| 758 | "integrity": "sha512-p36QQrwU3X6fj5d2JN20B4lcZI/O3fxMVUHc1xD7MNe5bcOf3jIgE0CIK5Zv/s5YAUGlobTJ/kKXrh1FJVrs3w==", | |
| 759 | "dev": true, | |
| 760 | "license": "Apache-2.0" | |
| 761 | }, | |
| 762 | "node_modules/picomatch": { | |
| 763 | "version": "4.0.3", | |
| 764 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", | |
| 765 | "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", | |
| 766 | "dev": true, | |
| 767 | "license": "MIT", | |
| 768 | "engines": { | |
| 769 | "node": ">=12" | |
| 770 | }, | |
| 771 | "funding": { | |
| 772 | "url": "https://github.com/sponsors/jonschlinkert" | |
| 773 | } | |
| 774 | }, | |
| 775 | "node_modules/require-addon": { | |
| 776 | "version": "1.2.0", | |
| 777 | "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz", | |
| 778 | "integrity": "sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA==", | |
| 779 | "dev": true, | |
| 780 | "license": "Apache-2.0", | |
| 781 | "dependencies": { | |
| 782 | "bare-addon-resolve": "^1.3.0" | |
| 783 | }, | |
| 784 | "engines": { | |
| 785 | "bare": ">=1.10.0" | |
| 786 | } | |
| 787 | }, | |
| 788 | "node_modules/same-object": { | |
| 789 | "version": "1.0.2", | |
| 790 | "resolved": "https://registry.npmjs.org/same-object/-/same-object-1.0.2.tgz", | |
| 791 | "integrity": "sha512-csHWhvUsLbIOHDM/nP+KHWM+BLPsIzWkFa8HbzaI0G7BqKXgx+7FJpKTGgLXyz5amfdY2OVBcmXTqYOMEk04og==", | |
| 792 | "dev": true, | |
| 793 | "license": "MIT" | |
| 794 | }, | |
| 795 | "node_modules/stackframe": { | |
| 796 | "version": "1.3.4", | |
| 797 | "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", | |
| 798 | "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", | |
| 799 | "dev": true, | |
| 800 | "license": "MIT" | |
| 801 | }, | |
| 802 | "node_modules/streamx": { | |
| 803 | "version": "2.23.0", | |
| 804 | "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", | |
| 805 | "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", | |
| 806 | "dev": true, | |
| 807 | "license": "MIT", | |
| 808 | "dependencies": { | |
| 809 | "events-universal": "^1.0.0", | |
| 810 | "fast-fifo": "^1.3.2", | |
| 811 | "text-decoder": "^1.1.0" | |
| 812 | } | |
| 813 | }, | |
| 814 | "node_modules/test-tmp": { | |
| 815 | "version": "1.4.0", | |
| 816 | "resolved": "https://registry.npmjs.org/test-tmp/-/test-tmp-1.4.0.tgz", | |
| 817 | "integrity": "sha512-GVggxGg+jXqP2Wbju50JVLo+9E+nIOPPyWqgr63EbOnNItIKu1cEbJpTWAJeflnyGqXOtcMI7ijHRp88GUkfDA==", | |
| 818 | "dev": true, | |
| 819 | "license": "MIT", | |
| 820 | "dependencies": { | |
| 821 | "bare-fs": "^4.0.1", | |
| 822 | "bare-os": "^3.3.0", | |
| 823 | "bare-path": "^3.0.0" | |
| 824 | } | |
| 825 | }, | |
| 826 | "node_modules/text-decoder": { | |
| 827 | "version": "1.2.6", | |
| 828 | "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.6.tgz", | |
| 829 | "integrity": "sha512-27FeW5GQFDfw0FpwMQhMagB7BztOOlmjcSRi97t2oplhKVTZtp0DZbSegSaXS5IIC6mxMvBG4AR1Sgc6BX3CQg==", | |
| 830 | "dev": true, | |
| 831 | "license": "Apache-2.0", | |
| 832 | "dependencies": { | |
| 833 | "b4a": "^1.6.4" | |
| 834 | } | |
| 835 | }, | |
| 836 | "node_modules/tmatch": { | |
| 837 | "version": "5.0.0", | |
| 838 | "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-5.0.0.tgz", | |
| 839 | "integrity": "sha512-Ib9OtBkpHn07tXP04SlN1SYRxFgTk6wSM2EBmjjxug4u5RXPRVLkdFJSS1PmrQidaSB8Lru9nRtViQBsbxzE5Q==", | |
| 840 | "dev": true, | |
| 841 | "license": "ISC", | |
| 842 | "engines": { | |
| 843 | "node": ">=8" | |
| 844 | } | |
| 845 | }, | |
| 846 | "node_modules/v8-to-istanbul": { | |
| 847 | "version": "9.3.0", | |
| 848 | "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", | |
| 849 | "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", | |
| 850 | "dev": true, | |
| 851 | "license": "ISC", | |
| 852 | "dependencies": { | |
| 853 | "@jridgewell/trace-mapping": "^0.3.12", | |
| 854 | "@types/istanbul-lib-coverage": "^2.0.1", | |
| 855 | "convert-source-map": "^2.0.0" | |
| 856 | }, | |
| 857 | "engines": { | |
| 858 | "node": ">=10.12.0" | |
| 859 | } | |
| 860 | }, | |
| 861 | "node_modules/which-runtime": { | |
| 862 | "version": "1.3.2", | |
| 863 | "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.3.2.tgz", | |
| 864 | "integrity": "sha512-5kwCfWml7+b2NO7KrLMhYihjRx0teKkd3yGp1Xk5Vaf2JGdSh+rgVhEALAD9c/59dP+YwJHXoEO7e8QPy7gOkw==", | |
| 865 | "dev": true, | |
| 866 | "license": "Apache-2.0" | |
| 867 | } | |
| 868 | } | |
| 869 | } |
| … | … | @@ -0,0 +1,26 @@ |
| 1 | { | |
| 2 | "name": "bridge-envelope", | |
| 3 | "version": "1.0.0", | |
| 4 | "description": "Universal message schema + compact-encoding codec for P2P bridges", | |
| 5 | "type": "module", | |
| 6 | "main": "index.js", | |
| 7 | "exports": { | |
| 8 | ".": "./index.js", | |
| 9 | "./codec": "./lib/codec.js" | |
| 10 | }, | |
| 11 | "files": [ | |
| 12 | "index.js", | |
| 13 | "lib/" | |
| 14 | ], | |
| 15 | "scripts": { | |
| 16 | "test": "brittle test/*.test.js" | |
| 17 | }, | |
| 18 | "dependencies": { | |
| 19 | "compact-encoding": "^2.0.0", | |
| 20 | "b4a": "^1.0.0" | |
| 21 | }, | |
| 22 | "devDependencies": { | |
| 23 | "brittle": "^3.0.0" | |
| 24 | }, | |
| 25 | "license": "MIT" | |
| 26 | } |
| … | … | @@ -0,0 +1,238 @@ |
| 1 | import test from 'brittle' | |
| 2 | import { createEnvelope, TYPES, PLATFORMS, encode, decode, envelopeCodec } from '../index.js' | |
| 3 | import cenc from 'compact-encoding' | |
| 4 | ||
| 5 | test('createEnvelope - basic text message', (t) => { | |
| 6 | const env = createEnvelope({ | |
| 7 | type: TYPES.TEXT, | |
| 8 | platform: PLATFORMS.IRC, | |
| 9 | author: 'testuser', | |
| 10 | content: 'Hello from IRC!', | |
| 11 | channel: '#test' | |
| 12 | }) | |
| 13 | ||
| 14 | t.is(env.type, TYPES.TEXT) | |
| 15 | t.is(env.platform, PLATFORMS.IRC) | |
| 16 | t.is(env.author, 'testuser') | |
| 17 | t.is(env.content, 'Hello from IRC!') | |
| 18 | t.is(env.channel, '#test') | |
| 19 | t.ok(env.id, 'has content-hash id') | |
| 20 | t.ok(env.timestamp > 0, 'has timestamp') | |
| 21 | }) | |
| 22 | ||
| 23 | test('createEnvelope - defaults', (t) => { | |
| 24 | const env = createEnvelope({ content: 'test' }) | |
| 25 | ||
| 26 | t.is(env.type, TYPES.TEXT) | |
| 27 | t.is(env.platform, PLATFORMS.KEET) | |
| 28 | t.is(env.author, '') | |
| 29 | t.is(env.replyTo, '') | |
| 30 | t.is(env.mediaUrl, '') | |
| 31 | t.is(env.editOf, '') | |
| 32 | t.is(env.deleteOf, '') | |
| 33 | }) | |
| 34 | ||
| 35 | test('createEnvelope - custom id', (t) => { | |
| 36 | const env = createEnvelope({ content: 'test', id: 'custom-123' }) | |
| 37 | t.is(env.id, 'custom-123') | |
| 38 | }) | |
| 39 | ||
| 40 | test('createEnvelope - content hash is deterministic', (t) => { | |
| 41 | const ts = Date.now() | |
| 42 | const a = createEnvelope({ platform: PLATFORMS.IRC, author: 'u', content: 'msg', timestamp: ts }) | |
| 43 | const b = createEnvelope({ platform: PLATFORMS.IRC, author: 'u', content: 'msg', timestamp: ts }) | |
| 44 | t.is(a.id, b.id) | |
| 45 | }) | |
| 46 | ||
| 47 | test('createEnvelope - different content gives different hash', (t) => { | |
| 48 | const ts = Date.now() | |
| 49 | const a = createEnvelope({ author: 'u', content: 'msg1', timestamp: ts }) | |
| 50 | const b = createEnvelope({ author: 'u', content: 'msg2', timestamp: ts }) | |
| 51 | t.not(a.id, b.id) | |
| 52 | }) | |
| 53 | ||
| 54 | test('createEnvelope - all message types', (t) => { | |
| 55 | for (const [name, value] of Object.entries(TYPES)) { | |
| 56 | const env = createEnvelope({ type: value, content: `${name} message` }) | |
| 57 | t.is(env.type, value, `type ${name} = ${value}`) | |
| 58 | } | |
| 59 | }) | |
| 60 | ||
| 61 | test('createEnvelope - all platforms', (t) => { | |
| 62 | for (const [name, value] of Object.entries(PLATFORMS)) { | |
| 63 | const env = createEnvelope({ platform: value, content: `from ${name}` }) | |
| 64 | t.is(env.platform, value, `platform ${name} = ${value}`) | |
| 65 | } | |
| 66 | }) | |
| 67 | ||
| 68 | test('createEnvelope - media message', (t) => { | |
| 69 | const env = createEnvelope({ | |
| 70 | type: TYPES.MEDIA, | |
| 71 | platform: PLATFORMS.DISCORD, | |
| 72 | author: 'user#1234', | |
| 73 | content: 'Check this out', | |
| 74 | mediaUrl: 'https://cdn.discord.com/attachments/abc.png', | |
| 75 | mediaType: 'image/png' | |
| 76 | }) | |
| 77 | ||
| 78 | t.is(env.type, TYPES.MEDIA) | |
| 79 | t.is(env.mediaUrl, 'https://cdn.discord.com/attachments/abc.png') | |
| 80 | t.is(env.mediaType, 'image/png') | |
| 81 | }) | |
| 82 | ||
| 83 | test('createEnvelope - reply message', (t) => { | |
| 84 | const original = createEnvelope({ content: 'original' }) | |
| 85 | const reply = createEnvelope({ | |
| 86 | type: TYPES.REPLY, | |
| 87 | content: 'reply to that', | |
| 88 | replyTo: original.id | |
| 89 | }) | |
| 90 | ||
| 91 | t.is(reply.type, TYPES.REPLY) | |
| 92 | t.is(reply.replyTo, original.id) | |
| 93 | }) | |
| 94 | ||
| 95 | test('createEnvelope - edit message', (t) => { | |
| 96 | const original = createEnvelope({ content: 'typo' }) | |
| 97 | const edit = createEnvelope({ | |
| 98 | type: TYPES.EDIT, | |
| 99 | content: 'fixed', | |
| 100 | editOf: original.id | |
| 101 | }) | |
| 102 | ||
| 103 | t.is(edit.type, TYPES.EDIT) | |
| 104 | t.is(edit.editOf, original.id) | |
| 105 | }) | |
| 106 | ||
| 107 | test('createEnvelope - delete message', (t) => { | |
| 108 | const original = createEnvelope({ content: 'oops' }) | |
| 109 | const del = createEnvelope({ | |
| 110 | type: TYPES.DELETE, | |
| 111 | deleteOf: original.id | |
| 112 | }) | |
| 113 | ||
| 114 | t.is(del.type, TYPES.DELETE) | |
| 115 | t.is(del.deleteOf, original.id) | |
| 116 | }) | |
| 117 | ||
| 118 | test('codec - encode/decode roundtrip', (t) => { | |
| 119 | const env = createEnvelope({ | |
| 120 | type: TYPES.TEXT, | |
| 121 | platform: PLATFORMS.IRC, | |
| 122 | author: 'testuser', | |
| 123 | content: 'Hello world!', | |
| 124 | channel: '#general', | |
| 125 | timestamp: 1707000000000 | |
| 126 | }) | |
| 127 | ||
| 128 | const buf = encode(env) | |
| 129 | t.ok(Buffer.isBuffer(buf), 'encoded to buffer') | |
| 130 | t.ok(buf.length > 0, 'buffer has content') | |
| 131 | ||
| 132 | const decoded = decode(buf) | |
| 133 | t.alike(decoded, env, 'roundtrip preserves all fields') | |
| 134 | }) | |
| 135 | ||
| 136 | test('codec - roundtrip all message types', (t) => { | |
| 137 | for (const [name, type] of Object.entries(TYPES)) { | |
| 138 | const env = createEnvelope({ | |
| 139 | type, | |
| 140 | platform: PLATFORMS.KEET, | |
| 141 | author: 'user', | |
| 142 | content: `${name} content`, | |
| 143 | channel: 'room1' | |
| 144 | }) | |
| 145 | ||
| 146 | const decoded = decode(encode(env)) | |
| 147 | t.is(decoded.type, type, `roundtrip for ${name}`) | |
| 148 | t.is(decoded.content, env.content) | |
| 149 | } | |
| 150 | }) | |
| 151 | ||
| 152 | test('codec - roundtrip with all fields populated', (t) => { | |
| 153 | const env = createEnvelope({ | |
| 154 | type: TYPES.REPLY, | |
| 155 | platform: PLATFORMS.DISCORD, | |
| 156 | author: 'alice#1234', | |
| 157 | content: 'replying with image', | |
| 158 | channel: 'discord-channel-123', | |
| 159 | replyTo: 'abc123', | |
| 160 | mediaUrl: 'https://example.com/image.png', | |
| 161 | mediaType: 'image/png', | |
| 162 | editOf: 'def456', | |
| 163 | deleteOf: 'ghi789', | |
| 164 | id: 'custom-id-000', | |
| 165 | timestamp: 1707123456789 | |
| 166 | }) | |
| 167 | ||
| 168 | const decoded = decode(encode(env)) | |
| 169 | t.alike(decoded, env, 'all fields preserved') | |
| 170 | }) | |
| 171 | ||
| 172 | test('codec - empty strings handled correctly', (t) => { | |
| 173 | const env = createEnvelope({ content: '' }) | |
| 174 | const decoded = decode(encode(env)) | |
| 175 | t.is(decoded.content, '') | |
| 176 | t.is(decoded.replyTo, '') | |
| 177 | t.is(decoded.mediaUrl, '') | |
| 178 | }) | |
| 179 | ||
| 180 | test('codec - unicode content', (t) => { | |
| 181 | const env = createEnvelope({ | |
| 182 | author: 'ユーザー', | |
| 183 | content: '🎉 Hello 世界! مرحبا' | |
| 184 | }) | |
| 185 | ||
| 186 | const decoded = decode(encode(env)) | |
| 187 | t.is(decoded.author, 'ユーザー') | |
| 188 | t.is(decoded.content, '🎉 Hello 世界! مرحبا') | |
| 189 | }) | |
| 190 | ||
| 191 | test('codec - large content', (t) => { | |
| 192 | const content = 'x'.repeat(10000) | |
| 193 | const env = createEnvelope({ content }) | |
| 194 | const decoded = decode(encode(env)) | |
| 195 | t.is(decoded.content, content) | |
| 196 | }) | |
| 197 | ||
| 198 | test('codec - compact size is smaller than JSON', (t) => { | |
| 199 | const env = createEnvelope({ | |
| 200 | type: TYPES.TEXT, | |
| 201 | platform: PLATFORMS.IRC, | |
| 202 | author: 'user', | |
| 203 | content: 'Hello world', | |
| 204 | channel: '#test' | |
| 205 | }) | |
| 206 | ||
| 207 | const compactBuf = encode(env) | |
| 208 | const jsonBuf = Buffer.from(JSON.stringify(env)) | |
| 209 | ||
| 210 | t.ok(compactBuf.length < jsonBuf.length, `compact (${compactBuf.length}) < JSON (${jsonBuf.length})`) | |
| 211 | }) | |
| 212 | ||
| 213 | test('codec - works with cenc.encode/decode directly', (t) => { | |
| 214 | const env = createEnvelope({ content: 'direct codec usage' }) | |
| 215 | const buf = cenc.encode(envelopeCodec, env) | |
| 216 | const decoded = cenc.decode(envelopeCodec, buf) | |
| 217 | t.alike(decoded, env) | |
| 218 | }) | |
| 219 | ||
| 220 | test('TYPES constants', (t) => { | |
| 221 | t.is(TYPES.TEXT, 0) | |
| 222 | t.is(TYPES.MEDIA, 1) | |
| 223 | t.is(TYPES.REACTION, 2) | |
| 224 | t.is(TYPES.EDIT, 3) | |
| 225 | t.is(TYPES.DELETE, 4) | |
| 226 | t.is(TYPES.SYSTEM, 5) | |
| 227 | t.is(TYPES.REPLY, 6) | |
| 228 | }) | |
| 229 | ||
| 230 | test('PLATFORMS constants', (t) => { | |
| 231 | t.is(PLATFORMS.KEET, 0) | |
| 232 | t.is(PLATFORMS.IRC, 1) | |
| 233 | t.is(PLATFORMS.DISCORD, 2) | |
| 234 | t.is(PLATFORMS.MATRIX, 3) | |
| 235 | t.is(PLATFORMS.MASTODON, 4) | |
| 236 | t.is(PLATFORMS.EMAIL, 5) | |
| 237 | t.is(PLATFORMS.ANDROID, 6) | |
| 238 | }) |
| … | … | @@ -0,0 +1,73 @@ |
| 1 | import { Bridge } from './lib/bridge.js' | |
| 2 | ||
| 3 | // CLI entry point | |
| 4 | const args = parseArgs(typeof Pear !== 'undefined' ? Pear.config.args : process.argv.slice(2)) | |
| 5 | ||
| 6 | if (args.help) { | |
| 7 | console.log(` | |
| 8 | bridge-irc — IRC ↔ Keet bidirectional bridge | |
| 9 | ||
| 10 | Usage: | |
| 11 | bridge-irc --irc-server <host> --channel <#chan> --keet-room <roomId> | |
| 12 | ||
| 13 | Options: | |
| 14 | --irc-server <host> IRC server hostname (required) | |
| 15 | --irc-port <port> IRC port (default: 6667, 6697 for TLS) | |
| 16 | --irc-nick <nick> IRC nickname (default: keet-bridge) | |
| 17 | --irc-password <pass> IRC server password | |
| 18 | --channel <#channel> IRC channel to bridge (required) | |
| 19 | --tls Use TLS for IRC connection | |
| 20 | --keet-room <roomId> Keet room ID to bridge (required) | |
| 21 | --keet-port <port> Keet daemon port (default: 3117) | |
| 22 | --help Show this help | |
| 23 | `) | |
| 24 | process.exit(0) | |
| 25 | } | |
| 26 | ||
| 27 | if (!args['irc-server'] || !args.channel || !args['keet-room']) { | |
| 28 | console.error('Error: --irc-server, --channel, and --keet-room are required') | |
| 29 | console.error('Run with --help for usage') | |
| 30 | process.exit(1) | |
| 31 | } | |
| 32 | ||
| 33 | const bridge = new Bridge({ | |
| 34 | ircServer: args['irc-server'], | |
| 35 | ircPort: parseInt(args['irc-port'] || '6667'), | |
| 36 | ircNick: args['irc-nick'] || 'keet-bridge', | |
| 37 | ircPassword: args['irc-password'], | |
| 38 | ircChannel: args.channel, | |
| 39 | ircTls: args.tls || parseInt(args['irc-port'] || '0') === 6697, | |
| 40 | keetRoom: args['keet-room'], | |
| 41 | keetPort: parseInt(args['keet-port'] || '3117') | |
| 42 | }) | |
| 43 | ||
| 44 | bridge.start().catch((err) => { | |
| 45 | console.error('Bridge failed to start:', err.message) | |
| 46 | process.exit(1) | |
| 47 | }) | |
| 48 | ||
| 49 | // Graceful shutdown | |
| 50 | process.on('SIGINT', () => bridge.stop().then(() => process.exit(0))) | |
| 51 | process.on('SIGTERM', () => bridge.stop().then(() => process.exit(0))) | |
| 52 | ||
| 53 | function parseArgs (argv) { | |
| 54 | const result = {} | |
| 55 | for (let i = 0; i < argv.length; i++) { | |
| 56 | const arg = argv[i] | |
| 57 | if (arg === '--help') { | |
| 58 | result.help = true | |
| 59 | } else if (arg === '--tls') { | |
| 60 | result.tls = true | |
| 61 | } else if (arg.startsWith('--')) { | |
| 62 | const key = arg.slice(2) | |
| 63 | const next = argv[i + 1] | |
| 64 | if (next && !next.startsWith('--')) { | |
| 65 | result[key] = next | |
| 66 | i++ | |
| 67 | } else { | |
| 68 | result[key] = true | |
| 69 | } | |
| 70 | } | |
| 71 | } | |
| 72 | return result | |
| 73 | } |
| … | … | @@ -0,0 +1,67 @@ |
| 1 | import { createEnvelope, TYPES, PLATFORMS } from 'bridge-envelope' | |
| 2 | ||
| 3 | function ircToEnvelope (nick, channel, text) { | |
| 4 | // Detect ACTION (/me) | |
| 5 | const actionMatch = text.match(/^\x01ACTION (.+)\x01$/) | |
| 6 | if (actionMatch) { | |
| 7 | return createEnvelope({ | |
| 8 | type: TYPES.TEXT, | |
| 9 | platform: PLATFORMS.IRC, | |
| 10 | author: nick, | |
| 11 | content: `* ${nick} ${actionMatch[1]}`, | |
| 12 | channel | |
| 13 | }) | |
| 14 | } | |
| 15 | ||
| 16 | return createEnvelope({ | |
| 17 | type: TYPES.TEXT, | |
| 18 | platform: PLATFORMS.IRC, | |
| 19 | author: nick, | |
| 20 | content: text, | |
| 21 | channel | |
| 22 | }) | |
| 23 | } | |
| 24 | ||
| 25 | function keetToIrc (keetMsg, bridgeIdentity) { | |
| 26 | // Skip messages from bridge identity (prevents echo) | |
| 27 | if (keetMsg.memberId === bridgeIdentity) return null | |
| 28 | // Skip system messages | |
| 29 | if (keetMsg.system) return null | |
| 30 | ||
| 31 | const name = keetMsg.name || 'anonymous' | |
| 32 | const text = keetMsg.text || '' | |
| 33 | ||
| 34 | if (!text.trim()) return null | |
| 35 | ||
| 36 | return { | |
| 37 | nick: name, | |
| 38 | text: `<${name}> ${text}` | |
| 39 | } | |
| 40 | } | |
| 41 | ||
| 42 | function envelopeToIrcText (envelope) { | |
| 43 | // Format envelope for IRC display | |
| 44 | const platform = platformName(envelope.platform) | |
| 45 | const prefix = platform !== 'irc' ? `[${platform}] ` : '' | |
| 46 | const author = envelope.author || 'anonymous' | |
| 47 | ||
| 48 | switch (envelope.type) { | |
| 49 | case TYPES.TEXT: | |
| 50 | return `${prefix}<${author}> ${envelope.content}` | |
| 51 | case TYPES.MEDIA: | |
| 52 | return `${prefix}<${author}> ${envelope.content || ''} ${envelope.mediaUrl || '[media]'}` | |
| 53 | case TYPES.REACTION: | |
| 54 | return `${prefix}* ${author} reacted: ${envelope.content}` | |
| 55 | case TYPES.SYSTEM: | |
| 56 | return `${prefix}** ${envelope.content}` | |
| 57 | default: | |
| 58 | return `${prefix}<${author}> ${envelope.content}` | |
| 59 | } | |
| 60 | } | |
| 61 | ||
| 62 | function platformName (id) { | |
| 63 | const names = ['keet', 'irc', 'discord', 'matrix', 'mastodon', 'email', 'android'] | |
| 64 | return names[id] || 'unknown' | |
| 65 | } | |
| 66 | ||
| 67 | export { ircToEnvelope, keetToIrc, envelopeToIrcText } |
| … | … | @@ -0,0 +1,181 @@ |
| 1 | import { IrcClient } from './irc-client.js' | |
| 2 | import { KeetClient } from './keet-client.js' | |
| 3 | import { Router } from './router.js' | |
| 4 | import { ircToEnvelope, keetToIrc } from './adapter.js' | |
| 5 | ||
| 6 | class Bridge { | |
| 7 | constructor (opts) { | |
| 8 | this.ircServer = opts.ircServer | |
| 9 | this.ircPort = opts.ircPort || 6667 | |
| 10 | this.ircNick = opts.ircNick || 'keet-bridge' | |
| 11 | this.ircChannel = opts.ircChannel | |
| 12 | this.ircPassword = opts.ircPassword || null | |
| 13 | this.ircTls = opts.ircTls | |
| 14 | ||
| 15 | this.keetRoom = opts.keetRoom | |
| 16 | this.keetHost = opts.keetHost || 'localhost' | |
| 17 | this.keetPort = opts.keetPort || 3117 | |
| 18 | ||
| 19 | this._irc = null | |
| 20 | this._keet = null | |
| 21 | this._router = null | |
| 22 | this._running = false | |
| 23 | this._bridgeIdentity = null | |
| 24 | this._lastSeq = -1 // highest seq seen at startup — skip history | |
| 25 | this._sendQueue = [] | |
| 26 | this._sending = false | |
| 27 | ||
| 28 | this.onLog = opts.onLog || console.log | |
| 29 | } | |
| 30 | ||
| 31 | async start () { | |
| 32 | this.onLog(`[bridge] Starting IRC ↔ Keet bridge`) | |
| 33 | this.onLog(`[bridge] IRC: ${this.ircServer}:${this.ircPort} ${this.ircChannel}`) | |
| 34 | this.onLog(`[bridge] Keet room: ${this.keetRoom}`) | |
| 35 | ||
| 36 | // Initialize Keet client | |
| 37 | this._keet = new KeetClient({ | |
| 38 | host: this.keetHost, | |
| 39 | port: this.keetPort | |
| 40 | }) | |
| 41 | ||
| 42 | // Get bridge identity so we can filter our own messages | |
| 43 | try { | |
| 44 | const identity = await this._keet.identity() | |
| 45 | this._bridgeIdentity = identity.memberId || identity.id | |
| 46 | this.onLog(`[bridge] Keet identity: ${identity.displayName || identity.username || 'unknown'}`) | |
| 47 | } catch (err) { | |
| 48 | this.onLog(`[bridge] Warning: could not get Keet identity: ${err.message}`) | |
| 49 | } | |
| 50 | ||
| 51 | // Snapshot the latest message seq to distinguish history from new messages | |
| 52 | try { | |
| 53 | const msgs = await this._keet.messages(this.keetRoom, 1) | |
| 54 | if (msgs && msgs.length > 0) { | |
| 55 | this._lastSeq = msgs[msgs.length - 1].seq || 0 | |
| 56 | this.onLog(`[bridge] History cutoff: seq ${this._lastSeq}`) | |
| 57 | } | |
| 58 | } catch (err) { | |
| 59 | this.onLog(`[bridge] Warning: could not get message history: ${err.message}`) | |
| 60 | } | |
| 61 | ||
| 62 | // Initialize router with dedup | |
| 63 | this._router = new Router({ | |
| 64 | onIrcMessage: (name, text) => { | |
| 65 | this._queueSay(`<${name}> ${text}`) | |
| 66 | }, | |
| 67 | onKeetMessage: (nick, text) => { | |
| 68 | this._keet.send(this.keetRoom, `[IRC] <${nick}> ${text}`).catch((err) => { | |
| 69 | this.onLog(`[bridge] Keet send error: ${err.message}`) | |
| 70 | }) | |
| 71 | } | |
| 72 | }) | |
| 73 | ||
| 74 | // Initialize IRC client | |
| 75 | this._irc = new IrcClient({ | |
| 76 | server: this.ircServer, | |
| 77 | port: this.ircPort, | |
| 78 | nick: this.ircNick, | |
| 79 | channel: this.ircChannel, | |
| 80 | password: this.ircPassword, | |
| 81 | tls: this.ircTls, | |
| 82 | onMessage: (nick, text) => { | |
| 83 | if (nick === this.ircNick) return // ignore self | |
| 84 | this.onLog(`[irc→keet] <${nick}> ${text}`) | |
| 85 | this._router.routeToKeet(nick, text) | |
| 86 | }, | |
| 87 | onJoin: (nick) => { | |
| 88 | if (nick === this.ircNick) return | |
| 89 | this.onLog(`[irc] ${nick} joined ${this.ircChannel}`) | |
| 90 | }, | |
| 91 | onPart: (nick) => { | |
| 92 | this.onLog(`[irc] ${nick} left ${this.ircChannel}`) | |
| 93 | } | |
| 94 | }) | |
| 95 | ||
| 96 | // Connect to IRC | |
| 97 | await this._irc.connect() | |
| 98 | this.onLog(`[bridge] Connected to IRC as ${this.ircNick}`) | |
| 99 | ||
| 100 | // Verify Keet daemon is reachable | |
| 101 | try { | |
| 102 | const status = await this._keet.status() | |
| 103 | this.onLog(`[bridge] Keet daemon online (${status.rooms || '?'} rooms)`) | |
| 104 | } catch (err) { | |
| 105 | this.onLog(`[bridge] Warning: Keet daemon unreachable: ${err.message}`) | |
| 106 | } | |
| 107 | ||
| 108 | // Start polling Keet room for new messages | |
| 109 | this._startKeetPoll() | |
| 110 | ||
| 111 | this._running = true | |
| 112 | this.onLog(`[bridge] Bridge active — messages flowing bidirectionally`) | |
| 113 | } | |
| 114 | ||
| 115 | _startKeetPoll () { | |
| 116 | this._pollTimer = setInterval(async () => { | |
| 117 | try { | |
| 118 | const msgs = await this._keet.messages(this.keetRoom, 10) | |
| 119 | if (!msgs || msgs.length === 0) return | |
| 120 | ||
| 121 | for (const msg of msgs) { | |
| 122 | if (msg.seq === undefined || msg.seq <= this._lastSeq) continue | |
| 123 | ||
| 124 | this._lastSeq = msg.seq | |
| 125 | ||
| 126 | const relayed = keetToIrc(msg, this._bridgeIdentity) | |
| 127 | if (!relayed) continue | |
| 128 | ||
| 129 | // Skip bridged messages (prevents echo) | |
| 130 | if (msg.text && msg.text.startsWith('[IRC]')) continue | |
| 131 | ||
| 132 | this.onLog(`[keet→irc] ${relayed.text}`) | |
| 133 | this._router.routeToIrc(relayed.nick, msg.text) | |
| 134 | } | |
| 135 | } catch (err) { | |
| 136 | this.onLog(`[bridge] Keet poll error: ${err.message}`) | |
| 137 | } | |
| 138 | }, 2000) | |
| 139 | } | |
| 140 | ||
| 141 | // Rate-limited IRC send — 1 message per second to avoid flood | |
| 142 | _queueSay (text) { | |
| 143 | this._sendQueue.push(text) | |
| 144 | if (!this._sending) this._drainQueue() | |
| 145 | } | |
| 146 | ||
| 147 | _drainQueue () { | |
| 148 | if (this._sendQueue.length === 0) { | |
| 149 | this._sending = false | |
| 150 | return | |
| 151 | } | |
| 152 | this._sending = true | |
| 153 | const text = this._sendQueue.shift() | |
| 154 | this._irc.say(text) | |
| 155 | setTimeout(() => this._drainQueue(), 1000) | |
| 156 | } | |
| 157 | ||
| 158 | async stop () { | |
| 159 | this.onLog(`[bridge] Shutting down...`) | |
| 160 | this._running = false | |
| 161 | ||
| 162 | if (this._pollTimer) clearInterval(this._pollTimer) | |
| 163 | if (this._irc) this._irc.disconnect('Bridge stopped') | |
| 164 | ||
| 165 | this.onLog(`[bridge] Stopped`) | |
| 166 | } | |
| 167 | ||
| 168 | get running () { | |
| 169 | return this._running | |
| 170 | } | |
| 171 | ||
| 172 | get stats () { | |
| 173 | return { | |
| 174 | running: this._running, | |
| 175 | ircConnected: this._irc?.connected || false, | |
| 176 | deduped: this._router?.seenCount || 0 | |
| 177 | } | |
| 178 | } | |
| 179 | } | |
| 180 | ||
| 181 | export { Bridge } |
| … | … | @@ -0,0 +1,102 @@ |
| 1 | import IRC from 'irc-framework' | |
| 2 | ||
| 3 | class IrcClient { | |
| 4 | constructor (opts) { | |
| 5 | this.server = opts.server | |
| 6 | this.port = opts.port || 6667 | |
| 7 | this.tls = opts.tls ?? (opts.port === 6697) | |
| 8 | this.nick = opts.nick || 'keet-bridge' | |
| 9 | this.channel = opts.channel | |
| 10 | this.password = opts.password || null | |
| 11 | ||
| 12 | this._client = null | |
| 13 | this._connected = false | |
| 14 | this._onMessage = opts.onMessage || null | |
| 15 | this._onJoin = opts.onJoin || null | |
| 16 | this._onPart = opts.onPart || null | |
| 17 | this._onError = opts.onError || null | |
| 18 | } | |
| 19 | ||
| 20 | connect () { | |
| 21 | return new Promise((resolve, reject) => { | |
| 22 | this._client = new IRC.Client() | |
| 23 | ||
| 24 | this._client.connect({ | |
| 25 | host: this.server, | |
| 26 | port: this.port, | |
| 27 | nick: this.nick, | |
| 28 | tls: this.tls, | |
| 29 | password: this.password | |
| 30 | }) | |
| 31 | ||
| 32 | this._client.on('registered', () => { | |
| 33 | this._connected = true | |
| 34 | if (this.channel) { | |
| 35 | this._client.join(this.channel) | |
| 36 | } | |
| 37 | resolve() | |
| 38 | }) | |
| 39 | ||
| 40 | this._client.on('message', (event) => { | |
| 41 | if (event.target === this.channel && this._onMessage) { | |
| 42 | this._onMessage(event.nick, event.message) | |
| 43 | } | |
| 44 | }) | |
| 45 | ||
| 46 | this._client.on('action', (event) => { | |
| 47 | if (event.target === this.channel && this._onMessage) { | |
| 48 | this._onMessage(event.nick, `\x01ACTION ${event.message}\x01`) | |
| 49 | } | |
| 50 | }) | |
| 51 | ||
| 52 | this._client.on('join', (event) => { | |
| 53 | if (event.channel === this.channel && this._onJoin) { | |
| 54 | this._onJoin(event.nick) | |
| 55 | } | |
| 56 | }) | |
| 57 | ||
| 58 | this._client.on('part', (event) => { | |
| 59 | if (event.channel === this.channel && this._onPart) { | |
| 60 | this._onPart(event.nick, event.message) | |
| 61 | } | |
| 62 | }) | |
| 63 | ||
| 64 | this._client.on('close', () => { | |
| 65 | this._connected = false | |
| 66 | }) | |
| 67 | ||
| 68 | this._client.on('socket close', () => { | |
| 69 | this._connected = false | |
| 70 | }) | |
| 71 | ||
| 72 | const errorTimeout = setTimeout(() => { | |
| 73 | if (!this._connected) reject(new Error('Connection timeout')) | |
| 74 | }, 15000) | |
| 75 | ||
| 76 | this._client.on('registered', () => clearTimeout(errorTimeout)) | |
| 77 | }) | |
| 78 | } | |
| 79 | ||
| 80 | say (text) { | |
| 81 | if (!this._connected || !this._client) return | |
| 82 | this._client.say(this.channel, text) | |
| 83 | } | |
| 84 | ||
| 85 | action (text) { | |
| 86 | if (!this._connected || !this._client) return | |
| 87 | this._client.action(this.channel, text) | |
| 88 | } | |
| 89 | ||
| 90 | disconnect (reason) { | |
| 91 | if (this._client) { | |
| 92 | this._client.quit(reason || 'Bridge shutting down') | |
| 93 | this._connected = false | |
| 94 | } | |
| 95 | } | |
| 96 | ||
| 97 | get connected () { | |
| 98 | return this._connected | |
| 99 | } | |
| 100 | } | |
| 101 | ||
| 102 | export { IrcClient } |
| … | … | @@ -0,0 +1,166 @@ |
| 1 | import http from 'http' | |
| 2 | import net from 'net' | |
| 3 | ||
| 4 | class KeetClient { | |
| 5 | constructor (opts = {}) { | |
| 6 | this.host = opts.host || 'localhost' | |
| 7 | this.port = opts.port || 3117 | |
| 8 | this._watching = false | |
| 9 | this._watchAbort = null | |
| 10 | } | |
| 11 | ||
| 12 | // Raw TCP request — bypasses Node's strict HTTP parser | |
| 13 | // Needed because keet-daemon sends duplicate Content-Length headers | |
| 14 | _rawRequest (method, path, body) { | |
| 15 | return new Promise((resolve, reject) => { | |
| 16 | const socket = net.connect(this.port, this.host) | |
| 17 | let payload = '' | |
| 18 | ||
| 19 | if (body) { | |
| 20 | payload = JSON.stringify(body) | |
| 21 | } | |
| 22 | ||
| 23 | socket.on('connect', () => { | |
| 24 | let req = `${method} ${path} HTTP/1.1\r\n` | |
| 25 | req += `Host: ${this.host}:${this.port}\r\n` | |
| 26 | if (payload) { | |
| 27 | req += `Content-Type: application/json\r\n` | |
| 28 | req += `Content-Length: ${Buffer.byteLength(payload)}\r\n` | |
| 29 | } | |
| 30 | req += `Connection: close\r\n` | |
| 31 | req += `\r\n` | |
| 32 | if (payload) req += payload | |
| 33 | socket.write(req) | |
| 34 | }) | |
| 35 | ||
| 36 | let data = '' | |
| 37 | socket.on('data', (chunk) => { data += chunk.toString() }) | |
| 38 | ||
| 39 | socket.on('end', () => { | |
| 40 | // Split headers from body | |
| 41 | const idx = data.indexOf('\r\n\r\n') | |
| 42 | if (idx === -1) return reject(new Error('No HTTP body')) | |
| 43 | const jsonBody = data.slice(idx + 4) | |
| 44 | ||
| 45 | try { | |
| 46 | const parsed = JSON.parse(jsonBody) | |
| 47 | if (!parsed.ok) reject(new Error(parsed.error || 'RPC error')) | |
| 48 | else resolve(parsed.data) | |
| 49 | } catch { | |
| 50 | reject(new Error('Invalid JSON: ' + jsonBody.slice(0, 200))) | |
| 51 | } | |
| 52 | }) | |
| 53 | ||
| 54 | socket.on('error', reject) | |
| 55 | socket.setTimeout(10000, () => { | |
| 56 | socket.destroy(new Error('Request timeout')) | |
| 57 | }) | |
| 58 | }) | |
| 59 | } | |
| 60 | ||
| 61 | rpc (method, params = {}) { | |
| 62 | return this._rawRequest('POST', '/rpc', { method, params }) | |
| 63 | } | |
| 64 | ||
| 65 | async send (roomId, text) { | |
| 66 | return this.rpc('send', { roomId, text }) | |
| 67 | } | |
| 68 | ||
| 69 | async messages (roomId, limit = 20) { | |
| 70 | return this._rawRequest('GET', `/rpc/messages/${roomId}?limit=${limit}`) | |
| 71 | } | |
| 72 | ||
| 73 | async rooms () { | |
| 74 | return this._rawRequest('GET', '/rpc/rooms') | |
| 75 | } | |
| 76 | ||
| 77 | async identity () { | |
| 78 | return this._rawRequest('GET', '/rpc/identity') | |
| 79 | } | |
| 80 | ||
| 81 | async status () { | |
| 82 | return this._rawRequest('GET', '/rpc/status') | |
| 83 | } | |
| 84 | ||
| 85 | async members (roomId) { | |
| 86 | return this._rawRequest('GET', `/rpc/members/${roomId}`) | |
| 87 | } | |
| 88 | ||
| 89 | watch (roomId, onMessage) { | |
| 90 | if (this._watching) return | |
| 91 | ||
| 92 | this._watching = true | |
| 93 | this._doWatch(roomId, onMessage) | |
| 94 | } | |
| 95 | ||
| 96 | _doWatch (roomId, onMessage) { | |
| 97 | if (!this._watching) return | |
| 98 | ||
| 99 | const req = http.request({ | |
| 100 | hostname: this.host, | |
| 101 | port: this.port, | |
| 102 | path: `/rpc/watch/${roomId}`, | |
| 103 | method: 'GET', | |
| 104 | headers: { Accept: 'text/event-stream' } | |
| 105 | }, (res) => { | |
| 106 | let buffer = '' | |
| 107 | ||
| 108 | res.on('data', (chunk) => { | |
| 109 | buffer += chunk.toString() | |
| 110 | const events = buffer.split('\n\n') | |
| 111 | buffer = events.pop() // keep incomplete tail | |
| 112 | ||
| 113 | for (const event of events) { | |
| 114 | if (!event.trim()) continue | |
| 115 | const lines = event.split('\n') | |
| 116 | let type = 'message' | |
| 117 | let data = null | |
| 118 | ||
| 119 | for (const line of lines) { | |
| 120 | if (line.startsWith('event: ')) type = line.slice(7) | |
| 121 | if (line.startsWith('data: ')) { | |
| 122 | try { | |
| 123 | data = JSON.parse(line.slice(6)) | |
| 124 | } catch {} | |
| 125 | } | |
| 126 | } | |
| 127 | ||
| 128 | if (type === 'message' && data) { | |
| 129 | onMessage(data) | |
| 130 | } | |
| 131 | } | |
| 132 | }) | |
| 133 | ||
| 134 | res.on('end', () => { | |
| 135 | if (this._watching) { | |
| 136 | setTimeout(() => this._doWatch(roomId, onMessage), 2000) | |
| 137 | } | |
| 138 | }) | |
| 139 | ||
| 140 | res.on('error', () => { | |
| 141 | if (this._watching) { | |
| 142 | setTimeout(() => this._doWatch(roomId, onMessage), 5000) | |
| 143 | } | |
| 144 | }) | |
| 145 | }) | |
| 146 | ||
| 147 | req.on('error', () => { | |
| 148 | if (this._watching) { | |
| 149 | setTimeout(() => this._doWatch(roomId, onMessage), 5000) | |
| 150 | } | |
| 151 | }) | |
| 152 | ||
| 153 | this._watchAbort = req | |
| 154 | req.end() | |
| 155 | } | |
| 156 | ||
| 157 | stopWatch () { | |
| 158 | this._watching = false | |
| 159 | if (this._watchAbort) { | |
| 160 | this._watchAbort.destroy() | |
| 161 | this._watchAbort = null | |
| 162 | } | |
| 163 | } | |
| 164 | } | |
| 165 | ||
| 166 | export { KeetClient } |
| … | … | @@ -0,0 +1,70 @@ |
| 1 | import { createHash } from 'crypto' | |
| 2 | ||
| 3 | class Router { | |
| 4 | constructor (opts = {}) { | |
| 5 | this._seen = new Map() | |
| 6 | this._maxSeen = opts.maxSeen || 10000 | |
| 7 | this._ttl = opts.ttl || 60000 // 1 minute dedup window | |
| 8 | this._onIrcMessage = opts.onIrcMessage || null | |
| 9 | this._onKeetMessage = opts.onKeetMessage || null | |
| 10 | } | |
| 11 | ||
| 12 | _hash (text, author) { | |
| 13 | return createHash('sha256') | |
| 14 | .update(author || '') | |
| 15 | .update(text || '') | |
| 16 | .digest('hex') | |
| 17 | .slice(0, 16) | |
| 18 | } | |
| 19 | ||
| 20 | _isDuplicate (hash) { | |
| 21 | const now = Date.now() | |
| 22 | ||
| 23 | // Cleanup old entries | |
| 24 | if (this._seen.size > this._maxSeen) { | |
| 25 | for (const [k, ts] of this._seen) { | |
| 26 | if (now - ts > this._ttl) this._seen.delete(k) | |
| 27 | } | |
| 28 | } | |
| 29 | ||
| 30 | if (this._seen.has(hash)) { | |
| 31 | const ts = this._seen.get(hash) | |
| 32 | if (now - ts < this._ttl) return true | |
| 33 | } | |
| 34 | ||
| 35 | this._seen.set(hash, now) | |
| 36 | return false | |
| 37 | } | |
| 38 | ||
| 39 | // Called when IRC receives a message — relay to Keet | |
| 40 | routeToKeet (nick, text) { | |
| 41 | const hash = this._hash(text, nick) | |
| 42 | if (this._isDuplicate(hash)) return false | |
| 43 | ||
| 44 | if (this._onKeetMessage) { | |
| 45 | this._onKeetMessage(nick, text) | |
| 46 | } | |
| 47 | return true | |
| 48 | } | |
| 49 | ||
| 50 | // Called when Keet receives a message — relay to IRC | |
| 51 | routeToIrc (name, text) { | |
| 52 | const hash = this._hash(text, name) | |
| 53 | if (this._isDuplicate(hash)) return false | |
| 54 | ||
| 55 | if (this._onIrcMessage) { | |
| 56 | this._onIrcMessage(name, text) | |
| 57 | } | |
| 58 | return true | |
| 59 | } | |
| 60 | ||
| 61 | get seenCount () { | |
| 62 | return this._seen.size | |
| 63 | } | |
| 64 | ||
| 65 | clearSeen () { | |
| 66 | this._seen.clear() | |
| 67 | } | |
| 68 | } | |
| 69 | ||
| 70 | export { Router } |
| … | … | @@ -0,0 +1,1553 @@ |
| 1 | { | |
| 2 | "name": "bridge-irc", | |
| 3 | "version": "1.0.0", | |
| 4 | "lockfileVersion": 3, | |
| 5 | "requires": true, | |
| 6 | "packages": { | |
| 7 | "": { | |
| 8 | "name": "bridge-irc", | |
| 9 | "version": "1.0.0", | |
| 10 | "license": "MIT", | |
| 11 | "dependencies": { | |
| 12 | "b4a": "^1.0.0", | |
| 13 | "bridge-envelope": "file:../bridge-envelope", | |
| 14 | "irc-framework": "^4.13.1" | |
| 15 | }, | |
| 16 | "devDependencies": { | |
| 17 | "brittle": "^3.0.0" | |
| 18 | } | |
| 19 | }, | |
| 20 | "../bridge-envelope": { | |
| 21 | "version": "1.0.0", | |
| 22 | "license": "MIT", | |
| 23 | "dependencies": { | |
| 24 | "b4a": "^1.0.0", | |
| 25 | "compact-encoding": "^2.0.0" | |
| 26 | }, | |
| 27 | "devDependencies": { | |
| 28 | "brittle": "^3.0.0" | |
| 29 | } | |
| 30 | }, | |
| 31 | "node_modules/@jridgewell/resolve-uri": { | |
| 32 | "version": "3.1.2", | |
| 33 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", | |
| 34 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", | |
| 35 | "dev": true, | |
| 36 | "license": "MIT", | |
| 37 | "engines": { | |
| 38 | "node": ">=6.0.0" | |
| 39 | } | |
| 40 | }, | |
| 41 | "node_modules/@jridgewell/sourcemap-codec": { | |
| 42 | "version": "1.5.5", | |
| 43 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", | |
| 44 | "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", | |
| 45 | "dev": true, | |
| 46 | "license": "MIT" | |
| 47 | }, | |
| 48 | "node_modules/@jridgewell/trace-mapping": { | |
| 49 | "version": "0.3.31", | |
| 50 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", | |
| 51 | "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", | |
| 52 | "dev": true, | |
| 53 | "license": "MIT", | |
| 54 | "dependencies": { | |
| 55 | "@jridgewell/resolve-uri": "^3.1.0", | |
| 56 | "@jridgewell/sourcemap-codec": "^1.4.14" | |
| 57 | } | |
| 58 | }, | |
| 59 | "node_modules/@types/istanbul-lib-coverage": { | |
| 60 | "version": "2.0.6", | |
| 61 | "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", | |
| 62 | "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", | |
| 63 | "dev": true, | |
| 64 | "license": "MIT" | |
| 65 | }, | |
| 66 | "node_modules/available-typed-arrays": { | |
| 67 | "version": "1.0.7", | |
| 68 | "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", | |
| 69 | "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", | |
| 70 | "license": "MIT", | |
| 71 | "dependencies": { | |
| 72 | "possible-typed-array-names": "^1.0.0" | |
| 73 | }, | |
| 74 | "engines": { | |
| 75 | "node": ">= 0.4" | |
| 76 | }, | |
| 77 | "funding": { | |
| 78 | "url": "https://github.com/sponsors/ljharb" | |
| 79 | } | |
| 80 | }, | |
| 81 | "node_modules/b4a": { | |
| 82 | "version": "1.7.4", | |
| 83 | "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.4.tgz", | |
| 84 | "integrity": "sha512-u20zJLDaSWpxaZ+zaAkEIB2dZZ1o+DF4T/MRbmsvGp9nletHOyiai19OzX1fF8xUBYsO1bPXxODvcd0978pnug==", | |
| 85 | "license": "Apache-2.0", | |
| 86 | "peerDependencies": { | |
| 87 | "react-native-b4a": "*" | |
| 88 | }, | |
| 89 | "peerDependenciesMeta": { | |
| 90 | "react-native-b4a": { | |
| 91 | "optional": true | |
| 92 | } | |
| 93 | } | |
| 94 | }, | |
| 95 | "node_modules/bare-addon-resolve": { | |
| 96 | "version": "1.10.0", | |
| 97 | "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.0.tgz", | |
| 98 | "integrity": "sha512-sSd0jieRJlDaODOzj0oe0RjFVC1QI0ZIjGIdPkbrTXsdVVtENg14c+lHHAhHwmWCZ2nQlMhy8jA3Y5LYPc/isA==", | |
| 99 | "dev": true, | |
| 100 | "license": "Apache-2.0", | |
| 101 | "dependencies": { | |
| 102 | "bare-module-resolve": "^1.10.0", | |
| 103 | "bare-semver": "^1.0.0" | |
| 104 | }, | |
| 105 | "peerDependencies": { | |
| 106 | "bare-url": "*" | |
| 107 | }, | |
| 108 | "peerDependenciesMeta": { | |
| 109 | "bare-url": { | |
| 110 | "optional": true | |
| 111 | } | |
| 112 | } | |
| 113 | }, | |
| 114 | "node_modules/bare-ansi-escapes": { | |
| 115 | "version": "2.2.3", | |
| 116 | "resolved": "https://registry.npmjs.org/bare-ansi-escapes/-/bare-ansi-escapes-2.2.3.tgz", | |
| 117 | "integrity": "sha512-02ES4/E2RbrtZSnHJ9LntBhYkLA6lPpSEeP8iqS3MccBIVhVBlEmruF1I7HZqx5Q8aiTeYfQVeqmrU9YO2yYoQ==", | |
| 118 | "dev": true, | |
| 119 | "license": "Apache-2.0", | |
| 120 | "dependencies": { | |
| 121 | "bare-stream": "^2.6.5" | |
| 122 | }, | |
| 123 | "peerDependencies": { | |
| 124 | "bare-buffer": "*" | |
| 125 | }, | |
| 126 | "peerDependenciesMeta": { | |
| 127 | "bare-buffer": { | |
| 128 | "optional": true | |
| 129 | } | |
| 130 | } | |
| 131 | }, | |
| 132 | "node_modules/bare-assert": { | |
| 133 | "version": "1.2.0", | |
| 134 | "resolved": "https://registry.npmjs.org/bare-assert/-/bare-assert-1.2.0.tgz", | |
| 135 | "integrity": "sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==", | |
| 136 | "dev": true, | |
| 137 | "license": "Apache-2.0", | |
| 138 | "dependencies": { | |
| 139 | "bare-inspect": "^3.1.2" | |
| 140 | } | |
| 141 | }, | |
| 142 | "node_modules/bare-bundle": { | |
| 143 | "version": "1.10.0", | |
| 144 | "resolved": "https://registry.npmjs.org/bare-bundle/-/bare-bundle-1.10.0.tgz", | |
| 145 | "integrity": "sha512-4LVlnJAHr00Hh6Vu6ZUJS38rcEtJT3b3vChXSsBsJ2mk1TN0lQ+gzd+Dw5L0aV7uqDZv84smuwW+O02X7PfDlw==", | |
| 146 | "dev": true, | |
| 147 | "license": "Apache-2.0", | |
| 148 | "peerDependencies": { | |
| 149 | "bare-buffer": "*", | |
| 150 | "bare-url": "*" | |
| 151 | }, | |
| 152 | "peerDependenciesMeta": { | |
| 153 | "bare-buffer": { | |
| 154 | "optional": true | |
| 155 | }, | |
| 156 | "bare-url": { | |
| 157 | "optional": true | |
| 158 | } | |
| 159 | } | |
| 160 | }, | |
| 161 | "node_modules/bare-cov": { | |
| 162 | "version": "1.2.1", | |
| 163 | "resolved": "https://registry.npmjs.org/bare-cov/-/bare-cov-1.2.1.tgz", | |
| 164 | "integrity": "sha512-SHNqb/WG9udUEj8m5qGSehoUQ9o0Xzv8nrf4HIdmG+KNU/XdhPLhTP5VPrHnSAMAXhAzEB4Q+2IYZ0x9qPzN1g==", | |
| 165 | "dev": true, | |
| 166 | "license": "Apache-2.0", | |
| 167 | "dependencies": { | |
| 168 | "bare-fs": "^4.1.2", | |
| 169 | "bare-inspector": "^4.0.1", | |
| 170 | "bare-path": "^3.0.0", | |
| 171 | "bare-process": "^4.2.1", | |
| 172 | "bare-url": "^2.1.5", | |
| 173 | "bare-v8-to-istanbul": "^1.0.2", | |
| 174 | "picomatch": "^4.0.2", | |
| 175 | "which-runtime": "^1.2.1" | |
| 176 | } | |
| 177 | }, | |
| 178 | "node_modules/bare-crypto": { | |
| 179 | "version": "1.13.0", | |
| 180 | "resolved": "https://registry.npmjs.org/bare-crypto/-/bare-crypto-1.13.0.tgz", | |
| 181 | "integrity": "sha512-RQl13yD+YTACWUZHMMck0C6LNjXgGgRyhVC557ag0xIrkxXf4IQwpnc8WOB58f4k5kSzKhIjy05oW3HUBrFpSQ==", | |
| 182 | "dev": true, | |
| 183 | "license": "Apache-2.0", | |
| 184 | "dependencies": { | |
| 185 | "bare-stream": "^2.6.3" | |
| 186 | }, | |
| 187 | "peerDependencies": { | |
| 188 | "bare-buffer": "*" | |
| 189 | }, | |
| 190 | "peerDependenciesMeta": { | |
| 191 | "bare-buffer": { | |
| 192 | "optional": true | |
| 193 | } | |
| 194 | } | |
| 195 | }, | |
| 196 | "node_modules/bare-debug-log": { | |
| 197 | "version": "2.0.0", | |
| 198 | "resolved": "https://registry.npmjs.org/bare-debug-log/-/bare-debug-log-2.0.0.tgz", | |
| 199 | "integrity": "sha512-Vi42PkMQsNV9PUpx2Gl1hikshx5O9FzMJ6o9Nnopseg7qLBBK7Nl31d0RHcfwLEAfmcPApytpc0ZFfq68u22FQ==", | |
| 200 | "dev": true, | |
| 201 | "license": "Apache-2.0", | |
| 202 | "dependencies": { | |
| 203 | "bare-os": "^3.0.1" | |
| 204 | } | |
| 205 | }, | |
| 206 | "node_modules/bare-dns": { | |
| 207 | "version": "2.1.4", | |
| 208 | "resolved": "https://registry.npmjs.org/bare-dns/-/bare-dns-2.1.4.tgz", | |
| 209 | "integrity": "sha512-abwjHmpWqSRNB7V5615QxPH92L71AVzFm/kKTs8VYiNTAi2xVdonpv0BjJ0hwXLwomoW+xsSOPjW6PZPO14asg==", | |
| 210 | "dev": true, | |
| 211 | "license": "Apache-2.0", | |
| 212 | "engines": { | |
| 213 | "bare": ">=1.7.0" | |
| 214 | } | |
| 215 | }, | |
| 216 | "node_modules/bare-encoding": { | |
| 217 | "version": "1.0.3", | |
| 218 | "resolved": "https://registry.npmjs.org/bare-encoding/-/bare-encoding-1.0.3.tgz", | |
| 219 | "integrity": "sha512-Kqf+t/azs13lUeyK4Tb7ha4wdLRXKWCXQ8w1rVmt7KtoPCPdHD/Xwt7LBIsCSwwGglrcmblo5VOLa5avkJqULA==", | |
| 220 | "dev": true, | |
| 221 | "license": "Apache-2.0", | |
| 222 | "peerDependencies": { | |
| 223 | "bare-buffer": "*" | |
| 224 | }, | |
| 225 | "peerDependenciesMeta": { | |
| 226 | "bare-buffer": { | |
| 227 | "optional": true | |
| 228 | } | |
| 229 | } | |
| 230 | }, | |
| 231 | "node_modules/bare-env": { | |
| 232 | "version": "3.0.0", | |
| 233 | "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.0.tgz", | |
| 234 | "integrity": "sha512-0u964P5ZLAxTi+lW4Kjp7YRJQ5gZr9ycYOtjLxsSrupgMz3sn5Z9n4SH/JIifHwvadsf1brA2JAjP+9IOWwTiw==", | |
| 235 | "dev": true, | |
| 236 | "license": "Apache-2.0", | |
| 237 | "dependencies": { | |
| 238 | "bare-os": "^3.0.1" | |
| 239 | } | |
| 240 | }, | |
| 241 | "node_modules/bare-events": { | |
| 242 | "version": "2.8.2", | |
| 243 | "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", | |
| 244 | "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", | |
| 245 | "dev": true, | |
| 246 | "license": "Apache-2.0", | |
| 247 | "peerDependencies": { | |
| 248 | "bare-abort-controller": "*" | |
| 249 | }, | |
| 250 | "peerDependenciesMeta": { | |
| 251 | "bare-abort-controller": { | |
| 252 | "optional": true | |
| 253 | } | |
| 254 | } | |
| 255 | }, | |
| 256 | "node_modules/bare-format": { | |
| 257 | "version": "1.0.1", | |
| 258 | "resolved": "https://registry.npmjs.org/bare-format/-/bare-format-1.0.1.tgz", | |
| 259 | "integrity": "sha512-1oS+LZrWK6tnYnvNSHDGljc2MPunRxwhpFriuCgzNF+oklrnwmBKD91tS0yt+jpl2j3UgcSDzBIMiVTvLs9A8w==", | |
| 260 | "dev": true, | |
| 261 | "license": "Apache-2.0", | |
| 262 | "dependencies": { | |
| 263 | "bare-inspect": "^3.0.0" | |
| 264 | } | |
| 265 | }, | |
| 266 | "node_modules/bare-fs": { | |
| 267 | "version": "4.5.4", | |
| 268 | "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.4.tgz", | |
| 269 | "integrity": "sha512-POK4oplfA7P7gqvetNmCs4CNtm9fNsx+IAh7jH7GgU0OJdge2rso0R20TNWVq6VoWcCvsTdlNDaleLHGaKx8CA==", | |
| 270 | "dev": true, | |
| 271 | "license": "Apache-2.0", | |
| 272 | "dependencies": { | |
| 273 | "bare-events": "^2.5.4", | |
| 274 | "bare-path": "^3.0.0", | |
| 275 | "bare-stream": "^2.6.4", | |
| 276 | "bare-url": "^2.2.2", | |
| 277 | "fast-fifo": "^1.3.2" | |
| 278 | }, | |
| 279 | "engines": { | |
| 280 | "bare": ">=1.16.0" | |
| 281 | }, | |
| 282 | "peerDependencies": { | |
| 283 | "bare-buffer": "*" | |
| 284 | }, | |
| 285 | "peerDependenciesMeta": { | |
| 286 | "bare-buffer": { | |
| 287 | "optional": true | |
| 288 | } | |
| 289 | } | |
| 290 | }, | |
| 291 | "node_modules/bare-hrtime": { | |
| 292 | "version": "2.1.1", | |
| 293 | "resolved": "https://registry.npmjs.org/bare-hrtime/-/bare-hrtime-2.1.1.tgz", | |
| 294 | "integrity": "sha512-VMb3tHo05gsnbu3OXTmkDiwTjMlOsbQmKoysKqKEyR09m77TuDrYFbj3Q5GGk10dAKsUHrnXmwCaeJqzVpB5ZA==", | |
| 295 | "dev": true, | |
| 296 | "license": "Apache-2.0" | |
| 297 | }, | |
| 298 | "node_modules/bare-http-parser": { | |
| 299 | "version": "1.0.1", | |
| 300 | "resolved": "https://registry.npmjs.org/bare-http-parser/-/bare-http-parser-1.0.1.tgz", | |
| 301 | "integrity": "sha512-A3LTDTcELcmNJ3g5liIaS038v/BQxOhA9cjhBESn7eoV7QCuMoIRBKLDadDe08flxyLbxI2f+1l2MZ/5+HnKPA==", | |
| 302 | "dev": true, | |
| 303 | "license": "Apache-2.0" | |
| 304 | }, | |
| 305 | "node_modules/bare-http1": { | |
| 306 | "version": "4.2.3", | |
| 307 | "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.2.3.tgz", | |
| 308 | "integrity": "sha512-zyvHF3xzvBJNpuw0QydE9etTn2AyC/TRVDWTo8Ed3LQEEGaFOkv6KHhaJPlf4eWEeTekk41Fowie+fTsXoNxkA==", | |
| 309 | "dev": true, | |
| 310 | "license": "Apache-2.0", | |
| 311 | "dependencies": { | |
| 312 | "bare-events": "^2.6.0", | |
| 313 | "bare-http-parser": "^1.0.0", | |
| 314 | "bare-stream": "^2.3.0", | |
| 315 | "bare-tcp": "^2.2.0" | |
| 316 | }, | |
| 317 | "peerDependencies": { | |
| 318 | "bare-buffer": "*", | |
| 319 | "bare-url": "*" | |
| 320 | }, | |
| 321 | "peerDependenciesMeta": { | |
| 322 | "bare-buffer": { | |
| 323 | "optional": true | |
| 324 | }, | |
| 325 | "bare-url": { | |
| 326 | "optional": true | |
| 327 | } | |
| 328 | } | |
| 329 | }, | |
| 330 | "node_modules/bare-https": { | |
| 331 | "version": "2.1.2", | |
| 332 | "resolved": "https://registry.npmjs.org/bare-https/-/bare-https-2.1.2.tgz", | |
| 333 | "integrity": "sha512-Q+TTydUDsuKQJvh8dX2dvOXCR9fM3xR5TBmKaFrs5p7Lj7XbKX7v4vIUJ36H0SXg2xCOQxXKIbjwrLg5tfJNYg==", | |
| 334 | "dev": true, | |
| 335 | "license": "Apache-2.0", | |
| 336 | "dependencies": { | |
| 337 | "bare-http1": "^4.0.0", | |
| 338 | "bare-tcp": "^2.2.0", | |
| 339 | "bare-tls": "^2.0.0" | |
| 340 | } | |
| 341 | }, | |
| 342 | "node_modules/bare-inspect": { | |
| 343 | "version": "3.1.4", | |
| 344 | "resolved": "https://registry.npmjs.org/bare-inspect/-/bare-inspect-3.1.4.tgz", | |
| 345 | "integrity": "sha512-jfW5KRA84o3REpI6Vr4nbvMn+hqVAw8GU1mMdRwUsY5yJovQamxYeKGVKGqdzs+8ZbG4jRzGUXP/3Ji/DnqfPg==", | |
| 346 | "dev": true, | |
| 347 | "license": "Apache-2.0", | |
| 348 | "dependencies": { | |
| 349 | "bare-ansi-escapes": "^2.1.0", | |
| 350 | "bare-type": "^1.0.0" | |
| 351 | }, | |
| 352 | "engines": { | |
| 353 | "bare": ">=1.18.0" | |
| 354 | } | |
| 355 | }, | |
| 356 | "node_modules/bare-inspector": { | |
| 357 | "version": "4.0.3", | |
| 358 | "resolved": "https://registry.npmjs.org/bare-inspector/-/bare-inspector-4.0.3.tgz", | |
| 359 | "integrity": "sha512-yYk6+7tTVKJKHOja5+QBn989ETv8oVsmwoH0nCM+QYC6w5MC03GC4aQNK+lipAZSz1EaAj42WZ8Wewjpy0L9TA==", | |
| 360 | "dev": true, | |
| 361 | "license": "Apache-2.0", | |
| 362 | "dependencies": { | |
| 363 | "bare-events": "^2.1.0", | |
| 364 | "bare-http1": "^4.0.0", | |
| 365 | "bare-stream": "^2.0.0", | |
| 366 | "bare-url": "^2.0.0", | |
| 367 | "bare-ws": "^2.0.0" | |
| 368 | }, | |
| 369 | "engines": { | |
| 370 | "bare": ">=1.2.0" | |
| 371 | }, | |
| 372 | "peerDependencies": { | |
| 373 | "bare-tcp": "*" | |
| 374 | }, | |
| 375 | "peerDependenciesMeta": { | |
| 376 | "bare-tcp": { | |
| 377 | "optional": true | |
| 378 | } | |
| 379 | } | |
| 380 | }, | |
| 381 | "node_modules/bare-module": { | |
| 382 | "version": "6.1.3", | |
| 383 | "resolved": "https://registry.npmjs.org/bare-module/-/bare-module-6.1.3.tgz", | |
| 384 | "integrity": "sha512-5XWsVHsvtWMH4tK4DQWgpNTV0t/sg3ZrAaQLIxrwjrS5+u8Q9vEgc/zQ4QaDPWDse/y/5h+d+YG1Q0JfSMt0zA==", | |
| 385 | "dev": true, | |
| 386 | "license": "Apache-2.0", | |
| 387 | "dependencies": { | |
| 388 | "bare-bundle": "^1.3.0", | |
| 389 | "bare-module-lexer": "^1.0.0", | |
| 390 | "bare-module-resolve": "^1.8.0", | |
| 391 | "bare-path": "^3.0.0", | |
| 392 | "bare-url": "^2.0.1" | |
| 393 | }, | |
| 394 | "engines": { | |
| 395 | "bare": ">=1.23.0" | |
| 396 | }, | |
| 397 | "peerDependencies": { | |
| 398 | "bare-buffer": "*" | |
| 399 | }, | |
| 400 | "peerDependenciesMeta": { | |
| 401 | "bare-buffer": { | |
| 402 | "optional": true | |
| 403 | } | |
| 404 | } | |
| 405 | }, | |
| 406 | "node_modules/bare-module-lexer": { | |
| 407 | "version": "1.4.7", | |
| 408 | "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.4.7.tgz", | |
| 409 | "integrity": "sha512-0klU4eMsjh/wcxi8FdHmNom2j2F4kmkXOhyJFL9qTaSFp2lE3m6BtbKgMHY8R5miqC9r8/IfA8wzXnC5Os14WA==", | |
| 410 | "dev": true, | |
| 411 | "license": "Apache-2.0", | |
| 412 | "dependencies": { | |
| 413 | "require-addon": "^1.0.2" | |
| 414 | }, | |
| 415 | "peerDependencies": { | |
| 416 | "bare-buffer": "*" | |
| 417 | }, | |
| 418 | "peerDependenciesMeta": { | |
| 419 | "bare-buffer": { | |
| 420 | "optional": true | |
| 421 | } | |
| 422 | } | |
| 423 | }, | |
| 424 | "node_modules/bare-module-resolve": { | |
| 425 | "version": "1.12.1", | |
| 426 | "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.1.tgz", | |
| 427 | "integrity": "sha512-hbmAPyFpEq8FoZMd5sFO3u6MC5feluWoGE8YKlA8fCrl6mNtx68Wjg4DTiDJcqRJaovTvOYKfYngoBUnbaT7eg==", | |
| 428 | "dev": true, | |
| 429 | "license": "Apache-2.0", | |
| 430 | "dependencies": { | |
| 431 | "bare-semver": "^1.0.0" | |
| 432 | }, | |
| 433 | "peerDependencies": { | |
| 434 | "bare-url": "*" | |
| 435 | }, | |
| 436 | "peerDependenciesMeta": { | |
| 437 | "bare-url": { | |
| 438 | "optional": true | |
| 439 | } | |
| 440 | } | |
| 441 | }, | |
| 442 | "node_modules/bare-net": { | |
| 443 | "version": "2.2.0", | |
| 444 | "resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.2.0.tgz", | |
| 445 | "integrity": "sha512-UF7cAbHsGE+H6uEqWF5IULBow1x58chZz4g3ALgHtv7wZsFcCbRDt0JKWEumf5Oma3QWS1Q6aLi0Rpll8RElMg==", | |
| 446 | "dev": true, | |
| 447 | "license": "Apache-2.0", | |
| 448 | "dependencies": { | |
| 449 | "bare-events": "^2.2.2", | |
| 450 | "bare-pipe": "^4.0.0", | |
| 451 | "bare-stream": "^2.0.0", | |
| 452 | "bare-tcp": "^2.0.0" | |
| 453 | } | |
| 454 | }, | |
| 455 | "node_modules/bare-os": { | |
| 456 | "version": "3.6.2", | |
| 457 | "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", | |
| 458 | "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", | |
| 459 | "dev": true, | |
| 460 | "license": "Apache-2.0", | |
| 461 | "engines": { | |
| 462 | "bare": ">=1.14.0" | |
| 463 | } | |
| 464 | }, | |
| 465 | "node_modules/bare-path": { | |
| 466 | "version": "3.0.0", | |
| 467 | "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", | |
| 468 | "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", | |
| 469 | "dev": true, | |
| 470 | "license": "Apache-2.0", | |
| 471 | "dependencies": { | |
| 472 | "bare-os": "^3.0.1" | |
| 473 | } | |
| 474 | }, | |
| 475 | "node_modules/bare-pipe": { | |
| 476 | "version": "4.1.3", | |
| 477 | "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.1.3.tgz", | |
| 478 | "integrity": "sha512-DqQsx93rAzre6yJ9T6l/Vgh+X+bntkVMB1X5ggtXjXtqtMmF2Y2RVlCzxxy/09R6yeR9FSWBEUIaMYJL1/5VDA==", | |
| 479 | "dev": true, | |
| 480 | "license": "Apache-2.0", | |
| 481 | "dependencies": { | |
| 482 | "bare-events": "^2.0.0", | |
| 483 | "bare-stream": "^2.0.0" | |
| 484 | }, | |
| 485 | "engines": { | |
| 486 | "bare": ">=1.16.0" | |
| 487 | } | |
| 488 | }, | |
| 489 | "node_modules/bare-process": { | |
| 490 | "version": "4.2.2", | |
| 491 | "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.2.2.tgz", | |
| 492 | "integrity": "sha512-ikzEw+HGLB+2lS/WLVEsqi8BXBwzleG3n7DYI0v6/YNklvNabOIGlLd1dof+7Jw5ob4jsBRPtyflweVahY7rFA==", | |
| 493 | "dev": true, | |
| 494 | "license": "Apache-2.0", | |
| 495 | "dependencies": { | |
| 496 | "bare-env": "^3.0.0", | |
| 497 | "bare-events": "^2.3.1", | |
| 498 | "bare-hrtime": "^2.0.0", | |
| 499 | "bare-os": "^3.5.0", | |
| 500 | "bare-pipe": "^4.0.0", | |
| 501 | "bare-signals": "^4.0.0", | |
| 502 | "bare-tty": "^5.0.0" | |
| 503 | } | |
| 504 | }, | |
| 505 | "node_modules/bare-semver": { | |
| 506 | "version": "1.0.2", | |
| 507 | "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.0.2.tgz", | |
| 508 | "integrity": "sha512-ESVaN2nzWhcI5tf3Zzcq9aqCZ676VWzqw07eEZ0qxAcEOAFYBa0pWq8sK34OQeHLY3JsfKXZS9mDyzyxGjeLzA==", | |
| 509 | "dev": true, | |
| 510 | "license": "Apache-2.0" | |
| 511 | }, | |
| 512 | "node_modules/bare-signals": { | |
| 513 | "version": "4.2.0", | |
| 514 | "resolved": "https://registry.npmjs.org/bare-signals/-/bare-signals-4.2.0.tgz", | |
| 515 | "integrity": "sha512-fNHMOdQIlYuTvMB3Oh9Apk99hLKn351+Ir8vz+khiPTcOqIyGG4uWWjdLTzxWdYGsA0eT+We3y0K74hjj2nq7A==", | |
| 516 | "dev": true, | |
| 517 | "license": "Apache-2.0", | |
| 518 | "dependencies": { | |
| 519 | "bare-events": "^2.5.3", | |
| 520 | "bare-os": "^3.3.1" | |
| 521 | }, | |
| 522 | "engines": { | |
| 523 | "bare": ">=1.7.0" | |
| 524 | } | |
| 525 | }, | |
| 526 | "node_modules/bare-stream": { | |
| 527 | "version": "2.7.0", | |
| 528 | "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", | |
| 529 | "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", | |
| 530 | "dev": true, | |
| 531 | "license": "Apache-2.0", | |
| 532 | "dependencies": { | |
| 533 | "streamx": "^2.21.0" | |
| 534 | }, | |
| 535 | "peerDependencies": { | |
| 536 | "bare-buffer": "*", | |
| 537 | "bare-events": "*" | |
| 538 | }, | |
| 539 | "peerDependenciesMeta": { | |
| 540 | "bare-buffer": { | |
| 541 | "optional": true | |
| 542 | }, | |
| 543 | "bare-events": { | |
| 544 | "optional": true | |
| 545 | } | |
| 546 | } | |
| 547 | }, | |
| 548 | "node_modules/bare-subprocess": { | |
| 549 | "version": "5.2.2", | |
| 550 | "resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.2.tgz", | |
| 551 | "integrity": "sha512-L6oXgQ1aWs25RtG5Ky0bDD06p3RAcVVrDDMWb1DfXpHtyEWxamcyIWUbSykxMTWrpLlmSj6ytbb6yoKehGFfmw==", | |
| 552 | "dev": true, | |
| 553 | "license": "Apache-2.0", | |
| 554 | "dependencies": { | |
| 555 | "bare-env": "^3.0.0", | |
| 556 | "bare-events": "^2.5.4", | |
| 557 | "bare-os": "^3.0.1", | |
| 558 | "bare-pipe": "^4.0.0", | |
| 559 | "bare-url": "^2.2.2" | |
| 560 | }, | |
| 561 | "engines": { | |
| 562 | "bare": ">=1.7.0" | |
| 563 | }, | |
| 564 | "peerDependencies": { | |
| 565 | "bare-buffer": "*" | |
| 566 | }, | |
| 567 | "peerDependenciesMeta": { | |
| 568 | "bare-buffer": { | |
| 569 | "optional": true | |
| 570 | } | |
| 571 | } | |
| 572 | }, | |
| 573 | "node_modules/bare-tcp": { | |
| 574 | "version": "2.2.4", | |
| 575 | "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.2.4.tgz", | |
| 576 | "integrity": "sha512-vGjfMkyhS1lxp+Y4+gXRDR1ijZMK2cKx0RBwtYKUuZbD90ARCoM6bAiTa1HBztumCIC+NXb8eXdMFXyuYwlPyw==", | |
| 577 | "dev": true, | |
| 578 | "license": "Apache-2.0", | |
| 579 | "dependencies": { | |
| 580 | "bare-dns": "^2.0.4", | |
| 581 | "bare-events": "^2.5.4", | |
| 582 | "bare-stream": "^2.6.4" | |
| 583 | }, | |
| 584 | "engines": { | |
| 585 | "bare": ">=1.16.0" | |
| 586 | } | |
| 587 | }, | |
| 588 | "node_modules/bare-tls": { | |
| 589 | "version": "2.1.7", | |
| 590 | "resolved": "https://registry.npmjs.org/bare-tls/-/bare-tls-2.1.7.tgz", | |
| 591 | "integrity": "sha512-h6wcNXQdBeTX7fed9tjPp0/9cA/QfcBTv3ItgjnbUk4rWAU8bEFalZCZnUDdCK/t9zrNfJ+yvcPx4D/1Y6biyA==", | |
| 592 | "dev": true, | |
| 593 | "license": "Apache-2.0", | |
| 594 | "dependencies": { | |
| 595 | "bare-net": "^2.0.1", | |
| 596 | "bare-stream": "^2.6.4" | |
| 597 | }, | |
| 598 | "engines": { | |
| 599 | "bare": ">=1.7.0" | |
| 600 | } | |
| 601 | }, | |
| 602 | "node_modules/bare-tty": { | |
| 603 | "version": "5.0.3", | |
| 604 | "resolved": "https://registry.npmjs.org/bare-tty/-/bare-tty-5.0.3.tgz", | |
| 605 | "integrity": "sha512-jW24RBWRZOMHuSEWC9mh8wUKO5WUNl0UWUTNPn3yZ8qkOqwa8vaV2dR3a+BvblONPk7V35wuvK9P8508+judAg==", | |
| 606 | "dev": true, | |
| 607 | "license": "Apache-2.0", | |
| 608 | "dependencies": { | |
| 609 | "bare-events": "^2.2.0", | |
| 610 | "bare-signals": "^4.0.0", | |
| 611 | "bare-stream": "^2.0.0" | |
| 612 | }, | |
| 613 | "engines": { | |
| 614 | "bare": ">=1.16.0" | |
| 615 | } | |
| 616 | }, | |
| 617 | "node_modules/bare-type": { | |
| 618 | "version": "1.1.0", | |
| 619 | "resolved": "https://registry.npmjs.org/bare-type/-/bare-type-1.1.0.tgz", | |
| 620 | "integrity": "sha512-LdtnnEEYldOc87Dr4GpsKnStStZk3zfgoEMXy8yvEZkXrcCv9RtYDrUYWFsBQHtaB0s1EUWmcvS6XmEZYIj3Bw==", | |
| 621 | "dev": true, | |
| 622 | "license": "Apache-2.0", | |
| 623 | "engines": { | |
| 624 | "bare": ">=1.2.0" | |
| 625 | } | |
| 626 | }, | |
| 627 | "node_modules/bare-url": { | |
| 628 | "version": "2.3.2", | |
| 629 | "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", | |
| 630 | "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", | |
| 631 | "dev": true, | |
| 632 | "license": "Apache-2.0", | |
| 633 | "peer": true, | |
| 634 | "dependencies": { | |
| 635 | "bare-path": "^3.0.0" | |
| 636 | } | |
| 637 | }, | |
| 638 | "node_modules/bare-utils": { | |
| 639 | "version": "1.5.1", | |
| 640 | "resolved": "https://registry.npmjs.org/bare-utils/-/bare-utils-1.5.1.tgz", | |
| 641 | "integrity": "sha512-mxCkFvmDU3mlD/mb+pT64kKXOsx2KMsWLQbngN1LB+NOXfhfnRnyvpy3VZc6m7gzQxe57Bsi+aTCBqA4/S3elQ==", | |
| 642 | "dev": true, | |
| 643 | "license": "Apache-2.0", | |
| 644 | "dependencies": { | |
| 645 | "bare-debug-log": "^2.0.0", | |
| 646 | "bare-encoding": "^1.0.0", | |
| 647 | "bare-format": "^1.0.0", | |
| 648 | "bare-inspect": "^3.0.0", | |
| 649 | "bare-type": "^1.0.6" | |
| 650 | } | |
| 651 | }, | |
| 652 | "node_modules/bare-v8-to-istanbul": { | |
| 653 | "version": "1.0.2", | |
| 654 | "resolved": "https://registry.npmjs.org/bare-v8-to-istanbul/-/bare-v8-to-istanbul-1.0.2.tgz", | |
| 655 | "integrity": "sha512-CM0wPgyQtXTQ7h4q8NjbTZ/FH5he0Jkiwgpq0lFZZA5tkVZzmQ/9ul4R7E+cNlGZJIZlQZ9v4CwbgVaFG3zOAA==", | |
| 656 | "dev": true, | |
| 657 | "license": "Apache-2.0", | |
| 658 | "dependencies": { | |
| 659 | "bare-assert": "^1.0.2", | |
| 660 | "bare-fs": "^4.1.2", | |
| 661 | "bare-module": "^6.1.2", | |
| 662 | "bare-path": "^3.0.0", | |
| 663 | "bare-process": "^4.2.0", | |
| 664 | "bare-url": "^2.1.5", | |
| 665 | "bare-utils": "^1.2.0", | |
| 666 | "v8-to-istanbul": "^9.3.0", | |
| 667 | "which-runtime": "^1.2.1" | |
| 668 | } | |
| 669 | }, | |
| 670 | "node_modules/bare-ws": { | |
| 671 | "version": "2.0.4", | |
| 672 | "resolved": "https://registry.npmjs.org/bare-ws/-/bare-ws-2.0.4.tgz", | |
| 673 | "integrity": "sha512-SQMXzBYna9dRj57Dz1/ag+VWHCRXbfCjMHgyfM2F2lhkVLzMjnVSZP72aVeFWPFqe494Rd70Kzhe2JElGwFlJQ==", | |
| 674 | "dev": true, | |
| 675 | "license": "Apache-2.0", | |
| 676 | "dependencies": { | |
| 677 | "bare-crypto": "^1.2.0", | |
| 678 | "bare-events": "^2.3.1", | |
| 679 | "bare-http1": "^4.0.0", | |
| 680 | "bare-https": "^2.0.0", | |
| 681 | "bare-stream": "^2.1.2" | |
| 682 | }, | |
| 683 | "peerDependencies": { | |
| 684 | "bare-buffer": "*", | |
| 685 | "bare-url": "*" | |
| 686 | }, | |
| 687 | "peerDependenciesMeta": { | |
| 688 | "bare-buffer": { | |
| 689 | "optional": true | |
| 690 | }, | |
| 691 | "bare-url": { | |
| 692 | "optional": true | |
| 693 | } | |
| 694 | } | |
| 695 | }, | |
| 696 | "node_modules/base64-js": { | |
| 697 | "version": "1.5.1", | |
| 698 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", | |
| 699 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", | |
| 700 | "funding": [ | |
| 701 | { | |
| 702 | "type": "github", | |
| 703 | "url": "https://github.com/sponsors/feross" | |
| 704 | }, | |
| 705 | { | |
| 706 | "type": "patreon", | |
| 707 | "url": "https://www.patreon.com/feross" | |
| 708 | }, | |
| 709 | { | |
| 710 | "type": "consulting", | |
| 711 | "url": "https://feross.org/support" | |
| 712 | } | |
| 713 | ], | |
| 714 | "license": "MIT" | |
| 715 | }, | |
| 716 | "node_modules/bridge-envelope": { | |
| 717 | "resolved": "../bridge-envelope", | |
| 718 | "link": true | |
| 719 | }, | |
| 720 | "node_modules/brittle": { | |
| 721 | "version": "3.19.1", | |
| 722 | "resolved": "https://registry.npmjs.org/brittle/-/brittle-3.19.1.tgz", | |
| 723 | "integrity": "sha512-4Ted1Mt9o9B6oIA6ImJJCtB/Fv++ZO3IDNQgRcHOXWSYGRUidgxchaGrUBaRn+4mlneXrgInPkCPzi0jO8qKbA==", | |
| 724 | "dev": true, | |
| 725 | "license": "Apache-2.0", | |
| 726 | "dependencies": { | |
| 727 | "b4a": "^1.6.0", | |
| 728 | "bare-assert": "^1.0.2", | |
| 729 | "bare-cov": "^1.1.0", | |
| 730 | "bare-fs": "^4.1.6", | |
| 731 | "bare-os": "^3.6.1", | |
| 732 | "bare-path": "^3.0.0", | |
| 733 | "bare-process": "^4.2.1", | |
| 734 | "bare-subprocess": "^5.0.0", | |
| 735 | "bare-url": "^2.1.6", | |
| 736 | "error-stack-parser": "^2.1.4", | |
| 737 | "globbie": "^1.0.2", | |
| 738 | "paparam": "^1.6.2", | |
| 739 | "same-object": "^1.0.2", | |
| 740 | "test-tmp": "^1.4.0", | |
| 741 | "tmatch": "^5.0.0" | |
| 742 | }, | |
| 743 | "bin": { | |
| 744 | "brittle": "bin/node.js", | |
| 745 | "brittle-bare": "bin/bare.js", | |
| 746 | "brittle-node": "bin/node.js", | |
| 747 | "brittle-pear": "bin/pear.js" | |
| 748 | } | |
| 749 | }, | |
| 750 | "node_modules/buffer": { | |
| 751 | "version": "6.0.3", | |
| 752 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", | |
| 753 | "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", | |
| 754 | "funding": [ | |
| 755 | { | |
| 756 | "type": "github", | |
| 757 | "url": "https://github.com/sponsors/feross" | |
| 758 | }, | |
| 759 | { | |
| 760 | "type": "patreon", | |
| 761 | "url": "https://www.patreon.com/feross" | |
| 762 | }, | |
| 763 | { | |
| 764 | "type": "consulting", | |
| 765 | "url": "https://feross.org/support" | |
| 766 | } | |
| 767 | ], | |
| 768 | "license": "MIT", | |
| 769 | "dependencies": { | |
| 770 | "base64-js": "^1.3.1", | |
| 771 | "ieee754": "^1.2.1" | |
| 772 | } | |
| 773 | }, | |
| 774 | "node_modules/call-bind": { | |
| 775 | "version": "1.0.8", | |
| 776 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", | |
| 777 | "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", | |
| 778 | "license": "MIT", | |
| 779 | "dependencies": { | |
| 780 | "call-bind-apply-helpers": "^1.0.0", | |
| 781 | "es-define-property": "^1.0.0", | |
| 782 | "get-intrinsic": "^1.2.4", | |
| 783 | "set-function-length": "^1.2.2" | |
| 784 | }, | |
| 785 | "engines": { | |
| 786 | "node": ">= 0.4" | |
| 787 | }, | |
| 788 | "funding": { | |
| 789 | "url": "https://github.com/sponsors/ljharb" | |
| 790 | } | |
| 791 | }, | |
| 792 | "node_modules/call-bind-apply-helpers": { | |
| 793 | "version": "1.0.2", | |
| 794 | "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", | |
| 795 | "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", | |
| 796 | "license": "MIT", | |
| 797 | "dependencies": { | |
| 798 | "es-errors": "^1.3.0", | |
| 799 | "function-bind": "^1.1.2" | |
| 800 | }, | |
| 801 | "engines": { | |
| 802 | "node": ">= 0.4" | |
| 803 | } | |
| 804 | }, | |
| 805 | "node_modules/call-bound": { | |
| 806 | "version": "1.0.4", | |
| 807 | "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", | |
| 808 | "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", | |
| 809 | "license": "MIT", | |
| 810 | "dependencies": { | |
| 811 | "call-bind-apply-helpers": "^1.0.2", | |
| 812 | "get-intrinsic": "^1.3.0" | |
| 813 | }, | |
| 814 | "engines": { | |
| 815 | "node": ">= 0.4" | |
| 816 | }, | |
| 817 | "funding": { | |
| 818 | "url": "https://github.com/sponsors/ljharb" | |
| 819 | } | |
| 820 | }, | |
| 821 | "node_modules/convert-source-map": { | |
| 822 | "version": "2.0.0", | |
| 823 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", | |
| 824 | "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", | |
| 825 | "dev": true, | |
| 826 | "license": "MIT" | |
| 827 | }, | |
| 828 | "node_modules/core-js": { | |
| 829 | "version": "3.48.0", | |
| 830 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", | |
| 831 | "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", | |
| 832 | "hasInstallScript": true, | |
| 833 | "license": "MIT", | |
| 834 | "funding": { | |
| 835 | "type": "opencollective", | |
| 836 | "url": "https://opencollective.com/core-js" | |
| 837 | } | |
| 838 | }, | |
| 839 | "node_modules/define-data-property": { | |
| 840 | "version": "1.1.4", | |
| 841 | "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", | |
| 842 | "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", | |
| 843 | "license": "MIT", | |
| 844 | "dependencies": { | |
| 845 | "es-define-property": "^1.0.0", | |
| 846 | "es-errors": "^1.3.0", | |
| 847 | "gopd": "^1.0.1" | |
| 848 | }, | |
| 849 | "engines": { | |
| 850 | "node": ">= 0.4" | |
| 851 | }, | |
| 852 | "funding": { | |
| 853 | "url": "https://github.com/sponsors/ljharb" | |
| 854 | } | |
| 855 | }, | |
| 856 | "node_modules/dunder-proto": { | |
| 857 | "version": "1.0.1", | |
| 858 | "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", | |
| 859 | "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", | |
| 860 | "license": "MIT", | |
| 861 | "dependencies": { | |
| 862 | "call-bind-apply-helpers": "^1.0.1", | |
| 863 | "es-errors": "^1.3.0", | |
| 864 | "gopd": "^1.2.0" | |
| 865 | }, | |
| 866 | "engines": { | |
| 867 | "node": ">= 0.4" | |
| 868 | } | |
| 869 | }, | |
| 870 | "node_modules/error-stack-parser": { | |
| 871 | "version": "2.1.4", | |
| 872 | "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", | |
| 873 | "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", | |
| 874 | "dev": true, | |
| 875 | "license": "MIT", | |
| 876 | "dependencies": { | |
| 877 | "stackframe": "^1.3.4" | |
| 878 | } | |
| 879 | }, | |
| 880 | "node_modules/es-define-property": { | |
| 881 | "version": "1.0.1", | |
| 882 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", | |
| 883 | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", | |
| 884 | "license": "MIT", | |
| 885 | "engines": { | |
| 886 | "node": ">= 0.4" | |
| 887 | } | |
| 888 | }, | |
| 889 | "node_modules/es-errors": { | |
| 890 | "version": "1.3.0", | |
| 891 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", | |
| 892 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", | |
| 893 | "license": "MIT", | |
| 894 | "engines": { | |
| 895 | "node": ">= 0.4" | |
| 896 | } | |
| 897 | }, | |
| 898 | "node_modules/es-object-atoms": { | |
| 899 | "version": "1.1.1", | |
| 900 | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", | |
| 901 | "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", | |
| 902 | "license": "MIT", | |
| 903 | "dependencies": { | |
| 904 | "es-errors": "^1.3.0" | |
| 905 | }, | |
| 906 | "engines": { | |
| 907 | "node": ">= 0.4" | |
| 908 | } | |
| 909 | }, | |
| 910 | "node_modules/eventemitter3": { | |
| 911 | "version": "5.0.4", | |
| 912 | "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", | |
| 913 | "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", | |
| 914 | "license": "MIT" | |
| 915 | }, | |
| 916 | "node_modules/events-universal": { | |
| 917 | "version": "1.0.1", | |
| 918 | "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", | |
| 919 | "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", | |
| 920 | "dev": true, | |
| 921 | "license": "Apache-2.0", | |
| 922 | "dependencies": { | |
| 923 | "bare-events": "^2.7.0" | |
| 924 | } | |
| 925 | }, | |
| 926 | "node_modules/fast-fifo": { | |
| 927 | "version": "1.3.2", | |
| 928 | "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", | |
| 929 | "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", | |
| 930 | "dev": true, | |
| 931 | "license": "MIT" | |
| 932 | }, | |
| 933 | "node_modules/fast-text-encoding": { | |
| 934 | "version": "1.0.6", | |
| 935 | "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", | |
| 936 | "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==", | |
| 937 | "license": "Apache-2.0" | |
| 938 | }, | |
| 939 | "node_modules/for-each": { | |
| 940 | "version": "0.3.5", | |
| 941 | "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", | |
| 942 | "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", | |
| 943 | "license": "MIT", | |
| 944 | "dependencies": { | |
| 945 | "is-callable": "^1.2.7" | |
| 946 | }, | |
| 947 | "engines": { | |
| 948 | "node": ">= 0.4" | |
| 949 | }, | |
| 950 | "funding": { | |
| 951 | "url": "https://github.com/sponsors/ljharb" | |
| 952 | } | |
| 953 | }, | |
| 954 | "node_modules/function-bind": { | |
| 955 | "version": "1.1.2", | |
| 956 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", | |
| 957 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", | |
| 958 | "license": "MIT", | |
| 959 | "funding": { | |
| 960 | "url": "https://github.com/sponsors/ljharb" | |
| 961 | } | |
| 962 | }, | |
| 963 | "node_modules/generator-function": { | |
| 964 | "version": "2.0.1", | |
| 965 | "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", | |
| 966 | "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", | |
| 967 | "license": "MIT", | |
| 968 | "engines": { | |
| 969 | "node": ">= 0.4" | |
| 970 | } | |
| 971 | }, | |
| 972 | "node_modules/get-intrinsic": { | |
| 973 | "version": "1.3.0", | |
| 974 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", | |
| 975 | "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", | |
| 976 | "license": "MIT", | |
| 977 | "dependencies": { | |
| 978 | "call-bind-apply-helpers": "^1.0.2", | |
| 979 | "es-define-property": "^1.0.1", | |
| 980 | "es-errors": "^1.3.0", | |
| 981 | "es-object-atoms": "^1.1.1", | |
| 982 | "function-bind": "^1.1.2", | |
| 983 | "get-proto": "^1.0.1", | |
| 984 | "gopd": "^1.2.0", | |
| 985 | "has-symbols": "^1.1.0", | |
| 986 | "hasown": "^2.0.2", | |
| 987 | "math-intrinsics": "^1.1.0" | |
| 988 | }, | |
| 989 | "engines": { | |
| 990 | "node": ">= 0.4" | |
| 991 | }, | |
| 992 | "funding": { | |
| 993 | "url": "https://github.com/sponsors/ljharb" | |
| 994 | } | |
| 995 | }, | |
| 996 | "node_modules/get-proto": { | |
| 997 | "version": "1.0.1", | |
| 998 | "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", | |
| 999 | "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", | |
| 1000 | "license": "MIT", | |
| 1001 | "dependencies": { | |
| 1002 | "dunder-proto": "^1.0.1", | |
| 1003 | "es-object-atoms": "^1.0.0" | |
| 1004 | }, | |
| 1005 | "engines": { | |
| 1006 | "node": ">= 0.4" | |
| 1007 | } | |
| 1008 | }, | |
| 1009 | "node_modules/globbie": { | |
| 1010 | "version": "1.0.3", | |
| 1011 | "resolved": "https://registry.npmjs.org/globbie/-/globbie-1.0.3.tgz", | |
| 1012 | "integrity": "sha512-hcryJmKcftf82xfBWTWxuUITWIIoYO3ec104V17SMHGFl6VLbm1d1Ju9LX9L+jyJTwICpemX/dmPI/HGYoKr1A==", | |
| 1013 | "dev": true, | |
| 1014 | "license": "Apache-2.0", | |
| 1015 | "dependencies": { | |
| 1016 | "bare-fs": "^4.1.2", | |
| 1017 | "bare-path": "^3.0.0", | |
| 1018 | "bare-process": "^4.2.1", | |
| 1019 | "picomatch": "^4.0.2" | |
| 1020 | } | |
| 1021 | }, | |
| 1022 | "node_modules/gopd": { | |
| 1023 | "version": "1.2.0", | |
| 1024 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", | |
| 1025 | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", | |
| 1026 | "license": "MIT", | |
| 1027 | "engines": { | |
| 1028 | "node": ">= 0.4" | |
| 1029 | }, | |
| 1030 | "funding": { | |
| 1031 | "url": "https://github.com/sponsors/ljharb" | |
| 1032 | } | |
| 1033 | }, | |
| 1034 | "node_modules/grapheme-splitter": { | |
| 1035 | "version": "1.0.4", | |
| 1036 | "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", | |
| 1037 | "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", | |
| 1038 | "license": "MIT" | |
| 1039 | }, | |
| 1040 | "node_modules/has-property-descriptors": { | |
| 1041 | "version": "1.0.2", | |
| 1042 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", | |
| 1043 | "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", | |
| 1044 | "license": "MIT", | |
| 1045 | "dependencies": { | |
| 1046 | "es-define-property": "^1.0.0" | |
| 1047 | }, | |
| 1048 | "funding": { | |
| 1049 | "url": "https://github.com/sponsors/ljharb" | |
| 1050 | } | |
| 1051 | }, | |
| 1052 | "node_modules/has-symbols": { | |
| 1053 | "version": "1.1.0", | |
| 1054 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", | |
| 1055 | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", | |
| 1056 | "license": "MIT", | |
| 1057 | "engines": { | |
| 1058 | "node": ">= 0.4" | |
| 1059 | }, | |
| 1060 | "funding": { | |
| 1061 | "url": "https://github.com/sponsors/ljharb" | |
| 1062 | } | |
| 1063 | }, | |
| 1064 | "node_modules/has-tostringtag": { | |
| 1065 | "version": "1.0.2", | |
| 1066 | "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", | |
| 1067 | "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", | |
| 1068 | "license": "MIT", | |
| 1069 | "dependencies": { | |
| 1070 | "has-symbols": "^1.0.3" | |
| 1071 | }, | |
| 1072 | "engines": { | |
| 1073 | "node": ">= 0.4" | |
| 1074 | }, | |
| 1075 | "funding": { | |
| 1076 | "url": "https://github.com/sponsors/ljharb" | |
| 1077 | } | |
| 1078 | }, | |
| 1079 | "node_modules/hasown": { | |
| 1080 | "version": "2.0.2", | |
| 1081 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", | |
| 1082 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", | |
| 1083 | "license": "MIT", | |
| 1084 | "dependencies": { | |
| 1085 | "function-bind": "^1.1.2" | |
| 1086 | }, | |
| 1087 | "engines": { | |
| 1088 | "node": ">= 0.4" | |
| 1089 | } | |
| 1090 | }, | |
| 1091 | "node_modules/iconv-lite": { | |
| 1092 | "version": "0.6.3", | |
| 1093 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", | |
| 1094 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", | |
| 1095 | "license": "MIT", | |
| 1096 | "dependencies": { | |
| 1097 | "safer-buffer": ">= 2.1.2 < 3.0.0" | |
| 1098 | }, | |
| 1099 | "engines": { | |
| 1100 | "node": ">=0.10.0" | |
| 1101 | } | |
| 1102 | }, | |
| 1103 | "node_modules/ieee754": { | |
| 1104 | "version": "1.2.1", | |
| 1105 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", | |
| 1106 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", | |
| 1107 | "funding": [ | |
| 1108 | { | |
| 1109 | "type": "github", | |
| 1110 | "url": "https://github.com/sponsors/feross" | |
| 1111 | }, | |
| 1112 | { | |
| 1113 | "type": "patreon", | |
| 1114 | "url": "https://www.patreon.com/feross" | |
| 1115 | }, | |
| 1116 | { | |
| 1117 | "type": "consulting", | |
| 1118 | "url": "https://feross.org/support" | |
| 1119 | } | |
| 1120 | ], | |
| 1121 | "license": "BSD-3-Clause" | |
| 1122 | }, | |
| 1123 | "node_modules/inherits": { | |
| 1124 | "version": "2.0.4", | |
| 1125 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", | |
| 1126 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", | |
| 1127 | "license": "ISC" | |
| 1128 | }, | |
| 1129 | "node_modules/ip-address": { | |
| 1130 | "version": "10.1.0", | |
| 1131 | "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", | |
| 1132 | "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", | |
| 1133 | "license": "MIT", | |
| 1134 | "engines": { | |
| 1135 | "node": ">= 12" | |
| 1136 | } | |
| 1137 | }, | |
| 1138 | "node_modules/irc-framework": { | |
| 1139 | "version": "4.14.0", | |
| 1140 | "resolved": "https://registry.npmjs.org/irc-framework/-/irc-framework-4.14.0.tgz", | |
| 1141 | "integrity": "sha512-lNujDAxy9kcu89WbU5H7IDWly64aD1B9nN9AV5M6btfx88qyQuyH16j1tjS40nmkQH6ld6vvaihKRn9cjk1JrA==", | |
| 1142 | "license": "MIT", | |
| 1143 | "dependencies": { | |
| 1144 | "buffer": "^6.0.3", | |
| 1145 | "core-js": "^3.38.1", | |
| 1146 | "eventemitter3": "^5.0.1", | |
| 1147 | "grapheme-splitter": "^1.0.4", | |
| 1148 | "iconv-lite": "^0.6.3", | |
| 1149 | "isomorphic-textencoder": "^1.0.1", | |
| 1150 | "lodash": "^4.17.21", | |
| 1151 | "middleware-handler": "^0.2.0", | |
| 1152 | "regenerator-runtime": "^0.14.1", | |
| 1153 | "socks": "^2.8.3", | |
| 1154 | "stream-browserify": "^3.0.0", | |
| 1155 | "util": "^0.12.5" | |
| 1156 | } | |
| 1157 | }, | |
| 1158 | "node_modules/is-arguments": { | |
| 1159 | "version": "1.2.0", | |
| 1160 | "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", | |
| 1161 | "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", | |
| 1162 | "license": "MIT", | |
| 1163 | "dependencies": { | |
| 1164 | "call-bound": "^1.0.2", | |
| 1165 | "has-tostringtag": "^1.0.2" | |
| 1166 | }, | |
| 1167 | "engines": { | |
| 1168 | "node": ">= 0.4" | |
| 1169 | }, | |
| 1170 | "funding": { | |
| 1171 | "url": "https://github.com/sponsors/ljharb" | |
| 1172 | } | |
| 1173 | }, | |
| 1174 | "node_modules/is-callable": { | |
| 1175 | "version": "1.2.7", | |
| 1176 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", | |
| 1177 | "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", | |
| 1178 | "license": "MIT", | |
| 1179 | "engines": { | |
| 1180 | "node": ">= 0.4" | |
| 1181 | }, | |
| 1182 | "funding": { | |
| 1183 | "url": "https://github.com/sponsors/ljharb" | |
| 1184 | } | |
| 1185 | }, | |
| 1186 | "node_modules/is-generator-function": { | |
| 1187 | "version": "1.1.2", | |
| 1188 | "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", | |
| 1189 | "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", | |
| 1190 | "license": "MIT", | |
| 1191 | "dependencies": { | |
| 1192 | "call-bound": "^1.0.4", | |
| 1193 | "generator-function": "^2.0.0", | |
| 1194 | "get-proto": "^1.0.1", | |
| 1195 | "has-tostringtag": "^1.0.2", | |
| 1196 | "safe-regex-test": "^1.1.0" | |
| 1197 | }, | |
| 1198 | "engines": { | |
| 1199 | "node": ">= 0.4" | |
| 1200 | }, | |
| 1201 | "funding": { | |
| 1202 | "url": "https://github.com/sponsors/ljharb" | |
| 1203 | } | |
| 1204 | }, | |
| 1205 | "node_modules/is-regex": { | |
| 1206 | "version": "1.2.1", | |
| 1207 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", | |
| 1208 | "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", | |
| 1209 | "license": "MIT", | |
| 1210 | "dependencies": { | |
| 1211 | "call-bound": "^1.0.2", | |
| 1212 | "gopd": "^1.2.0", | |
| 1213 | "has-tostringtag": "^1.0.2", | |
| 1214 | "hasown": "^2.0.2" | |
| 1215 | }, | |
| 1216 | "engines": { | |
| 1217 | "node": ">= 0.4" | |
| 1218 | }, | |
| 1219 | "funding": { | |
| 1220 | "url": "https://github.com/sponsors/ljharb" | |
| 1221 | } | |
| 1222 | }, | |
| 1223 | "node_modules/is-typed-array": { | |
| 1224 | "version": "1.1.15", | |
| 1225 | "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", | |
| 1226 | "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", | |
| 1227 | "license": "MIT", | |
| 1228 | "dependencies": { | |
| 1229 | "which-typed-array": "^1.1.16" | |
| 1230 | }, | |
| 1231 | "engines": { | |
| 1232 | "node": ">= 0.4" | |
| 1233 | }, | |
| 1234 | "funding": { | |
| 1235 | "url": "https://github.com/sponsors/ljharb" | |
| 1236 | } | |
| 1237 | }, | |
| 1238 | "node_modules/isomorphic-textencoder": { | |
| 1239 | "version": "1.0.1", | |
| 1240 | "resolved": "https://registry.npmjs.org/isomorphic-textencoder/-/isomorphic-textencoder-1.0.1.tgz", | |
| 1241 | "integrity": "sha512-676hESgHullDdHDsj469hr+7t3i/neBKU9J7q1T4RHaWwLAsaQnywC0D1dIUId0YZ+JtVrShzuBk1soo0+GVcQ==", | |
| 1242 | "license": "MIT", | |
| 1243 | "dependencies": { | |
| 1244 | "fast-text-encoding": "^1.0.0" | |
| 1245 | } | |
| 1246 | }, | |
| 1247 | "node_modules/lodash": { | |
| 1248 | "version": "4.17.23", | |
| 1249 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", | |
| 1250 | "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", | |
| 1251 | "license": "MIT" | |
| 1252 | }, | |
| 1253 | "node_modules/math-intrinsics": { | |
| 1254 | "version": "1.1.0", | |
| 1255 | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", | |
| 1256 | "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", | |
| 1257 | "license": "MIT", | |
| 1258 | "engines": { | |
| 1259 | "node": ">= 0.4" | |
| 1260 | } | |
| 1261 | }, | |
| 1262 | "node_modules/middleware-handler": { | |
| 1263 | "version": "0.2.0", | |
| 1264 | "resolved": "https://registry.npmjs.org/middleware-handler/-/middleware-handler-0.2.0.tgz", | |
| 1265 | "integrity": "sha512-Qz4B0yWndSokapr3Kl7fpMRysS0DaBlOuATrExFuZbr+oXZ3rsAPufdLe8mUJXiG5A4aJGW6GfKS4PDfQwu7Mg==" | |
| 1266 | }, | |
| 1267 | "node_modules/paparam": { | |
| 1268 | "version": "1.10.0", | |
| 1269 | "resolved": "https://registry.npmjs.org/paparam/-/paparam-1.10.0.tgz", | |
| 1270 | "integrity": "sha512-p36QQrwU3X6fj5d2JN20B4lcZI/O3fxMVUHc1xD7MNe5bcOf3jIgE0CIK5Zv/s5YAUGlobTJ/kKXrh1FJVrs3w==", | |
| 1271 | "dev": true, | |
| 1272 | "license": "Apache-2.0" | |
| 1273 | }, | |
| 1274 | "node_modules/picomatch": { | |
| 1275 | "version": "4.0.3", | |
| 1276 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", | |
| 1277 | "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", | |
| 1278 | "dev": true, | |
| 1279 | "license": "MIT", | |
| 1280 | "engines": { | |
| 1281 | "node": ">=12" | |
| 1282 | }, | |
| 1283 | "funding": { | |
| 1284 | "url": "https://github.com/sponsors/jonschlinkert" | |
| 1285 | } | |
| 1286 | }, | |
| 1287 | "node_modules/possible-typed-array-names": { | |
| 1288 | "version": "1.1.0", | |
| 1289 | "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", | |
| 1290 | "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", | |
| 1291 | "license": "MIT", | |
| 1292 | "engines": { | |
| 1293 | "node": ">= 0.4" | |
| 1294 | } | |
| 1295 | }, | |
| 1296 | "node_modules/readable-stream": { | |
| 1297 | "version": "3.6.2", | |
| 1298 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", | |
| 1299 | "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", | |
| 1300 | "license": "MIT", | |
| 1301 | "dependencies": { | |
| 1302 | "inherits": "^2.0.3", | |
| 1303 | "string_decoder": "^1.1.1", | |
| 1304 | "util-deprecate": "^1.0.1" | |
| 1305 | }, | |
| 1306 | "engines": { | |
| 1307 | "node": ">= 6" | |
| 1308 | } | |
| 1309 | }, | |
| 1310 | "node_modules/regenerator-runtime": { | |
| 1311 | "version": "0.14.1", | |
| 1312 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", | |
| 1313 | "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", | |
| 1314 | "license": "MIT" | |
| 1315 | }, | |
| 1316 | "node_modules/require-addon": { | |
| 1317 | "version": "1.2.0", | |
| 1318 | "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz", | |
| 1319 | "integrity": "sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA==", | |
| 1320 | "dev": true, | |
| 1321 | "license": "Apache-2.0", | |
| 1322 | "dependencies": { | |
| 1323 | "bare-addon-resolve": "^1.3.0" | |
| 1324 | }, | |
| 1325 | "engines": { | |
| 1326 | "bare": ">=1.10.0" | |
| 1327 | } | |
| 1328 | }, | |
| 1329 | "node_modules/safe-buffer": { | |
| 1330 | "version": "5.2.1", | |
| 1331 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", | |
| 1332 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", | |
| 1333 | "funding": [ | |
| 1334 | { | |
| 1335 | "type": "github", | |
| 1336 | "url": "https://github.com/sponsors/feross" | |
| 1337 | }, | |
| 1338 | { | |
| 1339 | "type": "patreon", | |
| 1340 | "url": "https://www.patreon.com/feross" | |
| 1341 | }, | |
| 1342 | { | |
| 1343 | "type": "consulting", | |
| 1344 | "url": "https://feross.org/support" | |
| 1345 | } | |
| 1346 | ], | |
| 1347 | "license": "MIT" | |
| 1348 | }, | |
| 1349 | "node_modules/safe-regex-test": { | |
| 1350 | "version": "1.1.0", | |
| 1351 | "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", | |
| 1352 | "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", | |
| 1353 | "license": "MIT", | |
| 1354 | "dependencies": { | |
| 1355 | "call-bound": "^1.0.2", | |
| 1356 | "es-errors": "^1.3.0", | |
| 1357 | "is-regex": "^1.2.1" | |
| 1358 | }, | |
| 1359 | "engines": { | |
| 1360 | "node": ">= 0.4" | |
| 1361 | }, | |
| 1362 | "funding": { | |
| 1363 | "url": "https://github.com/sponsors/ljharb" | |
| 1364 | } | |
| 1365 | }, | |
| 1366 | "node_modules/safer-buffer": { | |
| 1367 | "version": "2.1.2", | |
| 1368 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", | |
| 1369 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", | |
| 1370 | "license": "MIT" | |
| 1371 | }, | |
| 1372 | "node_modules/same-object": { | |
| 1373 | "version": "1.0.2", | |
| 1374 | "resolved": "https://registry.npmjs.org/same-object/-/same-object-1.0.2.tgz", | |
| 1375 | "integrity": "sha512-csHWhvUsLbIOHDM/nP+KHWM+BLPsIzWkFa8HbzaI0G7BqKXgx+7FJpKTGgLXyz5amfdY2OVBcmXTqYOMEk04og==", | |
| 1376 | "dev": true, | |
| 1377 | "license": "MIT" | |
| 1378 | }, | |
| 1379 | "node_modules/set-function-length": { | |
| 1380 | "version": "1.2.2", | |
| 1381 | "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", | |
| 1382 | "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", | |
| 1383 | "license": "MIT", | |
| 1384 | "dependencies": { | |
| 1385 | "define-data-property": "^1.1.4", | |
| 1386 | "es-errors": "^1.3.0", | |
| 1387 | "function-bind": "^1.1.2", | |
| 1388 | "get-intrinsic": "^1.2.4", | |
| 1389 | "gopd": "^1.0.1", | |
| 1390 | "has-property-descriptors": "^1.0.2" | |
| 1391 | }, | |
| 1392 | "engines": { | |
| 1393 | "node": ">= 0.4" | |
| 1394 | } | |
| 1395 | }, | |
| 1396 | "node_modules/smart-buffer": { | |
| 1397 | "version": "4.2.0", | |
| 1398 | "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", | |
| 1399 | "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", | |
| 1400 | "license": "MIT", | |
| 1401 | "engines": { | |
| 1402 | "node": ">= 6.0.0", | |
| 1403 | "npm": ">= 3.0.0" | |
| 1404 | } | |
| 1405 | }, | |
| 1406 | "node_modules/socks": { | |
| 1407 | "version": "2.8.7", | |
| 1408 | "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", | |
| 1409 | "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", | |
| 1410 | "license": "MIT", | |
| 1411 | "dependencies": { | |
| 1412 | "ip-address": "^10.0.1", | |
| 1413 | "smart-buffer": "^4.2.0" | |
| 1414 | }, | |
| 1415 | "engines": { | |
| 1416 | "node": ">= 10.0.0", | |
| 1417 | "npm": ">= 3.0.0" | |
| 1418 | } | |
| 1419 | }, | |
| 1420 | "node_modules/stackframe": { | |
| 1421 | "version": "1.3.4", | |
| 1422 | "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", | |
| 1423 | "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", | |
| 1424 | "dev": true, | |
| 1425 | "license": "MIT" | |
| 1426 | }, | |
| 1427 | "node_modules/stream-browserify": { | |
| 1428 | "version": "3.0.0", | |
| 1429 | "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", | |
| 1430 | "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", | |
| 1431 | "license": "MIT", | |
| 1432 | "dependencies": { | |
| 1433 | "inherits": "~2.0.4", | |
| 1434 | "readable-stream": "^3.5.0" | |
| 1435 | } | |
| 1436 | }, | |
| 1437 | "node_modules/streamx": { | |
| 1438 | "version": "2.23.0", | |
| 1439 | "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", | |
| 1440 | "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", | |
| 1441 | "dev": true, | |
| 1442 | "license": "MIT", | |
| 1443 | "dependencies": { | |
| 1444 | "events-universal": "^1.0.0", | |
| 1445 | "fast-fifo": "^1.3.2", | |
| 1446 | "text-decoder": "^1.1.0" | |
| 1447 | } | |
| 1448 | }, | |
| 1449 | "node_modules/string_decoder": { | |
| 1450 | "version": "1.3.0", | |
| 1451 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", | |
| 1452 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", | |
| 1453 | "license": "MIT", | |
| 1454 | "dependencies": { | |
| 1455 | "safe-buffer": "~5.2.0" | |
| 1456 | } | |
| 1457 | }, | |
| 1458 | "node_modules/test-tmp": { | |
| 1459 | "version": "1.4.0", | |
| 1460 | "resolved": "https://registry.npmjs.org/test-tmp/-/test-tmp-1.4.0.tgz", | |
| 1461 | "integrity": "sha512-GVggxGg+jXqP2Wbju50JVLo+9E+nIOPPyWqgr63EbOnNItIKu1cEbJpTWAJeflnyGqXOtcMI7ijHRp88GUkfDA==", | |
| 1462 | "dev": true, | |
| 1463 | "license": "MIT", | |
| 1464 | "dependencies": { | |
| 1465 | "bare-fs": "^4.0.1", | |
| 1466 | "bare-os": "^3.3.0", | |
| 1467 | "bare-path": "^3.0.0" | |
| 1468 | } | |
| 1469 | }, | |
| 1470 | "node_modules/text-decoder": { | |
| 1471 | "version": "1.2.6", | |
| 1472 | "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.6.tgz", | |
| 1473 | "integrity": "sha512-27FeW5GQFDfw0FpwMQhMagB7BztOOlmjcSRi97t2oplhKVTZtp0DZbSegSaXS5IIC6mxMvBG4AR1Sgc6BX3CQg==", | |
| 1474 | "dev": true, | |
| 1475 | "license": "Apache-2.0", | |
| 1476 | "dependencies": { | |
| 1477 | "b4a": "^1.6.4" | |
| 1478 | } | |
| 1479 | }, | |
| 1480 | "node_modules/tmatch": { | |
| 1481 | "version": "5.0.0", | |
| 1482 | "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-5.0.0.tgz", | |
| 1483 | "integrity": "sha512-Ib9OtBkpHn07tXP04SlN1SYRxFgTk6wSM2EBmjjxug4u5RXPRVLkdFJSS1PmrQidaSB8Lru9nRtViQBsbxzE5Q==", | |
| 1484 | "dev": true, | |
| 1485 | "license": "ISC", | |
| 1486 | "engines": { | |
| 1487 | "node": ">=8" | |
| 1488 | } | |
| 1489 | }, | |
| 1490 | "node_modules/util": { | |
| 1491 | "version": "0.12.5", | |
| 1492 | "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", | |
| 1493 | "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", | |
| 1494 | "license": "MIT", | |
| 1495 | "dependencies": { | |
| 1496 | "inherits": "^2.0.3", | |
| 1497 | "is-arguments": "^1.0.4", | |
| 1498 | "is-generator-function": "^1.0.7", | |
| 1499 | "is-typed-array": "^1.1.3", | |
| 1500 | "which-typed-array": "^1.1.2" | |
| 1501 | } | |
| 1502 | }, | |
| 1503 | "node_modules/util-deprecate": { | |
| 1504 | "version": "1.0.2", | |
| 1505 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", | |
| 1506 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", | |
| 1507 | "license": "MIT" | |
| 1508 | }, | |
| 1509 | "node_modules/v8-to-istanbul": { | |
| 1510 | "version": "9.3.0", | |
| 1511 | "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", | |
| 1512 | "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", | |
| 1513 | "dev": true, | |
| 1514 | "license": "ISC", | |
| 1515 | "dependencies": { | |
| 1516 | "@jridgewell/trace-mapping": "^0.3.12", | |
| 1517 | "@types/istanbul-lib-coverage": "^2.0.1", | |
| 1518 | "convert-source-map": "^2.0.0" | |
| 1519 | }, | |
| 1520 | "engines": { | |
| 1521 | "node": ">=10.12.0" | |
| 1522 | } | |
| 1523 | }, | |
| 1524 | "node_modules/which-runtime": { | |
| 1525 | "version": "1.3.2", | |
| 1526 | "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.3.2.tgz", | |
| 1527 | "integrity": "sha512-5kwCfWml7+b2NO7KrLMhYihjRx0teKkd3yGp1Xk5Vaf2JGdSh+rgVhEALAD9c/59dP+YwJHXoEO7e8QPy7gOkw==", | |
| 1528 | "dev": true, | |
| 1529 | "license": "Apache-2.0" | |
| 1530 | }, | |
| 1531 | "node_modules/which-typed-array": { | |
| 1532 | "version": "1.1.20", | |
| 1533 | "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", | |
| 1534 | "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", | |
| 1535 | "license": "MIT", | |
| 1536 | "dependencies": { | |
| 1537 | "available-typed-arrays": "^1.0.7", | |
| 1538 | "call-bind": "^1.0.8", | |
| 1539 | "call-bound": "^1.0.4", | |
| 1540 | "for-each": "^0.3.5", | |
| 1541 | "get-proto": "^1.0.1", | |
| 1542 | "gopd": "^1.2.0", | |
| 1543 | "has-tostringtag": "^1.0.2" | |
| 1544 | }, | |
| 1545 | "engines": { | |
| 1546 | "node": ">= 0.4" | |
| 1547 | }, | |
| 1548 | "funding": { | |
| 1549 | "url": "https://github.com/sponsors/ljharb" | |
| 1550 | } | |
| 1551 | } | |
| 1552 | } | |
| 1553 | } |
| … | … | @@ -0,0 +1,23 @@ |
| 1 | { | |
| 2 | "name": "bridge-irc", | |
| 3 | "version": "1.0.0", | |
| 4 | "description": "IRC ↔ Keet bidirectional bridge", | |
| 5 | "type": "module", | |
| 6 | "main": "index.js", | |
| 7 | "pear": { | |
| 8 | "name": "bridge-irc", | |
| 9 | "type": "terminal" | |
| 10 | }, | |
| 11 | "scripts": { | |
| 12 | "test": "brittle test/*.test.js" | |
| 13 | }, | |
| 14 | "dependencies": { | |
| 15 | "irc-framework": "^4.13.1", | |
| 16 | "bridge-envelope": "file:../bridge-envelope", | |
| 17 | "b4a": "^1.0.0" | |
| 18 | }, | |
| 19 | "devDependencies": { | |
| 20 | "brittle": "^3.0.0" | |
| 21 | }, | |
| 22 | "license": "MIT" | |
| 23 | } |
| … | … | @@ -0,0 +1,171 @@ |
| 1 | import test from 'brittle' | |
| 2 | import { ircToEnvelope, keetToIrc, envelopeToIrcText } from '../lib/adapter.js' | |
| 3 | import { TYPES, PLATFORMS, createEnvelope } from 'bridge-envelope' | |
| 4 | ||
| 5 | test('ircToEnvelope - basic text message', (t) => { | |
| 6 | const env = ircToEnvelope('alice', '#test', 'Hello world!') | |
| 7 | ||
| 8 | t.is(env.type, TYPES.TEXT) | |
| 9 | t.is(env.platform, PLATFORMS.IRC) | |
| 10 | t.is(env.author, 'alice') | |
| 11 | t.is(env.content, 'Hello world!') | |
| 12 | t.is(env.channel, '#test') | |
| 13 | t.ok(env.id, 'has id') | |
| 14 | }) | |
| 15 | ||
| 16 | test('ircToEnvelope - ACTION message', (t) => { | |
| 17 | const env = ircToEnvelope('bob', '#test', '\x01ACTION waves hello\x01') | |
| 18 | ||
| 19 | t.is(env.type, TYPES.TEXT) | |
| 20 | t.is(env.content, '* bob waves hello') | |
| 21 | }) | |
| 22 | ||
| 23 | test('ircToEnvelope - empty message', (t) => { | |
| 24 | const env = ircToEnvelope('user', '#test', '') | |
| 25 | t.is(env.content, '') | |
| 26 | }) | |
| 27 | ||
| 28 | test('ircToEnvelope - unicode message', (t) => { | |
| 29 | const env = ircToEnvelope('ユーザー', '#日本語', '🎉 こんにちは') | |
| 30 | t.is(env.author, 'ユーザー') | |
| 31 | t.is(env.channel, '#日本語') | |
| 32 | t.is(env.content, '🎉 こんにちは') | |
| 33 | }) | |
| 34 | ||
| 35 | test('keetToIrc - basic message', (t) => { | |
| 36 | const msg = { | |
| 37 | memberId: 'member-123', | |
| 38 | name: 'Alice', | |
| 39 | text: 'Hello from Keet!', | |
| 40 | timestamp: Date.now() | |
| 41 | } | |
| 42 | ||
| 43 | const result = keetToIrc(msg, 'bridge-identity') | |
| 44 | t.ok(result) | |
| 45 | t.is(result.nick, 'Alice') | |
| 46 | t.ok(result.text.includes('Alice')) | |
| 47 | t.ok(result.text.includes('Hello from Keet!')) | |
| 48 | }) | |
| 49 | ||
| 50 | test('keetToIrc - skips own messages', (t) => { | |
| 51 | const msg = { | |
| 52 | memberId: 'bridge-identity', | |
| 53 | name: 'Bridge', | |
| 54 | text: 'echoed message' | |
| 55 | } | |
| 56 | ||
| 57 | const result = keetToIrc(msg, 'bridge-identity') | |
| 58 | t.is(result, null, 'skips bridge identity messages') | |
| 59 | }) | |
| 60 | ||
| 61 | test('keetToIrc - skips system messages', (t) => { | |
| 62 | const msg = { | |
| 63 | memberId: 'member-456', | |
| 64 | name: 'System', | |
| 65 | text: 'User joined', | |
| 66 | system: { type: 'join' } | |
| 67 | } | |
| 68 | ||
| 69 | const result = keetToIrc(msg, 'bridge-identity') | |
| 70 | t.is(result, null, 'skips system messages') | |
| 71 | }) | |
| 72 | ||
| 73 | test('keetToIrc - skips empty text', (t) => { | |
| 74 | const msg = { | |
| 75 | memberId: 'member-789', | |
| 76 | name: 'User', | |
| 77 | text: ' ' | |
| 78 | } | |
| 79 | ||
| 80 | const result = keetToIrc(msg, 'bridge-identity') | |
| 81 | t.is(result, null) | |
| 82 | }) | |
| 83 | ||
| 84 | test('keetToIrc - anonymous if no name', (t) => { | |
| 85 | const msg = { | |
| 86 | memberId: 'member-abc', | |
| 87 | text: 'hello' | |
| 88 | } | |
| 89 | ||
| 90 | const result = keetToIrc(msg, 'bridge-identity') | |
| 91 | t.is(result.nick, 'anonymous') | |
| 92 | }) | |
| 93 | ||
| 94 | test('envelopeToIrcText - keet text message', (t) => { | |
| 95 | const env = createEnvelope({ | |
| 96 | type: TYPES.TEXT, | |
| 97 | platform: PLATFORMS.KEET, | |
| 98 | author: 'Alice', | |
| 99 | content: 'Hello!' | |
| 100 | }) | |
| 101 | ||
| 102 | const text = envelopeToIrcText(env) | |
| 103 | t.ok(text.includes('[keet]')) | |
| 104 | t.ok(text.includes('Alice')) | |
| 105 | t.ok(text.includes('Hello!')) | |
| 106 | }) | |
| 107 | ||
| 108 | test('envelopeToIrcText - irc message has no prefix', (t) => { | |
| 109 | const env = createEnvelope({ | |
| 110 | type: TYPES.TEXT, | |
| 111 | platform: PLATFORMS.IRC, | |
| 112 | author: 'bob', | |
| 113 | content: 'Hi there' | |
| 114 | }) | |
| 115 | ||
| 116 | const text = envelopeToIrcText(env) | |
| 117 | t.absent(text.includes('[irc]'), 'no platform prefix for IRC messages') | |
| 118 | t.ok(text.includes('bob')) | |
| 119 | t.ok(text.includes('Hi there')) | |
| 120 | }) | |
| 121 | ||
| 122 | test('envelopeToIrcText - discord message', (t) => { | |
| 123 | const env = createEnvelope({ | |
| 124 | type: TYPES.TEXT, | |
| 125 | platform: PLATFORMS.DISCORD, | |
| 126 | author: 'user#1234', | |
| 127 | content: 'From Discord' | |
| 128 | }) | |
| 129 | ||
| 130 | const text = envelopeToIrcText(env) | |
| 131 | t.ok(text.includes('[discord]')) | |
| 132 | t.ok(text.includes('user#1234')) | |
| 133 | }) | |
| 134 | ||
| 135 | test('envelopeToIrcText - media message', (t) => { | |
| 136 | const env = createEnvelope({ | |
| 137 | type: TYPES.MEDIA, | |
| 138 | platform: PLATFORMS.KEET, | |
| 139 | author: 'Alice', | |
| 140 | content: 'Check this', | |
| 141 | mediaUrl: 'https://example.com/img.png' | |
| 142 | }) | |
| 143 | ||
| 144 | const text = envelopeToIrcText(env) | |
| 145 | t.ok(text.includes('https://example.com/img.png')) | |
| 146 | }) | |
| 147 | ||
| 148 | test('envelopeToIrcText - reaction', (t) => { | |
| 149 | const env = createEnvelope({ | |
| 150 | type: TYPES.REACTION, | |
| 151 | platform: PLATFORMS.KEET, | |
| 152 | author: 'Alice', | |
| 153 | content: '👍' | |
| 154 | }) | |
| 155 | ||
| 156 | const text = envelopeToIrcText(env) | |
| 157 | t.ok(text.includes('reacted')) | |
| 158 | t.ok(text.includes('👍')) | |
| 159 | }) | |
| 160 | ||
| 161 | test('envelopeToIrcText - system message', (t) => { | |
| 162 | const env = createEnvelope({ | |
| 163 | type: TYPES.SYSTEM, | |
| 164 | platform: PLATFORMS.KEET, | |
| 165 | content: 'User joined the room' | |
| 166 | }) | |
| 167 | ||
| 168 | const text = envelopeToIrcText(env) | |
| 169 | t.ok(text.includes('**')) | |
| 170 | t.ok(text.includes('User joined the room')) | |
| 171 | }) |
| … | … | @@ -0,0 +1,143 @@ |
| 1 | import test from 'brittle' | |
| 2 | import { Router } from '../lib/router.js' | |
| 3 | ||
| 4 | test('Router - routes to Keet', (t) => { | |
| 5 | let received = null | |
| 6 | ||
| 7 | const router = new Router({ | |
| 8 | onKeetMessage: (nick, text) => { | |
| 9 | received = { nick, text } | |
| 10 | } | |
| 11 | }) | |
| 12 | ||
| 13 | const ok = router.routeToKeet('alice', 'hello') | |
| 14 | t.ok(ok) | |
| 15 | t.is(received.nick, 'alice') | |
| 16 | t.is(received.text, 'hello') | |
| 17 | }) | |
| 18 | ||
| 19 | test('Router - routes to IRC', (t) => { | |
| 20 | let received = null | |
| 21 | ||
| 22 | const router = new Router({ | |
| 23 | onIrcMessage: (name, text) => { | |
| 24 | received = { name, text } | |
| 25 | } | |
| 26 | }) | |
| 27 | ||
| 28 | const ok = router.routeToIrc('Bob', 'hello from keet') | |
| 29 | t.ok(ok) | |
| 30 | t.is(received.name, 'Bob') | |
| 31 | t.is(received.text, 'hello from keet') | |
| 32 | }) | |
| 33 | ||
| 34 | test('Router - dedup prevents echo', (t) => { | |
| 35 | let keetCount = 0 | |
| 36 | let ircCount = 0 | |
| 37 | ||
| 38 | const router = new Router({ | |
| 39 | onKeetMessage: () => keetCount++, | |
| 40 | onIrcMessage: () => ircCount++ | |
| 41 | }) | |
| 42 | ||
| 43 | // Message from IRC → Keet | |
| 44 | t.ok(router.routeToKeet('alice', 'hello')) | |
| 45 | t.is(keetCount, 1) | |
| 46 | ||
| 47 | // Same message from Keet → IRC should be deduped | |
| 48 | t.absent(router.routeToIrc('alice', 'hello')) | |
| 49 | t.is(ircCount, 0, 'deduped — same content') | |
| 50 | }) | |
| 51 | ||
| 52 | test('Router - different messages not deduped', (t) => { | |
| 53 | let count = 0 | |
| 54 | const router = new Router({ | |
| 55 | onKeetMessage: () => count++ | |
| 56 | }) | |
| 57 | ||
| 58 | router.routeToKeet('alice', 'message 1') | |
| 59 | router.routeToKeet('alice', 'message 2') | |
| 60 | t.is(count, 2) | |
| 61 | }) | |
| 62 | ||
| 63 | test('Router - same text different authors not deduped', (t) => { | |
| 64 | let count = 0 | |
| 65 | const router = new Router({ | |
| 66 | onKeetMessage: () => count++ | |
| 67 | }) | |
| 68 | ||
| 69 | router.routeToKeet('alice', 'hello') | |
| 70 | router.routeToKeet('bob', 'hello') | |
| 71 | t.is(count, 2) | |
| 72 | }) | |
| 73 | ||
| 74 | test('Router - seenCount tracks entries', (t) => { | |
| 75 | const router = new Router() | |
| 76 | ||
| 77 | t.is(router.seenCount, 0) | |
| 78 | router.routeToKeet('a', 'msg1') | |
| 79 | router.routeToKeet('b', 'msg2') | |
| 80 | t.is(router.seenCount, 2) | |
| 81 | }) | |
| 82 | ||
| 83 | test('Router - clearSeen resets dedup', (t) => { | |
| 84 | let count = 0 | |
| 85 | const router = new Router({ | |
| 86 | onKeetMessage: () => count++ | |
| 87 | }) | |
| 88 | ||
| 89 | router.routeToKeet('alice', 'hello') | |
| 90 | t.is(count, 1) | |
| 91 | ||
| 92 | router.clearSeen() | |
| 93 | ||
| 94 | router.routeToKeet('alice', 'hello') | |
| 95 | t.is(count, 2, 'after clear, same message routes again') | |
| 96 | }) | |
| 97 | ||
| 98 | test('Router - TTL expiry allows re-routing', async (t) => { | |
| 99 | let count = 0 | |
| 100 | const router = new Router({ | |
| 101 | ttl: 50, // 50ms TTL for testing | |
| 102 | onKeetMessage: () => count++ | |
| 103 | }) | |
| 104 | ||
| 105 | router.routeToKeet('alice', 'hello') | |
| 106 | t.is(count, 1) | |
| 107 | ||
| 108 | // Immediately should be deduped | |
| 109 | router.routeToKeet('alice', 'hello') | |
| 110 | t.is(count, 1) | |
| 111 | ||
| 112 | // After TTL, should route again | |
| 113 | await new Promise(r => setTimeout(r, 60)) | |
| 114 | router.routeToKeet('alice', 'hello') | |
| 115 | t.is(count, 2, 'after TTL, same message routes again') | |
| 116 | }) | |
| 117 | ||
| 118 | test('Router - no callback does not throw', (t) => { | |
| 119 | const router = new Router() | |
| 120 | ||
| 121 | const ok = router.routeToKeet('alice', 'hello') | |
| 122 | t.ok(ok, 'returns true even without callback') | |
| 123 | }) | |
| 124 | ||
| 125 | test('Router - handles empty strings', (t) => { | |
| 126 | let count = 0 | |
| 127 | const router = new Router({ | |
| 128 | onKeetMessage: () => count++ | |
| 129 | }) | |
| 130 | ||
| 131 | router.routeToKeet('', '') | |
| 132 | t.is(count, 1) | |
| 133 | }) | |
| 134 | ||
| 135 | test('Router - handles unicode', (t) => { | |
| 136 | let received = null | |
| 137 | const router = new Router({ | |
| 138 | onKeetMessage: (nick, text) => { received = text } | |
| 139 | }) | |
| 140 | ||
| 141 | router.routeToKeet('ユーザー', '🎉 こんにちは') | |
| 142 | t.is(received, '🎉 こんにちは') | |
| 143 | }) |
| … | … | @@ -0,0 +1,70 @@ |
| 1 | import { MastodonBridge } from './lib/bridge.js' | |
| 2 | import { MastodonClient } from './lib/mastodon-client.js' | |
| 3 | import { KeetClient } from './lib/keet-client.js' | |
| 4 | import { Router } from './lib/router.js' | |
| 5 | import { statusToEnvelope, keetToMastodon, envelopeToStatusText, statusToKeetText, stripHtml } from './lib/adapter.js' | |
| 6 | ||
| 7 | export { | |
| 8 | MastodonBridge, | |
| 9 | MastodonClient, | |
| 10 | KeetClient, | |
| 11 | Router, | |
| 12 | statusToEnvelope, | |
| 13 | keetToMastodon, | |
| 14 | envelopeToStatusText, | |
| 15 | statusToKeetText, | |
| 16 | stripHtml | |
| 17 | } | |
| 18 | ||
| 19 | // CLI entry point | |
| 20 | const args = process.argv.slice(2) | |
| 21 | if (args.includes('--help') || args.includes('-h')) { | |
| 22 | console.log(`bridge-mastodon — Mastodon ↔ Keet bridge | |
| 23 | ||
| 24 | Usage: | |
| 25 | bridge-mastodon --instance <host> --token <token> --keet-room <roomId> [options] | |
| 26 | ||
| 27 | Options: | |
| 28 | --instance <host> Mastodon instance (e.g. mastodon.social) | |
| 29 | --token <token> Mastodon access token (or MASTODON_TOKEN env) | |
| 30 | --keet-room <roomId> Keet room ID to bridge | |
| 31 | --keet-port <port> Keet daemon port (default: 3117) | |
| 32 | --visibility <vis> Post visibility: public, unlisted, private, direct (default: unlisted) | |
| 33 | --tag <hashtag> Filter: only bridge posts with this hashtag | |
| 34 | --help Show this help`) | |
| 35 | process.exit(0) | |
| 36 | } | |
| 37 | ||
| 38 | function getArg (flag) { | |
| 39 | const idx = args.indexOf(flag) | |
| 40 | return idx !== -1 ? args[idx + 1] : null | |
| 41 | } | |
| 42 | ||
| 43 | const instance = getArg('--instance') | |
| 44 | const token = getArg('--token') || process.env.MASTODON_TOKEN | |
| 45 | const roomId = getArg('--keet-room') | |
| 46 | const keetPort = parseInt(getArg('--keet-port') || '3117', 10) | |
| 47 | const visibility = getArg('--visibility') || 'unlisted' | |
| 48 | const tag = getArg('--tag') | |
| 49 | ||
| 50 | if (instance && token && roomId) { | |
| 51 | const bridge = new MastodonBridge({ | |
| 52 | instance, | |
| 53 | token, | |
| 54 | roomId, | |
| 55 | keetPort, | |
| 56 | visibility, | |
| 57 | tag, | |
| 58 | onLog: (msg) => console.log(`[mastodon-bridge] ${msg}`) | |
| 59 | }) | |
| 60 | ||
| 61 | bridge.start().catch(err => { | |
| 62 | console.error('Bridge failed to start:', err.message) | |
| 63 | process.exit(1) | |
| 64 | }) | |
| 65 | ||
| 66 | process.on('SIGINT', () => { | |
| 67 | bridge.stop() | |
| 68 | process.exit(0) | |
| 69 | }) | |
| 70 | } |
| … | … | @@ -0,0 +1,141 @@ |
| 1 | import { createEnvelope, TYPES, PLATFORMS } from 'bridge-envelope' | |
| 2 | ||
| 3 | // Strip HTML tags from Mastodon status content | |
| 4 | function stripHtml (html) { | |
| 5 | return html | |
| 6 | .replace(/<br\s*\/?>/gi, '\n') | |
| 7 | .replace(/<\/p>\s*<p>/gi, '\n\n') | |
| 8 | .replace(/<[^>]+>/g, '') | |
| 9 | .replace(/&/g, '&') | |
| 10 | .replace(/</g, '<') | |
| 11 | .replace(/>/g, '>') | |
| 12 | .replace(/"/g, '"') | |
| 13 | .replace(/'/g, "'") | |
| 14 | .trim() | |
| 15 | } | |
| 16 | ||
| 17 | // Convert Mastodon status to bridge envelope | |
| 18 | function statusToEnvelope (status) { | |
| 19 | if (!status) return null | |
| 20 | ||
| 21 | // Skip boosts — handle separately if needed | |
| 22 | const actual = status.reblog || status | |
| 23 | const author = actual.account?.acct || actual.account?.username || 'unknown' | |
| 24 | const content = stripHtml(actual.content || '') | |
| 25 | ||
| 26 | if (!content && !actual.media_attachments?.length) return null | |
| 27 | ||
| 28 | let type = TYPES.TEXT | |
| 29 | let mediaUrl = '' | |
| 30 | let mediaType = '' | |
| 31 | let replyTo = '' | |
| 32 | ||
| 33 | // Media attachments | |
| 34 | const attachment = actual.media_attachments?.[0] | |
| 35 | if (attachment) { | |
| 36 | type = TYPES.MEDIA | |
| 37 | mediaUrl = attachment.url || attachment.preview_url || '' | |
| 38 | mediaType = attachment.type || '' // image, video, gifv, audio | |
| 39 | } | |
| 40 | ||
| 41 | // Reply | |
| 42 | if (actual.in_reply_to_id) { | |
| 43 | replyTo = actual.in_reply_to_id | |
| 44 | if (type === TYPES.TEXT) type = TYPES.REPLY | |
| 45 | } | |
| 46 | ||
| 47 | const envelope = createEnvelope({ | |
| 48 | type, | |
| 49 | platform: PLATFORMS.MASTODON, | |
| 50 | author, | |
| 51 | content, | |
| 52 | channel: status.reblog ? 'boost' : (actual.visibility || 'public'), | |
| 53 | replyTo, | |
| 54 | mediaUrl, | |
| 55 | mediaType | |
| 56 | }) | |
| 57 | ||
| 58 | // Preserve Mastodon status ID for dedup and threading | |
| 59 | envelope._statusId = actual.id | |
| 60 | envelope._spoilerText = actual.spoiler_text || '' | |
| 61 | envelope._tags = (actual.tags || []).map(t => t.name) | |
| 62 | envelope._isBoost = !!status.reblog | |
| 63 | envelope._boostBy = status.reblog ? (status.account?.acct || '') : '' | |
| 64 | ||
| 65 | return envelope | |
| 66 | } | |
| 67 | ||
| 68 | // Convert Keet message to Mastodon post text | |
| 69 | function keetToMastodon (keetMsg, bridgeIdentity) { | |
| 70 | if (!keetMsg) return null | |
| 71 | ||
| 72 | // Skip own messages (bridge identity) | |
| 73 | if (keetMsg.memberId === bridgeIdentity) return null | |
| 74 | ||
| 75 | // Skip system messages | |
| 76 | if (keetMsg.type === 'system') return null | |
| 77 | ||
| 78 | const text = keetMsg.text || '' | |
| 79 | if (!text.trim()) return null | |
| 80 | ||
| 81 | // Skip messages already bridged from Mastodon | |
| 82 | if (text.includes('[mastodon]') || text.includes('[Mastodon]')) return null | |
| 83 | ||
| 84 | const name = keetMsg.name || keetMsg.username || 'someone' | |
| 85 | return { author: name, text } | |
| 86 | } | |
| 87 | ||
| 88 | // Format envelope for Mastodon posting (from Keet to Mastodon) | |
| 89 | function envelopeToStatusText (envelope) { | |
| 90 | if (!envelope) return '' | |
| 91 | ||
| 92 | const platformName = { | |
| 93 | [PLATFORMS.KEET]: 'Keet', | |
| 94 | [PLATFORMS.IRC]: 'IRC', | |
| 95 | [PLATFORMS.DISCORD]: 'Discord', | |
| 96 | [PLATFORMS.MATRIX]: 'Matrix', | |
| 97 | [PLATFORMS.EMAIL]: 'Email' | |
| 98 | } | |
| 99 | ||
| 100 | const source = platformName[envelope.platform] || 'P2P' | |
| 101 | return `[${source}] ${envelope.author}: ${envelope.content}` | |
| 102 | } | |
| 103 | ||
| 104 | // Format a Mastodon status for display in Keet | |
| 105 | function statusToKeetText (envelope) { | |
| 106 | if (!envelope) return '' | |
| 107 | ||
| 108 | let text = '' | |
| 109 | if (envelope._isBoost) { | |
| 110 | text += `[mastodon] ${envelope._boostBy} boosted @${envelope.author}: ` | |
| 111 | } else { | |
| 112 | text += `[mastodon] @${envelope.author}: ` | |
| 113 | } | |
| 114 | ||
| 115 | if (envelope._spoilerText) { | |
| 116 | text += `[CW: ${envelope._spoilerText}] ` | |
| 117 | } | |
| 118 | ||
| 119 | text += envelope.content | |
| 120 | ||
| 121 | if (envelope._tags?.length) { | |
| 122 | const tags = envelope._tags.map(t => `#${t}`).join(' ') | |
| 123 | if (!envelope.content.includes('#')) { | |
| 124 | text += ` ${tags}` | |
| 125 | } | |
| 126 | } | |
| 127 | ||
| 128 | if (envelope.mediaUrl) { | |
| 129 | text += ` [${envelope.mediaType || 'media'}: ${envelope.mediaUrl}]` | |
| 130 | } | |
| 131 | ||
| 132 | return text | |
| 133 | } | |
| 134 | ||
| 135 | export { | |
| 136 | statusToEnvelope, | |
| 137 | keetToMastodon, | |
| 138 | envelopeToStatusText, | |
| 139 | statusToKeetText, | |
| 140 | stripHtml | |
| 141 | } |
| … | … | @@ -0,0 +1,86 @@ |
| 1 | import { MastodonClient } from './mastodon-client.js' | |
| 2 | import { KeetClient } from './keet-client.js' | |
| 3 | import { Router } from './router.js' | |
| 4 | import { statusToEnvelope, keetToMastodon, statusToKeetText, envelopeToStatusText } from './adapter.js' | |
| 5 | ||
| 6 | class MastodonBridge { | |
| 7 | constructor (opts = {}) { | |
| 8 | this.mastodon = new MastodonClient({ | |
| 9 | instance: opts.instance, | |
| 10 | token: opts.token | |
| 11 | }) | |
| 12 | this.keet = new KeetClient({ | |
| 13 | host: opts.keetHost, | |
| 14 | port: opts.keetPort | |
| 15 | }) | |
| 16 | this.roomId = opts.roomId | |
| 17 | this.visibility = opts.visibility || 'unlisted' // default post visibility | |
| 18 | this.bridgeIdentity = opts.bridgeIdentity || null | |
| 19 | this.tag = opts.tag || null // optional hashtag filter | |
| 20 | this.onLog = opts.onLog || (() => {}) | |
| 21 | ||
| 22 | this.router = new Router({ | |
| 23 | onKeetMessage: (author, text) => { | |
| 24 | this.keet.send(this.roomId, text).catch(err => { | |
| 25 | this.onLog(`Keet send error: ${err.message}`) | |
| 26 | }) | |
| 27 | }, | |
| 28 | onExternalMessage: (author, text) => { | |
| 29 | this.mastodon.postStatus(text, { visibility: this.visibility }).catch(err => { | |
| 30 | this.onLog(`Mastodon post error: ${err.message}`) | |
| 31 | }) | |
| 32 | } | |
| 33 | }) | |
| 34 | } | |
| 35 | ||
| 36 | async start () { | |
| 37 | // Get bridge identity from keet-daemon | |
| 38 | if (!this.bridgeIdentity) { | |
| 39 | try { | |
| 40 | const identity = await this.keet.identity() | |
| 41 | this.bridgeIdentity = identity.memberId || identity.id || '' | |
| 42 | this.onLog(`Bridge identity: ${this.bridgeIdentity}`) | |
| 43 | } catch (err) { | |
| 44 | this.onLog(`Warning: Could not get identity: ${err.message}`) | |
| 45 | } | |
| 46 | } | |
| 47 | ||
| 48 | // Verify Mastodon credentials | |
| 49 | try { | |
| 50 | const account = await this.mastodon.verifyCredentials() | |
| 51 | this.onLog(`Mastodon: logged in as @${account.acct || account.username}`) | |
| 52 | } catch (err) { | |
| 53 | this.onLog(`Mastodon auth failed: ${err.message}`) | |
| 54 | throw err | |
| 55 | } | |
| 56 | ||
| 57 | // Stream Mastodon → Keet | |
| 58 | const streamType = this.tag ? `hashtag&tag=${this.tag}` : 'user' | |
| 59 | this.mastodon.stream(streamType, (status) => { | |
| 60 | const envelope = statusToEnvelope(status) | |
| 61 | if (!envelope) return | |
| 62 | ||
| 63 | const text = statusToKeetText(envelope) | |
| 64 | this.router.routeToKeet(envelope.author, text) | |
| 65 | }) | |
| 66 | ||
| 67 | // Watch Keet → Mastodon | |
| 68 | this.keet.watch(this.roomId, (msg) => { | |
| 69 | const result = keetToMastodon(msg, this.bridgeIdentity) | |
| 70 | if (!result) return | |
| 71 | ||
| 72 | const text = `${result.author}: ${result.text}` | |
| 73 | this.router.routeToExternal(result.author, text) | |
| 74 | }) | |
| 75 | ||
| 76 | this.onLog(`Bridge started: Mastodon (${this.mastodon.instance}) ↔ Keet`) | |
| 77 | } | |
| 78 | ||
| 79 | stop () { | |
| 80 | this.mastodon.stopStream() | |
| 81 | this.keet.stopWatch() | |
| 82 | this.onLog('Bridge stopped') | |
| 83 | } | |
| 84 | } | |
| 85 | ||
| 86 | export { MastodonBridge } |
| … | … | @@ -0,0 +1,162 @@ |
| 1 | import http from 'http' | |
| 2 | import net from 'net' | |
| 3 | ||
| 4 | class KeetClient { | |
| 5 | constructor (opts = {}) { | |
| 6 | this.host = opts.host || 'localhost' | |
| 7 | this.port = opts.port || 3117 | |
| 8 | this._watching = false | |
| 9 | this._watchAbort = null | |
| 10 | } | |
| 11 | ||
| 12 | // Raw TCP request — bypasses Node's strict HTTP parser | |
| 13 | // Needed because keet-daemon sends duplicate Content-Length headers | |
| 14 | _rawRequest (method, path, body) { | |
| 15 | return new Promise((resolve, reject) => { | |
| 16 | const socket = net.connect(this.port, this.host) | |
| 17 | let payload = '' | |
| 18 | ||
| 19 | if (body) { | |
| 20 | payload = JSON.stringify(body) | |
| 21 | } | |
| 22 | ||
| 23 | socket.on('connect', () => { | |
| 24 | let req = `${method} ${path} HTTP/1.1\r\n` | |
| 25 | req += `Host: ${this.host}:${this.port}\r\n` | |
| 26 | if (payload) { | |
| 27 | req += `Content-Type: application/json\r\n` | |
| 28 | req += `Content-Length: ${Buffer.byteLength(payload)}\r\n` | |
| 29 | } | |
| 30 | req += `Connection: close\r\n` | |
| 31 | req += `\r\n` | |
| 32 | if (payload) req += payload | |
| 33 | socket.write(req) | |
| 34 | }) | |
| 35 | ||
| 36 | let data = '' | |
| 37 | socket.on('data', (chunk) => { data += chunk.toString() }) | |
| 38 | ||
| 39 | socket.on('end', () => { | |
| 40 | const idx = data.indexOf('\r\n\r\n') | |
| 41 | if (idx === -1) return reject(new Error('No HTTP body')) | |
| 42 | const jsonBody = data.slice(idx + 4) | |
| 43 | ||
| 44 | try { | |
| 45 | const parsed = JSON.parse(jsonBody) | |
| 46 | if (!parsed.ok) reject(new Error(parsed.error || 'RPC error')) | |
| 47 | else resolve(parsed.data) | |
| 48 | } catch { | |
| 49 | reject(new Error('Invalid JSON: ' + jsonBody.slice(0, 200))) | |
| 50 | } | |
| 51 | }) | |
| 52 | ||
| 53 | socket.on('error', reject) | |
| 54 | socket.setTimeout(10000, () => { | |
| 55 | socket.destroy(new Error('Request timeout')) | |
| 56 | }) | |
| 57 | }) | |
| 58 | } | |
| 59 | ||
| 60 | rpc (method, params = {}) { | |
| 61 | return this._rawRequest('POST', '/rpc', { method, params }) | |
| 62 | } | |
| 63 | ||
| 64 | async send (roomId, text) { | |
| 65 | return this.rpc('send', { roomId, text }) | |
| 66 | } | |
| 67 | ||
| 68 | async messages (roomId, limit = 20) { | |
| 69 | return this._rawRequest('GET', `/rpc/messages/${roomId}?limit=${limit}`) | |
| 70 | } | |
| 71 | ||
| 72 | async rooms () { | |
| 73 | return this._rawRequest('GET', '/rpc/rooms') | |
| 74 | } | |
| 75 | ||
| 76 | async identity () { | |
| 77 | return this._rawRequest('GET', '/rpc/identity') | |
| 78 | } | |
| 79 | ||
| 80 | async status () { | |
| 81 | return this._rawRequest('GET', '/rpc/status') | |
| 82 | } | |
| 83 | ||
| 84 | async members (roomId) { | |
| 85 | return this._rawRequest('GET', `/rpc/members/${roomId}`) | |
| 86 | } | |
| 87 | ||
| 88 | watch (roomId, onMessage) { | |
| 89 | if (this._watching) return | |
| 90 | this._watching = true | |
| 91 | this._doWatch(roomId, onMessage) | |
| 92 | } | |
| 93 | ||
| 94 | _doWatch (roomId, onMessage) { | |
| 95 | if (!this._watching) return | |
| 96 | ||
| 97 | const req = http.request({ | |
| 98 | hostname: this.host, | |
| 99 | port: this.port, | |
| 100 | path: `/rpc/watch/${roomId}`, | |
| 101 | method: 'GET', | |
| 102 | headers: { Accept: 'text/event-stream' } | |
| 103 | }, (res) => { | |
| 104 | let buffer = '' | |
| 105 | ||
| 106 | res.on('data', (chunk) => { | |
| 107 | buffer += chunk.toString() | |
| 108 | const events = buffer.split('\n\n') | |
| 109 | buffer = events.pop() | |
| 110 | ||
| 111 | for (const event of events) { | |
| 112 | if (!event.trim()) continue | |
| 113 | const lines = event.split('\n') | |
| 114 | let type = 'message' | |
| 115 | let data = null | |
| 116 | ||
| 117 | for (const line of lines) { | |
| 118 | if (line.startsWith('event: ')) type = line.slice(7) | |
| 119 | if (line.startsWith('data: ')) { | |
| 120 | try { data = JSON.parse(line.slice(6)) } catch {} | |
| 121 | } | |
| 122 | } | |
| 123 | ||
| 124 | if (type === 'message' && data) { | |
| 125 | onMessage(data) | |
| 126 | } | |
| 127 | } | |
| 128 | }) | |
| 129 | ||
| 130 | res.on('end', () => { | |
| 131 | if (this._watching) { | |
| 132 | setTimeout(() => this._doWatch(roomId, onMessage), 2000) | |
| 133 | } | |
| 134 | }) | |
| 135 | ||
| 136 | res.on('error', () => { | |
| 137 | if (this._watching) { | |
| 138 | setTimeout(() => this._doWatch(roomId, onMessage), 5000) | |
| 139 | } | |
| 140 | }) | |
| 141 | }) | |
| 142 | ||
| 143 | req.on('error', () => { | |
| 144 | if (this._watching) { | |
| 145 | setTimeout(() => this._doWatch(roomId, onMessage), 5000) | |
| 146 | } | |
| 147 | }) | |
| 148 | ||
| 149 | this._watchAbort = req | |
| 150 | req.end() | |
| 151 | } | |
| 152 | ||
| 153 | stopWatch () { | |
| 154 | this._watching = false | |
| 155 | if (this._watchAbort) { | |
| 156 | this._watchAbort.destroy() | |
| 157 | this._watchAbort = null | |
| 158 | } | |
| 159 | } | |
| 160 | } | |
| 161 | ||
| 162 | export { KeetClient } |
| … | … | @@ -0,0 +1,182 @@ |
| 1 | import https from 'https' | |
| 2 | import { URL } from 'url' | |
| 3 | ||
| 4 | class MastodonClient { | |
| 5 | constructor (opts = {}) { | |
| 6 | this.instance = opts.instance // e.g. 'mastodon.social' | |
| 7 | this.token = opts.token | |
| 8 | this._streaming = false | |
| 9 | this._streamReq = null | |
| 10 | } | |
| 11 | ||
| 12 | _request (method, path, body) { | |
| 13 | return new Promise((resolve, reject) => { | |
| 14 | const url = new URL(`https://${this.instance}${path}`) | |
| 15 | ||
| 16 | const opts = { | |
| 17 | hostname: url.hostname, | |
| 18 | port: 443, | |
| 19 | path: url.pathname + url.search, | |
| 20 | method, | |
| 21 | headers: { | |
| 22 | Authorization: `Bearer ${this.token}`, | |
| 23 | Accept: 'application/json' | |
| 24 | } | |
| 25 | } | |
| 26 | ||
| 27 | let payload = '' | |
| 28 | if (body) { | |
| 29 | payload = JSON.stringify(body) | |
| 30 | opts.headers['Content-Type'] = 'application/json' | |
| 31 | opts.headers['Content-Length'] = Buffer.byteLength(payload) | |
| 32 | } | |
| 33 | ||
| 34 | const req = https.request(opts, (res) => { | |
| 35 | let data = '' | |
| 36 | res.on('data', (chunk) => { data += chunk.toString() }) | |
| 37 | res.on('end', () => { | |
| 38 | if (res.statusCode >= 400) { | |
| 39 | return reject(new Error(`HTTP ${res.statusCode}: ${data.slice(0, 200)}`)) | |
| 40 | } | |
| 41 | try { | |
| 42 | resolve(JSON.parse(data)) | |
| 43 | } catch { | |
| 44 | reject(new Error('Invalid JSON: ' + data.slice(0, 200))) | |
| 45 | } | |
| 46 | }) | |
| 47 | }) | |
| 48 | ||
| 49 | req.on('error', reject) | |
| 50 | req.setTimeout(15000, () => req.destroy(new Error('Request timeout'))) | |
| 51 | ||
| 52 | if (payload) req.write(payload) | |
| 53 | req.end() | |
| 54 | }) | |
| 55 | } | |
| 56 | ||
| 57 | // Post a status (toot) | |
| 58 | async postStatus (text, opts = {}) { | |
| 59 | const body = { status: text } | |
| 60 | if (opts.visibility) body.visibility = opts.visibility // public, unlisted, private, direct | |
| 61 | if (opts.spoilerText) body.spoiler_text = opts.spoilerText | |
| 62 | if (opts.inReplyTo) body.in_reply_to_id = opts.inReplyTo | |
| 63 | if (opts.mediaIds) body.media_ids = opts.mediaIds | |
| 64 | return this._request('POST', '/api/v1/statuses', body) | |
| 65 | } | |
| 66 | ||
| 67 | // Get home timeline | |
| 68 | async homeTimeline (limit = 20, sinceId) { | |
| 69 | let path = `/api/v1/timelines/home?limit=${limit}` | |
| 70 | if (sinceId) path += `&since_id=${sinceId}` | |
| 71 | return this._request('GET', path) | |
| 72 | } | |
| 73 | ||
| 74 | // Get hashtag timeline | |
| 75 | async hashtagTimeline (tag, limit = 20, sinceId) { | |
| 76 | let path = `/api/v1/timelines/tag/${encodeURIComponent(tag)}?limit=${limit}` | |
| 77 | if (sinceId) path += `&since_id=${sinceId}` | |
| 78 | return this._request('GET', path) | |
| 79 | } | |
| 80 | ||
| 81 | // Get notifications | |
| 82 | async notifications (limit = 15, sinceId) { | |
| 83 | let path = `/api/v1/notifications?limit=${limit}` | |
| 84 | if (sinceId) path += `&since_id=${sinceId}` | |
| 85 | return this._request('GET', path) | |
| 86 | } | |
| 87 | ||
| 88 | // Boost (reblog) a status | |
| 89 | async boost (statusId) { | |
| 90 | return this._request('POST', `/api/v1/statuses/${statusId}/reblog`) | |
| 91 | } | |
| 92 | ||
| 93 | // Favourite a status | |
| 94 | async favourite (statusId) { | |
| 95 | return this._request('POST', `/api/v1/statuses/${statusId}/favourite`) | |
| 96 | } | |
| 97 | ||
| 98 | // Verify credentials | |
| 99 | async verifyCredentials () { | |
| 100 | return this._request('GET', '/api/v1/accounts/verify_credentials') | |
| 101 | } | |
| 102 | ||
| 103 | // SSE streaming for user timeline | |
| 104 | stream (streamType, onStatus) { | |
| 105 | if (this._streaming) return | |
| 106 | this._streaming = true | |
| 107 | this._doStream(streamType, onStatus) | |
| 108 | } | |
| 109 | ||
| 110 | _doStream (streamType, onStatus) { | |
| 111 | if (!this._streaming) return | |
| 112 | ||
| 113 | // streamType: 'user', 'public', 'public:local', 'hashtag&tag=foo' | |
| 114 | const req = https.request({ | |
| 115 | hostname: this.instance, | |
| 116 | port: 443, | |
| 117 | path: `/api/v1/streaming/${streamType}`, | |
| 118 | method: 'GET', | |
| 119 | headers: { | |
| 120 | Authorization: `Bearer ${this.token}`, | |
| 121 | Accept: 'text/event-stream' | |
| 122 | } | |
| 123 | }, (res) => { | |
| 124 | let buffer = '' | |
| 125 | ||
| 126 | res.on('data', (chunk) => { | |
| 127 | buffer += chunk.toString() | |
| 128 | const events = buffer.split('\n\n') | |
| 129 | buffer = events.pop() | |
| 130 | ||
| 131 | for (const event of events) { | |
| 132 | if (!event.trim()) continue | |
| 133 | const lines = event.split('\n') | |
| 134 | let type = '' | |
| 135 | let data = null | |
| 136 | ||
| 137 | for (const line of lines) { | |
| 138 | if (line.startsWith('event: ')) type = line.slice(7).trim() | |
| 139 | if (line.startsWith('data: ')) { | |
| 140 | try { data = JSON.parse(line.slice(6)) } catch {} | |
| 141 | } | |
| 142 | } | |
| 143 | ||
| 144 | if (type === 'update' && data) { | |
| 145 | onStatus(data) | |
| 146 | } | |
| 147 | } | |
| 148 | }) | |
| 149 | ||
| 150 | res.on('end', () => { | |
| 151 | if (this._streaming) { | |
| 152 | setTimeout(() => this._doStream(streamType, onStatus), 3000) | |
| 153 | } | |
| 154 | }) | |
| 155 | ||
| 156 | res.on('error', () => { | |
| 157 | if (this._streaming) { | |
| 158 | setTimeout(() => this._doStream(streamType, onStatus), 5000) | |
| 159 | } | |
| 160 | }) | |
| 161 | }) | |
| 162 | ||
| 163 | req.on('error', () => { | |
| 164 | if (this._streaming) { | |
| 165 | setTimeout(() => this._doStream(streamType, onStatus), 5000) | |
| 166 | } | |
| 167 | }) | |
| 168 | ||
| 169 | this._streamReq = req | |
| 170 | req.end() | |
| 171 | } | |
| 172 | ||
| 173 | stopStream () { | |
| 174 | this._streaming = false | |
| 175 | if (this._streamReq) { | |
| 176 | this._streamReq.destroy() | |
| 177 | this._streamReq = null | |
| 178 | } | |
| 179 | } | |
| 180 | } | |
| 181 | ||
| 182 | export { MastodonClient } |
| … | … | @@ -0,0 +1,69 @@ |
| 1 | import { createHash } from 'crypto' | |
| 2 | ||
| 3 | class Router { | |
| 4 | constructor (opts = {}) { | |
| 5 | this._seen = new Map() | |
| 6 | this._maxSeen = opts.maxSeen || 10000 | |
| 7 | this._ttl = opts.ttl || 60000 | |
| 8 | this._onExternalMessage = opts.onExternalMessage || null | |
| 9 | this._onKeetMessage = opts.onKeetMessage || null | |
| 10 | } | |
| 11 | ||
| 12 | _hash (text, author) { | |
| 13 | return createHash('sha256') | |
| 14 | .update(author || '') | |
| 15 | .update(text || '') | |
| 16 | .digest('hex') | |
| 17 | .slice(0, 16) | |
| 18 | } | |
| 19 | ||
| 20 | _isDuplicate (hash) { | |
| 21 | const now = Date.now() | |
| 22 | ||
| 23 | if (this._seen.size > this._maxSeen) { | |
| 24 | for (const [k, ts] of this._seen) { | |
| 25 | if (now - ts > this._ttl) this._seen.delete(k) | |
| 26 | } | |
| 27 | } | |
| 28 | ||
| 29 | if (this._seen.has(hash)) { | |
| 30 | const ts = this._seen.get(hash) | |
| 31 | if (now - ts < this._ttl) return true | |
| 32 | } | |
| 33 | ||
| 34 | this._seen.set(hash, now) | |
| 35 | return false | |
| 36 | } | |
| 37 | ||
| 38 | // External platform message → relay to Keet | |
| 39 | routeToKeet (author, text) { | |
| 40 | const hash = this._hash(text, author) | |
| 41 | if (this._isDuplicate(hash)) return false | |
| 42 | ||
| 43 | if (this._onKeetMessage) { | |
| 44 | this._onKeetMessage(author, text) | |
| 45 | } | |
| 46 | return true | |
| 47 | } | |
| 48 | ||
| 49 | // Keet message → relay to external platform | |
| 50 | routeToExternal (name, text) { | |
| 51 | const hash = this._hash(text, name) | |
| 52 | if (this._isDuplicate(hash)) return false | |
| 53 | ||
| 54 | if (this._onExternalMessage) { | |
| 55 | this._onExternalMessage(name, text) | |
| 56 | } | |
| 57 | return true | |
| 58 | } | |
| 59 | ||
| 60 | get seenCount () { | |
| 61 | return this._seen.size | |
| 62 | } | |
| 63 | ||
| 64 | clearSeen () { | |
| 65 | this._seen.clear() | |
| 66 | } | |
| 67 | } | |
| 68 | ||
| 69 | export { Router } |
| … | … | @@ -0,0 +1,875 @@ |
| 1 | { | |
| 2 | "name": "bridge-mastodon", | |
| 3 | "version": "1.0.0", | |
| 4 | "lockfileVersion": 3, | |
| 5 | "requires": true, | |
| 6 | "packages": { | |
| 7 | "": { | |
| 8 | "name": "bridge-mastodon", | |
| 9 | "version": "1.0.0", | |
| 10 | "license": "MIT", | |
| 11 | "dependencies": { | |
| 12 | "b4a": "^1.0.0", | |
| 13 | "bridge-envelope": "file:../bridge-envelope" | |
| 14 | }, | |
| 15 | "devDependencies": { | |
| 16 | "brittle": "^3.0.0" | |
| 17 | } | |
| 18 | }, | |
| 19 | "../bridge-envelope": { | |
| 20 | "version": "1.0.0", | |
| 21 | "license": "MIT", | |
| 22 | "dependencies": { | |
| 23 | "b4a": "^1.0.0", | |
| 24 | "compact-encoding": "^2.0.0" | |
| 25 | }, | |
| 26 | "devDependencies": { | |
| 27 | "brittle": "^3.0.0" | |
| 28 | } | |
| 29 | }, | |
| 30 | "node_modules/@jridgewell/resolve-uri": { | |
| 31 | "version": "3.1.2", | |
| 32 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", | |
| 33 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", | |
| 34 | "dev": true, | |
| 35 | "license": "MIT", | |
| 36 | "engines": { | |
| 37 | "node": ">=6.0.0" | |
| 38 | } | |
| 39 | }, | |
| 40 | "node_modules/@jridgewell/sourcemap-codec": { | |
| 41 | "version": "1.5.5", | |
| 42 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", | |
| 43 | "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", | |
| 44 | "dev": true, | |
| 45 | "license": "MIT" | |
| 46 | }, | |
| 47 | "node_modules/@jridgewell/trace-mapping": { | |
| 48 | "version": "0.3.31", | |
| 49 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", | |
| 50 | "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", | |
| 51 | "dev": true, | |
| 52 | "license": "MIT", | |
| 53 | "dependencies": { | |
| 54 | "@jridgewell/resolve-uri": "^3.1.0", | |
| 55 | "@jridgewell/sourcemap-codec": "^1.4.14" | |
| 56 | } | |
| 57 | }, | |
| 58 | "node_modules/@types/istanbul-lib-coverage": { | |
| 59 | "version": "2.0.6", | |
| 60 | "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", | |
| 61 | "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", | |
| 62 | "dev": true, | |
| 63 | "license": "MIT" | |
| 64 | }, | |
| 65 | "node_modules/b4a": { | |
| 66 | "version": "1.7.4", | |
| 67 | "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.4.tgz", | |
| 68 | "integrity": "sha512-u20zJLDaSWpxaZ+zaAkEIB2dZZ1o+DF4T/MRbmsvGp9nletHOyiai19OzX1fF8xUBYsO1bPXxODvcd0978pnug==", | |
| 69 | "license": "Apache-2.0", | |
| 70 | "peerDependencies": { | |
| 71 | "react-native-b4a": "*" | |
| 72 | }, | |
| 73 | "peerDependenciesMeta": { | |
| 74 | "react-native-b4a": { | |
| 75 | "optional": true | |
| 76 | } | |
| 77 | } | |
| 78 | }, | |
| 79 | "node_modules/bare-addon-resolve": { | |
| 80 | "version": "1.10.0", | |
| 81 | "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.0.tgz", | |
| 82 | "integrity": "sha512-sSd0jieRJlDaODOzj0oe0RjFVC1QI0ZIjGIdPkbrTXsdVVtENg14c+lHHAhHwmWCZ2nQlMhy8jA3Y5LYPc/isA==", | |
| 83 | "dev": true, | |
| 84 | "license": "Apache-2.0", | |
| 85 | "dependencies": { | |
| 86 | "bare-module-resolve": "^1.10.0", | |
| 87 | "bare-semver": "^1.0.0" | |
| 88 | }, | |
| 89 | "peerDependencies": { | |
| 90 | "bare-url": "*" | |
| 91 | }, | |
| 92 | "peerDependenciesMeta": { | |
| 93 | "bare-url": { | |
| 94 | "optional": true | |
| 95 | } | |
| 96 | } | |
| 97 | }, | |
| 98 | "node_modules/bare-ansi-escapes": { | |
| 99 | "version": "2.2.3", | |
| 100 | "resolved": "https://registry.npmjs.org/bare-ansi-escapes/-/bare-ansi-escapes-2.2.3.tgz", | |
| 101 | "integrity": "sha512-02ES4/E2RbrtZSnHJ9LntBhYkLA6lPpSEeP8iqS3MccBIVhVBlEmruF1I7HZqx5Q8aiTeYfQVeqmrU9YO2yYoQ==", | |
| 102 | "dev": true, | |
| 103 | "license": "Apache-2.0", | |
| 104 | "dependencies": { | |
| 105 | "bare-stream": "^2.6.5" | |
| 106 | }, | |
| 107 | "peerDependencies": { | |
| 108 | "bare-buffer": "*" | |
| 109 | }, | |
| 110 | "peerDependenciesMeta": { | |
| 111 | "bare-buffer": { | |
| 112 | "optional": true | |
| 113 | } | |
| 114 | } | |
| 115 | }, | |
| 116 | "node_modules/bare-assert": { | |
| 117 | "version": "1.2.0", | |
| 118 | "resolved": "https://registry.npmjs.org/bare-assert/-/bare-assert-1.2.0.tgz", | |
| 119 | "integrity": "sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==", | |
| 120 | "dev": true, | |
| 121 | "license": "Apache-2.0", | |
| 122 | "dependencies": { | |
| 123 | "bare-inspect": "^3.1.2" | |
| 124 | } | |
| 125 | }, | |
| 126 | "node_modules/bare-bundle": { | |
| 127 | "version": "1.10.0", | |
| 128 | "resolved": "https://registry.npmjs.org/bare-bundle/-/bare-bundle-1.10.0.tgz", | |
| 129 | "integrity": "sha512-4LVlnJAHr00Hh6Vu6ZUJS38rcEtJT3b3vChXSsBsJ2mk1TN0lQ+gzd+Dw5L0aV7uqDZv84smuwW+O02X7PfDlw==", | |
| 130 | "dev": true, | |
| 131 | "license": "Apache-2.0", | |
| 132 | "peerDependencies": { | |
| 133 | "bare-buffer": "*", | |
| 134 | "bare-url": "*" | |
| 135 | }, | |
| 136 | "peerDependenciesMeta": { | |
| 137 | "bare-buffer": { | |
| 138 | "optional": true | |
| 139 | }, | |
| 140 | "bare-url": { | |
| 141 | "optional": true | |
| 142 | } | |
| 143 | } | |
| 144 | }, | |
| 145 | "node_modules/bare-cov": { | |
| 146 | "version": "1.2.1", | |
| 147 | "resolved": "https://registry.npmjs.org/bare-cov/-/bare-cov-1.2.1.tgz", | |
| 148 | "integrity": "sha512-SHNqb/WG9udUEj8m5qGSehoUQ9o0Xzv8nrf4HIdmG+KNU/XdhPLhTP5VPrHnSAMAXhAzEB4Q+2IYZ0x9qPzN1g==", | |
| 149 | "dev": true, | |
| 150 | "license": "Apache-2.0", | |
| 151 | "dependencies": { | |
| 152 | "bare-fs": "^4.1.2", | |
| 153 | "bare-inspector": "^4.0.1", | |
| 154 | "bare-path": "^3.0.0", | |
| 155 | "bare-process": "^4.2.1", | |
| 156 | "bare-url": "^2.1.5", | |
| 157 | "bare-v8-to-istanbul": "^1.0.2", | |
| 158 | "picomatch": "^4.0.2", | |
| 159 | "which-runtime": "^1.2.1" | |
| 160 | } | |
| 161 | }, | |
| 162 | "node_modules/bare-crypto": { | |
| 163 | "version": "1.13.0", | |
| 164 | "resolved": "https://registry.npmjs.org/bare-crypto/-/bare-crypto-1.13.0.tgz", | |
| 165 | "integrity": "sha512-RQl13yD+YTACWUZHMMck0C6LNjXgGgRyhVC557ag0xIrkxXf4IQwpnc8WOB58f4k5kSzKhIjy05oW3HUBrFpSQ==", | |
| 166 | "dev": true, | |
| 167 | "license": "Apache-2.0", | |
| 168 | "dependencies": { | |
| 169 | "bare-stream": "^2.6.3" | |
| 170 | }, | |
| 171 | "peerDependencies": { | |
| 172 | "bare-buffer": "*" | |
| 173 | }, | |
| 174 | "peerDependenciesMeta": { | |
| 175 | "bare-buffer": { | |
| 176 | "optional": true | |
| 177 | } | |
| 178 | } | |
| 179 | }, | |
| 180 | "node_modules/bare-debug-log": { | |
| 181 | "version": "2.0.0", | |
| 182 | "resolved": "https://registry.npmjs.org/bare-debug-log/-/bare-debug-log-2.0.0.tgz", | |
| 183 | "integrity": "sha512-Vi42PkMQsNV9PUpx2Gl1hikshx5O9FzMJ6o9Nnopseg7qLBBK7Nl31d0RHcfwLEAfmcPApytpc0ZFfq68u22FQ==", | |
| 184 | "dev": true, | |
| 185 | "license": "Apache-2.0", | |
| 186 | "dependencies": { | |
| 187 | "bare-os": "^3.0.1" | |
| 188 | } | |
| 189 | }, | |
| 190 | "node_modules/bare-dns": { | |
| 191 | "version": "2.1.4", | |
| 192 | "resolved": "https://registry.npmjs.org/bare-dns/-/bare-dns-2.1.4.tgz", | |
| 193 | "integrity": "sha512-abwjHmpWqSRNB7V5615QxPH92L71AVzFm/kKTs8VYiNTAi2xVdonpv0BjJ0hwXLwomoW+xsSOPjW6PZPO14asg==", | |
| 194 | "dev": true, | |
| 195 | "license": "Apache-2.0", | |
| 196 | "engines": { | |
| 197 | "bare": ">=1.7.0" | |
| 198 | } | |
| 199 | }, | |
| 200 | "node_modules/bare-encoding": { | |
| 201 | "version": "1.0.3", | |
| 202 | "resolved": "https://registry.npmjs.org/bare-encoding/-/bare-encoding-1.0.3.tgz", | |
| 203 | "integrity": "sha512-Kqf+t/azs13lUeyK4Tb7ha4wdLRXKWCXQ8w1rVmt7KtoPCPdHD/Xwt7LBIsCSwwGglrcmblo5VOLa5avkJqULA==", | |
| 204 | "dev": true, | |
| 205 | "license": "Apache-2.0", | |
| 206 | "peerDependencies": { | |
| 207 | "bare-buffer": "*" | |
| 208 | }, | |
| 209 | "peerDependenciesMeta": { | |
| 210 | "bare-buffer": { | |
| 211 | "optional": true | |
| 212 | } | |
| 213 | } | |
| 214 | }, | |
| 215 | "node_modules/bare-env": { | |
| 216 | "version": "3.0.0", | |
| 217 | "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.0.tgz", | |
| 218 | "integrity": "sha512-0u964P5ZLAxTi+lW4Kjp7YRJQ5gZr9ycYOtjLxsSrupgMz3sn5Z9n4SH/JIifHwvadsf1brA2JAjP+9IOWwTiw==", | |
| 219 | "dev": true, | |
| 220 | "license": "Apache-2.0", | |
| 221 | "dependencies": { | |
| 222 | "bare-os": "^3.0.1" | |
| 223 | } | |
| 224 | }, | |
| 225 | "node_modules/bare-events": { | |
| 226 | "version": "2.8.2", | |
| 227 | "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", | |
| 228 | "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", | |
| 229 | "dev": true, | |
| 230 | "license": "Apache-2.0", | |
| 231 | "peerDependencies": { | |
| 232 | "bare-abort-controller": "*" | |
| 233 | }, | |
| 234 | "peerDependenciesMeta": { | |
| 235 | "bare-abort-controller": { | |
| 236 | "optional": true | |
| 237 | } | |
| 238 | } | |
| 239 | }, | |
| 240 | "node_modules/bare-format": { | |
| 241 | "version": "1.0.1", | |
| 242 | "resolved": "https://registry.npmjs.org/bare-format/-/bare-format-1.0.1.tgz", | |
| 243 | "integrity": "sha512-1oS+LZrWK6tnYnvNSHDGljc2MPunRxwhpFriuCgzNF+oklrnwmBKD91tS0yt+jpl2j3UgcSDzBIMiVTvLs9A8w==", | |
| 244 | "dev": true, | |
| 245 | "license": "Apache-2.0", | |
| 246 | "dependencies": { | |
| 247 | "bare-inspect": "^3.0.0" | |
| 248 | } | |
| 249 | }, | |
| 250 | "node_modules/bare-fs": { | |
| 251 | "version": "4.5.4", | |
| 252 | "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.4.tgz", | |
| 253 | "integrity": "sha512-POK4oplfA7P7gqvetNmCs4CNtm9fNsx+IAh7jH7GgU0OJdge2rso0R20TNWVq6VoWcCvsTdlNDaleLHGaKx8CA==", | |
| 254 | "dev": true, | |
| 255 | "license": "Apache-2.0", | |
| 256 | "dependencies": { | |
| 257 | "bare-events": "^2.5.4", | |
| 258 | "bare-path": "^3.0.0", | |
| 259 | "bare-stream": "^2.6.4", | |
| 260 | "bare-url": "^2.2.2", | |
| 261 | "fast-fifo": "^1.3.2" | |
| 262 | }, | |
| 263 | "engines": { | |
| 264 | "bare": ">=1.16.0" | |
| 265 | }, | |
| 266 | "peerDependencies": { | |
| 267 | "bare-buffer": "*" | |
| 268 | }, | |
| 269 | "peerDependenciesMeta": { | |
| 270 | "bare-buffer": { | |
| 271 | "optional": true | |
| 272 | } | |
| 273 | } | |
| 274 | }, | |
| 275 | "node_modules/bare-hrtime": { | |
| 276 | "version": "2.1.1", | |
| 277 | "resolved": "https://registry.npmjs.org/bare-hrtime/-/bare-hrtime-2.1.1.tgz", | |
| 278 | "integrity": "sha512-VMb3tHo05gsnbu3OXTmkDiwTjMlOsbQmKoysKqKEyR09m77TuDrYFbj3Q5GGk10dAKsUHrnXmwCaeJqzVpB5ZA==", | |
| 279 | "dev": true, | |
| 280 | "license": "Apache-2.0" | |
| 281 | }, | |
| 282 | "node_modules/bare-http-parser": { | |
| 283 | "version": "1.0.1", | |
| 284 | "resolved": "https://registry.npmjs.org/bare-http-parser/-/bare-http-parser-1.0.1.tgz", | |
| 285 | "integrity": "sha512-A3LTDTcELcmNJ3g5liIaS038v/BQxOhA9cjhBESn7eoV7QCuMoIRBKLDadDe08flxyLbxI2f+1l2MZ/5+HnKPA==", | |
| 286 | "dev": true, | |
| 287 | "license": "Apache-2.0" | |
| 288 | }, | |
| 289 | "node_modules/bare-http1": { | |
| 290 | "version": "4.2.3", | |
| 291 | "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.2.3.tgz", | |
| 292 | "integrity": "sha512-zyvHF3xzvBJNpuw0QydE9etTn2AyC/TRVDWTo8Ed3LQEEGaFOkv6KHhaJPlf4eWEeTekk41Fowie+fTsXoNxkA==", | |
| 293 | "dev": true, | |
| 294 | "license": "Apache-2.0", | |
| 295 | "dependencies": { | |
| 296 | "bare-events": "^2.6.0", | |
| 297 | "bare-http-parser": "^1.0.0", | |
| 298 | "bare-stream": "^2.3.0", | |
| 299 | "bare-tcp": "^2.2.0" | |
| 300 | }, | |
| 301 | "peerDependencies": { | |
| 302 | "bare-buffer": "*", | |
| 303 | "bare-url": "*" | |
| 304 | }, | |
| 305 | "peerDependenciesMeta": { | |
| 306 | "bare-buffer": { | |
| 307 | "optional": true | |
| 308 | }, | |
| 309 | "bare-url": { | |
| 310 | "optional": true | |
| 311 | } | |
| 312 | } | |
| 313 | }, | |
| 314 | "node_modules/bare-https": { | |
| 315 | "version": "2.1.2", | |
| 316 | "resolved": "https://registry.npmjs.org/bare-https/-/bare-https-2.1.2.tgz", | |
| 317 | "integrity": "sha512-Q+TTydUDsuKQJvh8dX2dvOXCR9fM3xR5TBmKaFrs5p7Lj7XbKX7v4vIUJ36H0SXg2xCOQxXKIbjwrLg5tfJNYg==", | |
| 318 | "dev": true, | |
| 319 | "license": "Apache-2.0", | |
| 320 | "dependencies": { | |
| 321 | "bare-http1": "^4.0.0", | |
| 322 | "bare-tcp": "^2.2.0", | |
| 323 | "bare-tls": "^2.0.0" | |
| 324 | } | |
| 325 | }, | |
| 326 | "node_modules/bare-inspect": { | |
| 327 | "version": "3.1.4", | |
| 328 | "resolved": "https://registry.npmjs.org/bare-inspect/-/bare-inspect-3.1.4.tgz", | |
| 329 | "integrity": "sha512-jfW5KRA84o3REpI6Vr4nbvMn+hqVAw8GU1mMdRwUsY5yJovQamxYeKGVKGqdzs+8ZbG4jRzGUXP/3Ji/DnqfPg==", | |
| 330 | "dev": true, | |
| 331 | "license": "Apache-2.0", | |
| 332 | "dependencies": { | |
| 333 | "bare-ansi-escapes": "^2.1.0", | |
| 334 | "bare-type": "^1.0.0" | |
| 335 | }, | |
| 336 | "engines": { | |
| 337 | "bare": ">=1.18.0" | |
| 338 | } | |
| 339 | }, | |
| 340 | "node_modules/bare-inspector": { | |
| 341 | "version": "4.0.3", | |
| 342 | "resolved": "https://registry.npmjs.org/bare-inspector/-/bare-inspector-4.0.3.tgz", | |
| 343 | "integrity": "sha512-yYk6+7tTVKJKHOja5+QBn989ETv8oVsmwoH0nCM+QYC6w5MC03GC4aQNK+lipAZSz1EaAj42WZ8Wewjpy0L9TA==", | |
| 344 | "dev": true, | |
| 345 | "license": "Apache-2.0", | |
| 346 | "dependencies": { | |
| 347 | "bare-events": "^2.1.0", | |
| 348 | "bare-http1": "^4.0.0", | |
| 349 | "bare-stream": "^2.0.0", | |
| 350 | "bare-url": "^2.0.0", | |
| 351 | "bare-ws": "^2.0.0" | |
| 352 | }, | |
| 353 | "engines": { | |
| 354 | "bare": ">=1.2.0" | |
| 355 | }, | |
| 356 | "peerDependencies": { | |
| 357 | "bare-tcp": "*" | |
| 358 | }, | |
| 359 | "peerDependenciesMeta": { | |
| 360 | "bare-tcp": { | |
| 361 | "optional": true | |
| 362 | } | |
| 363 | } | |
| 364 | }, | |
| 365 | "node_modules/bare-module": { | |
| 366 | "version": "6.1.3", | |
| 367 | "resolved": "https://registry.npmjs.org/bare-module/-/bare-module-6.1.3.tgz", | |
| 368 | "integrity": "sha512-5XWsVHsvtWMH4tK4DQWgpNTV0t/sg3ZrAaQLIxrwjrS5+u8Q9vEgc/zQ4QaDPWDse/y/5h+d+YG1Q0JfSMt0zA==", | |
| 369 | "dev": true, | |
| 370 | "license": "Apache-2.0", | |
| 371 | "dependencies": { | |
| 372 | "bare-bundle": "^1.3.0", | |
| 373 | "bare-module-lexer": "^1.0.0", | |
| 374 | "bare-module-resolve": "^1.8.0", | |
| 375 | "bare-path": "^3.0.0", | |
| 376 | "bare-url": "^2.0.1" | |
| 377 | }, | |
| 378 | "engines": { | |
| 379 | "bare": ">=1.23.0" | |
| 380 | }, | |
| 381 | "peerDependencies": { | |
| 382 | "bare-buffer": "*" | |
| 383 | }, | |
| 384 | "peerDependenciesMeta": { | |
| 385 | "bare-buffer": { | |
| 386 | "optional": true | |
| 387 | } | |
| 388 | } | |
| 389 | }, | |
| 390 | "node_modules/bare-module-lexer": { | |
| 391 | "version": "1.4.7", | |
| 392 | "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.4.7.tgz", | |
| 393 | "integrity": "sha512-0klU4eMsjh/wcxi8FdHmNom2j2F4kmkXOhyJFL9qTaSFp2lE3m6BtbKgMHY8R5miqC9r8/IfA8wzXnC5Os14WA==", | |
| 394 | "dev": true, | |
| 395 | "license": "Apache-2.0", | |
| 396 | "dependencies": { | |
| 397 | "require-addon": "^1.0.2" | |
| 398 | }, | |
| 399 | "peerDependencies": { | |
| 400 | "bare-buffer": "*" | |
| 401 | }, | |
| 402 | "peerDependenciesMeta": { | |
| 403 | "bare-buffer": { | |
| 404 | "optional": true | |
| 405 | } | |
| 406 | } | |
| 407 | }, | |
| 408 | "node_modules/bare-module-resolve": { | |
| 409 | "version": "1.12.1", | |
| 410 | "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.1.tgz", | |
| 411 | "integrity": "sha512-hbmAPyFpEq8FoZMd5sFO3u6MC5feluWoGE8YKlA8fCrl6mNtx68Wjg4DTiDJcqRJaovTvOYKfYngoBUnbaT7eg==", | |
| 412 | "dev": true, | |
| 413 | "license": "Apache-2.0", | |
| 414 | "dependencies": { | |
| 415 | "bare-semver": "^1.0.0" | |
| 416 | }, | |
| 417 | "peerDependencies": { | |
| 418 | "bare-url": "*" | |
| 419 | }, | |
| 420 | "peerDependenciesMeta": { | |
| 421 | "bare-url": { | |
| 422 | "optional": true | |
| 423 | } | |
| 424 | } | |
| 425 | }, | |
| 426 | "node_modules/bare-net": { | |
| 427 | "version": "2.2.0", | |
| 428 | "resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.2.0.tgz", | |
| 429 | "integrity": "sha512-UF7cAbHsGE+H6uEqWF5IULBow1x58chZz4g3ALgHtv7wZsFcCbRDt0JKWEumf5Oma3QWS1Q6aLi0Rpll8RElMg==", | |
| 430 | "dev": true, | |
| 431 | "license": "Apache-2.0", | |
| 432 | "dependencies": { | |
| 433 | "bare-events": "^2.2.2", | |
| 434 | "bare-pipe": "^4.0.0", | |
| 435 | "bare-stream": "^2.0.0", | |
| 436 | "bare-tcp": "^2.0.0" | |
| 437 | } | |
| 438 | }, | |
| 439 | "node_modules/bare-os": { | |
| 440 | "version": "3.6.2", | |
| 441 | "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", | |
| 442 | "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", | |
| 443 | "dev": true, | |
| 444 | "license": "Apache-2.0", | |
| 445 | "engines": { | |
| 446 | "bare": ">=1.14.0" | |
| 447 | } | |
| 448 | }, | |
| 449 | "node_modules/bare-path": { | |
| 450 | "version": "3.0.0", | |
| 451 | "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", | |
| 452 | "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", | |
| 453 | "dev": true, | |
| 454 | "license": "Apache-2.0", | |
| 455 | "dependencies": { | |
| 456 | "bare-os": "^3.0.1" | |
| 457 | } | |
| 458 | }, | |
| 459 | "node_modules/bare-pipe": { | |
| 460 | "version": "4.1.3", | |
| 461 | "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.1.3.tgz", | |
| 462 | "integrity": "sha512-DqQsx93rAzre6yJ9T6l/Vgh+X+bntkVMB1X5ggtXjXtqtMmF2Y2RVlCzxxy/09R6yeR9FSWBEUIaMYJL1/5VDA==", | |
| 463 | "dev": true, | |
| 464 | "license": "Apache-2.0", | |
| 465 | "dependencies": { | |
| 466 | "bare-events": "^2.0.0", | |
| 467 | "bare-stream": "^2.0.0" | |
| 468 | }, | |
| 469 | "engines": { | |
| 470 | "bare": ">=1.16.0" | |
| 471 | } | |
| 472 | }, | |
| 473 | "node_modules/bare-process": { | |
| 474 | "version": "4.2.2", | |
| 475 | "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.2.2.tgz", | |
| 476 | "integrity": "sha512-ikzEw+HGLB+2lS/WLVEsqi8BXBwzleG3n7DYI0v6/YNklvNabOIGlLd1dof+7Jw5ob4jsBRPtyflweVahY7rFA==", | |
| 477 | "dev": true, | |
| 478 | "license": "Apache-2.0", | |
| 479 | "dependencies": { | |
| 480 | "bare-env": "^3.0.0", | |
| 481 | "bare-events": "^2.3.1", | |
| 482 | "bare-hrtime": "^2.0.0", | |
| 483 | "bare-os": "^3.5.0", | |
| 484 | "bare-pipe": "^4.0.0", | |
| 485 | "bare-signals": "^4.0.0", | |
| 486 | "bare-tty": "^5.0.0" | |
| 487 | } | |
| 488 | }, | |
| 489 | "node_modules/bare-semver": { | |
| 490 | "version": "1.0.2", | |
| 491 | "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.0.2.tgz", | |
| 492 | "integrity": "sha512-ESVaN2nzWhcI5tf3Zzcq9aqCZ676VWzqw07eEZ0qxAcEOAFYBa0pWq8sK34OQeHLY3JsfKXZS9mDyzyxGjeLzA==", | |
| 493 | "dev": true, | |
| 494 | "license": "Apache-2.0" | |
| 495 | }, | |
| 496 | "node_modules/bare-signals": { | |
| 497 | "version": "4.2.0", | |
| 498 | "resolved": "https://registry.npmjs.org/bare-signals/-/bare-signals-4.2.0.tgz", | |
| 499 | "integrity": "sha512-fNHMOdQIlYuTvMB3Oh9Apk99hLKn351+Ir8vz+khiPTcOqIyGG4uWWjdLTzxWdYGsA0eT+We3y0K74hjj2nq7A==", | |
| 500 | "dev": true, | |
| 501 | "license": "Apache-2.0", | |
| 502 | "dependencies": { | |
| 503 | "bare-events": "^2.5.3", | |
| 504 | "bare-os": "^3.3.1" | |
| 505 | }, | |
| 506 | "engines": { | |
| 507 | "bare": ">=1.7.0" | |
| 508 | } | |
| 509 | }, | |
| 510 | "node_modules/bare-stream": { | |
| 511 | "version": "2.7.0", | |
| 512 | "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", | |
| 513 | "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", | |
| 514 | "dev": true, | |
| 515 | "license": "Apache-2.0", | |
| 516 | "dependencies": { | |
| 517 | "streamx": "^2.21.0" | |
| 518 | }, | |
| 519 | "peerDependencies": { | |
| 520 | "bare-buffer": "*", | |
| 521 | "bare-events": "*" | |
| 522 | }, | |
| 523 | "peerDependenciesMeta": { | |
| 524 | "bare-buffer": { | |
| 525 | "optional": true | |
| 526 | }, | |
| 527 | "bare-events": { | |
| 528 | "optional": true | |
| 529 | } | |
| 530 | } | |
| 531 | }, | |
| 532 | "node_modules/bare-subprocess": { | |
| 533 | "version": "5.2.2", | |
| 534 | "resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.2.tgz", | |
| 535 | "integrity": "sha512-L6oXgQ1aWs25RtG5Ky0bDD06p3RAcVVrDDMWb1DfXpHtyEWxamcyIWUbSykxMTWrpLlmSj6ytbb6yoKehGFfmw==", | |
| 536 | "dev": true, | |
| 537 | "license": "Apache-2.0", | |
| 538 | "dependencies": { | |
| 539 | "bare-env": "^3.0.0", | |
| 540 | "bare-events": "^2.5.4", | |
| 541 | "bare-os": "^3.0.1", | |
| 542 | "bare-pipe": "^4.0.0", | |
| 543 | "bare-url": "^2.2.2" | |
| 544 | }, | |
| 545 | "engines": { | |
| 546 | "bare": ">=1.7.0" | |
| 547 | }, | |
| 548 | "peerDependencies": { | |
| 549 | "bare-buffer": "*" | |
| 550 | }, | |
| 551 | "peerDependenciesMeta": { | |
| 552 | "bare-buffer": { | |
| 553 | "optional": true | |
| 554 | } | |
| 555 | } | |
| 556 | }, | |
| 557 | "node_modules/bare-tcp": { | |
| 558 | "version": "2.2.4", | |
| 559 | "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.2.4.tgz", | |
| 560 | "integrity": "sha512-vGjfMkyhS1lxp+Y4+gXRDR1ijZMK2cKx0RBwtYKUuZbD90ARCoM6bAiTa1HBztumCIC+NXb8eXdMFXyuYwlPyw==", | |
| 561 | "dev": true, | |
| 562 | "license": "Apache-2.0", | |
| 563 | "dependencies": { | |
| 564 | "bare-dns": "^2.0.4", | |
| 565 | "bare-events": "^2.5.4", | |
| 566 | "bare-stream": "^2.6.4" | |
| 567 | }, | |
| 568 | "engines": { | |
| 569 | "bare": ">=1.16.0" | |
| 570 | } | |
| 571 | }, | |
| 572 | "node_modules/bare-tls": { | |
| 573 | "version": "2.1.7", | |
| 574 | "resolved": "https://registry.npmjs.org/bare-tls/-/bare-tls-2.1.7.tgz", | |
| 575 | "integrity": "sha512-h6wcNXQdBeTX7fed9tjPp0/9cA/QfcBTv3ItgjnbUk4rWAU8bEFalZCZnUDdCK/t9zrNfJ+yvcPx4D/1Y6biyA==", | |
| 576 | "dev": true, | |
| 577 | "license": "Apache-2.0", | |
| 578 | "dependencies": { | |
| 579 | "bare-net": "^2.0.1", | |
| 580 | "bare-stream": "^2.6.4" | |
| 581 | }, | |
| 582 | "engines": { | |
| 583 | "bare": ">=1.7.0" | |
| 584 | } | |
| 585 | }, | |
| 586 | "node_modules/bare-tty": { | |
| 587 | "version": "5.0.3", | |
| 588 | "resolved": "https://registry.npmjs.org/bare-tty/-/bare-tty-5.0.3.tgz", | |
| 589 | "integrity": "sha512-jW24RBWRZOMHuSEWC9mh8wUKO5WUNl0UWUTNPn3yZ8qkOqwa8vaV2dR3a+BvblONPk7V35wuvK9P8508+judAg==", | |
| 590 | "dev": true, | |
| 591 | "license": "Apache-2.0", | |
| 592 | "dependencies": { | |
| 593 | "bare-events": "^2.2.0", | |
| 594 | "bare-signals": "^4.0.0", | |
| 595 | "bare-stream": "^2.0.0" | |
| 596 | }, | |
| 597 | "engines": { | |
| 598 | "bare": ">=1.16.0" | |
| 599 | } | |
| 600 | }, | |
| 601 | "node_modules/bare-type": { | |
| 602 | "version": "1.1.0", | |
| 603 | "resolved": "https://registry.npmjs.org/bare-type/-/bare-type-1.1.0.tgz", | |
| 604 | "integrity": "sha512-LdtnnEEYldOc87Dr4GpsKnStStZk3zfgoEMXy8yvEZkXrcCv9RtYDrUYWFsBQHtaB0s1EUWmcvS6XmEZYIj3Bw==", | |
| 605 | "dev": true, | |
| 606 | "license": "Apache-2.0", | |
| 607 | "engines": { | |
| 608 | "bare": ">=1.2.0" | |
| 609 | } | |
| 610 | }, | |
| 611 | "node_modules/bare-url": { | |
| 612 | "version": "2.3.2", | |
| 613 | "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", | |
| 614 | "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", | |
| 615 | "dev": true, | |
| 616 | "license": "Apache-2.0", | |
| 617 | "peer": true, | |
| 618 | "dependencies": { | |
| 619 | "bare-path": "^3.0.0" | |
| 620 | } | |
| 621 | }, | |
| 622 | "node_modules/bare-utils": { | |
| 623 | "version": "1.5.1", | |
| 624 | "resolved": "https://registry.npmjs.org/bare-utils/-/bare-utils-1.5.1.tgz", | |
| 625 | "integrity": "sha512-mxCkFvmDU3mlD/mb+pT64kKXOsx2KMsWLQbngN1LB+NOXfhfnRnyvpy3VZc6m7gzQxe57Bsi+aTCBqA4/S3elQ==", | |
| 626 | "dev": true, | |
| 627 | "license": "Apache-2.0", | |
| 628 | "dependencies": { | |
| 629 | "bare-debug-log": "^2.0.0", | |
| 630 | "bare-encoding": "^1.0.0", | |
| 631 | "bare-format": "^1.0.0", | |
| 632 | "bare-inspect": "^3.0.0", | |
| 633 | "bare-type": "^1.0.6" | |
| 634 | } | |
| 635 | }, | |
| 636 | "node_modules/bare-v8-to-istanbul": { | |
| 637 | "version": "1.0.2", | |
| 638 | "resolved": "https://registry.npmjs.org/bare-v8-to-istanbul/-/bare-v8-to-istanbul-1.0.2.tgz", | |
| 639 | "integrity": "sha512-CM0wPgyQtXTQ7h4q8NjbTZ/FH5he0Jkiwgpq0lFZZA5tkVZzmQ/9ul4R7E+cNlGZJIZlQZ9v4CwbgVaFG3zOAA==", | |
| 640 | "dev": true, | |
| 641 | "license": "Apache-2.0", | |
| 642 | "dependencies": { | |
| 643 | "bare-assert": "^1.0.2", | |
| 644 | "bare-fs": "^4.1.2", | |
| 645 | "bare-module": "^6.1.2", | |
| 646 | "bare-path": "^3.0.0", | |
| 647 | "bare-process": "^4.2.0", | |
| 648 | "bare-url": "^2.1.5", | |
| 649 | "bare-utils": "^1.2.0", | |
| 650 | "v8-to-istanbul": "^9.3.0", | |
| 651 | "which-runtime": "^1.2.1" | |
| 652 | } | |
| 653 | }, | |
| 654 | "node_modules/bare-ws": { | |
| 655 | "version": "2.0.4", | |
| 656 | "resolved": "https://registry.npmjs.org/bare-ws/-/bare-ws-2.0.4.tgz", | |
| 657 | "integrity": "sha512-SQMXzBYna9dRj57Dz1/ag+VWHCRXbfCjMHgyfM2F2lhkVLzMjnVSZP72aVeFWPFqe494Rd70Kzhe2JElGwFlJQ==", | |
| 658 | "dev": true, | |
| 659 | "license": "Apache-2.0", | |
| 660 | "dependencies": { | |
| 661 | "bare-crypto": "^1.2.0", | |
| 662 | "bare-events": "^2.3.1", | |
| 663 | "bare-http1": "^4.0.0", | |
| 664 | "bare-https": "^2.0.0", | |
| 665 | "bare-stream": "^2.1.2" | |
| 666 | }, | |
| 667 | "peerDependencies": { | |
| 668 | "bare-buffer": "*", | |
| 669 | "bare-url": "*" | |
| 670 | }, | |
| 671 | "peerDependenciesMeta": { | |
| 672 | "bare-buffer": { | |
| 673 | "optional": true | |
| 674 | }, | |
| 675 | "bare-url": { | |
| 676 | "optional": true | |
| 677 | } | |
| 678 | } | |
| 679 | }, | |
| 680 | "node_modules/bridge-envelope": { | |
| 681 | "resolved": "../bridge-envelope", | |
| 682 | "link": true | |
| 683 | }, | |
| 684 | "node_modules/brittle": { | |
| 685 | "version": "3.19.1", | |
| 686 | "resolved": "https://registry.npmjs.org/brittle/-/brittle-3.19.1.tgz", | |
| 687 | "integrity": "sha512-4Ted1Mt9o9B6oIA6ImJJCtB/Fv++ZO3IDNQgRcHOXWSYGRUidgxchaGrUBaRn+4mlneXrgInPkCPzi0jO8qKbA==", | |
| 688 | "dev": true, | |
| 689 | "license": "Apache-2.0", | |
| 690 | "dependencies": { | |
| 691 | "b4a": "^1.6.0", | |
| 692 | "bare-assert": "^1.0.2", | |
| 693 | "bare-cov": "^1.1.0", | |
| 694 | "bare-fs": "^4.1.6", | |
| 695 | "bare-os": "^3.6.1", | |
| 696 | "bare-path": "^3.0.0", | |
| 697 | "bare-process": "^4.2.1", | |
| 698 | "bare-subprocess": "^5.0.0", | |
| 699 | "bare-url": "^2.1.6", | |
| 700 | "error-stack-parser": "^2.1.4", | |
| 701 | "globbie": "^1.0.2", | |
| 702 | "paparam": "^1.6.2", | |
| 703 | "same-object": "^1.0.2", | |
| 704 | "test-tmp": "^1.4.0", | |
| 705 | "tmatch": "^5.0.0" | |
| 706 | }, | |
| 707 | "bin": { | |
| 708 | "brittle": "bin/node.js", | |
| 709 | "brittle-bare": "bin/bare.js", | |
| 710 | "brittle-node": "bin/node.js", | |
| 711 | "brittle-pear": "bin/pear.js" | |
| 712 | } | |
| 713 | }, | |
| 714 | "node_modules/convert-source-map": { | |
| 715 | "version": "2.0.0", | |
| 716 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", | |
| 717 | "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", | |
| 718 | "dev": true, | |
| 719 | "license": "MIT" | |
| 720 | }, | |
| 721 | "node_modules/error-stack-parser": { | |
| 722 | "version": "2.1.4", | |
| 723 | "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", | |
| 724 | "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", | |
| 725 | "dev": true, | |
| 726 | "license": "MIT", | |
| 727 | "dependencies": { | |
| 728 | "stackframe": "^1.3.4" | |
| 729 | } | |
| 730 | }, | |
| 731 | "node_modules/events-universal": { | |
| 732 | "version": "1.0.1", | |
| 733 | "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", | |
| 734 | "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", | |
| 735 | "dev": true, | |
| 736 | "license": "Apache-2.0", | |
| 737 | "dependencies": { | |
| 738 | "bare-events": "^2.7.0" | |
| 739 | } | |
| 740 | }, | |
| 741 | "node_modules/fast-fifo": { | |
| 742 | "version": "1.3.2", | |
| 743 | "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", | |
| 744 | "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", | |
| 745 | "dev": true, | |
| 746 | "license": "MIT" | |
| 747 | }, | |
| 748 | "node_modules/globbie": { | |
| 749 | "version": "1.0.3", | |
| 750 | "resolved": "https://registry.npmjs.org/globbie/-/globbie-1.0.3.tgz", | |
| 751 | "integrity": "sha512-hcryJmKcftf82xfBWTWxuUITWIIoYO3ec104V17SMHGFl6VLbm1d1Ju9LX9L+jyJTwICpemX/dmPI/HGYoKr1A==", | |
| 752 | "dev": true, | |
| 753 | "license": "Apache-2.0", | |
| 754 | "dependencies": { | |
| 755 | "bare-fs": "^4.1.2", | |
| 756 | "bare-path": "^3.0.0", | |
| 757 | "bare-process": "^4.2.1", | |
| 758 | "picomatch": "^4.0.2" | |
| 759 | } | |
| 760 | }, | |
| 761 | "node_modules/paparam": { | |
| 762 | "version": "1.10.0", | |
| 763 | "resolved": "https://registry.npmjs.org/paparam/-/paparam-1.10.0.tgz", | |
| 764 | "integrity": "sha512-p36QQrwU3X6fj5d2JN20B4lcZI/O3fxMVUHc1xD7MNe5bcOf3jIgE0CIK5Zv/s5YAUGlobTJ/kKXrh1FJVrs3w==", | |
| 765 | "dev": true, | |
| 766 | "license": "Apache-2.0" | |
| 767 | }, | |
| 768 | "node_modules/picomatch": { | |
| 769 | "version": "4.0.3", | |
| 770 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", | |
| 771 | "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", | |
| 772 | "dev": true, | |
| 773 | "license": "MIT", | |
| 774 | "engines": { | |
| 775 | "node": ">=12" | |
| 776 | }, | |
| 777 | "funding": { | |
| 778 | "url": "https://github.com/sponsors/jonschlinkert" | |
| 779 | } | |
| 780 | }, | |
| 781 | "node_modules/require-addon": { | |
| 782 | "version": "1.2.0", | |
| 783 | "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz", | |
| 784 | "integrity": "sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA==", | |
| 785 | "dev": true, | |
| 786 | "license": "Apache-2.0", | |
| 787 | "dependencies": { | |
| 788 | "bare-addon-resolve": "^1.3.0" | |
| 789 | }, | |
| 790 | "engines": { | |
| 791 | "bare": ">=1.10.0" | |
| 792 | } | |
| 793 | }, | |
| 794 | "node_modules/same-object": { | |
| 795 | "version": "1.0.2", | |
| 796 | "resolved": "https://registry.npmjs.org/same-object/-/same-object-1.0.2.tgz", | |
| 797 | "integrity": "sha512-csHWhvUsLbIOHDM/nP+KHWM+BLPsIzWkFa8HbzaI0G7BqKXgx+7FJpKTGgLXyz5amfdY2OVBcmXTqYOMEk04og==", | |
| 798 | "dev": true, | |
| 799 | "license": "MIT" | |
| 800 | }, | |
| 801 | "node_modules/stackframe": { | |
| 802 | "version": "1.3.4", | |
| 803 | "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", | |
| 804 | "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", | |
| 805 | "dev": true, | |
| 806 | "license": "MIT" | |
| 807 | }, | |
| 808 | "node_modules/streamx": { | |
| 809 | "version": "2.23.0", | |
| 810 | "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", | |
| 811 | "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", | |
| 812 | "dev": true, | |
| 813 | "license": "MIT", | |
| 814 | "dependencies": { | |
| 815 | "events-universal": "^1.0.0", | |
| 816 | "fast-fifo": "^1.3.2", | |
| 817 | "text-decoder": "^1.1.0" | |
| 818 | } | |
| 819 | }, | |
| 820 | "node_modules/test-tmp": { | |
| 821 | "version": "1.4.0", | |
| 822 | "resolved": "https://registry.npmjs.org/test-tmp/-/test-tmp-1.4.0.tgz", | |
| 823 | "integrity": "sha512-GVggxGg+jXqP2Wbju50JVLo+9E+nIOPPyWqgr63EbOnNItIKu1cEbJpTWAJeflnyGqXOtcMI7ijHRp88GUkfDA==", | |
| 824 | "dev": true, | |
| 825 | "license": "MIT", | |
| 826 | "dependencies": { | |
| 827 | "bare-fs": "^4.0.1", | |
| 828 | "bare-os": "^3.3.0", | |
| 829 | "bare-path": "^3.0.0" | |
| 830 | } | |
| 831 | }, | |
| 832 | "node_modules/text-decoder": { | |
| 833 | "version": "1.2.6", | |
| 834 | "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.6.tgz", | |
| 835 | "integrity": "sha512-27FeW5GQFDfw0FpwMQhMagB7BztOOlmjcSRi97t2oplhKVTZtp0DZbSegSaXS5IIC6mxMvBG4AR1Sgc6BX3CQg==", | |
| 836 | "dev": true, | |
| 837 | "license": "Apache-2.0", | |
| 838 | "dependencies": { | |
| 839 | "b4a": "^1.6.4" | |
| 840 | } | |
| 841 | }, | |
| 842 | "node_modules/tmatch": { | |
| 843 | "version": "5.0.0", | |
| 844 | "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-5.0.0.tgz", | |
| 845 | "integrity": "sha512-Ib9OtBkpHn07tXP04SlN1SYRxFgTk6wSM2EBmjjxug4u5RXPRVLkdFJSS1PmrQidaSB8Lru9nRtViQBsbxzE5Q==", | |
| 846 | "dev": true, | |
| 847 | "license": "ISC", | |
| 848 | "engines": { | |
| 849 | "node": ">=8" | |
| 850 | } | |
| 851 | }, | |
| 852 | "node_modules/v8-to-istanbul": { | |
| 853 | "version": "9.3.0", | |
| 854 | "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", | |
| 855 | "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", | |
| 856 | "dev": true, | |
| 857 | "license": "ISC", | |
| 858 | "dependencies": { | |
| 859 | "@jridgewell/trace-mapping": "^0.3.12", | |
| 860 | "@types/istanbul-lib-coverage": "^2.0.1", | |
| 861 | "convert-source-map": "^2.0.0" | |
| 862 | }, | |
| 863 | "engines": { | |
| 864 | "node": ">=10.12.0" | |
| 865 | } | |
| 866 | }, | |
| 867 | "node_modules/which-runtime": { | |
| 868 | "version": "1.3.2", | |
| 869 | "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.3.2.tgz", | |
| 870 | "integrity": "sha512-5kwCfWml7+b2NO7KrLMhYihjRx0teKkd3yGp1Xk5Vaf2JGdSh+rgVhEALAD9c/59dP+YwJHXoEO7e8QPy7gOkw==", | |
| 871 | "dev": true, | |
| 872 | "license": "Apache-2.0" | |
| 873 | } | |
| 874 | } | |
| 875 | } |
| … | … | @@ -0,0 +1,22 @@ |
| 1 | { | |
| 2 | "name": "bridge-mastodon", | |
| 3 | "version": "1.0.0", | |
| 4 | "description": "Mastodon ↔ Keet bidirectional bridge (POSSE syndication)", | |
| 5 | "type": "module", | |
| 6 | "main": "index.js", | |
| 7 | "pear": { | |
| 8 | "name": "bridge-mastodon", | |
| 9 | "type": "terminal" | |
| 10 | }, | |
| 11 | "scripts": { | |
| 12 | "test": "brittle test/*.test.js" | |
| 13 | }, | |
| 14 | "dependencies": { | |
| 15 | "bridge-envelope": "file:../bridge-envelope", | |
| 16 | "b4a": "^1.0.0" | |
| 17 | }, | |
| 18 | "devDependencies": { | |
| 19 | "brittle": "^3.0.0" | |
| 20 | }, | |
| 21 | "license": "MIT" | |
| 22 | } |
| … | … | @@ -0,0 +1,245 @@ |
| 1 | import test from 'brittle' | |
| 2 | import { statusToEnvelope, keetToMastodon, envelopeToStatusText, statusToKeetText, stripHtml } from '../lib/adapter.js' | |
| 3 | import { TYPES, PLATFORMS } from 'bridge-envelope' | |
| 4 | ||
| 5 | test('stripHtml - basic tags', (t) => { | |
| 6 | t.is(stripHtml('<p>Hello</p>'), 'Hello') | |
| 7 | t.is(stripHtml('<p>Line 1</p><p>Line 2</p>'), 'Line 1\n\nLine 2') | |
| 8 | t.is(stripHtml('Hello<br/>World'), 'Hello\nWorld') | |
| 9 | t.is(stripHtml('Hello<br>World'), 'Hello\nWorld') | |
| 10 | }) | |
| 11 | ||
| 12 | test('stripHtml - entities', (t) => { | |
| 13 | t.is(stripHtml('& < > " ''), '& < > " \'') | |
| 14 | }) | |
| 15 | ||
| 16 | test('stripHtml - nested tags', (t) => { | |
| 17 | t.is(stripHtml('<p>Hello <a href="https://example.com">link</a></p>'), 'Hello link') | |
| 18 | t.is(stripHtml('<p><span class="mention">@user</span> hi</p>'), '@user hi') | |
| 19 | }) | |
| 20 | ||
| 21 | test('statusToEnvelope - basic status', (t) => { | |
| 22 | const status = { | |
| 23 | id: '12345', | |
| 24 | account: { acct: 'alice@mastodon.social', username: 'alice' }, | |
| 25 | content: '<p>Hello world!</p>', | |
| 26 | visibility: 'public', | |
| 27 | media_attachments: [], | |
| 28 | tags: [], | |
| 29 | created_at: '2026-02-14T10:00:00.000Z' | |
| 30 | } | |
| 31 | ||
| 32 | const env = statusToEnvelope(status) | |
| 33 | t.ok(env) | |
| 34 | t.is(env.type, TYPES.TEXT) | |
| 35 | t.is(env.platform, PLATFORMS.MASTODON) | |
| 36 | t.is(env.author, 'alice@mastodon.social') | |
| 37 | t.is(env.content, 'Hello world!') | |
| 38 | t.is(env.channel, 'public') | |
| 39 | t.ok(env.id) | |
| 40 | }) | |
| 41 | ||
| 42 | test('statusToEnvelope - status with media', (t) => { | |
| 43 | const status = { | |
| 44 | id: '12346', | |
| 45 | account: { acct: 'bob' }, | |
| 46 | content: '<p>Check this out</p>', | |
| 47 | visibility: 'unlisted', | |
| 48 | media_attachments: [{ | |
| 49 | url: 'https://files.mastodon.social/image.jpg', | |
| 50 | type: 'image', | |
| 51 | preview_url: 'https://files.mastodon.social/thumb.jpg' | |
| 52 | }], | |
| 53 | tags: [] | |
| 54 | } | |
| 55 | ||
| 56 | const env = statusToEnvelope(status) | |
| 57 | t.is(env.type, TYPES.MEDIA) | |
| 58 | t.is(env.mediaUrl, 'https://files.mastodon.social/image.jpg') | |
| 59 | t.is(env.mediaType, 'image') | |
| 60 | }) | |
| 61 | ||
| 62 | test('statusToEnvelope - reply', (t) => { | |
| 63 | const status = { | |
| 64 | id: '12347', | |
| 65 | account: { acct: 'carol' }, | |
| 66 | content: '<p>@alice I agree!</p>', | |
| 67 | in_reply_to_id: '12345', | |
| 68 | visibility: 'public', | |
| 69 | media_attachments: [], | |
| 70 | tags: [] | |
| 71 | } | |
| 72 | ||
| 73 | const env = statusToEnvelope(status) | |
| 74 | t.is(env.type, TYPES.REPLY) | |
| 75 | t.is(env.replyTo, '12345') | |
| 76 | }) | |
| 77 | ||
| 78 | test('statusToEnvelope - boost (reblog)', (t) => { | |
| 79 | const status = { | |
| 80 | id: '99999', | |
| 81 | account: { acct: 'dave' }, | |
| 82 | reblog: { | |
| 83 | id: '12345', | |
| 84 | account: { acct: 'alice@mastodon.social' }, | |
| 85 | content: '<p>Original post</p>', | |
| 86 | visibility: 'public', | |
| 87 | media_attachments: [], | |
| 88 | tags: [] | |
| 89 | } | |
| 90 | } | |
| 91 | ||
| 92 | const env = statusToEnvelope(status) | |
| 93 | t.ok(env) | |
| 94 | t.is(env.author, 'alice@mastodon.social') | |
| 95 | t.is(env.content, 'Original post') | |
| 96 | t.ok(env._isBoost) | |
| 97 | t.is(env._boostBy, 'dave') | |
| 98 | t.is(env.channel, 'boost') | |
| 99 | }) | |
| 100 | ||
| 101 | test('statusToEnvelope - with tags', (t) => { | |
| 102 | const status = { | |
| 103 | id: '12348', | |
| 104 | account: { acct: 'eve' }, | |
| 105 | content: '<p>Great discussion #p2p #hypercore</p>', | |
| 106 | visibility: 'public', | |
| 107 | media_attachments: [], | |
| 108 | tags: [{ name: 'p2p' }, { name: 'hypercore' }] | |
| 109 | } | |
| 110 | ||
| 111 | const env = statusToEnvelope(status) | |
| 112 | t.alike(env._tags, ['p2p', 'hypercore']) | |
| 113 | }) | |
| 114 | ||
| 115 | test('statusToEnvelope - with spoiler text', (t) => { | |
| 116 | const status = { | |
| 117 | id: '12349', | |
| 118 | account: { acct: 'frank' }, | |
| 119 | content: '<p>Spoiler content here</p>', | |
| 120 | spoiler_text: 'Movie spoiler', | |
| 121 | visibility: 'public', | |
| 122 | media_attachments: [], | |
| 123 | tags: [] | |
| 124 | } | |
| 125 | ||
| 126 | const env = statusToEnvelope(status) | |
| 127 | t.is(env._spoilerText, 'Movie spoiler') | |
| 128 | }) | |
| 129 | ||
| 130 | test('statusToEnvelope - null/empty', (t) => { | |
| 131 | t.is(statusToEnvelope(null), null) | |
| 132 | t.is(statusToEnvelope({ id: '1', account: { acct: 'x' }, content: '', media_attachments: [] }), null) | |
| 133 | }) | |
| 134 | ||
| 135 | test('keetToMastodon - normal message', (t) => { | |
| 136 | const msg = { name: 'fleeky', text: 'Hello from Keet!', memberId: 'abc123' } | |
| 137 | const result = keetToMastodon(msg, 'bridge-id') | |
| 138 | t.ok(result) | |
| 139 | t.is(result.author, 'fleeky') | |
| 140 | t.is(result.text, 'Hello from Keet!') | |
| 141 | }) | |
| 142 | ||
| 143 | test('keetToMastodon - skip bridge identity', (t) => { | |
| 144 | const msg = { name: 'clode', text: 'Hello', memberId: 'bridge-id' } | |
| 145 | const result = keetToMastodon(msg, 'bridge-id') | |
| 146 | t.is(result, null) | |
| 147 | }) | |
| 148 | ||
| 149 | test('keetToMastodon - skip system messages', (t) => { | |
| 150 | const msg = { name: 'system', text: 'joined', type: 'system' } | |
| 151 | t.is(keetToMastodon(msg, 'bridge-id'), null) | |
| 152 | }) | |
| 153 | ||
| 154 | test('keetToMastodon - skip already bridged', (t) => { | |
| 155 | const msg = { name: 'someone', text: '[mastodon] @alice: hello', memberId: 'x' } | |
| 156 | t.is(keetToMastodon(msg, 'bridge-id'), null) | |
| 157 | }) | |
| 158 | ||
| 159 | test('keetToMastodon - skip empty', (t) => { | |
| 160 | t.is(keetToMastodon({ name: 'x', text: '', memberId: 'y' }, 'b'), null) | |
| 161 | t.is(keetToMastodon({ name: 'x', text: ' ', memberId: 'y' }, 'b'), null) | |
| 162 | t.is(keetToMastodon(null, 'b'), null) | |
| 163 | }) | |
| 164 | ||
| 165 | test('envelopeToStatusText - from Keet', (t) => { | |
| 166 | const env = { | |
| 167 | platform: PLATFORMS.KEET, | |
| 168 | author: 'fleeky', | |
| 169 | content: 'Hello world' | |
| 170 | } | |
| 171 | t.is(envelopeToStatusText(env), '[Keet] fleeky: Hello world') | |
| 172 | }) | |
| 173 | ||
| 174 | test('envelopeToStatusText - from IRC', (t) => { | |
| 175 | const env = { | |
| 176 | platform: PLATFORMS.IRC, | |
| 177 | author: 'nick', | |
| 178 | content: 'IRC message' | |
| 179 | } | |
| 180 | t.is(envelopeToStatusText(env), '[IRC] nick: IRC message') | |
| 181 | }) | |
| 182 | ||
| 183 | test('statusToKeetText - basic', (t) => { | |
| 184 | const env = statusToEnvelope({ | |
| 185 | id: '1', | |
| 186 | account: { acct: 'alice' }, | |
| 187 | content: '<p>Hello!</p>', | |
| 188 | visibility: 'public', | |
| 189 | media_attachments: [], | |
| 190 | tags: [] | |
| 191 | }) | |
| 192 | const text = statusToKeetText(env) | |
| 193 | t.ok(text.includes('[mastodon]')) | |
| 194 | t.ok(text.includes('@alice')) | |
| 195 | t.ok(text.includes('Hello!')) | |
| 196 | }) | |
| 197 | ||
| 198 | test('statusToKeetText - boost', (t) => { | |
| 199 | const env = statusToEnvelope({ | |
| 200 | id: '2', | |
| 201 | account: { acct: 'bob' }, | |
| 202 | reblog: { | |
| 203 | id: '1', | |
| 204 | account: { acct: 'alice' }, | |
| 205 | content: '<p>Original</p>', | |
| 206 | visibility: 'public', | |
| 207 | media_attachments: [], | |
| 208 | tags: [] | |
| 209 | } | |
| 210 | }) | |
| 211 | const text = statusToKeetText(env) | |
| 212 | t.ok(text.includes('bob boosted')) | |
| 213 | t.ok(text.includes('@alice')) | |
| 214 | }) | |
| 215 | ||
| 216 | test('statusToKeetText - content warning', (t) => { | |
| 217 | const env = statusToEnvelope({ | |
| 218 | id: '3', | |
| 219 | account: { acct: 'carol' }, | |
| 220 | content: '<p>Secret info</p>', | |
| 221 | spoiler_text: 'Spoiler ahead', | |
| 222 | visibility: 'public', | |
| 223 | media_attachments: [], | |
| 224 | tags: [] | |
| 225 | }) | |
| 226 | const text = statusToKeetText(env) | |
| 227 | t.ok(text.includes('[CW: Spoiler ahead]')) | |
| 228 | }) | |
| 229 | ||
| 230 | test('statusToKeetText - with media', (t) => { | |
| 231 | const env = statusToEnvelope({ | |
| 232 | id: '4', | |
| 233 | account: { acct: 'dave' }, | |
| 234 | content: '<p>Look!</p>', | |
| 235 | visibility: 'public', | |
| 236 | media_attachments: [{ | |
| 237 | url: 'https://example.com/image.jpg', | |
| 238 | type: 'image' | |
| 239 | }], | |
| 240 | tags: [] | |
| 241 | }) | |
| 242 | const text = statusToKeetText(env) | |
| 243 | t.ok(text.includes('[image:')) | |
| 244 | t.ok(text.includes('image.jpg')) | |
| 245 | }) |
| … | … | @@ -0,0 +1,77 @@ |
| 1 | import test from 'brittle' | |
| 2 | import { Router } from '../lib/router.js' | |
| 3 | ||
| 4 | test('Router - route to Keet', (t) => { | |
| 5 | let received = null | |
| 6 | const router = new Router({ | |
| 7 | onKeetMessage: (author, text) => { received = { author, text } } | |
| 8 | }) | |
| 9 | ||
| 10 | const ok = router.routeToKeet('alice', 'hello') | |
| 11 | t.ok(ok) | |
| 12 | t.is(received.author, 'alice') | |
| 13 | t.is(received.text, 'hello') | |
| 14 | }) | |
| 15 | ||
| 16 | test('Router - route to external', (t) => { | |
| 17 | let received = null | |
| 18 | const router = new Router({ | |
| 19 | onExternalMessage: (author, text) => { received = { author, text } } | |
| 20 | }) | |
| 21 | ||
| 22 | const ok = router.routeToExternal('bob', 'world') | |
| 23 | t.ok(ok) | |
| 24 | t.is(received.author, 'bob') | |
| 25 | t.is(received.text, 'world') | |
| 26 | }) | |
| 27 | ||
| 28 | test('Router - dedup identical messages', (t) => { | |
| 29 | let count = 0 | |
| 30 | const router = new Router({ | |
| 31 | onKeetMessage: () => { count++ } | |
| 32 | }) | |
| 33 | ||
| 34 | router.routeToKeet('alice', 'hello') | |
| 35 | router.routeToKeet('alice', 'hello') | |
| 36 | router.routeToKeet('alice', 'hello') | |
| 37 | ||
| 38 | t.is(count, 1) | |
| 39 | }) | |
| 40 | ||
| 41 | test('Router - different messages not deduped', (t) => { | |
| 42 | let count = 0 | |
| 43 | const router = new Router({ | |
| 44 | onKeetMessage: () => { count++ } | |
| 45 | }) | |
| 46 | ||
| 47 | router.routeToKeet('alice', 'msg 1') | |
| 48 | router.routeToKeet('alice', 'msg 2') | |
| 49 | router.routeToKeet('bob', 'msg 1') | |
| 50 | ||
| 51 | t.is(count, 3) | |
| 52 | }) | |
| 53 | ||
| 54 | test('Router - cross-direction dedup', (t) => { | |
| 55 | let keetCount = 0 | |
| 56 | let extCount = 0 | |
| 57 | const router = new Router({ | |
| 58 | onKeetMessage: () => { keetCount++ }, | |
| 59 | onExternalMessage: () => { extCount++ } | |
| 60 | }) | |
| 61 | ||
| 62 | router.routeToKeet('alice', 'echo test') | |
| 63 | router.routeToExternal('alice', 'echo test') | |
| 64 | ||
| 65 | t.is(keetCount, 1) | |
| 66 | t.is(extCount, 0) // blocked by dedup | |
| 67 | }) | |
| 68 | ||
| 69 | test('Router - seenCount and clearSeen', (t) => { | |
| 70 | const router = new Router() | |
| 71 | router.routeToKeet('a', '1') | |
| 72 | router.routeToKeet('b', '2') | |
| 73 | t.is(router.seenCount, 2) | |
| 74 | ||
| 75 | router.clearSeen() | |
| 76 | t.is(router.seenCount, 0) | |
| 77 | }) |
| … | … | @@ -0,0 +1,81 @@ |
| 1 | import { createEnvelope, TYPES, PLATFORMS } from 'bridge-envelope' | |
| 2 | ||
| 3 | // Strip HTML tags (for Matrix formatted_body) | |
| 4 | function stripHtml (html) { | |
| 5 | return html | |
| 6 | .replace(/<br\s*\/?>/gi, '\n') | |
| 7 | .replace(/<\/p>\s*<p>/gi, '\n\n') | |
| 8 | .replace(/<[^>]+>/g, '') | |
| 9 | .replace(/&/g, '&') | |
| 10 | .replace(/</g, '<') | |
| 11 | .replace(/>/g, '>') | |
| 12 | .replace(/"/g, '"') | |
| 13 | .replace(/'/g, "'") | |
| 14 | .trim() | |
| 15 | } | |
| 16 | ||
| 17 | // Convert Matrix message to bridge envelope | |
| 18 | function matrixToEnvelope (sender, roomId, text) { | |
| 19 | return createEnvelope({ | |
| 20 | type: TYPES.TEXT, | |
| 21 | platform: PLATFORMS.MATRIX, | |
| 22 | author: sender, | |
| 23 | content: text, | |
| 24 | channel: roomId | |
| 25 | }) | |
| 26 | } | |
| 27 | ||
| 28 | // Format a Matrix message for sending to Keet | |
| 29 | function matrixToKeetText (sender, text) { | |
| 30 | return `[Matrix] <${sender}> ${text}` | |
| 31 | } | |
| 32 | ||
| 33 | // Convert a Keet message to text for Matrix, filtering out bridge echo | |
| 34 | function keetToMatrixText (keetMsg, bridgeUserId) { | |
| 35 | if (!keetMsg) return null | |
| 36 | ||
| 37 | // Skip messages from bridge identity (prevents echo) | |
| 38 | if (keetMsg.memberId === bridgeUserId) return null | |
| 39 | ||
| 40 | // Skip system messages | |
| 41 | if (keetMsg.system) return null | |
| 42 | ||
| 43 | const text = keetMsg.text || '' | |
| 44 | if (!text.trim()) return null | |
| 45 | ||
| 46 | // Skip messages already bridged from Matrix | |
| 47 | if (text.startsWith('[Matrix]')) return null | |
| 48 | ||
| 49 | const name = keetMsg.name || keetMsg.username || 'anonymous' | |
| 50 | return { sender: name, text } | |
| 51 | } | |
| 52 | ||
| 53 | // Format a Keet message for display in Matrix | |
| 54 | function keetToMatrixDisplayText (sender, text) { | |
| 55 | return `<${sender}> ${text}` | |
| 56 | } | |
| 57 | ||
| 58 | // Format envelope for Matrix posting | |
| 59 | function envelopeToMatrixText (envelope) { | |
| 60 | if (!envelope) return '' | |
| 61 | ||
| 62 | const platformName = { | |
| 63 | [PLATFORMS.KEET]: 'Keet', | |
| 64 | [PLATFORMS.IRC]: 'IRC', | |
| 65 | [PLATFORMS.DISCORD]: 'Discord', | |
| 66 | [PLATFORMS.MASTODON]: 'Mastodon', | |
| 67 | [PLATFORMS.EMAIL]: 'Email' | |
| 68 | } | |
| 69 | ||
| 70 | const source = platformName[envelope.platform] || 'P2P' | |
| 71 | return `[${source}] <${envelope.author}> ${envelope.content}` | |
| 72 | } | |
| 73 | ||
| 74 | export { | |
| 75 | matrixToEnvelope, | |
| 76 | matrixToKeetText, | |
| 77 | keetToMatrixText, | |
| 78 | keetToMatrixDisplayText, | |
| 79 | envelopeToMatrixText, | |
| 80 | stripHtml | |
| 81 | } |
| … | … | @@ -0,0 +1,168 @@ |
| 1 | import { MatrixClient } from './matrix-client.js' | |
| 2 | import { KeetClient } from './keet-client.js' | |
| 3 | import { Router } from './router.js' | |
| 4 | import { matrixToKeetText, keetToMatrixText } from './adapter.js' | |
| 5 | ||
| 6 | class Bridge { | |
| 7 | constructor (opts) { | |
| 8 | this.matrixHomeserver = opts.matrixHomeserver || 'matrix.org' | |
| 9 | this.matrixUser = opts.matrixUser | |
| 10 | this.matrixPassword = opts.matrixPassword | |
| 11 | this.matrixRoom = opts.matrixRoom | |
| 12 | ||
| 13 | this.keetRoom = opts.keetRoom | |
| 14 | this.keetHost = opts.keetHost || 'localhost' | |
| 15 | this.keetPort = opts.keetPort || 3117 | |
| 16 | ||
| 17 | this._matrix = null | |
| 18 | this._keet = null | |
| 19 | this._router = null | |
| 20 | this._running = false | |
| 21 | this._bridgeIdentity = null | |
| 22 | this._lastSeq = -1 | |
| 23 | this._pollTimer = null | |
| 24 | ||
| 25 | this.onLog = opts.onLog || console.log | |
| 26 | } | |
| 27 | ||
| 28 | async start () { | |
| 29 | this.onLog('[bridge] Starting Matrix <-> Keet bridge') | |
| 30 | this.onLog(`[bridge] Matrix: ${this.matrixHomeserver} room: ${this.matrixRoom}`) | |
| 31 | this.onLog(`[bridge] Keet room: ${this.keetRoom}`) | |
| 32 | ||
| 33 | // Initialize Keet client | |
| 34 | this._keet = new KeetClient({ | |
| 35 | host: this.keetHost, | |
| 36 | port: this.keetPort | |
| 37 | }) | |
| 38 | ||
| 39 | // Get bridge identity so we can filter our own messages | |
| 40 | try { | |
| 41 | const identity = await this._keet.identity() | |
| 42 | this._bridgeIdentity = identity.memberId || identity.id | |
| 43 | this.onLog(`[bridge] Keet identity: ${identity.displayName || identity.username || 'unknown'}`) | |
| 44 | } catch (err) { | |
| 45 | this.onLog(`[bridge] Warning: could not get Keet identity: ${err.message}`) | |
| 46 | } | |
| 47 | ||
| 48 | // Snapshot the latest message seq to distinguish history from new | |
| 49 | try { | |
| 50 | const msgs = await this._keet.messages(this.keetRoom, 1) | |
| 51 | if (msgs && msgs.length > 0) { | |
| 52 | this._lastSeq = msgs[msgs.length - 1].seq || 0 | |
| 53 | this.onLog(`[bridge] History cutoff: seq ${this._lastSeq}`) | |
| 54 | } | |
| 55 | } catch (err) { | |
| 56 | this.onLog(`[bridge] Warning: could not get message history: ${err.message}`) | |
| 57 | } | |
| 58 | ||
| 59 | // Initialize Matrix client | |
| 60 | this._matrix = new MatrixClient({ | |
| 61 | homeserver: this.matrixHomeserver, | |
| 62 | onMessage: (roomId, sender, text, eventId) => { | |
| 63 | // Only relay messages from the configured room | |
| 64 | if (roomId !== this.matrixRoom) return | |
| 65 | ||
| 66 | this.onLog(`[matrix->keet] <${sender}> ${text}`) | |
| 67 | const keetText = matrixToKeetText(sender, text) | |
| 68 | this._router.routeToKeet(sender, keetText) | |
| 69 | } | |
| 70 | }) | |
| 71 | ||
| 72 | // Login to Matrix | |
| 73 | this.onLog('[bridge] Logging in to Matrix...') | |
| 74 | const loginRes = await this._matrix.login(this.matrixHomeserver, this.matrixUser, this.matrixPassword) | |
| 75 | this.onLog(`[bridge] Matrix logged in as ${loginRes.user_id}`) | |
| 76 | ||
| 77 | // Join the Matrix room | |
| 78 | try { | |
| 79 | await this._matrix.joinRoom(this.matrixRoom) | |
| 80 | const roomName = await this._matrix.getRoomName(this.matrixRoom) | |
| 81 | this.onLog(`[bridge] Joined Matrix room: ${roomName || this.matrixRoom}`) | |
| 82 | } catch (err) { | |
| 83 | this.onLog(`[bridge] Warning: join room error: ${err.message}`) | |
| 84 | } | |
| 85 | ||
| 86 | // Initialize router with dedup | |
| 87 | this._router = new Router({ | |
| 88 | onKeetMessage: (author, text) => { | |
| 89 | this._keet.send(this.keetRoom, text).catch((err) => { | |
| 90 | this.onLog(`[bridge] Keet send error: ${err.message}`) | |
| 91 | }) | |
| 92 | }, | |
| 93 | onExternalMessage: (name, text) => { | |
| 94 | this._matrix.sendMessage(this.matrixRoom, text).catch((err) => { | |
| 95 | this.onLog(`[bridge] Matrix send error: ${err.message}`) | |
| 96 | }) | |
| 97 | } | |
| 98 | }) | |
| 99 | ||
| 100 | // Verify Keet daemon is reachable | |
| 101 | try { | |
| 102 | const status = await this._keet.status() | |
| 103 | this.onLog(`[bridge] Keet daemon online (${status.rooms || '?'} rooms)`) | |
| 104 | } catch (err) { | |
| 105 | this.onLog(`[bridge] Warning: Keet daemon unreachable: ${err.message}`) | |
| 106 | } | |
| 107 | ||
| 108 | // Start Matrix sync loop | |
| 109 | this._matrix._startSync() | |
| 110 | ||
| 111 | // Start polling Keet room for new messages | |
| 112 | this._startKeetPoll() | |
| 113 | ||
| 114 | this._running = true | |
| 115 | this.onLog('[bridge] Bridge active -- messages flowing bidirectionally') | |
| 116 | } | |
| 117 | ||
| 118 | _startKeetPoll () { | |
| 119 | this._pollTimer = setInterval(async () => { | |
| 120 | try { | |
| 121 | const msgs = await this._keet.messages(this.keetRoom, 10) | |
| 122 | if (!msgs || msgs.length === 0) return | |
| 123 | ||
| 124 | for (const msg of msgs) { | |
| 125 | if (msg.seq === undefined || msg.seq <= this._lastSeq) continue | |
| 126 | ||
| 127 | this._lastSeq = msg.seq | |
| 128 | ||
| 129 | const relayed = keetToMatrixText(msg, this._bridgeIdentity) | |
| 130 | if (!relayed) continue | |
| 131 | ||
| 132 | // Skip messages bridged from Matrix (prevents echo) | |
| 133 | if (msg.text && msg.text.startsWith('[Matrix]')) continue | |
| 134 | ||
| 135 | const displayText = `<${relayed.sender}> ${relayed.text}` | |
| 136 | this.onLog(`[keet->matrix] ${displayText}`) | |
| 137 | this._router.routeToExternal(relayed.sender, displayText) | |
| 138 | } | |
| 139 | } catch (err) { | |
| 140 | this.onLog(`[bridge] Keet poll error: ${err.message}`) | |
| 141 | } | |
| 142 | }, 2000) | |
| 143 | } | |
| 144 | ||
| 145 | async stop () { | |
| 146 | this.onLog('[bridge] Shutting down...') | |
| 147 | this._running = false | |
| 148 | ||
| 149 | if (this._pollTimer) clearInterval(this._pollTimer) | |
| 150 | if (this._matrix) this._matrix.stopSync() | |
| 151 | ||
| 152 | this.onLog('[bridge] Stopped') | |
| 153 | } | |
| 154 | ||
| 155 | get running () { | |
| 156 | return this._running | |
| 157 | } | |
| 158 | ||
| 159 | get stats () { | |
| 160 | return { | |
| 161 | running: this._running, | |
| 162 | matrixUser: this._matrix?.userId || null, | |
| 163 | deduped: this._router?.seenCount || 0 | |
| 164 | } | |
| 165 | } | |
| 166 | } | |
| 167 | ||
| 168 | export { Bridge } |
| … | … | @@ -0,0 +1,166 @@ |
| 1 | import http from 'http' | |
| 2 | import net from 'net' | |
| 3 | ||
| 4 | class KeetClient { | |
| 5 | constructor (opts = {}) { | |
| 6 | this.host = opts.host || 'localhost' | |
| 7 | this.port = opts.port || 3117 | |
| 8 | this._watching = false | |
| 9 | this._watchAbort = null | |
| 10 | } | |
| 11 | ||
| 12 | // Raw TCP request — bypasses Node's strict HTTP parser | |
| 13 | // Needed because keet-daemon sends duplicate Content-Length headers | |
| 14 | _rawRequest (method, path, body) { | |
| 15 | return new Promise((resolve, reject) => { | |
| 16 | const socket = net.connect(this.port, this.host) | |
| 17 | let payload = '' | |
| 18 | ||
| 19 | if (body) { | |
| 20 | payload = JSON.stringify(body) | |
| 21 | } | |
| 22 | ||
| 23 | socket.on('connect', () => { | |
| 24 | let req = `${method} ${path} HTTP/1.1\r\n` | |
| 25 | req += `Host: ${this.host}:${this.port}\r\n` | |
| 26 | if (payload) { | |
| 27 | req += `Content-Type: application/json\r\n` | |
| 28 | req += `Content-Length: ${Buffer.byteLength(payload)}\r\n` | |
| 29 | } | |
| 30 | req += `Connection: close\r\n` | |
| 31 | req += `\r\n` | |
| 32 | if (payload) req += payload | |
| 33 | socket.write(req) | |
| 34 | }) | |
| 35 | ||
| 36 | let data = '' | |
| 37 | socket.on('data', (chunk) => { data += chunk.toString() }) | |
| 38 | ||
| 39 | socket.on('end', () => { | |
| 40 | // Split headers from body | |
| 41 | const idx = data.indexOf('\r\n\r\n') | |
| 42 | if (idx === -1) return reject(new Error('No HTTP body')) | |
| 43 | const jsonBody = data.slice(idx + 4) | |
| 44 | ||
| 45 | try { | |
| 46 | const parsed = JSON.parse(jsonBody) | |
| 47 | if (!parsed.ok) reject(new Error(parsed.error || 'RPC error')) | |
| 48 | else resolve(parsed.data) | |
| 49 | } catch { | |
| 50 | reject(new Error('Invalid JSON: ' + jsonBody.slice(0, 200))) | |
| 51 | } | |
| 52 | }) | |
| 53 | ||
| 54 | socket.on('error', reject) | |
| 55 | socket.setTimeout(10000, () => { | |
| 56 | socket.destroy(new Error('Request timeout')) | |
| 57 | }) | |
| 58 | }) | |
| 59 | } | |
| 60 | ||
| 61 | rpc (method, params = {}) { | |
| 62 | return this._rawRequest('POST', '/rpc', { method, params }) | |
| 63 | } | |
| 64 | ||
| 65 | async send (roomId, text) { | |
| 66 | return this.rpc('send', { roomId, text }) | |
| 67 | } | |
| 68 | ||
| 69 | async messages (roomId, limit = 20) { | |
| 70 | return this._rawRequest('GET', `/rpc/messages/${roomId}?limit=${limit}`) | |
| 71 | } | |
| 72 | ||
| 73 | async rooms () { | |
| 74 | return this._rawRequest('GET', '/rpc/rooms') | |
| 75 | } | |
| 76 | ||
| 77 | async identity () { | |
| 78 | return this._rawRequest('GET', '/rpc/identity') | |
| 79 | } | |
| 80 | ||
| 81 | async status () { | |
| 82 | return this._rawRequest('GET', '/rpc/status') | |
| 83 | } | |
| 84 | ||
| 85 | async members (roomId) { | |
| 86 | return this._rawRequest('GET', `/rpc/members/${roomId}`) | |
| 87 | } | |
| 88 | ||
| 89 | watch (roomId, onMessage) { | |
| 90 | if (this._watching) return | |
| 91 | ||
| 92 | this._watching = true | |
| 93 | this._doWatch(roomId, onMessage) | |
| 94 | } | |
| 95 | ||
| 96 | _doWatch (roomId, onMessage) { | |
| 97 | if (!this._watching) return | |
| 98 | ||
| 99 | const req = http.request({ | |
| 100 | hostname: this.host, | |
| 101 | port: this.port, | |
| 102 | path: `/rpc/watch/${roomId}`, | |
| 103 | method: 'GET', | |
| 104 | headers: { Accept: 'text/event-stream' } | |
| 105 | }, (res) => { | |
| 106 | let buffer = '' | |
| 107 | ||
| 108 | res.on('data', (chunk) => { | |
| 109 | buffer += chunk.toString() | |
| 110 | const events = buffer.split('\n\n') | |
| 111 | buffer = events.pop() // keep incomplete tail | |
| 112 | ||
| 113 | for (const event of events) { | |
| 114 | if (!event.trim()) continue | |
| 115 | const lines = event.split('\n') | |
| 116 | let type = 'message' | |
| 117 | let data = null | |
| 118 | ||
| 119 | for (const line of lines) { | |
| 120 | if (line.startsWith('event: ')) type = line.slice(7) | |
| 121 | if (line.startsWith('data: ')) { | |
| 122 | try { | |
| 123 | data = JSON.parse(line.slice(6)) | |
| 124 | } catch {} | |
| 125 | } | |
| 126 | } | |
| 127 | ||
| 128 | if (type === 'message' && data) { | |
| 129 | onMessage(data) | |
| 130 | } | |
| 131 | } | |
| 132 | }) | |
| 133 | ||
| 134 | res.on('end', () => { | |
| 135 | if (this._watching) { | |
| 136 | setTimeout(() => this._doWatch(roomId, onMessage), 2000) | |
| 137 | } | |
| 138 | }) | |
| 139 | ||
| 140 | res.on('error', () => { | |
| 141 | if (this._watching) { | |
| 142 | setTimeout(() => this._doWatch(roomId, onMessage), 5000) | |
| 143 | } | |
| 144 | }) | |
| 145 | }) | |
| 146 | ||
| 147 | req.on('error', () => { | |
| 148 | if (this._watching) { | |
| 149 | setTimeout(() => this._doWatch(roomId, onMessage), 5000) | |
| 150 | } | |
| 151 | }) | |
| 152 | ||
| 153 | this._watchAbort = req | |
| 154 | req.end() | |
| 155 | } | |
| 156 | ||
| 157 | stopWatch () { | |
| 158 | this._watching = false | |
| 159 | if (this._watchAbort) { | |
| 160 | this._watchAbort.destroy() | |
| 161 | this._watchAbort = null | |
| 162 | } | |
| 163 | } | |
| 164 | } | |
| 165 | ||
| 166 | export { KeetClient } |
| … | … | @@ -0,0 +1,284 @@ |
| 1 | import https from 'https' | |
| 2 | import http from 'http' | |
| 3 | ||
| 4 | class MatrixClient { | |
| 5 | constructor (opts = {}) { | |
| 6 | this.homeserver = opts.homeserver || 'matrix.org' | |
| 7 | this.userId = opts.userId || null | |
| 8 | this.accessToken = opts.accessToken || null | |
| 9 | ||
| 10 | this._onMessage = opts.onMessage || null | |
| 11 | this._onJoin = opts.onJoin || null | |
| 12 | this._onLeave = opts.onLeave || null | |
| 13 | ||
| 14 | this._nextBatch = null | |
| 15 | this._syncing = false | |
| 16 | this._txnId = 0 | |
| 17 | this._displayNameCache = new Map() | |
| 18 | this._seenEvents = new Set() | |
| 19 | this._maxSeenEvents = 5000 | |
| 20 | } | |
| 21 | ||
| 22 | // Authenticate with username/password and get an access token | |
| 23 | async login (homeserver, user, password) { | |
| 24 | if (homeserver) this.homeserver = homeserver | |
| 25 | ||
| 26 | const body = { | |
| 27 | type: 'm.login.password', | |
| 28 | identifier: { | |
| 29 | type: 'm.id.user', | |
| 30 | user | |
| 31 | }, | |
| 32 | password | |
| 33 | } | |
| 34 | ||
| 35 | const res = await this._request('POST', '/_matrix/client/v3/login', body) | |
| 36 | this.accessToken = res.access_token | |
| 37 | this.userId = res.user_id | |
| 38 | return res | |
| 39 | } | |
| 40 | ||
| 41 | // Long-poll the /sync endpoint | |
| 42 | async sync (since, timeout) { | |
| 43 | let path = '/_matrix/client/v3/sync?timeout=' + (timeout || 30000) | |
| 44 | if (since) path += '&since=' + encodeURIComponent(since) | |
| 45 | ||
| 46 | // Filter to only get room messages (reduce payload) | |
| 47 | const filter = JSON.stringify({ | |
| 48 | room: { | |
| 49 | timeline: { limit: 20 }, | |
| 50 | state: { lazy_load_members: true } | |
| 51 | }, | |
| 52 | presence: { types: [] }, | |
| 53 | account_data: { types: [] } | |
| 54 | }) | |
| 55 | path += '&filter=' + encodeURIComponent(filter) | |
| 56 | ||
| 57 | return this._request('GET', path, null, (timeout || 30000) + 15000) | |
| 58 | } | |
| 59 | ||
| 60 | // Send a text message to a room | |
| 61 | async sendMessage (roomId, text) { | |
| 62 | const txnId = 'm' + Date.now() + '.' + (this._txnId++) | |
| 63 | const encodedRoom = encodeURIComponent(roomId) | |
| 64 | const path = `/_matrix/client/v3/rooms/${encodedRoom}/send/m.room.message/${txnId}` | |
| 65 | ||
| 66 | return this._request('PUT', path, { | |
| 67 | msgtype: 'm.text', | |
| 68 | body: text | |
| 69 | }) | |
| 70 | } | |
| 71 | ||
| 72 | // Join a room by ID or alias | |
| 73 | async joinRoom (roomIdOrAlias) { | |
| 74 | const encoded = encodeURIComponent(roomIdOrAlias) | |
| 75 | return this._request('POST', `/_matrix/client/v3/join/${encoded}`, {}) | |
| 76 | } | |
| 77 | ||
| 78 | // Get room display name | |
| 79 | async getRoomName (roomId) { | |
| 80 | const encoded = encodeURIComponent(roomId) | |
| 81 | try { | |
| 82 | const res = await this._request('GET', `/_matrix/client/v3/rooms/${encoded}/state/m.room.name`) | |
| 83 | return res.name || null | |
| 84 | } catch { | |
| 85 | return null | |
| 86 | } | |
| 87 | } | |
| 88 | ||
| 89 | // Get display name for a user (cached) | |
| 90 | async getDisplayName (userId) { | |
| 91 | if (this._displayNameCache.has(userId)) { | |
| 92 | return this._displayNameCache.get(userId) | |
| 93 | } | |
| 94 | ||
| 95 | try { | |
| 96 | const encoded = encodeURIComponent(userId) | |
| 97 | const res = await this._request('GET', `/_matrix/client/v3/profile/${encoded}/displayname`) | |
| 98 | const name = res.displayname || userId | |
| 99 | this._displayNameCache.set(userId, name) | |
| 100 | return name | |
| 101 | } catch { | |
| 102 | this._displayNameCache.set(userId, userId) | |
| 103 | return userId | |
| 104 | } | |
| 105 | } | |
| 106 | ||
| 107 | // Start the long-polling sync loop | |
| 108 | _startSync () { | |
| 109 | if (this._syncing) return | |
| 110 | this._syncing = true | |
| 111 | this._syncLoop() | |
| 112 | } | |
| 113 | ||
| 114 | async _syncLoop () { | |
| 115 | let backoff = 1000 | |
| 116 | ||
| 117 | while (this._syncing) { | |
| 118 | try { | |
| 119 | const res = await this.sync(this._nextBatch, 30000) | |
| 120 | this._nextBatch = res.next_batch | |
| 121 | backoff = 1000 // reset on success | |
| 122 | ||
| 123 | // Process joined rooms | |
| 124 | const joined = res.rooms?.join | |
| 125 | if (joined) { | |
| 126 | for (const [roomId, roomData] of Object.entries(joined)) { | |
| 127 | const events = roomData.timeline?.events || [] | |
| 128 | for (const event of events) { | |
| 129 | await this._handleEvent(roomId, event) | |
| 130 | } | |
| 131 | } | |
| 132 | } | |
| 133 | ||
| 134 | // Process invites (auto-join if callback present) | |
| 135 | const invited = res.rooms?.invite | |
| 136 | if (invited && this._onJoin) { | |
| 137 | for (const roomId of Object.keys(invited)) { | |
| 138 | this._onJoin(roomId) | |
| 139 | } | |
| 140 | } | |
| 141 | ||
| 142 | // Process leaves | |
| 143 | const left = res.rooms?.leave | |
| 144 | if (left && this._onLeave) { | |
| 145 | for (const roomId of Object.keys(left)) { | |
| 146 | this._onLeave(roomId) | |
| 147 | } | |
| 148 | } | |
| 149 | } catch (err) { | |
| 150 | if (!this._syncing) break | |
| 151 | ||
| 152 | // Exponential backoff on error | |
| 153 | await sleep(backoff) | |
| 154 | backoff = Math.min(backoff * 2, 30000) | |
| 155 | } | |
| 156 | } | |
| 157 | } | |
| 158 | ||
| 159 | async _handleEvent (roomId, event) { | |
| 160 | if (event.type !== 'm.room.message') return | |
| 161 | if (!event.content) return | |
| 162 | ||
| 163 | // Skip own messages | |
| 164 | if (event.sender === this.userId) return | |
| 165 | ||
| 166 | // Dedup by event_id | |
| 167 | if (this._seenEvents.has(event.event_id)) return | |
| 168 | ||
| 169 | // Prune cache when it gets too large | |
| 170 | if (this._seenEvents.size > this._maxSeenEvents) { | |
| 171 | const arr = [...this._seenEvents] | |
| 172 | this._seenEvents = new Set(arr.slice(-Math.floor(this._maxSeenEvents / 2))) | |
| 173 | } | |
| 174 | this._seenEvents.add(event.event_id) | |
| 175 | ||
| 176 | // Extract text — prefer plain body over formatted | |
| 177 | let text = event.content.body || '' | |
| 178 | ||
| 179 | // If formatted_body exists and body is the same, use body (plain text) | |
| 180 | // formatted_body is HTML, body is the plain text fallback | |
| 181 | if (event.content.format === 'org.matrix.custom.html' && event.content.formatted_body) { | |
| 182 | // body already contains the plain text version, use it as-is | |
| 183 | // Only strip HTML if for some reason body is missing | |
| 184 | if (!text) { | |
| 185 | text = stripHtml(event.content.formatted_body) | |
| 186 | } | |
| 187 | } | |
| 188 | ||
| 189 | // Get display name | |
| 190 | const sender = await this.getDisplayName(event.sender) | |
| 191 | ||
| 192 | if (this._onMessage) { | |
| 193 | this._onMessage(roomId, sender, text, event.event_id) | |
| 194 | } | |
| 195 | } | |
| 196 | ||
| 197 | // Stop the sync loop | |
| 198 | stopSync () { | |
| 199 | this._syncing = false | |
| 200 | } | |
| 201 | ||
| 202 | // Generic HTTPS request to the homeserver | |
| 203 | _request (method, path, body, timeout) { | |
| 204 | return new Promise((resolve, reject) => { | |
| 205 | const isHttps = !this.homeserver.includes('://') || this.homeserver.startsWith('https') | |
| 206 | const mod = isHttps ? https : http | |
| 207 | ||
| 208 | let hostname = this.homeserver | |
| 209 | .replace('https://', '') | |
| 210 | .replace('http://', '') | |
| 211 | ||
| 212 | // Extract port if present | |
| 213 | let port = isHttps ? 443 : 80 | |
| 214 | const colonIdx = hostname.indexOf(':') | |
| 215 | if (colonIdx !== -1) { | |
| 216 | port = parseInt(hostname.slice(colonIdx + 1), 10) | |
| 217 | hostname = hostname.slice(0, colonIdx) | |
| 218 | } | |
| 219 | ||
| 220 | const headers = {} | |
| 221 | if (this.accessToken) { | |
| 222 | headers.Authorization = 'Bearer ' + this.accessToken | |
| 223 | } | |
| 224 | ||
| 225 | let payload = null | |
| 226 | if (body !== null && body !== undefined) { | |
| 227 | payload = JSON.stringify(body) | |
| 228 | headers['Content-Type'] = 'application/json' | |
| 229 | headers['Content-Length'] = Buffer.byteLength(payload) | |
| 230 | } | |
| 231 | ||
| 232 | const req = mod.request({ | |
| 233 | hostname, | |
| 234 | port, | |
| 235 | path, | |
| 236 | method, | |
| 237 | headers | |
| 238 | }, (res) => { | |
| 239 | let data = '' | |
| 240 | res.on('data', (chunk) => { data += chunk.toString() }) | |
| 241 | res.on('end', () => { | |
| 242 | try { | |
| 243 | const parsed = JSON.parse(data) | |
| 244 | if (parsed.errcode) { | |
| 245 | reject(new Error(`Matrix error: ${parsed.errcode} - ${parsed.error}`)) | |
| 246 | } else { | |
| 247 | resolve(parsed) | |
| 248 | } | |
| 249 | } catch { | |
| 250 | reject(new Error('Invalid JSON response: ' + data.slice(0, 200))) | |
| 251 | } | |
| 252 | }) | |
| 253 | }) | |
| 254 | ||
| 255 | req.on('error', reject) | |
| 256 | req.setTimeout(timeout || 30000, () => { | |
| 257 | req.destroy(new Error('Request timeout')) | |
| 258 | }) | |
| 259 | ||
| 260 | if (payload) req.write(payload) | |
| 261 | req.end() | |
| 262 | }) | |
| 263 | } | |
| 264 | } | |
| 265 | ||
| 266 | // Strip HTML tags (for formatted_body fallback) | |
| 267 | function stripHtml (html) { | |
| 268 | return html | |
| 269 | .replace(/<br\s*\/?>/gi, '\n') | |
| 270 | .replace(/<\/p>\s*<p>/gi, '\n\n') | |
| 271 | .replace(/<[^>]+>/g, '') | |
| 272 | .replace(/&/g, '&') | |
| 273 | .replace(/</g, '<') | |
| 274 | .replace(/>/g, '>') | |
| 275 | .replace(/"/g, '"') | |
| 276 | .replace(/'/g, "'") | |
| 277 | .trim() | |
| 278 | } | |
| 279 | ||
| 280 | function sleep (ms) { | |
| 281 | return new Promise(resolve => setTimeout(resolve, ms)) | |
| 282 | } | |
| 283 | ||
| 284 | export { MatrixClient, stripHtml } |
| … | … | @@ -0,0 +1,69 @@ |
| 1 | import { createHash } from 'crypto' | |
| 2 | ||
| 3 | class Router { | |
| 4 | constructor (opts = {}) { | |
| 5 | this._seen = new Map() | |
| 6 | this._maxSeen = opts.maxSeen || 10000 | |
| 7 | this._ttl = opts.ttl || 60000 | |
| 8 | this._onExternalMessage = opts.onExternalMessage || null | |
| 9 | this._onKeetMessage = opts.onKeetMessage || null | |
| 10 | } | |
| 11 | ||
| 12 | _hash (text, author) { | |
| 13 | return createHash('sha256') | |
| 14 | .update(author || '') | |
| 15 | .update(text || '') | |
| 16 | .digest('hex') | |
| 17 | .slice(0, 16) | |
| 18 | } | |
| 19 | ||
| 20 | _isDuplicate (hash) { | |
| 21 | const now = Date.now() | |
| 22 | ||
| 23 | if (this._seen.size > this._maxSeen) { | |
| 24 | for (const [k, ts] of this._seen) { | |
| 25 | if (now - ts > this._ttl) this._seen.delete(k) | |
| 26 | } | |
| 27 | } | |
| 28 | ||
| 29 | if (this._seen.has(hash)) { | |
| 30 | const ts = this._seen.get(hash) | |
| 31 | if (now - ts < this._ttl) return true | |
| 32 | } | |
| 33 | ||
| 34 | this._seen.set(hash, now) | |
| 35 | return false | |
| 36 | } | |
| 37 | ||
| 38 | // External platform message -> relay to Keet | |
| 39 | routeToKeet (author, text) { | |
| 40 | const hash = this._hash(text, author) | |
| 41 | if (this._isDuplicate(hash)) return false | |
| 42 | ||
| 43 | if (this._onKeetMessage) { | |
| 44 | this._onKeetMessage(author, text) | |
| 45 | } | |
| 46 | return true | |
| 47 | } | |
| 48 | ||
| 49 | // Keet message -> relay to external platform | |
| 50 | routeToExternal (name, text) { | |
| 51 | const hash = this._hash(text, name) | |
| 52 | if (this._isDuplicate(hash)) return false | |
| 53 | ||
| 54 | if (this._onExternalMessage) { | |
| 55 | this._onExternalMessage(name, text) | |
| 56 | } | |
| 57 | return true | |
| 58 | } | |
| 59 | ||
| 60 | get seenCount () { | |
| 61 | return this._seen.size | |
| 62 | } | |
| 63 | ||
| 64 | clearSeen () { | |
| 65 | this._seen.clear() | |
| 66 | } | |
| 67 | } | |
| 68 | ||
| 69 | export { Router } |
| … | … | @@ -0,0 +1,875 @@ |
| 1 | { | |
| 2 | "name": "bridge-matrix", | |
| 3 | "version": "1.0.0", | |
| 4 | "lockfileVersion": 3, | |
| 5 | "requires": true, | |
| 6 | "packages": { | |
| 7 | "": { | |
| 8 | "name": "bridge-matrix", | |
| 9 | "version": "1.0.0", | |
| 10 | "license": "MIT", | |
| 11 | "dependencies": { | |
| 12 | "b4a": "^1.0.0", | |
| 13 | "bridge-envelope": "file:../bridge-envelope" | |
| 14 | }, | |
| 15 | "devDependencies": { | |
| 16 | "brittle": "^3.0.0" | |
| 17 | } | |
| 18 | }, | |
| 19 | "../bridge-envelope": { | |
| 20 | "version": "1.0.0", | |
| 21 | "license": "MIT", | |
| 22 | "dependencies": { | |
| 23 | "b4a": "^1.0.0", | |
| 24 | "compact-encoding": "^2.0.0" | |
| 25 | }, | |
| 26 | "devDependencies": { | |
| 27 | "brittle": "^3.0.0" | |
| 28 | } | |
| 29 | }, | |
| 30 | "node_modules/@jridgewell/resolve-uri": { | |
| 31 | "version": "3.1.2", | |
| 32 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", | |
| 33 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", | |
| 34 | "dev": true, | |
| 35 | "license": "MIT", | |
| 36 | "engines": { | |
| 37 | "node": ">=6.0.0" | |
| 38 | } | |
| 39 | }, | |
| 40 | "node_modules/@jridgewell/sourcemap-codec": { | |
| 41 | "version": "1.5.5", | |
| 42 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", | |
| 43 | "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", | |
| 44 | "dev": true, | |
| 45 | "license": "MIT" | |
| 46 | }, | |
| 47 | "node_modules/@jridgewell/trace-mapping": { | |
| 48 | "version": "0.3.31", | |
| 49 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", | |
| 50 | "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", | |
| 51 | "dev": true, | |
| 52 | "license": "MIT", | |
| 53 | "dependencies": { | |
| 54 | "@jridgewell/resolve-uri": "^3.1.0", | |
| 55 | "@jridgewell/sourcemap-codec": "^1.4.14" | |
| 56 | } | |
| 57 | }, | |
| 58 | "node_modules/@types/istanbul-lib-coverage": { | |
| 59 | "version": "2.0.6", | |
| 60 | "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", | |
| 61 | "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", | |
| 62 | "dev": true, | |
| 63 | "license": "MIT" | |
| 64 | }, | |
| 65 | "node_modules/b4a": { | |
| 66 | "version": "1.7.4", | |
| 67 | "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.4.tgz", | |
| 68 | "integrity": "sha512-u20zJLDaSWpxaZ+zaAkEIB2dZZ1o+DF4T/MRbmsvGp9nletHOyiai19OzX1fF8xUBYsO1bPXxODvcd0978pnug==", | |
| 69 | "license": "Apache-2.0", | |
| 70 | "peerDependencies": { | |
| 71 | "react-native-b4a": "*" | |
| 72 | }, | |
| 73 | "peerDependenciesMeta": { | |
| 74 | "react-native-b4a": { | |
| 75 | "optional": true | |
| 76 | } | |
| 77 | } | |
| 78 | }, | |
| 79 | "node_modules/bare-addon-resolve": { | |
| 80 | "version": "1.10.0", | |
| 81 | "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.0.tgz", | |
| 82 | "integrity": "sha512-sSd0jieRJlDaODOzj0oe0RjFVC1QI0ZIjGIdPkbrTXsdVVtENg14c+lHHAhHwmWCZ2nQlMhy8jA3Y5LYPc/isA==", | |
| 83 | "dev": true, | |
| 84 | "license": "Apache-2.0", | |
| 85 | "dependencies": { | |
| 86 | "bare-module-resolve": "^1.10.0", | |
| 87 | "bare-semver": "^1.0.0" | |
| 88 | }, | |
| 89 | "peerDependencies": { | |
| 90 | "bare-url": "*" | |
| 91 | }, | |
| 92 | "peerDependenciesMeta": { | |
| 93 | "bare-url": { | |
| 94 | "optional": true | |
| 95 | } | |
| 96 | } | |
| 97 | }, | |
| 98 | "node_modules/bare-ansi-escapes": { | |
| 99 | "version": "2.2.3", | |
| 100 | "resolved": "https://registry.npmjs.org/bare-ansi-escapes/-/bare-ansi-escapes-2.2.3.tgz", | |
| 101 | "integrity": "sha512-02ES4/E2RbrtZSnHJ9LntBhYkLA6lPpSEeP8iqS3MccBIVhVBlEmruF1I7HZqx5Q8aiTeYfQVeqmrU9YO2yYoQ==", | |
| 102 | "dev": true, | |
| 103 | "license": "Apache-2.0", | |
| 104 | "dependencies": { | |
| 105 | "bare-stream": "^2.6.5" | |
| 106 | }, | |
| 107 | "peerDependencies": { | |
| 108 | "bare-buffer": "*" | |
| 109 | }, | |
| 110 | "peerDependenciesMeta": { | |
| 111 | "bare-buffer": { | |
| 112 | "optional": true | |
| 113 | } | |
| 114 | } | |
| 115 | }, | |
| 116 | "node_modules/bare-assert": { | |
| 117 | "version": "1.2.0", | |
| 118 | "resolved": "https://registry.npmjs.org/bare-assert/-/bare-assert-1.2.0.tgz", | |
| 119 | "integrity": "sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==", | |
| 120 | "dev": true, | |
| 121 | "license": "Apache-2.0", | |
| 122 | "dependencies": { | |
| 123 | "bare-inspect": "^3.1.2" | |
| 124 | } | |
| 125 | }, | |
| 126 | "node_modules/bare-bundle": { | |
| 127 | "version": "1.10.0", | |
| 128 | "resolved": "https://registry.npmjs.org/bare-bundle/-/bare-bundle-1.10.0.tgz", | |
| 129 | "integrity": "sha512-4LVlnJAHr00Hh6Vu6ZUJS38rcEtJT3b3vChXSsBsJ2mk1TN0lQ+gzd+Dw5L0aV7uqDZv84smuwW+O02X7PfDlw==", | |
| 130 | "dev": true, | |
| 131 | "license": "Apache-2.0", | |
| 132 | "peerDependencies": { | |
| 133 | "bare-buffer": "*", | |
| 134 | "bare-url": "*" | |
| 135 | }, | |
| 136 | "peerDependenciesMeta": { | |
| 137 | "bare-buffer": { | |
| 138 | "optional": true | |
| 139 | }, | |
| 140 | "bare-url": { | |
| 141 | "optional": true | |
| 142 | } | |
| 143 | } | |
| 144 | }, | |
| 145 | "node_modules/bare-cov": { | |
| 146 | "version": "1.2.1", | |
| 147 | "resolved": "https://registry.npmjs.org/bare-cov/-/bare-cov-1.2.1.tgz", | |
| 148 | "integrity": "sha512-SHNqb/WG9udUEj8m5qGSehoUQ9o0Xzv8nrf4HIdmG+KNU/XdhPLhTP5VPrHnSAMAXhAzEB4Q+2IYZ0x9qPzN1g==", | |
| 149 | "dev": true, | |
| 150 | "license": "Apache-2.0", | |
| 151 | "dependencies": { | |
| 152 | "bare-fs": "^4.1.2", | |
| 153 | "bare-inspector": "^4.0.1", | |
| 154 | "bare-path": "^3.0.0", | |
| 155 | "bare-process": "^4.2.1", | |
| 156 | "bare-url": "^2.1.5", | |
| 157 | "bare-v8-to-istanbul": "^1.0.2", | |
| 158 | "picomatch": "^4.0.2", | |
| 159 | "which-runtime": "^1.2.1" | |
| 160 | } | |
| 161 | }, | |
| 162 | "node_modules/bare-crypto": { | |
| 163 | "version": "1.13.0", | |
| 164 | "resolved": "https://registry.npmjs.org/bare-crypto/-/bare-crypto-1.13.0.tgz", | |
| 165 | "integrity": "sha512-RQl13yD+YTACWUZHMMck0C6LNjXgGgRyhVC557ag0xIrkxXf4IQwpnc8WOB58f4k5kSzKhIjy05oW3HUBrFpSQ==", | |
| 166 | "dev": true, | |
| 167 | "license": "Apache-2.0", | |
| 168 | "dependencies": { | |
| 169 | "bare-stream": "^2.6.3" | |
| 170 | }, | |
| 171 | "peerDependencies": { | |
| 172 | "bare-buffer": "*" | |
| 173 | }, | |
| 174 | "peerDependenciesMeta": { | |
| 175 | "bare-buffer": { | |
| 176 | "optional": true | |
| 177 | } | |
| 178 | } | |
| 179 | }, | |
| 180 | "node_modules/bare-debug-log": { | |
| 181 | "version": "2.0.0", | |
| 182 | "resolved": "https://registry.npmjs.org/bare-debug-log/-/bare-debug-log-2.0.0.tgz", | |
| 183 | "integrity": "sha512-Vi42PkMQsNV9PUpx2Gl1hikshx5O9FzMJ6o9Nnopseg7qLBBK7Nl31d0RHcfwLEAfmcPApytpc0ZFfq68u22FQ==", | |
| 184 | "dev": true, | |
| 185 | "license": "Apache-2.0", | |
| 186 | "dependencies": { | |
| 187 | "bare-os": "^3.0.1" | |
| 188 | } | |
| 189 | }, | |
| 190 | "node_modules/bare-dns": { | |
| 191 | "version": "2.1.4", | |
| 192 | "resolved": "https://registry.npmjs.org/bare-dns/-/bare-dns-2.1.4.tgz", | |
| 193 | "integrity": "sha512-abwjHmpWqSRNB7V5615QxPH92L71AVzFm/kKTs8VYiNTAi2xVdonpv0BjJ0hwXLwomoW+xsSOPjW6PZPO14asg==", | |
| 194 | "dev": true, | |
| 195 | "license": "Apache-2.0", | |
| 196 | "engines": { | |
| 197 | "bare": ">=1.7.0" | |
| 198 | } | |
| 199 | }, | |
| 200 | "node_modules/bare-encoding": { | |
| 201 | "version": "1.0.3", | |
| 202 | "resolved": "https://registry.npmjs.org/bare-encoding/-/bare-encoding-1.0.3.tgz", | |
| 203 | "integrity": "sha512-Kqf+t/azs13lUeyK4Tb7ha4wdLRXKWCXQ8w1rVmt7KtoPCPdHD/Xwt7LBIsCSwwGglrcmblo5VOLa5avkJqULA==", | |
| 204 | "dev": true, | |
| 205 | "license": "Apache-2.0", | |
| 206 | "peerDependencies": { | |
| 207 | "bare-buffer": "*" | |
| 208 | }, | |
| 209 | "peerDependenciesMeta": { | |
| 210 | "bare-buffer": { | |
| 211 | "optional": true | |
| 212 | } | |
| 213 | } | |
| 214 | }, | |
| 215 | "node_modules/bare-env": { | |
| 216 | "version": "3.0.0", | |
| 217 | "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.0.tgz", | |
| 218 | "integrity": "sha512-0u964P5ZLAxTi+lW4Kjp7YRJQ5gZr9ycYOtjLxsSrupgMz3sn5Z9n4SH/JIifHwvadsf1brA2JAjP+9IOWwTiw==", | |
| 219 | "dev": true, | |
| 220 | "license": "Apache-2.0", | |
| 221 | "dependencies": { | |
| 222 | "bare-os": "^3.0.1" | |
| 223 | } | |
| 224 | }, | |
| 225 | "node_modules/bare-events": { | |
| 226 | "version": "2.8.2", | |
| 227 | "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", | |
| 228 | "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", | |
| 229 | "dev": true, | |
| 230 | "license": "Apache-2.0", | |
| 231 | "peerDependencies": { | |
| 232 | "bare-abort-controller": "*" | |
| 233 | }, | |
| 234 | "peerDependenciesMeta": { | |
| 235 | "bare-abort-controller": { | |
| 236 | "optional": true | |
| 237 | } | |
| 238 | } | |
| 239 | }, | |
| 240 | "node_modules/bare-format": { | |
| 241 | "version": "1.0.1", | |
| 242 | "resolved": "https://registry.npmjs.org/bare-format/-/bare-format-1.0.1.tgz", | |
| 243 | "integrity": "sha512-1oS+LZrWK6tnYnvNSHDGljc2MPunRxwhpFriuCgzNF+oklrnwmBKD91tS0yt+jpl2j3UgcSDzBIMiVTvLs9A8w==", | |
| 244 | "dev": true, | |
| 245 | "license": "Apache-2.0", | |
| 246 | "dependencies": { | |
| 247 | "bare-inspect": "^3.0.0" | |
| 248 | } | |
| 249 | }, | |
| 250 | "node_modules/bare-fs": { | |
| 251 | "version": "4.5.4", | |
| 252 | "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.4.tgz", | |
| 253 | "integrity": "sha512-POK4oplfA7P7gqvetNmCs4CNtm9fNsx+IAh7jH7GgU0OJdge2rso0R20TNWVq6VoWcCvsTdlNDaleLHGaKx8CA==", | |
| 254 | "dev": true, | |
| 255 | "license": "Apache-2.0", | |
| 256 | "dependencies": { | |
| 257 | "bare-events": "^2.5.4", | |
| 258 | "bare-path": "^3.0.0", | |
| 259 | "bare-stream": "^2.6.4", | |
| 260 | "bare-url": "^2.2.2", | |
| 261 | "fast-fifo": "^1.3.2" | |
| 262 | }, | |
| 263 | "engines": { | |
| 264 | "bare": ">=1.16.0" | |
| 265 | }, | |
| 266 | "peerDependencies": { | |
| 267 | "bare-buffer": "*" | |
| 268 | }, | |
| 269 | "peerDependenciesMeta": { | |
| 270 | "bare-buffer": { | |
| 271 | "optional": true | |
| 272 | } | |
| 273 | } | |
| 274 | }, | |
| 275 | "node_modules/bare-hrtime": { | |
| 276 | "version": "2.1.1", | |
| 277 | "resolved": "https://registry.npmjs.org/bare-hrtime/-/bare-hrtime-2.1.1.tgz", | |
| 278 | "integrity": "sha512-VMb3tHo05gsnbu3OXTmkDiwTjMlOsbQmKoysKqKEyR09m77TuDrYFbj3Q5GGk10dAKsUHrnXmwCaeJqzVpB5ZA==", | |
| 279 | "dev": true, | |
| 280 | "license": "Apache-2.0" | |
| 281 | }, | |
| 282 | "node_modules/bare-http-parser": { | |
| 283 | "version": "1.0.1", | |
| 284 | "resolved": "https://registry.npmjs.org/bare-http-parser/-/bare-http-parser-1.0.1.tgz", | |
| 285 | "integrity": "sha512-A3LTDTcELcmNJ3g5liIaS038v/BQxOhA9cjhBESn7eoV7QCuMoIRBKLDadDe08flxyLbxI2f+1l2MZ/5+HnKPA==", | |
| 286 | "dev": true, | |
| 287 | "license": "Apache-2.0" | |
| 288 | }, | |
| 289 | "node_modules/bare-http1": { | |
| 290 | "version": "4.2.3", | |
| 291 | "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.2.3.tgz", | |
| 292 | "integrity": "sha512-zyvHF3xzvBJNpuw0QydE9etTn2AyC/TRVDWTo8Ed3LQEEGaFOkv6KHhaJPlf4eWEeTekk41Fowie+fTsXoNxkA==", | |
| 293 | "dev": true, | |
| 294 | "license": "Apache-2.0", | |
| 295 | "dependencies": { | |
| 296 | "bare-events": "^2.6.0", | |
| 297 | "bare-http-parser": "^1.0.0", | |
| 298 | "bare-stream": "^2.3.0", | |
| 299 | "bare-tcp": "^2.2.0" | |
| 300 | }, | |
| 301 | "peerDependencies": { | |
| 302 | "bare-buffer": "*", | |
| 303 | "bare-url": "*" | |
| 304 | }, | |
| 305 | "peerDependenciesMeta": { | |
| 306 | "bare-buffer": { | |
| 307 | "optional": true | |
| 308 | }, | |
| 309 | "bare-url": { | |
| 310 | "optional": true | |
| 311 | } | |
| 312 | } | |
| 313 | }, | |
| 314 | "node_modules/bare-https": { | |
| 315 | "version": "2.1.2", | |
| 316 | "resolved": "https://registry.npmjs.org/bare-https/-/bare-https-2.1.2.tgz", | |
| 317 | "integrity": "sha512-Q+TTydUDsuKQJvh8dX2dvOXCR9fM3xR5TBmKaFrs5p7Lj7XbKX7v4vIUJ36H0SXg2xCOQxXKIbjwrLg5tfJNYg==", | |
| 318 | "dev": true, | |
| 319 | "license": "Apache-2.0", | |
| 320 | "dependencies": { | |
| 321 | "bare-http1": "^4.0.0", | |
| 322 | "bare-tcp": "^2.2.0", | |
| 323 | "bare-tls": "^2.0.0" | |
| 324 | } | |
| 325 | }, | |
| 326 | "node_modules/bare-inspect": { | |
| 327 | "version": "3.1.4", | |
| 328 | "resolved": "https://registry.npmjs.org/bare-inspect/-/bare-inspect-3.1.4.tgz", | |
| 329 | "integrity": "sha512-jfW5KRA84o3REpI6Vr4nbvMn+hqVAw8GU1mMdRwUsY5yJovQamxYeKGVKGqdzs+8ZbG4jRzGUXP/3Ji/DnqfPg==", | |
| 330 | "dev": true, | |
| 331 | "license": "Apache-2.0", | |
| 332 | "dependencies": { | |
| 333 | "bare-ansi-escapes": "^2.1.0", | |
| 334 | "bare-type": "^1.0.0" | |
| 335 | }, | |
| 336 | "engines": { | |
| 337 | "bare": ">=1.18.0" | |
| 338 | } | |
| 339 | }, | |
| 340 | "node_modules/bare-inspector": { | |
| 341 | "version": "4.0.3", | |
| 342 | "resolved": "https://registry.npmjs.org/bare-inspector/-/bare-inspector-4.0.3.tgz", | |
| 343 | "integrity": "sha512-yYk6+7tTVKJKHOja5+QBn989ETv8oVsmwoH0nCM+QYC6w5MC03GC4aQNK+lipAZSz1EaAj42WZ8Wewjpy0L9TA==", | |
| 344 | "dev": true, | |
| 345 | "license": "Apache-2.0", | |
| 346 | "dependencies": { | |
| 347 | "bare-events": "^2.1.0", | |
| 348 | "bare-http1": "^4.0.0", | |
| 349 | "bare-stream": "^2.0.0", | |
| 350 | "bare-url": "^2.0.0", | |
| 351 | "bare-ws": "^2.0.0" | |
| 352 | }, | |
| 353 | "engines": { | |
| 354 | "bare": ">=1.2.0" | |
| 355 | }, | |
| 356 | "peerDependencies": { | |
| 357 | "bare-tcp": "*" | |
| 358 | }, | |
| 359 | "peerDependenciesMeta": { | |
| 360 | "bare-tcp": { | |
| 361 | "optional": true | |
| 362 | } | |
| 363 | } | |
| 364 | }, | |
| 365 | "node_modules/bare-module": { | |
| 366 | "version": "6.1.3", | |
| 367 | "resolved": "https://registry.npmjs.org/bare-module/-/bare-module-6.1.3.tgz", | |
| 368 | "integrity": "sha512-5XWsVHsvtWMH4tK4DQWgpNTV0t/sg3ZrAaQLIxrwjrS5+u8Q9vEgc/zQ4QaDPWDse/y/5h+d+YG1Q0JfSMt0zA==", | |
| 369 | "dev": true, | |
| 370 | "license": "Apache-2.0", | |
| 371 | "dependencies": { | |
| 372 | "bare-bundle": "^1.3.0", | |
| 373 | "bare-module-lexer": "^1.0.0", | |
| 374 | "bare-module-resolve": "^1.8.0", | |
| 375 | "bare-path": "^3.0.0", | |
| 376 | "bare-url": "^2.0.1" | |
| 377 | }, | |
| 378 | "engines": { | |
| 379 | "bare": ">=1.23.0" | |
| 380 | }, | |
| 381 | "peerDependencies": { | |
| 382 | "bare-buffer": "*" | |
| 383 | }, | |
| 384 | "peerDependenciesMeta": { | |
| 385 | "bare-buffer": { | |
| 386 | "optional": true | |
| 387 | } | |
| 388 | } | |
| 389 | }, | |
| 390 | "node_modules/bare-module-lexer": { | |
| 391 | "version": "1.4.7", | |
| 392 | "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.4.7.tgz", | |
| 393 | "integrity": "sha512-0klU4eMsjh/wcxi8FdHmNom2j2F4kmkXOhyJFL9qTaSFp2lE3m6BtbKgMHY8R5miqC9r8/IfA8wzXnC5Os14WA==", | |
| 394 | "dev": true, | |
| 395 | "license": "Apache-2.0", | |
| 396 | "dependencies": { | |
| 397 | "require-addon": "^1.0.2" | |
| 398 | }, | |
| 399 | "peerDependencies": { | |
| 400 | "bare-buffer": "*" | |
| 401 | }, | |
| 402 | "peerDependenciesMeta": { | |
| 403 | "bare-buffer": { | |
| 404 | "optional": true | |
| 405 | } | |
| 406 | } | |
| 407 | }, | |
| 408 | "node_modules/bare-module-resolve": { | |
| 409 | "version": "1.12.1", | |
| 410 | "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.1.tgz", | |
| 411 | "integrity": "sha512-hbmAPyFpEq8FoZMd5sFO3u6MC5feluWoGE8YKlA8fCrl6mNtx68Wjg4DTiDJcqRJaovTvOYKfYngoBUnbaT7eg==", | |
| 412 | "dev": true, | |
| 413 | "license": "Apache-2.0", | |
| 414 | "dependencies": { | |
| 415 | "bare-semver": "^1.0.0" | |
| 416 | }, | |
| 417 | "peerDependencies": { | |
| 418 | "bare-url": "*" | |
| 419 | }, | |
| 420 | "peerDependenciesMeta": { | |
| 421 | "bare-url": { | |
| 422 | "optional": true | |
| 423 | } | |
| 424 | } | |
| 425 | }, | |
| 426 | "node_modules/bare-net": { | |
| 427 | "version": "2.2.0", | |
| 428 | "resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.2.0.tgz", | |
| 429 | "integrity": "sha512-UF7cAbHsGE+H6uEqWF5IULBow1x58chZz4g3ALgHtv7wZsFcCbRDt0JKWEumf5Oma3QWS1Q6aLi0Rpll8RElMg==", | |
| 430 | "dev": true, | |
| 431 | "license": "Apache-2.0", | |
| 432 | "dependencies": { | |
| 433 | "bare-events": "^2.2.2", | |
| 434 | "bare-pipe": "^4.0.0", | |
| 435 | "bare-stream": "^2.0.0", | |
| 436 | "bare-tcp": "^2.0.0" | |
| 437 | } | |
| 438 | }, | |
| 439 | "node_modules/bare-os": { | |
| 440 | "version": "3.6.2", | |
| 441 | "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", | |
| 442 | "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", | |
| 443 | "dev": true, | |
| 444 | "license": "Apache-2.0", | |
| 445 | "engines": { | |
| 446 | "bare": ">=1.14.0" | |
| 447 | } | |
| 448 | }, | |
| 449 | "node_modules/bare-path": { | |
| 450 | "version": "3.0.0", | |
| 451 | "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", | |
| 452 | "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", | |
| 453 | "dev": true, | |
| 454 | "license": "Apache-2.0", | |
| 455 | "dependencies": { | |
| 456 | "bare-os": "^3.0.1" | |
| 457 | } | |
| 458 | }, | |
| 459 | "node_modules/bare-pipe": { | |
| 460 | "version": "4.1.3", | |
| 461 | "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.1.3.tgz", | |
| 462 | "integrity": "sha512-DqQsx93rAzre6yJ9T6l/Vgh+X+bntkVMB1X5ggtXjXtqtMmF2Y2RVlCzxxy/09R6yeR9FSWBEUIaMYJL1/5VDA==", | |
| 463 | "dev": true, | |
| 464 | "license": "Apache-2.0", | |
| 465 | "dependencies": { | |
| 466 | "bare-events": "^2.0.0", | |
| 467 | "bare-stream": "^2.0.0" | |
| 468 | }, | |
| 469 | "engines": { | |
| 470 | "bare": ">=1.16.0" | |
| 471 | } | |
| 472 | }, | |
| 473 | "node_modules/bare-process": { | |
| 474 | "version": "4.2.2", | |
| 475 | "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.2.2.tgz", | |
| 476 | "integrity": "sha512-ikzEw+HGLB+2lS/WLVEsqi8BXBwzleG3n7DYI0v6/YNklvNabOIGlLd1dof+7Jw5ob4jsBRPtyflweVahY7rFA==", | |
| 477 | "dev": true, | |
| 478 | "license": "Apache-2.0", | |
| 479 | "dependencies": { | |
| 480 | "bare-env": "^3.0.0", | |
| 481 | "bare-events": "^2.3.1", | |
| 482 | "bare-hrtime": "^2.0.0", | |
| 483 | "bare-os": "^3.5.0", | |
| 484 | "bare-pipe": "^4.0.0", | |
| 485 | "bare-signals": "^4.0.0", | |
| 486 | "bare-tty": "^5.0.0" | |
| 487 | } | |
| 488 | }, | |
| 489 | "node_modules/bare-semver": { | |
| 490 | "version": "1.0.2", | |
| 491 | "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.0.2.tgz", | |
| 492 | "integrity": "sha512-ESVaN2nzWhcI5tf3Zzcq9aqCZ676VWzqw07eEZ0qxAcEOAFYBa0pWq8sK34OQeHLY3JsfKXZS9mDyzyxGjeLzA==", | |
| 493 | "dev": true, | |
| 494 | "license": "Apache-2.0" | |
| 495 | }, | |
| 496 | "node_modules/bare-signals": { | |
| 497 | "version": "4.2.0", | |
| 498 | "resolved": "https://registry.npmjs.org/bare-signals/-/bare-signals-4.2.0.tgz", | |
| 499 | "integrity": "sha512-fNHMOdQIlYuTvMB3Oh9Apk99hLKn351+Ir8vz+khiPTcOqIyGG4uWWjdLTzxWdYGsA0eT+We3y0K74hjj2nq7A==", | |
| 500 | "dev": true, | |
| 501 | "license": "Apache-2.0", | |
| 502 | "dependencies": { | |
| 503 | "bare-events": "^2.5.3", | |
| 504 | "bare-os": "^3.3.1" | |
| 505 | }, | |
| 506 | "engines": { | |
| 507 | "bare": ">=1.7.0" | |
| 508 | } | |
| 509 | }, | |
| 510 | "node_modules/bare-stream": { | |
| 511 | "version": "2.7.0", | |
| 512 | "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", | |
| 513 | "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", | |
| 514 | "dev": true, | |
| 515 | "license": "Apache-2.0", | |
| 516 | "dependencies": { | |
| 517 | "streamx": "^2.21.0" | |
| 518 | }, | |
| 519 | "peerDependencies": { | |
| 520 | "bare-buffer": "*", | |
| 521 | "bare-events": "*" | |
| 522 | }, | |
| 523 | "peerDependenciesMeta": { | |
| 524 | "bare-buffer": { | |
| 525 | "optional": true | |
| 526 | }, | |
| 527 | "bare-events": { | |
| 528 | "optional": true | |
| 529 | } | |
| 530 | } | |
| 531 | }, | |
| 532 | "node_modules/bare-subprocess": { | |
| 533 | "version": "5.2.2", | |
| 534 | "resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.2.tgz", | |
| 535 | "integrity": "sha512-L6oXgQ1aWs25RtG5Ky0bDD06p3RAcVVrDDMWb1DfXpHtyEWxamcyIWUbSykxMTWrpLlmSj6ytbb6yoKehGFfmw==", | |
| 536 | "dev": true, | |
| 537 | "license": "Apache-2.0", | |
| 538 | "dependencies": { | |
| 539 | "bare-env": "^3.0.0", | |
| 540 | "bare-events": "^2.5.4", | |
| 541 | "bare-os": "^3.0.1", | |
| 542 | "bare-pipe": "^4.0.0", | |
| 543 | "bare-url": "^2.2.2" | |
| 544 | }, | |
| 545 | "engines": { | |
| 546 | "bare": ">=1.7.0" | |
| 547 | }, | |
| 548 | "peerDependencies": { | |
| 549 | "bare-buffer": "*" | |
| 550 | }, | |
| 551 | "peerDependenciesMeta": { | |
| 552 | "bare-buffer": { | |
| 553 | "optional": true | |
| 554 | } | |
| 555 | } | |
| 556 | }, | |
| 557 | "node_modules/bare-tcp": { | |
| 558 | "version": "2.2.4", | |
| 559 | "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.2.4.tgz", | |
| 560 | "integrity": "sha512-vGjfMkyhS1lxp+Y4+gXRDR1ijZMK2cKx0RBwtYKUuZbD90ARCoM6bAiTa1HBztumCIC+NXb8eXdMFXyuYwlPyw==", | |
| 561 | "dev": true, | |
| 562 | "license": "Apache-2.0", | |
| 563 | "dependencies": { | |
| 564 | "bare-dns": "^2.0.4", | |
| 565 | "bare-events": "^2.5.4", | |
| 566 | "bare-stream": "^2.6.4" | |
| 567 | }, | |
| 568 | "engines": { | |
| 569 | "bare": ">=1.16.0" | |
| 570 | } | |
| 571 | }, | |
| 572 | "node_modules/bare-tls": { | |
| 573 | "version": "2.1.7", | |
| 574 | "resolved": "https://registry.npmjs.org/bare-tls/-/bare-tls-2.1.7.tgz", | |
| 575 | "integrity": "sha512-h6wcNXQdBeTX7fed9tjPp0/9cA/QfcBTv3ItgjnbUk4rWAU8bEFalZCZnUDdCK/t9zrNfJ+yvcPx4D/1Y6biyA==", | |
| 576 | "dev": true, | |
| 577 | "license": "Apache-2.0", | |
| 578 | "dependencies": { | |
| 579 | "bare-net": "^2.0.1", | |
| 580 | "bare-stream": "^2.6.4" | |
| 581 | }, | |
| 582 | "engines": { | |
| 583 | "bare": ">=1.7.0" | |
| 584 | } | |
| 585 | }, | |
| 586 | "node_modules/bare-tty": { | |
| 587 | "version": "5.0.3", | |
| 588 | "resolved": "https://registry.npmjs.org/bare-tty/-/bare-tty-5.0.3.tgz", | |
| 589 | "integrity": "sha512-jW24RBWRZOMHuSEWC9mh8wUKO5WUNl0UWUTNPn3yZ8qkOqwa8vaV2dR3a+BvblONPk7V35wuvK9P8508+judAg==", | |
| 590 | "dev": true, | |
| 591 | "license": "Apache-2.0", | |
| 592 | "dependencies": { | |
| 593 | "bare-events": "^2.2.0", | |
| 594 | "bare-signals": "^4.0.0", | |
| 595 | "bare-stream": "^2.0.0" | |
| 596 | }, | |
| 597 | "engines": { | |
| 598 | "bare": ">=1.16.0" | |
| 599 | } | |
| 600 | }, | |
| 601 | "node_modules/bare-type": { | |
| 602 | "version": "1.1.0", | |
| 603 | "resolved": "https://registry.npmjs.org/bare-type/-/bare-type-1.1.0.tgz", | |
| 604 | "integrity": "sha512-LdtnnEEYldOc87Dr4GpsKnStStZk3zfgoEMXy8yvEZkXrcCv9RtYDrUYWFsBQHtaB0s1EUWmcvS6XmEZYIj3Bw==", | |
| 605 | "dev": true, | |
| 606 | "license": "Apache-2.0", | |
| 607 | "engines": { | |
| 608 | "bare": ">=1.2.0" | |
| 609 | } | |
| 610 | }, | |
| 611 | "node_modules/bare-url": { | |
| 612 | "version": "2.3.2", | |
| 613 | "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", | |
| 614 | "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", | |
| 615 | "dev": true, | |
| 616 | "license": "Apache-2.0", | |
| 617 | "peer": true, | |
| 618 | "dependencies": { | |
| 619 | "bare-path": "^3.0.0" | |
| 620 | } | |
| 621 | }, | |
| 622 | "node_modules/bare-utils": { | |
| 623 | "version": "1.5.1", | |
| 624 | "resolved": "https://registry.npmjs.org/bare-utils/-/bare-utils-1.5.1.tgz", | |
| 625 | "integrity": "sha512-mxCkFvmDU3mlD/mb+pT64kKXOsx2KMsWLQbngN1LB+NOXfhfnRnyvpy3VZc6m7gzQxe57Bsi+aTCBqA4/S3elQ==", | |
| 626 | "dev": true, | |
| 627 | "license": "Apache-2.0", | |
| 628 | "dependencies": { | |
| 629 | "bare-debug-log": "^2.0.0", | |
| 630 | "bare-encoding": "^1.0.0", | |
| 631 | "bare-format": "^1.0.0", | |
| 632 | "bare-inspect": "^3.0.0", | |
| 633 | "bare-type": "^1.0.6" | |
| 634 | } | |
| 635 | }, | |
| 636 | "node_modules/bare-v8-to-istanbul": { | |
| 637 | "version": "1.0.2", | |
| 638 | "resolved": "https://registry.npmjs.org/bare-v8-to-istanbul/-/bare-v8-to-istanbul-1.0.2.tgz", | |
| 639 | "integrity": "sha512-CM0wPgyQtXTQ7h4q8NjbTZ/FH5he0Jkiwgpq0lFZZA5tkVZzmQ/9ul4R7E+cNlGZJIZlQZ9v4CwbgVaFG3zOAA==", | |
| 640 | "dev": true, | |
| 641 | "license": "Apache-2.0", | |
| 642 | "dependencies": { | |
| 643 | "bare-assert": "^1.0.2", | |
| 644 | "bare-fs": "^4.1.2", | |
| 645 | "bare-module": "^6.1.2", | |
| 646 | "bare-path": "^3.0.0", | |
| 647 | "bare-process": "^4.2.0", | |
| 648 | "bare-url": "^2.1.5", | |
| 649 | "bare-utils": "^1.2.0", | |
| 650 | "v8-to-istanbul": "^9.3.0", | |
| 651 | "which-runtime": "^1.2.1" | |
| 652 | } | |
| 653 | }, | |
| 654 | "node_modules/bare-ws": { | |
| 655 | "version": "2.0.4", | |
| 656 | "resolved": "https://registry.npmjs.org/bare-ws/-/bare-ws-2.0.4.tgz", | |
| 657 | "integrity": "sha512-SQMXzBYna9dRj57Dz1/ag+VWHCRXbfCjMHgyfM2F2lhkVLzMjnVSZP72aVeFWPFqe494Rd70Kzhe2JElGwFlJQ==", | |
| 658 | "dev": true, | |
| 659 | "license": "Apache-2.0", | |
| 660 | "dependencies": { | |
| 661 | "bare-crypto": "^1.2.0", | |
| 662 | "bare-events": "^2.3.1", | |
| 663 | "bare-http1": "^4.0.0", | |
| 664 | "bare-https": "^2.0.0", | |
| 665 | "bare-stream": "^2.1.2" | |
| 666 | }, | |
| 667 | "peerDependencies": { | |
| 668 | "bare-buffer": "*", | |
| 669 | "bare-url": "*" | |
| 670 | }, | |
| 671 | "peerDependenciesMeta": { | |
| 672 | "bare-buffer": { | |
| 673 | "optional": true | |
| 674 | }, | |
| 675 | "bare-url": { | |
| 676 | "optional": true | |
| 677 | } | |
| 678 | } | |
| 679 | }, | |
| 680 | "node_modules/bridge-envelope": { | |
| 681 | "resolved": "../bridge-envelope", | |
| 682 | "link": true | |
| 683 | }, | |
| 684 | "node_modules/brittle": { | |
| 685 | "version": "3.19.1", | |
| 686 | "resolved": "https://registry.npmjs.org/brittle/-/brittle-3.19.1.tgz", | |
| 687 | "integrity": "sha512-4Ted1Mt9o9B6oIA6ImJJCtB/Fv++ZO3IDNQgRcHOXWSYGRUidgxchaGrUBaRn+4mlneXrgInPkCPzi0jO8qKbA==", | |
| 688 | "dev": true, | |
| 689 | "license": "Apache-2.0", | |
| 690 | "dependencies": { | |
| 691 | "b4a": "^1.6.0", | |
| 692 | "bare-assert": "^1.0.2", | |
| 693 | "bare-cov": "^1.1.0", | |
| 694 | "bare-fs": "^4.1.6", | |
| 695 | "bare-os": "^3.6.1", | |
| 696 | "bare-path": "^3.0.0", | |
| 697 | "bare-process": "^4.2.1", | |
| 698 | "bare-subprocess": "^5.0.0", | |
| 699 | "bare-url": "^2.1.6", | |
| 700 | "error-stack-parser": "^2.1.4", | |
| 701 | "globbie": "^1.0.2", | |
| 702 | "paparam": "^1.6.2", | |
| 703 | "same-object": "^1.0.2", | |
| 704 | "test-tmp": "^1.4.0", | |
| 705 | "tmatch": "^5.0.0" | |
| 706 | }, | |
| 707 | "bin": { | |
| 708 | "brittle": "bin/node.js", | |
| 709 | "brittle-bare": "bin/bare.js", | |
| 710 | "brittle-node": "bin/node.js", | |
| 711 | "brittle-pear": "bin/pear.js" | |
| 712 | } | |
| 713 | }, | |
| 714 | "node_modules/convert-source-map": { | |
| 715 | "version": "2.0.0", | |
| 716 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", | |
| 717 | "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", | |
| 718 | "dev": true, | |
| 719 | "license": "MIT" | |
| 720 | }, | |
| 721 | "node_modules/error-stack-parser": { | |
| 722 | "version": "2.1.4", | |
| 723 | "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", | |
| 724 | "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", | |
| 725 | "dev": true, | |
| 726 | "license": "MIT", | |
| 727 | "dependencies": { | |
| 728 | "stackframe": "^1.3.4" | |
| 729 | } | |
| 730 | }, | |
| 731 | "node_modules/events-universal": { | |
| 732 | "version": "1.0.1", | |
| 733 | "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", | |
| 734 | "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", | |
| 735 | "dev": true, | |
| 736 | "license": "Apache-2.0", | |
| 737 | "dependencies": { | |
| 738 | "bare-events": "^2.7.0" | |
| 739 | } | |
| 740 | }, | |
| 741 | "node_modules/fast-fifo": { | |
| 742 | "version": "1.3.2", | |
| 743 | "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", | |
| 744 | "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", | |
| 745 | "dev": true, | |
| 746 | "license": "MIT" | |
| 747 | }, | |
| 748 | "node_modules/globbie": { | |
| 749 | "version": "1.0.3", | |
| 750 | "resolved": "https://registry.npmjs.org/globbie/-/globbie-1.0.3.tgz", | |
| 751 | "integrity": "sha512-hcryJmKcftf82xfBWTWxuUITWIIoYO3ec104V17SMHGFl6VLbm1d1Ju9LX9L+jyJTwICpemX/dmPI/HGYoKr1A==", | |
| 752 | "dev": true, | |
| 753 | "license": "Apache-2.0", | |
| 754 | "dependencies": { | |
| 755 | "bare-fs": "^4.1.2", | |
| 756 | "bare-path": "^3.0.0", | |
| 757 | "bare-process": "^4.2.1", | |
| 758 | "picomatch": "^4.0.2" | |
| 759 | } | |
| 760 | }, | |
| 761 | "node_modules/paparam": { | |
| 762 | "version": "1.10.0", | |
| 763 | "resolved": "https://registry.npmjs.org/paparam/-/paparam-1.10.0.tgz", | |
| 764 | "integrity": "sha512-p36QQrwU3X6fj5d2JN20B4lcZI/O3fxMVUHc1xD7MNe5bcOf3jIgE0CIK5Zv/s5YAUGlobTJ/kKXrh1FJVrs3w==", | |
| 765 | "dev": true, | |
| 766 | "license": "Apache-2.0" | |
| 767 | }, | |
| 768 | "node_modules/picomatch": { | |
| 769 | "version": "4.0.3", | |
| 770 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", | |
| 771 | "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", | |
| 772 | "dev": true, | |
| 773 | "license": "MIT", | |
| 774 | "engines": { | |
| 775 | "node": ">=12" | |
| 776 | }, | |
| 777 | "funding": { | |
| 778 | "url": "https://github.com/sponsors/jonschlinkert" | |
| 779 | } | |
| 780 | }, | |
| 781 | "node_modules/require-addon": { | |
| 782 | "version": "1.2.0", | |
| 783 | "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz", | |
| 784 | "integrity": "sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA==", | |
| 785 | "dev": true, | |
| 786 | "license": "Apache-2.0", | |
| 787 | "dependencies": { | |
| 788 | "bare-addon-resolve": "^1.3.0" | |
| 789 | }, | |
| 790 | "engines": { | |
| 791 | "bare": ">=1.10.0" | |
| 792 | } | |
| 793 | }, | |
| 794 | "node_modules/same-object": { | |
| 795 | "version": "1.0.2", | |
| 796 | "resolved": "https://registry.npmjs.org/same-object/-/same-object-1.0.2.tgz", | |
| 797 | "integrity": "sha512-csHWhvUsLbIOHDM/nP+KHWM+BLPsIzWkFa8HbzaI0G7BqKXgx+7FJpKTGgLXyz5amfdY2OVBcmXTqYOMEk04og==", | |
| 798 | "dev": true, | |
| 799 | "license": "MIT" | |
| 800 | }, | |
| 801 | "node_modules/stackframe": { | |
| 802 | "version": "1.3.4", | |
| 803 | "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", | |
| 804 | "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", | |
| 805 | "dev": true, | |
| 806 | "license": "MIT" | |
| 807 | }, | |
| 808 | "node_modules/streamx": { | |
| 809 | "version": "2.23.0", | |
| 810 | "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", | |
| 811 | "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", | |
| 812 | "dev": true, | |
| 813 | "license": "MIT", | |
| 814 | "dependencies": { | |
| 815 | "events-universal": "^1.0.0", | |
| 816 | "fast-fifo": "^1.3.2", | |
| 817 | "text-decoder": "^1.1.0" | |
| 818 | } | |
| 819 | }, | |
| 820 | "node_modules/test-tmp": { | |
| 821 | "version": "1.4.0", | |
| 822 | "resolved": "https://registry.npmjs.org/test-tmp/-/test-tmp-1.4.0.tgz", | |
| 823 | "integrity": "sha512-GVggxGg+jXqP2Wbju50JVLo+9E+nIOPPyWqgr63EbOnNItIKu1cEbJpTWAJeflnyGqXOtcMI7ijHRp88GUkfDA==", | |
| 824 | "dev": true, | |
| 825 | "license": "MIT", | |
| 826 | "dependencies": { | |
| 827 | "bare-fs": "^4.0.1", | |
| 828 | "bare-os": "^3.3.0", | |
| 829 | "bare-path": "^3.0.0" | |
| 830 | } | |
| 831 | }, | |
| 832 | "node_modules/text-decoder": { | |
| 833 | "version": "1.2.6", | |
| 834 | "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.6.tgz", | |
| 835 | "integrity": "sha512-27FeW5GQFDfw0FpwMQhMagB7BztOOlmjcSRi97t2oplhKVTZtp0DZbSegSaXS5IIC6mxMvBG4AR1Sgc6BX3CQg==", | |
| 836 | "dev": true, | |
| 837 | "license": "Apache-2.0", | |
| 838 | "dependencies": { | |
| 839 | "b4a": "^1.6.4" | |
| 840 | } | |
| 841 | }, | |
| 842 | "node_modules/tmatch": { | |
| 843 | "version": "5.0.0", | |
| 844 | "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-5.0.0.tgz", | |
| 845 | "integrity": "sha512-Ib9OtBkpHn07tXP04SlN1SYRxFgTk6wSM2EBmjjxug4u5RXPRVLkdFJSS1PmrQidaSB8Lru9nRtViQBsbxzE5Q==", | |
| 846 | "dev": true, | |
| 847 | "license": "ISC", | |
| 848 | "engines": { | |
| 849 | "node": ">=8" | |
| 850 | } | |
| 851 | }, | |
| 852 | "node_modules/v8-to-istanbul": { | |
| 853 | "version": "9.3.0", | |
| 854 | "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", | |
| 855 | "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", | |
| 856 | "dev": true, | |
| 857 | "license": "ISC", | |
| 858 | "dependencies": { | |
| 859 | "@jridgewell/trace-mapping": "^0.3.12", | |
| 860 | "@types/istanbul-lib-coverage": "^2.0.1", | |
| 861 | "convert-source-map": "^2.0.0" | |
| 862 | }, | |
| 863 | "engines": { | |
| 864 | "node": ">=10.12.0" | |
| 865 | } | |
| 866 | }, | |
| 867 | "node_modules/which-runtime": { | |
| 868 | "version": "1.3.2", | |
| 869 | "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.3.2.tgz", | |
| 870 | "integrity": "sha512-5kwCfWml7+b2NO7KrLMhYihjRx0teKkd3yGp1Xk5Vaf2JGdSh+rgVhEALAD9c/59dP+YwJHXoEO7e8QPy7gOkw==", | |
| 871 | "dev": true, | |
| 872 | "license": "Apache-2.0" | |
| 873 | } | |
| 874 | } | |
| 875 | } |
| … | … | @@ -0,0 +1,22 @@ |
| 1 | { | |
| 2 | "name": "bridge-matrix", | |
| 3 | "version": "1.0.0", | |
| 4 | "description": "Matrix ↔ Keet bidirectional bridge", | |
| 5 | "type": "module", | |
| 6 | "main": "lib/bridge.js", | |
| 7 | "pear": { | |
| 8 | "name": "bridge-matrix", | |
| 9 | "type": "terminal" | |
| 10 | }, | |
| 11 | "scripts": { | |
| 12 | "test": "brittle test/*.test.js" | |
| 13 | }, | |
| 14 | "dependencies": { | |
| 15 | "bridge-envelope": "file:../bridge-envelope", | |
| 16 | "b4a": "^1.0.0" | |
| 17 | }, | |
| 18 | "devDependencies": { | |
| 19 | "brittle": "^3.0.0" | |
| 20 | }, | |
| 21 | "license": "MIT" | |
| 22 | } |
| … | … | @@ -0,0 +1,156 @@ |
| 1 | import test from 'brittle' | |
| 2 | import { matrixToKeetText, keetToMatrixText, matrixToEnvelope, envelopeToMatrixText, stripHtml } from '../lib/adapter.js' | |
| 3 | import { TYPES, PLATFORMS } from 'bridge-envelope' | |
| 4 | ||
| 5 | test('matrixToKeetText - formats correctly', (t) => { | |
| 6 | const result = matrixToKeetText('Alice', 'Hello world!') | |
| 7 | t.is(result, '[Matrix] <Alice> Hello world!') | |
| 8 | }) | |
| 9 | ||
| 10 | test('matrixToKeetText - preserves unicode', (t) => { | |
| 11 | const result = matrixToKeetText('Bob', 'Hej hej!') | |
| 12 | t.is(result, '[Matrix] <Bob> Hej hej!') | |
| 13 | }) | |
| 14 | ||
| 15 | test('matrixToKeetText - empty text', (t) => { | |
| 16 | const result = matrixToKeetText('user', '') | |
| 17 | t.is(result, '[Matrix] <user> ') | |
| 18 | }) | |
| 19 | ||
| 20 | test('keetToMatrixText - normal message', (t) => { | |
| 21 | const msg = { | |
| 22 | memberId: 'member-123', | |
| 23 | name: 'fleeky', | |
| 24 | text: 'Hello from Keet!' | |
| 25 | } | |
| 26 | ||
| 27 | const result = keetToMatrixText(msg, 'bridge-id') | |
| 28 | t.ok(result) | |
| 29 | t.is(result.sender, 'fleeky') | |
| 30 | t.is(result.text, 'Hello from Keet!') | |
| 31 | }) | |
| 32 | ||
| 33 | test('keetToMatrixText - skips bridge identity', (t) => { | |
| 34 | const msg = { | |
| 35 | memberId: 'bridge-id', | |
| 36 | name: 'clode', | |
| 37 | text: 'echoed message' | |
| 38 | } | |
| 39 | ||
| 40 | const result = keetToMatrixText(msg, 'bridge-id') | |
| 41 | t.is(result, null, 'skips bridge identity messages') | |
| 42 | }) | |
| 43 | ||
| 44 | test('keetToMatrixText - skips already-bridged messages', (t) => { | |
| 45 | const msg = { | |
| 46 | memberId: 'member-456', | |
| 47 | name: 'someone', | |
| 48 | text: '[Matrix] <Alice> hello' | |
| 49 | } | |
| 50 | ||
| 51 | const result = keetToMatrixText(msg, 'bridge-id') | |
| 52 | t.is(result, null, 'skips already-bridged Matrix messages') | |
| 53 | }) | |
| 54 | ||
| 55 | test('keetToMatrixText - skips system messages', (t) => { | |
| 56 | const msg = { | |
| 57 | memberId: 'member-789', | |
| 58 | name: 'System', | |
| 59 | text: 'User joined', | |
| 60 | system: { type: 'join' } | |
| 61 | } | |
| 62 | ||
| 63 | const result = keetToMatrixText(msg, 'bridge-id') | |
| 64 | t.is(result, null, 'skips system messages') | |
| 65 | }) | |
| 66 | ||
| 67 | test('keetToMatrixText - skips empty text', (t) => { | |
| 68 | const msg = { | |
| 69 | memberId: 'member-abc', | |
| 70 | name: 'User', | |
| 71 | text: ' ' | |
| 72 | } | |
| 73 | ||
| 74 | const result = keetToMatrixText(msg, 'bridge-id') | |
| 75 | t.is(result, null) | |
| 76 | }) | |
| 77 | ||
| 78 | test('keetToMatrixText - null input', (t) => { | |
| 79 | const result = keetToMatrixText(null, 'bridge-id') | |
| 80 | t.is(result, null) | |
| 81 | }) | |
| 82 | ||
| 83 | test('keetToMatrixText - anonymous if no name', (t) => { | |
| 84 | const msg = { | |
| 85 | memberId: 'member-def', | |
| 86 | text: 'hello' | |
| 87 | } | |
| 88 | ||
| 89 | const result = keetToMatrixText(msg, 'bridge-id') | |
| 90 | t.is(result.sender, 'anonymous') | |
| 91 | }) | |
| 92 | ||
| 93 | test('keetToMatrixText - uses username fallback', (t) => { | |
| 94 | const msg = { | |
| 95 | memberId: 'member-ghi', | |
| 96 | username: 'user42', | |
| 97 | text: 'hello' | |
| 98 | } | |
| 99 | ||
| 100 | const result = keetToMatrixText(msg, 'bridge-id') | |
| 101 | t.is(result.sender, 'user42') | |
| 102 | }) | |
| 103 | ||
| 104 | test('stripHtml - basic tags', (t) => { | |
| 105 | t.is(stripHtml('<p>Hello</p>'), 'Hello') | |
| 106 | t.is(stripHtml('<p>Line 1</p><p>Line 2</p>'), 'Line 1\n\nLine 2') | |
| 107 | t.is(stripHtml('Hello<br/>World'), 'Hello\nWorld') | |
| 108 | t.is(stripHtml('Hello<br>World'), 'Hello\nWorld') | |
| 109 | }) | |
| 110 | ||
| 111 | test('stripHtml - entities', (t) => { | |
| 112 | t.is(stripHtml('& < > " ''), '& < > " \'') | |
| 113 | }) | |
| 114 | ||
| 115 | test('stripHtml - nested tags', (t) => { | |
| 116 | t.is(stripHtml('<p>Hello <a href="https://example.com">link</a></p>'), 'Hello link') | |
| 117 | t.is(stripHtml('<p><span class="mention">@user</span> hi</p>'), '@user hi') | |
| 118 | }) | |
| 119 | ||
| 120 | test('stripHtml - Matrix mention format', (t) => { | |
| 121 | const html = '<a href="https://matrix.to/#/@alice:matrix.org">Alice</a> said hi' | |
| 122 | t.is(stripHtml(html), 'Alice said hi') | |
| 123 | }) | |
| 124 | ||
| 125 | test('matrixToEnvelope - creates correct envelope', (t) => { | |
| 126 | const env = matrixToEnvelope('Alice', '!room:matrix.org', 'Hello!') | |
| 127 | ||
| 128 | t.is(env.type, TYPES.TEXT) | |
| 129 | t.is(env.platform, PLATFORMS.MATRIX) | |
| 130 | t.is(env.author, 'Alice') | |
| 131 | t.is(env.content, 'Hello!') | |
| 132 | t.is(env.channel, '!room:matrix.org') | |
| 133 | t.ok(env.id, 'has id') | |
| 134 | }) | |
| 135 | ||
| 136 | test('envelopeToMatrixText - from Keet', (t) => { | |
| 137 | const env = { | |
| 138 | platform: PLATFORMS.KEET, | |
| 139 | author: 'fleeky', | |
| 140 | content: 'Hello world' | |
| 141 | } | |
| 142 | t.is(envelopeToMatrixText(env), '[Keet] <fleeky> Hello world') | |
| 143 | }) | |
| 144 | ||
| 145 | test('envelopeToMatrixText - from IRC', (t) => { | |
| 146 | const env = { | |
| 147 | platform: PLATFORMS.IRC, | |
| 148 | author: 'nick', | |
| 149 | content: 'IRC message' | |
| 150 | } | |
| 151 | t.is(envelopeToMatrixText(env), '[IRC] <nick> IRC message') | |
| 152 | }) | |
| 153 | ||
| 154 | test('envelopeToMatrixText - null envelope', (t) => { | |
| 155 | t.is(envelopeToMatrixText(null), '') | |
| 156 | }) |
| … | … | @@ -0,0 +1,276 @@ |
| 1 | import test from 'brittle' | |
| 2 | import { MatrixClient, stripHtml } from '../lib/matrix-client.js' | |
| 3 | ||
| 4 | test('MatrixClient constructor - defaults', (t) => { | |
| 5 | const client = new MatrixClient() | |
| 6 | t.is(client.homeserver, 'matrix.org') | |
| 7 | t.is(client.userId, null) | |
| 8 | t.is(client.accessToken, null) | |
| 9 | t.is(client._syncing, false) | |
| 10 | t.is(client._txnId, 0) | |
| 11 | }) | |
| 12 | ||
| 13 | test('MatrixClient constructor - custom options', (t) => { | |
| 14 | const onMsg = () => {} | |
| 15 | const client = new MatrixClient({ | |
| 16 | homeserver: 'example.org', | |
| 17 | userId: '@bot:example.org', | |
| 18 | accessToken: 'syt_token123', | |
| 19 | onMessage: onMsg | |
| 20 | }) | |
| 21 | t.is(client.homeserver, 'example.org') | |
| 22 | t.is(client.userId, '@bot:example.org') | |
| 23 | t.is(client.accessToken, 'syt_token123') | |
| 24 | t.is(client._onMessage, onMsg) | |
| 25 | }) | |
| 26 | ||
| 27 | test('MatrixClient - stopSync sets flag', (t) => { | |
| 28 | const client = new MatrixClient() | |
| 29 | client._syncing = true | |
| 30 | client.stopSync() | |
| 31 | t.is(client._syncing, false) | |
| 32 | }) | |
| 33 | ||
| 34 | test('MatrixClient - display name cache', async (t) => { | |
| 35 | const client = new MatrixClient() | |
| 36 | ||
| 37 | // Pre-populate cache | |
| 38 | client._displayNameCache.set('@alice:matrix.org', 'Alice') | |
| 39 | ||
| 40 | const name = await client.getDisplayName('@alice:matrix.org') | |
| 41 | t.is(name, 'Alice') | |
| 42 | }) | |
| 43 | ||
| 44 | test('MatrixClient - event dedup', (t) => { | |
| 45 | const client = new MatrixClient() | |
| 46 | ||
| 47 | client._seenEvents.add('$event1') | |
| 48 | client._seenEvents.add('$event2') | |
| 49 | ||
| 50 | t.is(client._seenEvents.has('$event1'), true) | |
| 51 | t.is(client._seenEvents.has('$event3'), false) | |
| 52 | t.is(client._seenEvents.size, 2) | |
| 53 | }) | |
| 54 | ||
| 55 | test('MatrixClient - event dedup prunes at limit', (t) => { | |
| 56 | const client = new MatrixClient() | |
| 57 | client._maxSeenEvents = 10 | |
| 58 | ||
| 59 | // Fill past limit | |
| 60 | for (let i = 0; i < 15; i++) { | |
| 61 | client._seenEvents.add('$event' + i) | |
| 62 | } | |
| 63 | ||
| 64 | t.is(client._seenEvents.size, 15) // not pruned until _handleEvent | |
| 65 | }) | |
| 66 | ||
| 67 | test('MatrixClient - _handleEvent skips own messages', async (t) => { | |
| 68 | let called = false | |
| 69 | const client = new MatrixClient({ | |
| 70 | userId: '@bot:matrix.org', | |
| 71 | onMessage: () => { called = true } | |
| 72 | }) | |
| 73 | ||
| 74 | await client._handleEvent('!room:matrix.org', { | |
| 75 | type: 'm.room.message', | |
| 76 | sender: '@bot:matrix.org', | |
| 77 | event_id: '$own1', | |
| 78 | content: { msgtype: 'm.text', body: 'my own msg' } | |
| 79 | }) | |
| 80 | ||
| 81 | t.is(called, false, 'should not relay own messages') | |
| 82 | }) | |
| 83 | ||
| 84 | test('MatrixClient - _handleEvent relays others messages', async (t) => { | |
| 85 | let received = null | |
| 86 | const client = new MatrixClient({ | |
| 87 | userId: '@bot:matrix.org', | |
| 88 | onMessage: (roomId, sender, text, eventId) => { | |
| 89 | received = { roomId, sender, text, eventId } | |
| 90 | } | |
| 91 | }) | |
| 92 | ||
| 93 | // Pre-populate display name cache | |
| 94 | client._displayNameCache.set('@alice:matrix.org', 'Alice') | |
| 95 | ||
| 96 | await client._handleEvent('!room:matrix.org', { | |
| 97 | type: 'm.room.message', | |
| 98 | sender: '@alice:matrix.org', | |
| 99 | event_id: '$msg1', | |
| 100 | content: { msgtype: 'm.text', body: 'Hello!' } | |
| 101 | }) | |
| 102 | ||
| 103 | t.ok(received) | |
| 104 | t.is(received.roomId, '!room:matrix.org') | |
| 105 | t.is(received.sender, 'Alice') | |
| 106 | t.is(received.text, 'Hello!') | |
| 107 | t.is(received.eventId, '$msg1') | |
| 108 | }) | |
| 109 | ||
| 110 | test('MatrixClient - _handleEvent dedup by event_id', async (t) => { | |
| 111 | let count = 0 | |
| 112 | const client = new MatrixClient({ | |
| 113 | userId: '@bot:matrix.org', | |
| 114 | onMessage: () => { count++ } | |
| 115 | }) | |
| 116 | client._displayNameCache.set('@alice:matrix.org', 'Alice') | |
| 117 | ||
| 118 | const event = { | |
| 119 | type: 'm.room.message', | |
| 120 | sender: '@alice:matrix.org', | |
| 121 | event_id: '$dup1', | |
| 122 | content: { msgtype: 'm.text', body: 'Hello!' } | |
| 123 | } | |
| 124 | ||
| 125 | await client._handleEvent('!room:matrix.org', event) | |
| 126 | await client._handleEvent('!room:matrix.org', event) | |
| 127 | await client._handleEvent('!room:matrix.org', event) | |
| 128 | ||
| 129 | t.is(count, 1, 'only relayed once despite 3 deliveries') | |
| 130 | }) | |
| 131 | ||
| 132 | test('MatrixClient - _handleEvent ignores non-message events', async (t) => { | |
| 133 | let called = false | |
| 134 | const client = new MatrixClient({ | |
| 135 | userId: '@bot:matrix.org', | |
| 136 | onMessage: () => { called = true } | |
| 137 | }) | |
| 138 | ||
| 139 | await client._handleEvent('!room:matrix.org', { | |
| 140 | type: 'm.room.member', | |
| 141 | sender: '@alice:matrix.org', | |
| 142 | event_id: '$state1', | |
| 143 | content: { membership: 'join' } | |
| 144 | }) | |
| 145 | ||
| 146 | t.is(called, false, 'should ignore state events') | |
| 147 | }) | |
| 148 | ||
| 149 | test('MatrixClient - _handleEvent ignores events with no content', async (t) => { | |
| 150 | let called = false | |
| 151 | const client = new MatrixClient({ | |
| 152 | userId: '@bot:matrix.org', | |
| 153 | onMessage: () => { called = true } | |
| 154 | }) | |
| 155 | ||
| 156 | await client._handleEvent('!room:matrix.org', { | |
| 157 | type: 'm.room.message', | |
| 158 | sender: '@alice:matrix.org', | |
| 159 | event_id: '$nocontent1', | |
| 160 | content: null | |
| 161 | }) | |
| 162 | ||
| 163 | t.is(called, false, 'should ignore events with no content') | |
| 164 | }) | |
| 165 | ||
| 166 | test('MatrixClient - _handleEvent uses plain body over formatted', async (t) => { | |
| 167 | let received = null | |
| 168 | const client = new MatrixClient({ | |
| 169 | userId: '@bot:matrix.org', | |
| 170 | onMessage: (roomId, sender, text) => { received = text } | |
| 171 | }) | |
| 172 | client._displayNameCache.set('@alice:matrix.org', 'Alice') | |
| 173 | ||
| 174 | await client._handleEvent('!room:matrix.org', { | |
| 175 | type: 'm.room.message', | |
| 176 | sender: '@alice:matrix.org', | |
| 177 | event_id: '$formatted1', | |
| 178 | content: { | |
| 179 | msgtype: 'm.text', | |
| 180 | body: 'Hello **world**', | |
| 181 | format: 'org.matrix.custom.html', | |
| 182 | formatted_body: 'Hello <strong>world</strong>' | |
| 183 | } | |
| 184 | }) | |
| 185 | ||
| 186 | t.is(received, 'Hello **world**', 'uses plain body text, not HTML') | |
| 187 | }) | |
| 188 | ||
| 189 | test('MatrixClient - txnId increments', (t) => { | |
| 190 | const client = new MatrixClient() | |
| 191 | t.is(client._txnId, 0) | |
| 192 | // Note: can't easily test sendMessage without a server, but | |
| 193 | // verify the counter is accessible | |
| 194 | client._txnId++ | |
| 195 | t.is(client._txnId, 1) | |
| 196 | }) | |
| 197 | ||
| 198 | test('stripHtml in matrix-client module', (t) => { | |
| 199 | t.is(stripHtml('<p>Hello</p>'), 'Hello') | |
| 200 | t.is(stripHtml('& <'), '& <') | |
| 201 | t.is(stripHtml('Hello<br>World'), 'Hello\nWorld') | |
| 202 | }) | |
| 203 | ||
| 204 | test('sync response parsing - extract messages from rooms.join', async (t) => { | |
| 205 | // Simulate what _handleEvent would do with a sync response | |
| 206 | const messages = [] | |
| 207 | const client = new MatrixClient({ | |
| 208 | userId: '@bot:matrix.org', | |
| 209 | onMessage: (roomId, sender, text, eventId) => { | |
| 210 | messages.push({ roomId, sender, text, eventId }) | |
| 211 | } | |
| 212 | }) | |
| 213 | client._displayNameCache.set('@alice:matrix.org', 'Alice') | |
| 214 | client._displayNameCache.set('@bob:matrix.org', 'Bob') | |
| 215 | ||
| 216 | // Simulate sync response structure | |
| 217 | const syncResponse = { | |
| 218 | next_batch: 's1234', | |
| 219 | rooms: { | |
| 220 | join: { | |
| 221 | '!room:matrix.org': { | |
| 222 | timeline: { | |
| 223 | events: [ | |
| 224 | { | |
| 225 | type: 'm.room.message', | |
| 226 | sender: '@alice:matrix.org', | |
| 227 | event_id: '$ev1', | |
| 228 | content: { msgtype: 'm.text', body: 'First message' } | |
| 229 | }, | |
| 230 | { | |
| 231 | type: 'm.room.message', | |
| 232 | sender: '@bob:matrix.org', | |
| 233 | event_id: '$ev2', | |
| 234 | content: { msgtype: 'm.text', body: 'Second message' } | |
| 235 | } | |
| 236 | ] | |
| 237 | } | |
| 238 | } | |
| 239 | } | |
| 240 | } | |
| 241 | } | |
| 242 | ||
| 243 | // Process like the sync loop would | |
| 244 | const joined = syncResponse.rooms.join | |
| 245 | for (const [roomId, roomData] of Object.entries(joined)) { | |
| 246 | const events = roomData.timeline?.events || [] | |
| 247 | for (const event of events) { | |
| 248 | await client._handleEvent(roomId, event) | |
| 249 | } | |
| 250 | } | |
| 251 | ||
| 252 | t.is(messages.length, 2) | |
| 253 | t.is(messages[0].sender, 'Alice') | |
| 254 | t.is(messages[0].text, 'First message') | |
| 255 | t.is(messages[1].sender, 'Bob') | |
| 256 | t.is(messages[1].text, 'Second message') | |
| 257 | }) | |
| 258 | ||
| 259 | test('login response parsing', (t) => { | |
| 260 | // Verify the client can process a login response structure | |
| 261 | const client = new MatrixClient() | |
| 262 | ||
| 263 | // Simulate what login() does with the response | |
| 264 | const loginResponse = { | |
| 265 | user_id: '@bot:matrix.org', | |
| 266 | access_token: 'syt_long_token_here', | |
| 267 | device_id: 'ABCDEF', | |
| 268 | home_server: 'matrix.org' | |
| 269 | } | |
| 270 | ||
| 271 | client.accessToken = loginResponse.access_token | |
| 272 | client.userId = loginResponse.user_id | |
| 273 | ||
| 274 | t.is(client.accessToken, 'syt_long_token_here') | |
| 275 | t.is(client.userId, '@bot:matrix.org') | |
| 276 | }) |
| … | … | @@ -0,0 +1,4 @@ |
| 1 | import { Vault } from './lib/vault.js' | |
| 2 | import { generateKey, deriveKey } from './lib/crypto.js' | |
| 3 | ||
| 4 | export { Vault, generateKey, deriveKey } |
| … | … | @@ -0,0 +1,58 @@ |
| 1 | import sodium from 'sodium-native' | |
| 2 | import b4a from 'b4a' | |
| 3 | ||
| 4 | const NONCE_BYTES = sodium.crypto_secretbox_NONCEBYTES | |
| 5 | const KEY_BYTES = sodium.crypto_secretbox_KEYBYTES | |
| 6 | const MAC_BYTES = sodium.crypto_secretbox_MACBYTES | |
| 7 | ||
| 8 | function generateKey () { | |
| 9 | const key = b4a.allocUnsafe(KEY_BYTES) | |
| 10 | sodium.randombytes_buf(key) | |
| 11 | return key | |
| 12 | } | |
| 13 | ||
| 14 | function deriveKey (password, salt) { | |
| 15 | if (!salt) { | |
| 16 | salt = b4a.allocUnsafe(sodium.crypto_pwhash_SALTBYTES) | |
| 17 | sodium.randombytes_buf(salt) | |
| 18 | } | |
| 19 | const key = b4a.allocUnsafe(KEY_BYTES) | |
| 20 | sodium.crypto_pwhash( | |
| 21 | key, | |
| 22 | b4a.from(password), | |
| 23 | salt, | |
| 24 | sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE, | |
| 25 | sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE, | |
| 26 | sodium.crypto_pwhash_ALG_DEFAULT | |
| 27 | ) | |
| 28 | return { key, salt } | |
| 29 | } | |
| 30 | ||
| 31 | function encrypt (plaintext, key) { | |
| 32 | const nonce = b4a.allocUnsafe(NONCE_BYTES) | |
| 33 | sodium.randombytes_buf(nonce) | |
| 34 | ||
| 35 | const input = b4a.isBuffer(plaintext) ? plaintext : b4a.from(plaintext) | |
| 36 | const cipher = b4a.allocUnsafe(input.length + MAC_BYTES) | |
| 37 | ||
| 38 | sodium.crypto_secretbox_easy(cipher, input, nonce, key) | |
| 39 | ||
| 40 | // Return nonce + ciphertext | |
| 41 | const result = b4a.allocUnsafe(NONCE_BYTES + cipher.length) | |
| 42 | b4a.copy(nonce, result, 0) | |
| 43 | b4a.copy(cipher, result, NONCE_BYTES) | |
| 44 | return result | |
| 45 | } | |
| 46 | ||
| 47 | function decrypt (encrypted, key) { | |
| 48 | const nonce = encrypted.subarray(0, NONCE_BYTES) | |
| 49 | const cipher = encrypted.subarray(NONCE_BYTES) | |
| 50 | ||
| 51 | const plaintext = b4a.allocUnsafe(cipher.length - MAC_BYTES) | |
| 52 | const ok = sodium.crypto_secretbox_open_easy(plaintext, cipher, nonce, key) | |
| 53 | if (!ok) throw new Error('Decryption failed — wrong key or corrupted data') | |
| 54 | ||
| 55 | return plaintext | |
| 56 | } | |
| 57 | ||
| 58 | export { generateKey, deriveKey, encrypt, decrypt, KEY_BYTES } |
| … | … | @@ -0,0 +1,96 @@ |
| 1 | import Corestore from 'corestore' | |
| 2 | import Hyperbee from 'hyperbee' | |
| 3 | import b4a from 'b4a' | |
| 4 | import { encrypt, decrypt, generateKey } from './crypto.js' | |
| 5 | ||
| 6 | class Vault { | |
| 7 | constructor (storage, opts = {}) { | |
| 8 | this._storage = storage | |
| 9 | this._key = opts.key || generateKey() | |
| 10 | this._store = null | |
| 11 | this._db = null | |
| 12 | this._ready = false | |
| 13 | } | |
| 14 | ||
| 15 | async ready () { | |
| 16 | if (this._ready) return | |
| 17 | ||
| 18 | this._store = new Corestore(this._storage) | |
| 19 | const core = this._store.get({ name: 'bridge-vault' }) | |
| 20 | this._db = new Hyperbee(core, { | |
| 21 | keyEncoding: 'utf-8', | |
| 22 | valueEncoding: 'binary' | |
| 23 | }) | |
| 24 | await this._db.ready() | |
| 25 | this._ready = true | |
| 26 | } | |
| 27 | ||
| 28 | _key4 (platform, name) { | |
| 29 | return `cred/${platform}/${name}` | |
| 30 | } | |
| 31 | ||
| 32 | async put (platform, name, value) { | |
| 33 | await this.ready() | |
| 34 | const plaintext = b4a.from(JSON.stringify(value)) | |
| 35 | const encrypted = encrypt(plaintext, this._key) | |
| 36 | await this._db.put(this._key4(platform, name), encrypted) | |
| 37 | } | |
| 38 | ||
| 39 | async get (platform, name) { | |
| 40 | await this.ready() | |
| 41 | const node = await this._db.get(this._key4(platform, name)) | |
| 42 | if (!node) return null | |
| 43 | ||
| 44 | const plaintext = decrypt(node.value, this._key) | |
| 45 | return JSON.parse(b4a.toString(plaintext)) | |
| 46 | } | |
| 47 | ||
| 48 | async del (platform, name) { | |
| 49 | await this.ready() | |
| 50 | await this._db.del(this._key4(platform, name)) | |
| 51 | } | |
| 52 | ||
| 53 | async list (platform) { | |
| 54 | await this.ready() | |
| 55 | const prefix = `cred/${platform}/` | |
| 56 | const results = [] | |
| 57 | ||
| 58 | for await (const node of this._db.createReadStream({ | |
| 59 | gte: prefix, | |
| 60 | lt: prefix + '\xff' | |
| 61 | })) { | |
| 62 | const name = node.key.slice(prefix.length) | |
| 63 | try { | |
| 64 | const plaintext = decrypt(node.value, this._key) | |
| 65 | results.push({ name, value: JSON.parse(b4a.toString(plaintext)) }) | |
| 66 | } catch { | |
| 67 | results.push({ name, value: null, error: 'decrypt failed' }) | |
| 68 | } | |
| 69 | } | |
| 70 | ||
| 71 | return results | |
| 72 | } | |
| 73 | ||
| 74 | async listPlatforms () { | |
| 75 | await this.ready() | |
| 76 | const platforms = new Set() | |
| 77 | const prefix = 'cred/' | |
| 78 | ||
| 79 | for await (const node of this._db.createReadStream({ | |
| 80 | gte: prefix, | |
| 81 | lt: prefix + '\xff' | |
| 82 | })) { | |
| 83 | const parts = node.key.slice(prefix.length).split('/') | |
| 84 | if (parts.length >= 1) platforms.add(parts[0]) | |
| 85 | } | |
| 86 | ||
| 87 | return [...platforms] | |
| 88 | } | |
| 89 | ||
| 90 | async close () { | |
| 91 | if (this._store) await this._store.close() | |
| 92 | this._ready = false | |
| 93 | } | |
| 94 | } | |
| 95 | ||
| 96 | export { Vault } |
| … | … | @@ -0,0 +1,1445 @@ |
| 1 | { | |
| 2 | "name": "bridge-vault", | |
| 3 | "version": "1.0.0", | |
| 4 | "lockfileVersion": 3, | |
| 5 | "requires": true, | |
| 6 | "packages": { | |
| 7 | "": { | |
| 8 | "name": "bridge-vault", | |
| 9 | "version": "1.0.0", | |
| 10 | "license": "MIT", | |
| 11 | "dependencies": { | |
| 12 | "b4a": "^1.0.0", | |
| 13 | "compact-encoding": "^2.0.0", | |
| 14 | "corestore": "^6.0.0", | |
| 15 | "hyperbee": "^2.0.0", | |
| 16 | "hypercore": "^10.0.0", | |
| 17 | "sodium-native": "^4.0.0" | |
| 18 | }, | |
| 19 | "devDependencies": { | |
| 20 | "brittle": "^3.0.0" | |
| 21 | } | |
| 22 | }, | |
| 23 | "node_modules/@hyperswarm/secret-stream": { | |
| 24 | "version": "6.9.1", | |
| 25 | "resolved": "https://registry.npmjs.org/@hyperswarm/secret-stream/-/secret-stream-6.9.1.tgz", | |
| 26 | "integrity": "sha512-xb0S5y3YJwBakD77JOGBHlBxdp63mHClZoXBYoLv+9wH8e054ESKlmQptWqjJK5dv5VMUIVYOJB4MaOpB0JdGw==", | |
| 27 | "license": "Apache-2.0", | |
| 28 | "dependencies": { | |
| 29 | "b4a": "^1.1.0", | |
| 30 | "hypercore-crypto": "^3.3.1", | |
| 31 | "noise-curve-ed": "^2.0.1", | |
| 32 | "noise-handshake": "^4.0.0", | |
| 33 | "sodium-secretstream": "^1.1.0", | |
| 34 | "sodium-universal": "^5.0.0", | |
| 35 | "streamx": "^2.14.0", | |
| 36 | "timeout-refresh": "^2.0.0", | |
| 37 | "unslab": "^1.3.0" | |
| 38 | } | |
| 39 | }, | |
| 40 | "node_modules/@hyperswarm/secret-stream/node_modules/sodium-native": { | |
| 41 | "version": "5.0.10", | |
| 42 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 43 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 44 | "license": "MIT", | |
| 45 | "dependencies": { | |
| 46 | "require-addon": "^1.1.0", | |
| 47 | "which-runtime": "^1.2.1" | |
| 48 | }, | |
| 49 | "engines": { | |
| 50 | "bare": ">=1.16.0" | |
| 51 | } | |
| 52 | }, | |
| 53 | "node_modules/@hyperswarm/secret-stream/node_modules/sodium-universal": { | |
| 54 | "version": "5.0.1", | |
| 55 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 56 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 57 | "license": "MIT", | |
| 58 | "dependencies": { | |
| 59 | "sodium-native": "^5.0.1" | |
| 60 | }, | |
| 61 | "peerDependencies": { | |
| 62 | "sodium-javascript": "~0.8.0" | |
| 63 | }, | |
| 64 | "peerDependenciesMeta": { | |
| 65 | "sodium-javascript": { | |
| 66 | "optional": true | |
| 67 | } | |
| 68 | } | |
| 69 | }, | |
| 70 | "node_modules/@jridgewell/resolve-uri": { | |
| 71 | "version": "3.1.2", | |
| 72 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", | |
| 73 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", | |
| 74 | "dev": true, | |
| 75 | "license": "MIT", | |
| 76 | "engines": { | |
| 77 | "node": ">=6.0.0" | |
| 78 | } | |
| 79 | }, | |
| 80 | "node_modules/@jridgewell/sourcemap-codec": { | |
| 81 | "version": "1.5.5", | |
| 82 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", | |
| 83 | "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", | |
| 84 | "dev": true, | |
| 85 | "license": "MIT" | |
| 86 | }, | |
| 87 | "node_modules/@jridgewell/trace-mapping": { | |
| 88 | "version": "0.3.31", | |
| 89 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", | |
| 90 | "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", | |
| 91 | "dev": true, | |
| 92 | "license": "MIT", | |
| 93 | "dependencies": { | |
| 94 | "@jridgewell/resolve-uri": "^3.1.0", | |
| 95 | "@jridgewell/sourcemap-codec": "^1.4.14" | |
| 96 | } | |
| 97 | }, | |
| 98 | "node_modules/@types/istanbul-lib-coverage": { | |
| 99 | "version": "2.0.6", | |
| 100 | "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", | |
| 101 | "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", | |
| 102 | "dev": true, | |
| 103 | "license": "MIT" | |
| 104 | }, | |
| 105 | "node_modules/b4a": { | |
| 106 | "version": "1.7.4", | |
| 107 | "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.4.tgz", | |
| 108 | "integrity": "sha512-u20zJLDaSWpxaZ+zaAkEIB2dZZ1o+DF4T/MRbmsvGp9nletHOyiai19OzX1fF8xUBYsO1bPXxODvcd0978pnug==", | |
| 109 | "license": "Apache-2.0", | |
| 110 | "peerDependencies": { | |
| 111 | "react-native-b4a": "*" | |
| 112 | }, | |
| 113 | "peerDependenciesMeta": { | |
| 114 | "react-native-b4a": { | |
| 115 | "optional": true | |
| 116 | } | |
| 117 | } | |
| 118 | }, | |
| 119 | "node_modules/bare-addon-resolve": { | |
| 120 | "version": "1.10.0", | |
| 121 | "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.0.tgz", | |
| 122 | "integrity": "sha512-sSd0jieRJlDaODOzj0oe0RjFVC1QI0ZIjGIdPkbrTXsdVVtENg14c+lHHAhHwmWCZ2nQlMhy8jA3Y5LYPc/isA==", | |
| 123 | "license": "Apache-2.0", | |
| 124 | "dependencies": { | |
| 125 | "bare-module-resolve": "^1.10.0", | |
| 126 | "bare-semver": "^1.0.0" | |
| 127 | }, | |
| 128 | "peerDependencies": { | |
| 129 | "bare-url": "*" | |
| 130 | }, | |
| 131 | "peerDependenciesMeta": { | |
| 132 | "bare-url": { | |
| 133 | "optional": true | |
| 134 | } | |
| 135 | } | |
| 136 | }, | |
| 137 | "node_modules/bare-ansi-escapes": { | |
| 138 | "version": "2.2.3", | |
| 139 | "resolved": "https://registry.npmjs.org/bare-ansi-escapes/-/bare-ansi-escapes-2.2.3.tgz", | |
| 140 | "integrity": "sha512-02ES4/E2RbrtZSnHJ9LntBhYkLA6lPpSEeP8iqS3MccBIVhVBlEmruF1I7HZqx5Q8aiTeYfQVeqmrU9YO2yYoQ==", | |
| 141 | "dev": true, | |
| 142 | "license": "Apache-2.0", | |
| 143 | "dependencies": { | |
| 144 | "bare-stream": "^2.6.5" | |
| 145 | }, | |
| 146 | "peerDependencies": { | |
| 147 | "bare-buffer": "*" | |
| 148 | }, | |
| 149 | "peerDependenciesMeta": { | |
| 150 | "bare-buffer": { | |
| 151 | "optional": true | |
| 152 | } | |
| 153 | } | |
| 154 | }, | |
| 155 | "node_modules/bare-assert": { | |
| 156 | "version": "1.2.0", | |
| 157 | "resolved": "https://registry.npmjs.org/bare-assert/-/bare-assert-1.2.0.tgz", | |
| 158 | "integrity": "sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==", | |
| 159 | "dev": true, | |
| 160 | "license": "Apache-2.0", | |
| 161 | "dependencies": { | |
| 162 | "bare-inspect": "^3.1.2" | |
| 163 | } | |
| 164 | }, | |
| 165 | "node_modules/bare-bundle": { | |
| 166 | "version": "1.10.0", | |
| 167 | "resolved": "https://registry.npmjs.org/bare-bundle/-/bare-bundle-1.10.0.tgz", | |
| 168 | "integrity": "sha512-4LVlnJAHr00Hh6Vu6ZUJS38rcEtJT3b3vChXSsBsJ2mk1TN0lQ+gzd+Dw5L0aV7uqDZv84smuwW+O02X7PfDlw==", | |
| 169 | "dev": true, | |
| 170 | "license": "Apache-2.0", | |
| 171 | "peerDependencies": { | |
| 172 | "bare-buffer": "*", | |
| 173 | "bare-url": "*" | |
| 174 | }, | |
| 175 | "peerDependenciesMeta": { | |
| 176 | "bare-buffer": { | |
| 177 | "optional": true | |
| 178 | }, | |
| 179 | "bare-url": { | |
| 180 | "optional": true | |
| 181 | } | |
| 182 | } | |
| 183 | }, | |
| 184 | "node_modules/bare-cov": { | |
| 185 | "version": "1.2.1", | |
| 186 | "resolved": "https://registry.npmjs.org/bare-cov/-/bare-cov-1.2.1.tgz", | |
| 187 | "integrity": "sha512-SHNqb/WG9udUEj8m5qGSehoUQ9o0Xzv8nrf4HIdmG+KNU/XdhPLhTP5VPrHnSAMAXhAzEB4Q+2IYZ0x9qPzN1g==", | |
| 188 | "dev": true, | |
| 189 | "license": "Apache-2.0", | |
| 190 | "dependencies": { | |
| 191 | "bare-fs": "^4.1.2", | |
| 192 | "bare-inspector": "^4.0.1", | |
| 193 | "bare-path": "^3.0.0", | |
| 194 | "bare-process": "^4.2.1", | |
| 195 | "bare-url": "^2.1.5", | |
| 196 | "bare-v8-to-istanbul": "^1.0.2", | |
| 197 | "picomatch": "^4.0.2", | |
| 198 | "which-runtime": "^1.2.1" | |
| 199 | } | |
| 200 | }, | |
| 201 | "node_modules/bare-crypto": { | |
| 202 | "version": "1.13.0", | |
| 203 | "resolved": "https://registry.npmjs.org/bare-crypto/-/bare-crypto-1.13.0.tgz", | |
| 204 | "integrity": "sha512-RQl13yD+YTACWUZHMMck0C6LNjXgGgRyhVC557ag0xIrkxXf4IQwpnc8WOB58f4k5kSzKhIjy05oW3HUBrFpSQ==", | |
| 205 | "dev": true, | |
| 206 | "license": "Apache-2.0", | |
| 207 | "dependencies": { | |
| 208 | "bare-stream": "^2.6.3" | |
| 209 | }, | |
| 210 | "peerDependencies": { | |
| 211 | "bare-buffer": "*" | |
| 212 | }, | |
| 213 | "peerDependenciesMeta": { | |
| 214 | "bare-buffer": { | |
| 215 | "optional": true | |
| 216 | } | |
| 217 | } | |
| 218 | }, | |
| 219 | "node_modules/bare-debug-log": { | |
| 220 | "version": "2.0.0", | |
| 221 | "resolved": "https://registry.npmjs.org/bare-debug-log/-/bare-debug-log-2.0.0.tgz", | |
| 222 | "integrity": "sha512-Vi42PkMQsNV9PUpx2Gl1hikshx5O9FzMJ6o9Nnopseg7qLBBK7Nl31d0RHcfwLEAfmcPApytpc0ZFfq68u22FQ==", | |
| 223 | "dev": true, | |
| 224 | "license": "Apache-2.0", | |
| 225 | "dependencies": { | |
| 226 | "bare-os": "^3.0.1" | |
| 227 | } | |
| 228 | }, | |
| 229 | "node_modules/bare-dns": { | |
| 230 | "version": "2.1.4", | |
| 231 | "resolved": "https://registry.npmjs.org/bare-dns/-/bare-dns-2.1.4.tgz", | |
| 232 | "integrity": "sha512-abwjHmpWqSRNB7V5615QxPH92L71AVzFm/kKTs8VYiNTAi2xVdonpv0BjJ0hwXLwomoW+xsSOPjW6PZPO14asg==", | |
| 233 | "dev": true, | |
| 234 | "license": "Apache-2.0", | |
| 235 | "engines": { | |
| 236 | "bare": ">=1.7.0" | |
| 237 | } | |
| 238 | }, | |
| 239 | "node_modules/bare-encoding": { | |
| 240 | "version": "1.0.3", | |
| 241 | "resolved": "https://registry.npmjs.org/bare-encoding/-/bare-encoding-1.0.3.tgz", | |
| 242 | "integrity": "sha512-Kqf+t/azs13lUeyK4Tb7ha4wdLRXKWCXQ8w1rVmt7KtoPCPdHD/Xwt7LBIsCSwwGglrcmblo5VOLa5avkJqULA==", | |
| 243 | "dev": true, | |
| 244 | "license": "Apache-2.0", | |
| 245 | "peerDependencies": { | |
| 246 | "bare-buffer": "*" | |
| 247 | }, | |
| 248 | "peerDependenciesMeta": { | |
| 249 | "bare-buffer": { | |
| 250 | "optional": true | |
| 251 | } | |
| 252 | } | |
| 253 | }, | |
| 254 | "node_modules/bare-env": { | |
| 255 | "version": "3.0.0", | |
| 256 | "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.0.tgz", | |
| 257 | "integrity": "sha512-0u964P5ZLAxTi+lW4Kjp7YRJQ5gZr9ycYOtjLxsSrupgMz3sn5Z9n4SH/JIifHwvadsf1brA2JAjP+9IOWwTiw==", | |
| 258 | "dev": true, | |
| 259 | "license": "Apache-2.0", | |
| 260 | "dependencies": { | |
| 261 | "bare-os": "^3.0.1" | |
| 262 | } | |
| 263 | }, | |
| 264 | "node_modules/bare-events": { | |
| 265 | "version": "2.8.2", | |
| 266 | "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", | |
| 267 | "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", | |
| 268 | "license": "Apache-2.0", | |
| 269 | "peerDependencies": { | |
| 270 | "bare-abort-controller": "*" | |
| 271 | }, | |
| 272 | "peerDependenciesMeta": { | |
| 273 | "bare-abort-controller": { | |
| 274 | "optional": true | |
| 275 | } | |
| 276 | } | |
| 277 | }, | |
| 278 | "node_modules/bare-format": { | |
| 279 | "version": "1.0.1", | |
| 280 | "resolved": "https://registry.npmjs.org/bare-format/-/bare-format-1.0.1.tgz", | |
| 281 | "integrity": "sha512-1oS+LZrWK6tnYnvNSHDGljc2MPunRxwhpFriuCgzNF+oklrnwmBKD91tS0yt+jpl2j3UgcSDzBIMiVTvLs9A8w==", | |
| 282 | "dev": true, | |
| 283 | "license": "Apache-2.0", | |
| 284 | "dependencies": { | |
| 285 | "bare-inspect": "^3.0.0" | |
| 286 | } | |
| 287 | }, | |
| 288 | "node_modules/bare-fs": { | |
| 289 | "version": "4.5.4", | |
| 290 | "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.4.tgz", | |
| 291 | "integrity": "sha512-POK4oplfA7P7gqvetNmCs4CNtm9fNsx+IAh7jH7GgU0OJdge2rso0R20TNWVq6VoWcCvsTdlNDaleLHGaKx8CA==", | |
| 292 | "license": "Apache-2.0", | |
| 293 | "dependencies": { | |
| 294 | "bare-events": "^2.5.4", | |
| 295 | "bare-path": "^3.0.0", | |
| 296 | "bare-stream": "^2.6.4", | |
| 297 | "bare-url": "^2.2.2", | |
| 298 | "fast-fifo": "^1.3.2" | |
| 299 | }, | |
| 300 | "engines": { | |
| 301 | "bare": ">=1.16.0" | |
| 302 | }, | |
| 303 | "peerDependencies": { | |
| 304 | "bare-buffer": "*" | |
| 305 | }, | |
| 306 | "peerDependenciesMeta": { | |
| 307 | "bare-buffer": { | |
| 308 | "optional": true | |
| 309 | } | |
| 310 | } | |
| 311 | }, | |
| 312 | "node_modules/bare-hrtime": { | |
| 313 | "version": "2.1.1", | |
| 314 | "resolved": "https://registry.npmjs.org/bare-hrtime/-/bare-hrtime-2.1.1.tgz", | |
| 315 | "integrity": "sha512-VMb3tHo05gsnbu3OXTmkDiwTjMlOsbQmKoysKqKEyR09m77TuDrYFbj3Q5GGk10dAKsUHrnXmwCaeJqzVpB5ZA==", | |
| 316 | "dev": true, | |
| 317 | "license": "Apache-2.0" | |
| 318 | }, | |
| 319 | "node_modules/bare-http-parser": { | |
| 320 | "version": "1.0.1", | |
| 321 | "resolved": "https://registry.npmjs.org/bare-http-parser/-/bare-http-parser-1.0.1.tgz", | |
| 322 | "integrity": "sha512-A3LTDTcELcmNJ3g5liIaS038v/BQxOhA9cjhBESn7eoV7QCuMoIRBKLDadDe08flxyLbxI2f+1l2MZ/5+HnKPA==", | |
| 323 | "dev": true, | |
| 324 | "license": "Apache-2.0" | |
| 325 | }, | |
| 326 | "node_modules/bare-http1": { | |
| 327 | "version": "4.2.3", | |
| 328 | "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.2.3.tgz", | |
| 329 | "integrity": "sha512-zyvHF3xzvBJNpuw0QydE9etTn2AyC/TRVDWTo8Ed3LQEEGaFOkv6KHhaJPlf4eWEeTekk41Fowie+fTsXoNxkA==", | |
| 330 | "dev": true, | |
| 331 | "license": "Apache-2.0", | |
| 332 | "dependencies": { | |
| 333 | "bare-events": "^2.6.0", | |
| 334 | "bare-http-parser": "^1.0.0", | |
| 335 | "bare-stream": "^2.3.0", | |
| 336 | "bare-tcp": "^2.2.0" | |
| 337 | }, | |
| 338 | "peerDependencies": { | |
| 339 | "bare-buffer": "*", | |
| 340 | "bare-url": "*" | |
| 341 | }, | |
| 342 | "peerDependenciesMeta": { | |
| 343 | "bare-buffer": { | |
| 344 | "optional": true | |
| 345 | }, | |
| 346 | "bare-url": { | |
| 347 | "optional": true | |
| 348 | } | |
| 349 | } | |
| 350 | }, | |
| 351 | "node_modules/bare-https": { | |
| 352 | "version": "2.1.2", | |
| 353 | "resolved": "https://registry.npmjs.org/bare-https/-/bare-https-2.1.2.tgz", | |
| 354 | "integrity": "sha512-Q+TTydUDsuKQJvh8dX2dvOXCR9fM3xR5TBmKaFrs5p7Lj7XbKX7v4vIUJ36H0SXg2xCOQxXKIbjwrLg5tfJNYg==", | |
| 355 | "dev": true, | |
| 356 | "license": "Apache-2.0", | |
| 357 | "dependencies": { | |
| 358 | "bare-http1": "^4.0.0", | |
| 359 | "bare-tcp": "^2.2.0", | |
| 360 | "bare-tls": "^2.0.0" | |
| 361 | } | |
| 362 | }, | |
| 363 | "node_modules/bare-inspect": { | |
| 364 | "version": "3.1.4", | |
| 365 | "resolved": "https://registry.npmjs.org/bare-inspect/-/bare-inspect-3.1.4.tgz", | |
| 366 | "integrity": "sha512-jfW5KRA84o3REpI6Vr4nbvMn+hqVAw8GU1mMdRwUsY5yJovQamxYeKGVKGqdzs+8ZbG4jRzGUXP/3Ji/DnqfPg==", | |
| 367 | "dev": true, | |
| 368 | "license": "Apache-2.0", | |
| 369 | "dependencies": { | |
| 370 | "bare-ansi-escapes": "^2.1.0", | |
| 371 | "bare-type": "^1.0.0" | |
| 372 | }, | |
| 373 | "engines": { | |
| 374 | "bare": ">=1.18.0" | |
| 375 | } | |
| 376 | }, | |
| 377 | "node_modules/bare-inspector": { | |
| 378 | "version": "4.0.3", | |
| 379 | "resolved": "https://registry.npmjs.org/bare-inspector/-/bare-inspector-4.0.3.tgz", | |
| 380 | "integrity": "sha512-yYk6+7tTVKJKHOja5+QBn989ETv8oVsmwoH0nCM+QYC6w5MC03GC4aQNK+lipAZSz1EaAj42WZ8Wewjpy0L9TA==", | |
| 381 | "dev": true, | |
| 382 | "license": "Apache-2.0", | |
| 383 | "dependencies": { | |
| 384 | "bare-events": "^2.1.0", | |
| 385 | "bare-http1": "^4.0.0", | |
| 386 | "bare-stream": "^2.0.0", | |
| 387 | "bare-url": "^2.0.0", | |
| 388 | "bare-ws": "^2.0.0" | |
| 389 | }, | |
| 390 | "engines": { | |
| 391 | "bare": ">=1.2.0" | |
| 392 | }, | |
| 393 | "peerDependencies": { | |
| 394 | "bare-tcp": "*" | |
| 395 | }, | |
| 396 | "peerDependenciesMeta": { | |
| 397 | "bare-tcp": { | |
| 398 | "optional": true | |
| 399 | } | |
| 400 | } | |
| 401 | }, | |
| 402 | "node_modules/bare-module": { | |
| 403 | "version": "6.1.3", | |
| 404 | "resolved": "https://registry.npmjs.org/bare-module/-/bare-module-6.1.3.tgz", | |
| 405 | "integrity": "sha512-5XWsVHsvtWMH4tK4DQWgpNTV0t/sg3ZrAaQLIxrwjrS5+u8Q9vEgc/zQ4QaDPWDse/y/5h+d+YG1Q0JfSMt0zA==", | |
| 406 | "dev": true, | |
| 407 | "license": "Apache-2.0", | |
| 408 | "dependencies": { | |
| 409 | "bare-bundle": "^1.3.0", | |
| 410 | "bare-module-lexer": "^1.0.0", | |
| 411 | "bare-module-resolve": "^1.8.0", | |
| 412 | "bare-path": "^3.0.0", | |
| 413 | "bare-url": "^2.0.1" | |
| 414 | }, | |
| 415 | "engines": { | |
| 416 | "bare": ">=1.23.0" | |
| 417 | }, | |
| 418 | "peerDependencies": { | |
| 419 | "bare-buffer": "*" | |
| 420 | }, | |
| 421 | "peerDependenciesMeta": { | |
| 422 | "bare-buffer": { | |
| 423 | "optional": true | |
| 424 | } | |
| 425 | } | |
| 426 | }, | |
| 427 | "node_modules/bare-module-lexer": { | |
| 428 | "version": "1.4.7", | |
| 429 | "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.4.7.tgz", | |
| 430 | "integrity": "sha512-0klU4eMsjh/wcxi8FdHmNom2j2F4kmkXOhyJFL9qTaSFp2lE3m6BtbKgMHY8R5miqC9r8/IfA8wzXnC5Os14WA==", | |
| 431 | "dev": true, | |
| 432 | "license": "Apache-2.0", | |
| 433 | "dependencies": { | |
| 434 | "require-addon": "^1.0.2" | |
| 435 | }, | |
| 436 | "peerDependencies": { | |
| 437 | "bare-buffer": "*" | |
| 438 | }, | |
| 439 | "peerDependenciesMeta": { | |
| 440 | "bare-buffer": { | |
| 441 | "optional": true | |
| 442 | } | |
| 443 | } | |
| 444 | }, | |
| 445 | "node_modules/bare-module-resolve": { | |
| 446 | "version": "1.12.1", | |
| 447 | "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.1.tgz", | |
| 448 | "integrity": "sha512-hbmAPyFpEq8FoZMd5sFO3u6MC5feluWoGE8YKlA8fCrl6mNtx68Wjg4DTiDJcqRJaovTvOYKfYngoBUnbaT7eg==", | |
| 449 | "license": "Apache-2.0", | |
| 450 | "dependencies": { | |
| 451 | "bare-semver": "^1.0.0" | |
| 452 | }, | |
| 453 | "peerDependencies": { | |
| 454 | "bare-url": "*" | |
| 455 | }, | |
| 456 | "peerDependenciesMeta": { | |
| 457 | "bare-url": { | |
| 458 | "optional": true | |
| 459 | } | |
| 460 | } | |
| 461 | }, | |
| 462 | "node_modules/bare-net": { | |
| 463 | "version": "2.2.0", | |
| 464 | "resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.2.0.tgz", | |
| 465 | "integrity": "sha512-UF7cAbHsGE+H6uEqWF5IULBow1x58chZz4g3ALgHtv7wZsFcCbRDt0JKWEumf5Oma3QWS1Q6aLi0Rpll8RElMg==", | |
| 466 | "dev": true, | |
| 467 | "license": "Apache-2.0", | |
| 468 | "dependencies": { | |
| 469 | "bare-events": "^2.2.2", | |
| 470 | "bare-pipe": "^4.0.0", | |
| 471 | "bare-stream": "^2.0.0", | |
| 472 | "bare-tcp": "^2.0.0" | |
| 473 | } | |
| 474 | }, | |
| 475 | "node_modules/bare-os": { | |
| 476 | "version": "3.6.2", | |
| 477 | "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", | |
| 478 | "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", | |
| 479 | "license": "Apache-2.0", | |
| 480 | "engines": { | |
| 481 | "bare": ">=1.14.0" | |
| 482 | } | |
| 483 | }, | |
| 484 | "node_modules/bare-path": { | |
| 485 | "version": "3.0.0", | |
| 486 | "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", | |
| 487 | "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", | |
| 488 | "license": "Apache-2.0", | |
| 489 | "dependencies": { | |
| 490 | "bare-os": "^3.0.1" | |
| 491 | } | |
| 492 | }, | |
| 493 | "node_modules/bare-pipe": { | |
| 494 | "version": "4.1.3", | |
| 495 | "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.1.3.tgz", | |
| 496 | "integrity": "sha512-DqQsx93rAzre6yJ9T6l/Vgh+X+bntkVMB1X5ggtXjXtqtMmF2Y2RVlCzxxy/09R6yeR9FSWBEUIaMYJL1/5VDA==", | |
| 497 | "dev": true, | |
| 498 | "license": "Apache-2.0", | |
| 499 | "dependencies": { | |
| 500 | "bare-events": "^2.0.0", | |
| 501 | "bare-stream": "^2.0.0" | |
| 502 | }, | |
| 503 | "engines": { | |
| 504 | "bare": ">=1.16.0" | |
| 505 | } | |
| 506 | }, | |
| 507 | "node_modules/bare-process": { | |
| 508 | "version": "4.2.2", | |
| 509 | "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.2.2.tgz", | |
| 510 | "integrity": "sha512-ikzEw+HGLB+2lS/WLVEsqi8BXBwzleG3n7DYI0v6/YNklvNabOIGlLd1dof+7Jw5ob4jsBRPtyflweVahY7rFA==", | |
| 511 | "dev": true, | |
| 512 | "license": "Apache-2.0", | |
| 513 | "dependencies": { | |
| 514 | "bare-env": "^3.0.0", | |
| 515 | "bare-events": "^2.3.1", | |
| 516 | "bare-hrtime": "^2.0.0", | |
| 517 | "bare-os": "^3.5.0", | |
| 518 | "bare-pipe": "^4.0.0", | |
| 519 | "bare-signals": "^4.0.0", | |
| 520 | "bare-tty": "^5.0.0" | |
| 521 | } | |
| 522 | }, | |
| 523 | "node_modules/bare-semver": { | |
| 524 | "version": "1.0.2", | |
| 525 | "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.0.2.tgz", | |
| 526 | "integrity": "sha512-ESVaN2nzWhcI5tf3Zzcq9aqCZ676VWzqw07eEZ0qxAcEOAFYBa0pWq8sK34OQeHLY3JsfKXZS9mDyzyxGjeLzA==", | |
| 527 | "license": "Apache-2.0" | |
| 528 | }, | |
| 529 | "node_modules/bare-signals": { | |
| 530 | "version": "4.2.0", | |
| 531 | "resolved": "https://registry.npmjs.org/bare-signals/-/bare-signals-4.2.0.tgz", | |
| 532 | "integrity": "sha512-fNHMOdQIlYuTvMB3Oh9Apk99hLKn351+Ir8vz+khiPTcOqIyGG4uWWjdLTzxWdYGsA0eT+We3y0K74hjj2nq7A==", | |
| 533 | "dev": true, | |
| 534 | "license": "Apache-2.0", | |
| 535 | "dependencies": { | |
| 536 | "bare-events": "^2.5.3", | |
| 537 | "bare-os": "^3.3.1" | |
| 538 | }, | |
| 539 | "engines": { | |
| 540 | "bare": ">=1.7.0" | |
| 541 | } | |
| 542 | }, | |
| 543 | "node_modules/bare-stream": { | |
| 544 | "version": "2.7.0", | |
| 545 | "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", | |
| 546 | "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", | |
| 547 | "license": "Apache-2.0", | |
| 548 | "dependencies": { | |
| 549 | "streamx": "^2.21.0" | |
| 550 | }, | |
| 551 | "peerDependencies": { | |
| 552 | "bare-buffer": "*", | |
| 553 | "bare-events": "*" | |
| 554 | }, | |
| 555 | "peerDependenciesMeta": { | |
| 556 | "bare-buffer": { | |
| 557 | "optional": true | |
| 558 | }, | |
| 559 | "bare-events": { | |
| 560 | "optional": true | |
| 561 | } | |
| 562 | } | |
| 563 | }, | |
| 564 | "node_modules/bare-subprocess": { | |
| 565 | "version": "5.2.2", | |
| 566 | "resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.2.tgz", | |
| 567 | "integrity": "sha512-L6oXgQ1aWs25RtG5Ky0bDD06p3RAcVVrDDMWb1DfXpHtyEWxamcyIWUbSykxMTWrpLlmSj6ytbb6yoKehGFfmw==", | |
| 568 | "dev": true, | |
| 569 | "license": "Apache-2.0", | |
| 570 | "dependencies": { | |
| 571 | "bare-env": "^3.0.0", | |
| 572 | "bare-events": "^2.5.4", | |
| 573 | "bare-os": "^3.0.1", | |
| 574 | "bare-pipe": "^4.0.0", | |
| 575 | "bare-url": "^2.2.2" | |
| 576 | }, | |
| 577 | "engines": { | |
| 578 | "bare": ">=1.7.0" | |
| 579 | }, | |
| 580 | "peerDependencies": { | |
| 581 | "bare-buffer": "*" | |
| 582 | }, | |
| 583 | "peerDependenciesMeta": { | |
| 584 | "bare-buffer": { | |
| 585 | "optional": true | |
| 586 | } | |
| 587 | } | |
| 588 | }, | |
| 589 | "node_modules/bare-tcp": { | |
| 590 | "version": "2.2.4", | |
| 591 | "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.2.4.tgz", | |
| 592 | "integrity": "sha512-vGjfMkyhS1lxp+Y4+gXRDR1ijZMK2cKx0RBwtYKUuZbD90ARCoM6bAiTa1HBztumCIC+NXb8eXdMFXyuYwlPyw==", | |
| 593 | "dev": true, | |
| 594 | "license": "Apache-2.0", | |
| 595 | "dependencies": { | |
| 596 | "bare-dns": "^2.0.4", | |
| 597 | "bare-events": "^2.5.4", | |
| 598 | "bare-stream": "^2.6.4" | |
| 599 | }, | |
| 600 | "engines": { | |
| 601 | "bare": ">=1.16.0" | |
| 602 | } | |
| 603 | }, | |
| 604 | "node_modules/bare-tls": { | |
| 605 | "version": "2.1.7", | |
| 606 | "resolved": "https://registry.npmjs.org/bare-tls/-/bare-tls-2.1.7.tgz", | |
| 607 | "integrity": "sha512-h6wcNXQdBeTX7fed9tjPp0/9cA/QfcBTv3ItgjnbUk4rWAU8bEFalZCZnUDdCK/t9zrNfJ+yvcPx4D/1Y6biyA==", | |
| 608 | "dev": true, | |
| 609 | "license": "Apache-2.0", | |
| 610 | "dependencies": { | |
| 611 | "bare-net": "^2.0.1", | |
| 612 | "bare-stream": "^2.6.4" | |
| 613 | }, | |
| 614 | "engines": { | |
| 615 | "bare": ">=1.7.0" | |
| 616 | } | |
| 617 | }, | |
| 618 | "node_modules/bare-tty": { | |
| 619 | "version": "5.0.3", | |
| 620 | "resolved": "https://registry.npmjs.org/bare-tty/-/bare-tty-5.0.3.tgz", | |
| 621 | "integrity": "sha512-jW24RBWRZOMHuSEWC9mh8wUKO5WUNl0UWUTNPn3yZ8qkOqwa8vaV2dR3a+BvblONPk7V35wuvK9P8508+judAg==", | |
| 622 | "dev": true, | |
| 623 | "license": "Apache-2.0", | |
| 624 | "dependencies": { | |
| 625 | "bare-events": "^2.2.0", | |
| 626 | "bare-signals": "^4.0.0", | |
| 627 | "bare-stream": "^2.0.0" | |
| 628 | }, | |
| 629 | "engines": { | |
| 630 | "bare": ">=1.16.0" | |
| 631 | } | |
| 632 | }, | |
| 633 | "node_modules/bare-type": { | |
| 634 | "version": "1.1.0", | |
| 635 | "resolved": "https://registry.npmjs.org/bare-type/-/bare-type-1.1.0.tgz", | |
| 636 | "integrity": "sha512-LdtnnEEYldOc87Dr4GpsKnStStZk3zfgoEMXy8yvEZkXrcCv9RtYDrUYWFsBQHtaB0s1EUWmcvS6XmEZYIj3Bw==", | |
| 637 | "dev": true, | |
| 638 | "license": "Apache-2.0", | |
| 639 | "engines": { | |
| 640 | "bare": ">=1.2.0" | |
| 641 | } | |
| 642 | }, | |
| 643 | "node_modules/bare-url": { | |
| 644 | "version": "2.3.2", | |
| 645 | "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", | |
| 646 | "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", | |
| 647 | "license": "Apache-2.0", | |
| 648 | "peer": true, | |
| 649 | "dependencies": { | |
| 650 | "bare-path": "^3.0.0" | |
| 651 | } | |
| 652 | }, | |
| 653 | "node_modules/bare-utils": { | |
| 654 | "version": "1.5.1", | |
| 655 | "resolved": "https://registry.npmjs.org/bare-utils/-/bare-utils-1.5.1.tgz", | |
| 656 | "integrity": "sha512-mxCkFvmDU3mlD/mb+pT64kKXOsx2KMsWLQbngN1LB+NOXfhfnRnyvpy3VZc6m7gzQxe57Bsi+aTCBqA4/S3elQ==", | |
| 657 | "dev": true, | |
| 658 | "license": "Apache-2.0", | |
| 659 | "dependencies": { | |
| 660 | "bare-debug-log": "^2.0.0", | |
| 661 | "bare-encoding": "^1.0.0", | |
| 662 | "bare-format": "^1.0.0", | |
| 663 | "bare-inspect": "^3.0.0", | |
| 664 | "bare-type": "^1.0.6" | |
| 665 | } | |
| 666 | }, | |
| 667 | "node_modules/bare-v8-to-istanbul": { | |
| 668 | "version": "1.0.2", | |
| 669 | "resolved": "https://registry.npmjs.org/bare-v8-to-istanbul/-/bare-v8-to-istanbul-1.0.2.tgz", | |
| 670 | "integrity": "sha512-CM0wPgyQtXTQ7h4q8NjbTZ/FH5he0Jkiwgpq0lFZZA5tkVZzmQ/9ul4R7E+cNlGZJIZlQZ9v4CwbgVaFG3zOAA==", | |
| 671 | "dev": true, | |
| 672 | "license": "Apache-2.0", | |
| 673 | "dependencies": { | |
| 674 | "bare-assert": "^1.0.2", | |
| 675 | "bare-fs": "^4.1.2", | |
| 676 | "bare-module": "^6.1.2", | |
| 677 | "bare-path": "^3.0.0", | |
| 678 | "bare-process": "^4.2.0", | |
| 679 | "bare-url": "^2.1.5", | |
| 680 | "bare-utils": "^1.2.0", | |
| 681 | "v8-to-istanbul": "^9.3.0", | |
| 682 | "which-runtime": "^1.2.1" | |
| 683 | } | |
| 684 | }, | |
| 685 | "node_modules/bare-ws": { | |
| 686 | "version": "2.0.4", | |
| 687 | "resolved": "https://registry.npmjs.org/bare-ws/-/bare-ws-2.0.4.tgz", | |
| 688 | "integrity": "sha512-SQMXzBYna9dRj57Dz1/ag+VWHCRXbfCjMHgyfM2F2lhkVLzMjnVSZP72aVeFWPFqe494Rd70Kzhe2JElGwFlJQ==", | |
| 689 | "dev": true, | |
| 690 | "license": "Apache-2.0", | |
| 691 | "dependencies": { | |
| 692 | "bare-crypto": "^1.2.0", | |
| 693 | "bare-events": "^2.3.1", | |
| 694 | "bare-http1": "^4.0.0", | |
| 695 | "bare-https": "^2.0.0", | |
| 696 | "bare-stream": "^2.1.2" | |
| 697 | }, | |
| 698 | "peerDependencies": { | |
| 699 | "bare-buffer": "*", | |
| 700 | "bare-url": "*" | |
| 701 | }, | |
| 702 | "peerDependenciesMeta": { | |
| 703 | "bare-buffer": { | |
| 704 | "optional": true | |
| 705 | }, | |
| 706 | "bare-url": { | |
| 707 | "optional": true | |
| 708 | } | |
| 709 | } | |
| 710 | }, | |
| 711 | "node_modules/big-sparse-array": { | |
| 712 | "version": "1.0.3", | |
| 713 | "resolved": "https://registry.npmjs.org/big-sparse-array/-/big-sparse-array-1.0.3.tgz", | |
| 714 | "integrity": "sha512-6RjV/3mSZORlMdpUaQ6rUSpG637cZm0//E54YYGtQg1c1O+AbZP8UTdJ/TchsDZcTVLmyWZcseBfp2HBeXUXOQ==", | |
| 715 | "license": "MIT" | |
| 716 | }, | |
| 717 | "node_modules/brittle": { | |
| 718 | "version": "3.19.1", | |
| 719 | "resolved": "https://registry.npmjs.org/brittle/-/brittle-3.19.1.tgz", | |
| 720 | "integrity": "sha512-4Ted1Mt9o9B6oIA6ImJJCtB/Fv++ZO3IDNQgRcHOXWSYGRUidgxchaGrUBaRn+4mlneXrgInPkCPzi0jO8qKbA==", | |
| 721 | "dev": true, | |
| 722 | "license": "Apache-2.0", | |
| 723 | "dependencies": { | |
| 724 | "b4a": "^1.6.0", | |
| 725 | "bare-assert": "^1.0.2", | |
| 726 | "bare-cov": "^1.1.0", | |
| 727 | "bare-fs": "^4.1.6", | |
| 728 | "bare-os": "^3.6.1", | |
| 729 | "bare-path": "^3.0.0", | |
| 730 | "bare-process": "^4.2.1", | |
| 731 | "bare-subprocess": "^5.0.0", | |
| 732 | "bare-url": "^2.1.6", | |
| 733 | "error-stack-parser": "^2.1.4", | |
| 734 | "globbie": "^1.0.2", | |
| 735 | "paparam": "^1.6.2", | |
| 736 | "same-object": "^1.0.2", | |
| 737 | "test-tmp": "^1.4.0", | |
| 738 | "tmatch": "^5.0.0" | |
| 739 | }, | |
| 740 | "bin": { | |
| 741 | "brittle": "bin/node.js", | |
| 742 | "brittle-bare": "bin/bare.js", | |
| 743 | "brittle-node": "bin/node.js", | |
| 744 | "brittle-pear": "bin/pear.js" | |
| 745 | } | |
| 746 | }, | |
| 747 | "node_modules/codecs": { | |
| 748 | "version": "3.1.0", | |
| 749 | "resolved": "https://registry.npmjs.org/codecs/-/codecs-3.1.0.tgz", | |
| 750 | "integrity": "sha512-Dqx8NwvBvnMeuPQdVKy/XEF71igjR5apxBvCGeV0pP1tXadOiaLvDTXt7xh+/5wI1ASB195mXQGJbw3Ml4YDWQ==", | |
| 751 | "license": "MIT", | |
| 752 | "dependencies": { | |
| 753 | "b4a": "^1.6.3" | |
| 754 | } | |
| 755 | }, | |
| 756 | "node_modules/compact-encoding": { | |
| 757 | "version": "2.18.0", | |
| 758 | "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-2.18.0.tgz", | |
| 759 | "integrity": "sha512-goACAOlhMI2xo5jGOMUDfOLnGdRE1jGfyZ+zie8N5114nHrbPIqf6GLUtzbLof6DSyrERlYRm3EcBplte5LcQw==", | |
| 760 | "license": "Apache-2.0", | |
| 761 | "dependencies": { | |
| 762 | "b4a": "^1.3.0" | |
| 763 | } | |
| 764 | }, | |
| 765 | "node_modules/convert-source-map": { | |
| 766 | "version": "2.0.0", | |
| 767 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", | |
| 768 | "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", | |
| 769 | "dev": true, | |
| 770 | "license": "MIT" | |
| 771 | }, | |
| 772 | "node_modules/corestore": { | |
| 773 | "version": "6.18.4", | |
| 774 | "resolved": "https://registry.npmjs.org/corestore/-/corestore-6.18.4.tgz", | |
| 775 | "integrity": "sha512-KL1cOUyj3DFRNzeVRekGPRiGN4sIfGo7BsncSJQQBT3Rh2y1nQ32MOR9X5DlOMlWIT4txwqBzhzam6+JXtwFVw==", | |
| 776 | "license": "MIT", | |
| 777 | "dependencies": { | |
| 778 | "b4a": "^1.6.4", | |
| 779 | "hypercore": "^10.37.10", | |
| 780 | "hypercore-crypto": "^3.4.0", | |
| 781 | "hypercore-id-encoding": "^1.2.0", | |
| 782 | "read-write-mutexify": "^2.1.0", | |
| 783 | "ready-resource": "^1.0.0", | |
| 784 | "safety-catch": "^1.0.1", | |
| 785 | "sodium-universal": "^4.0.0", | |
| 786 | "xache": "^1.1.0" | |
| 787 | } | |
| 788 | }, | |
| 789 | "node_modules/crc-native": { | |
| 790 | "version": "1.1.8", | |
| 791 | "resolved": "https://registry.npmjs.org/crc-native/-/crc-native-1.1.8.tgz", | |
| 792 | "integrity": "sha512-R/RYbD8lhG5M1k0iLX49tJdwHM0ipevJPpZw2Wk7b4EShyvx2P0uaq79xyRpcknjiVGiUfjFA8Gyy0mOlhABvw==", | |
| 793 | "license": "Apache-2.0", | |
| 794 | "optional": true, | |
| 795 | "dependencies": { | |
| 796 | "require-addon": "^1.1.0" | |
| 797 | } | |
| 798 | }, | |
| 799 | "node_modules/crc-universal": { | |
| 800 | "version": "1.0.4", | |
| 801 | "resolved": "https://registry.npmjs.org/crc-universal/-/crc-universal-1.0.4.tgz", | |
| 802 | "integrity": "sha512-CE9xWEI6Gd5V0Bdmj5NoWH3d7+EIe4zUpy1sv6uvKYznzsDP1vhiWsTBmL6q9IrH2P6RRshp+8AkhR4CMtY5Hg==", | |
| 803 | "license": "ISC", | |
| 804 | "optionalDependencies": { | |
| 805 | "crc-native": "^1.0.3" | |
| 806 | } | |
| 807 | }, | |
| 808 | "node_modules/debounceify": { | |
| 809 | "version": "1.1.0", | |
| 810 | "resolved": "https://registry.npmjs.org/debounceify/-/debounceify-1.1.0.tgz", | |
| 811 | "integrity": "sha512-eKuHDVfJVg+u/0nPy8P+fhnLgbyuTgVxuCRrS/R7EpDSMMkBDgSes41MJtSAY1F1hcqfHz3Zy/qpqHHIp/EhdA==", | |
| 812 | "license": "MIT" | |
| 813 | }, | |
| 814 | "node_modules/error-stack-parser": { | |
| 815 | "version": "2.1.4", | |
| 816 | "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", | |
| 817 | "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", | |
| 818 | "dev": true, | |
| 819 | "license": "MIT", | |
| 820 | "dependencies": { | |
| 821 | "stackframe": "^1.3.4" | |
| 822 | } | |
| 823 | }, | |
| 824 | "node_modules/events-universal": { | |
| 825 | "version": "1.0.1", | |
| 826 | "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", | |
| 827 | "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", | |
| 828 | "license": "Apache-2.0", | |
| 829 | "dependencies": { | |
| 830 | "bare-events": "^2.7.0" | |
| 831 | } | |
| 832 | }, | |
| 833 | "node_modules/fast-fifo": { | |
| 834 | "version": "1.3.2", | |
| 835 | "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", | |
| 836 | "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", | |
| 837 | "license": "MIT" | |
| 838 | }, | |
| 839 | "node_modules/flat-tree": { | |
| 840 | "version": "1.13.0", | |
| 841 | "resolved": "https://registry.npmjs.org/flat-tree/-/flat-tree-1.13.0.tgz", | |
| 842 | "integrity": "sha512-fT3HIuCPwHhFgJ20QYzDHgUG0zMmFg5cHvFiFo5h+QMSJ28TihsEVY0f8HGliuO+pOzmvjMx1odToeaEWkTnyQ==", | |
| 843 | "license": "MIT" | |
| 844 | }, | |
| 845 | "node_modules/fs-native-extensions": { | |
| 846 | "version": "1.4.5", | |
| 847 | "resolved": "https://registry.npmjs.org/fs-native-extensions/-/fs-native-extensions-1.4.5.tgz", | |
| 848 | "integrity": "sha512-ekV0T//iDm4AvhOcuPaHpxub4DI7HvY5ucLJVDvi7T2J+NZkQ9S6MuvgP0yeQvoqNUaAGyLjVYb1905BF9bpmg==", | |
| 849 | "license": "Apache-2.0", | |
| 850 | "optional": true, | |
| 851 | "dependencies": { | |
| 852 | "require-addon": "^1.1.0", | |
| 853 | "which-runtime": "^1.2.0" | |
| 854 | } | |
| 855 | }, | |
| 856 | "node_modules/globbie": { | |
| 857 | "version": "1.0.3", | |
| 858 | "resolved": "https://registry.npmjs.org/globbie/-/globbie-1.0.3.tgz", | |
| 859 | "integrity": "sha512-hcryJmKcftf82xfBWTWxuUITWIIoYO3ec104V17SMHGFl6VLbm1d1Ju9LX9L+jyJTwICpemX/dmPI/HGYoKr1A==", | |
| 860 | "dev": true, | |
| 861 | "license": "Apache-2.0", | |
| 862 | "dependencies": { | |
| 863 | "bare-fs": "^4.1.2", | |
| 864 | "bare-path": "^3.0.0", | |
| 865 | "bare-process": "^4.2.1", | |
| 866 | "picomatch": "^4.0.2" | |
| 867 | } | |
| 868 | }, | |
| 869 | "node_modules/hyperbee": { | |
| 870 | "version": "2.27.3", | |
| 871 | "resolved": "https://registry.npmjs.org/hyperbee/-/hyperbee-2.27.3.tgz", | |
| 872 | "integrity": "sha512-PXURH2U4juUZyJRKHTrY5z1zX851pmI1Q0jfv5F/hCIErDt/ND8jOZuxc3hfOLM9f0W3qJEDTMlV5AJBkVPy8w==", | |
| 873 | "license": "MIT", | |
| 874 | "dependencies": { | |
| 875 | "b4a": "^1.6.0", | |
| 876 | "codecs": "^3.0.0", | |
| 877 | "debounceify": "^1.0.0", | |
| 878 | "hypercore-errors": "^1.0.0", | |
| 879 | "mutexify": "^1.4.0", | |
| 880 | "protocol-buffers-encodings": "^1.2.0", | |
| 881 | "rache": "^1.0.0", | |
| 882 | "ready-resource": "^1.0.0", | |
| 883 | "resolve-reject-promise": "^1.1.0", | |
| 884 | "safety-catch": "^1.0.2", | |
| 885 | "streamx": "^2.12.4", | |
| 886 | "unslab": "^1.2.0" | |
| 887 | } | |
| 888 | }, | |
| 889 | "node_modules/hypercore": { | |
| 890 | "version": "10.38.2", | |
| 891 | "resolved": "https://registry.npmjs.org/hypercore/-/hypercore-10.38.2.tgz", | |
| 892 | "integrity": "sha512-2vx42g2RKdP/o2XzlxKfvgEaZ3+h/B855gCR8iscUDnxUoOZej7vXZ2buex0sweDUEW5aEfMxFR6o5hncio3Nw==", | |
| 893 | "license": "MIT", | |
| 894 | "dependencies": { | |
| 895 | "@hyperswarm/secret-stream": "^6.0.0", | |
| 896 | "b4a": "^1.1.0", | |
| 897 | "bare-events": "^2.2.0", | |
| 898 | "big-sparse-array": "^1.0.3", | |
| 899 | "compact-encoding": "^2.11.0", | |
| 900 | "crc-universal": "^1.0.2", | |
| 901 | "fast-fifo": "^1.3.0", | |
| 902 | "flat-tree": "^1.9.0", | |
| 903 | "hypercore-crypto": "^3.2.1", | |
| 904 | "hypercore-errors": "^1.2.0", | |
| 905 | "hypercore-id-encoding": "^1.2.0", | |
| 906 | "is-options": "^1.0.1", | |
| 907 | "protomux": "^3.5.0", | |
| 908 | "quickbit-universal": "^2.2.0", | |
| 909 | "random-access-file": "^4.0.0", | |
| 910 | "random-array-iterator": "^1.0.0", | |
| 911 | "safety-catch": "^1.0.1", | |
| 912 | "sodium-universal": "^4.0.0", | |
| 913 | "streamx": "^2.12.4", | |
| 914 | "unslab": "^1.3.0", | |
| 915 | "xache": "^1.1.0", | |
| 916 | "z32": "^1.0.0" | |
| 917 | } | |
| 918 | }, | |
| 919 | "node_modules/hypercore-crypto": { | |
| 920 | "version": "3.6.1", | |
| 921 | "resolved": "https://registry.npmjs.org/hypercore-crypto/-/hypercore-crypto-3.6.1.tgz", | |
| 922 | "integrity": "sha512-ltIz2uDwy9pO/ZGTvqcjzyBkvt6O4cVm4r/nNxh0GFs/RbQtqP/i4wCvLEdmU7ptgtnw7fI67WYD1aHPuv4OVA==", | |
| 923 | "license": "MIT", | |
| 924 | "dependencies": { | |
| 925 | "b4a": "^1.6.6", | |
| 926 | "compact-encoding": "^2.15.0", | |
| 927 | "sodium-universal": "^5.0.0" | |
| 928 | } | |
| 929 | }, | |
| 930 | "node_modules/hypercore-crypto/node_modules/sodium-native": { | |
| 931 | "version": "5.0.10", | |
| 932 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 933 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 934 | "license": "MIT", | |
| 935 | "dependencies": { | |
| 936 | "require-addon": "^1.1.0", | |
| 937 | "which-runtime": "^1.2.1" | |
| 938 | }, | |
| 939 | "engines": { | |
| 940 | "bare": ">=1.16.0" | |
| 941 | } | |
| 942 | }, | |
| 943 | "node_modules/hypercore-crypto/node_modules/sodium-universal": { | |
| 944 | "version": "5.0.1", | |
| 945 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 946 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 947 | "license": "MIT", | |
| 948 | "dependencies": { | |
| 949 | "sodium-native": "^5.0.1" | |
| 950 | }, | |
| 951 | "peerDependencies": { | |
| 952 | "sodium-javascript": "~0.8.0" | |
| 953 | }, | |
| 954 | "peerDependenciesMeta": { | |
| 955 | "sodium-javascript": { | |
| 956 | "optional": true | |
| 957 | } | |
| 958 | } | |
| 959 | }, | |
| 960 | "node_modules/hypercore-errors": { | |
| 961 | "version": "1.5.0", | |
| 962 | "resolved": "https://registry.npmjs.org/hypercore-errors/-/hypercore-errors-1.5.0.tgz", | |
| 963 | "integrity": "sha512-5KQ/SuDxsvet+7qWA35Ay6zdD9WyAHQoyWHGcPUTbmJBd300gvNIJoi3oma7kp4TTCSzii6qYumNZe/s0j/saQ==", | |
| 964 | "license": "Apache-2.0", | |
| 965 | "dependencies": { | |
| 966 | "hypercore-id-encoding": "^1.3.0" | |
| 967 | } | |
| 968 | }, | |
| 969 | "node_modules/hypercore-id-encoding": { | |
| 970 | "version": "1.3.0", | |
| 971 | "resolved": "https://registry.npmjs.org/hypercore-id-encoding/-/hypercore-id-encoding-1.3.0.tgz", | |
| 972 | "integrity": "sha512-W6sHdGo5h7LXEsoWfKf/KfuROZmZRQDlGqJF2EPHW+noCK66Vvr0+zE6cL0vqQi18s0kQPeN7Sq3QyR0Ytc2VQ==", | |
| 973 | "license": "Apache-2.0", | |
| 974 | "dependencies": { | |
| 975 | "b4a": "^1.5.3", | |
| 976 | "z32": "^1.0.0" | |
| 977 | } | |
| 978 | }, | |
| 979 | "node_modules/is-options": { | |
| 980 | "version": "1.0.2", | |
| 981 | "resolved": "https://registry.npmjs.org/is-options/-/is-options-1.0.2.tgz", | |
| 982 | "integrity": "sha512-u+Ai74c8Q74aS8BuHwPdI1jptGOT1FQXgCq8/zv0xRuE+wRgSMEJLj8lVO8Zp9BeGb29BXY6AsNPinfqjkr7Fg==", | |
| 983 | "license": "MIT", | |
| 984 | "dependencies": { | |
| 985 | "b4a": "^1.1.1" | |
| 986 | } | |
| 987 | }, | |
| 988 | "node_modules/mutexify": { | |
| 989 | "version": "1.4.0", | |
| 990 | "resolved": "https://registry.npmjs.org/mutexify/-/mutexify-1.4.0.tgz", | |
| 991 | "integrity": "sha512-pbYSsOrSB/AKN5h/WzzLRMFgZhClWccf2XIB4RSMC8JbquiB0e0/SH5AIfdQMdyHmYtv4seU7yV/TvAwPLJ1Yg==", | |
| 992 | "license": "MIT", | |
| 993 | "dependencies": { | |
| 994 | "queue-tick": "^1.0.0" | |
| 995 | } | |
| 996 | }, | |
| 997 | "node_modules/nanoassert": { | |
| 998 | "version": "2.0.0", | |
| 999 | "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz", | |
| 1000 | "integrity": "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==", | |
| 1001 | "license": "ISC" | |
| 1002 | }, | |
| 1003 | "node_modules/noise-curve-ed": { | |
| 1004 | "version": "2.1.0", | |
| 1005 | "resolved": "https://registry.npmjs.org/noise-curve-ed/-/noise-curve-ed-2.1.0.tgz", | |
| 1006 | "integrity": "sha512-zAzJx+VwZM3w6EA1hTmDhJfvAnCeBQn/1FAeZ0LtGxCcCtlAK/uJXQVF/eDVUOaAZ286lHlx77WJ+qj9SmsRRg==", | |
| 1007 | "license": "ISC", | |
| 1008 | "dependencies": { | |
| 1009 | "b4a": "^1.1.0", | |
| 1010 | "nanoassert": "^2.0.0", | |
| 1011 | "sodium-universal": "^5.0.0" | |
| 1012 | } | |
| 1013 | }, | |
| 1014 | "node_modules/noise-curve-ed/node_modules/sodium-native": { | |
| 1015 | "version": "5.0.10", | |
| 1016 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 1017 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 1018 | "license": "MIT", | |
| 1019 | "dependencies": { | |
| 1020 | "require-addon": "^1.1.0", | |
| 1021 | "which-runtime": "^1.2.1" | |
| 1022 | }, | |
| 1023 | "engines": { | |
| 1024 | "bare": ">=1.16.0" | |
| 1025 | } | |
| 1026 | }, | |
| 1027 | "node_modules/noise-curve-ed/node_modules/sodium-universal": { | |
| 1028 | "version": "5.0.1", | |
| 1029 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 1030 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 1031 | "license": "MIT", | |
| 1032 | "dependencies": { | |
| 1033 | "sodium-native": "^5.0.1" | |
| 1034 | }, | |
| 1035 | "peerDependencies": { | |
| 1036 | "sodium-javascript": "~0.8.0" | |
| 1037 | }, | |
| 1038 | "peerDependenciesMeta": { | |
| 1039 | "sodium-javascript": { | |
| 1040 | "optional": true | |
| 1041 | } | |
| 1042 | } | |
| 1043 | }, | |
| 1044 | "node_modules/noise-handshake": { | |
| 1045 | "version": "4.2.0", | |
| 1046 | "resolved": "https://registry.npmjs.org/noise-handshake/-/noise-handshake-4.2.0.tgz", | |
| 1047 | "integrity": "sha512-9O/VTNX/E2/AToyMTTDU0J/4WhaXMTdqc2DHs9vf+snoZ0cenSBq0dNYTVV1snYYEkmo6QeRrYMxtqtoYnY+LA==", | |
| 1048 | "license": "Apache-2.0", | |
| 1049 | "dependencies": { | |
| 1050 | "b4a": "^1.1.0", | |
| 1051 | "nanoassert": "^2.0.0", | |
| 1052 | "sodium-universal": "^5.0.0" | |
| 1053 | } | |
| 1054 | }, | |
| 1055 | "node_modules/noise-handshake/node_modules/sodium-native": { | |
| 1056 | "version": "5.0.10", | |
| 1057 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 1058 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 1059 | "license": "MIT", | |
| 1060 | "dependencies": { | |
| 1061 | "require-addon": "^1.1.0", | |
| 1062 | "which-runtime": "^1.2.1" | |
| 1063 | }, | |
| 1064 | "engines": { | |
| 1065 | "bare": ">=1.16.0" | |
| 1066 | } | |
| 1067 | }, | |
| 1068 | "node_modules/noise-handshake/node_modules/sodium-universal": { | |
| 1069 | "version": "5.0.1", | |
| 1070 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 1071 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 1072 | "license": "MIT", | |
| 1073 | "dependencies": { | |
| 1074 | "sodium-native": "^5.0.1" | |
| 1075 | }, | |
| 1076 | "peerDependencies": { | |
| 1077 | "sodium-javascript": "~0.8.0" | |
| 1078 | }, | |
| 1079 | "peerDependenciesMeta": { | |
| 1080 | "sodium-javascript": { | |
| 1081 | "optional": true | |
| 1082 | } | |
| 1083 | } | |
| 1084 | }, | |
| 1085 | "node_modules/paparam": { | |
| 1086 | "version": "1.10.0", | |
| 1087 | "resolved": "https://registry.npmjs.org/paparam/-/paparam-1.10.0.tgz", | |
| 1088 | "integrity": "sha512-p36QQrwU3X6fj5d2JN20B4lcZI/O3fxMVUHc1xD7MNe5bcOf3jIgE0CIK5Zv/s5YAUGlobTJ/kKXrh1FJVrs3w==", | |
| 1089 | "dev": true, | |
| 1090 | "license": "Apache-2.0" | |
| 1091 | }, | |
| 1092 | "node_modules/picomatch": { | |
| 1093 | "version": "4.0.3", | |
| 1094 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", | |
| 1095 | "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", | |
| 1096 | "dev": true, | |
| 1097 | "license": "MIT", | |
| 1098 | "engines": { | |
| 1099 | "node": ">=12" | |
| 1100 | }, | |
| 1101 | "funding": { | |
| 1102 | "url": "https://github.com/sponsors/jonschlinkert" | |
| 1103 | } | |
| 1104 | }, | |
| 1105 | "node_modules/protocol-buffers-encodings": { | |
| 1106 | "version": "1.2.0", | |
| 1107 | "resolved": "https://registry.npmjs.org/protocol-buffers-encodings/-/protocol-buffers-encodings-1.2.0.tgz", | |
| 1108 | "integrity": "sha512-daeNPuKh1NlLD1uDfbLpD+xyUTc07nEtfHwmBZmt/vH0B7VOM+JOCOpDcx9ZRpqHjAiIkGqyTDi+wfGSl17R9w==", | |
| 1109 | "license": "MIT", | |
| 1110 | "dependencies": { | |
| 1111 | "b4a": "^1.6.0", | |
| 1112 | "signed-varint": "^2.0.1", | |
| 1113 | "varint": "5.0.0" | |
| 1114 | } | |
| 1115 | }, | |
| 1116 | "node_modules/protomux": { | |
| 1117 | "version": "3.10.1", | |
| 1118 | "resolved": "https://registry.npmjs.org/protomux/-/protomux-3.10.1.tgz", | |
| 1119 | "integrity": "sha512-jgBqx8ZyaBWea/DFG4eOu1scOaeBwcnagiRC1XFVrjeGt7oAb0Pk5udPpBUpJ4DJBRjra50jD6YcZiQQTRqaaA==", | |
| 1120 | "license": "MIT", | |
| 1121 | "dependencies": { | |
| 1122 | "b4a": "^1.3.1", | |
| 1123 | "compact-encoding": "^2.5.1", | |
| 1124 | "queue-tick": "^1.0.0", | |
| 1125 | "safety-catch": "^1.0.1", | |
| 1126 | "unslab": "^1.3.0" | |
| 1127 | } | |
| 1128 | }, | |
| 1129 | "node_modules/queue-tick": { | |
| 1130 | "version": "1.0.1", | |
| 1131 | "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", | |
| 1132 | "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", | |
| 1133 | "license": "MIT" | |
| 1134 | }, | |
| 1135 | "node_modules/quickbit-native": { | |
| 1136 | "version": "2.4.8", | |
| 1137 | "resolved": "https://registry.npmjs.org/quickbit-native/-/quickbit-native-2.4.8.tgz", | |
| 1138 | "integrity": "sha512-FcCcqI+nIAWGknqhtrYT5TSD7t/N+Xd8ctM+2PrIIBuwOi5hx0SxAvuPtzLIEMfT/2h9+fhBakUe2uALOHX6yw==", | |
| 1139 | "license": "Apache-2.0", | |
| 1140 | "optional": true, | |
| 1141 | "dependencies": { | |
| 1142 | "require-addon": "^1.1.0" | |
| 1143 | } | |
| 1144 | }, | |
| 1145 | "node_modules/quickbit-universal": { | |
| 1146 | "version": "2.2.0", | |
| 1147 | "resolved": "https://registry.npmjs.org/quickbit-universal/-/quickbit-universal-2.2.0.tgz", | |
| 1148 | "integrity": "sha512-w02i1R8n7+6pEKTud8DfF8zbFY9o7RtPlUc3jWbtCkDKvhbx/AvV7oNnz4/TcmsPGpSJS+fq5Ud6RH6+YPvSGg==", | |
| 1149 | "license": "ISC", | |
| 1150 | "dependencies": { | |
| 1151 | "b4a": "^1.6.0", | |
| 1152 | "simdle-universal": "^1.1.0" | |
| 1153 | }, | |
| 1154 | "optionalDependencies": { | |
| 1155 | "quickbit-native": "^2.2.0" | |
| 1156 | } | |
| 1157 | }, | |
| 1158 | "node_modules/rache": { | |
| 1159 | "version": "1.0.0", | |
| 1160 | "resolved": "https://registry.npmjs.org/rache/-/rache-1.0.0.tgz", | |
| 1161 | "integrity": "sha512-e0k0g0w/8jOCB+7YqCIlOa+OJ38k0wrYS4x18pMSmqOvLKoyhmMhmQyCcvfY6VaP8D75cqkEnlakXs+RYYLqNg==", | |
| 1162 | "license": "Apache-2.0" | |
| 1163 | }, | |
| 1164 | "node_modules/random-access-file": { | |
| 1165 | "version": "4.1.2", | |
| 1166 | "resolved": "https://registry.npmjs.org/random-access-file/-/random-access-file-4.1.2.tgz", | |
| 1167 | "integrity": "sha512-GQM6R78DceZDcQod8KxlDFwXIiUvlvuy1EOzxTDsjuDjW5NlnlZi0MOk6iI4itAj/2vcvdqcEExYbVpC/dJcEw==", | |
| 1168 | "license": "MIT", | |
| 1169 | "dependencies": { | |
| 1170 | "bare-fs": "^4.0.1", | |
| 1171 | "bare-path": "^3.0.0", | |
| 1172 | "random-access-storage": "^3.0.0" | |
| 1173 | }, | |
| 1174 | "optionalDependencies": { | |
| 1175 | "fs-native-extensions": "^1.3.1" | |
| 1176 | } | |
| 1177 | }, | |
| 1178 | "node_modules/random-access-storage": { | |
| 1179 | "version": "3.0.2", | |
| 1180 | "resolved": "https://registry.npmjs.org/random-access-storage/-/random-access-storage-3.0.2.tgz", | |
| 1181 | "integrity": "sha512-Es9maUyWdJXWKckKy9s1+vT+DEgAt+PBb9lxPaake/0EDUsHehloKGv9v1zimS2V3gpFAcQXubvc1Rgci2sDPQ==", | |
| 1182 | "license": "MIT", | |
| 1183 | "dependencies": { | |
| 1184 | "bare-events": "^2.2.0", | |
| 1185 | "queue-tick": "^1.0.0" | |
| 1186 | } | |
| 1187 | }, | |
| 1188 | "node_modules/random-array-iterator": { | |
| 1189 | "version": "1.0.0", | |
| 1190 | "resolved": "https://registry.npmjs.org/random-array-iterator/-/random-array-iterator-1.0.0.tgz", | |
| 1191 | "integrity": "sha512-u7xCM93XqKEvPTP6xZp2ehttcAemKnh73oKNf1FvzuVCfpt6dILDt1Kxl1LeBjm2iNIeR49VGFhy4Iz3yOun+Q==", | |
| 1192 | "license": "MIT" | |
| 1193 | }, | |
| 1194 | "node_modules/read-write-mutexify": { | |
| 1195 | "version": "2.1.0", | |
| 1196 | "resolved": "https://registry.npmjs.org/read-write-mutexify/-/read-write-mutexify-2.1.0.tgz", | |
| 1197 | "integrity": "sha512-fDw/p5/acI1ytVY1UbxEDma/ej1yJH/n9NcjS9YNzcE6sPBPWdlru3ydRa/UBowUg4zqOvNMD5SOGYJrlQ6MzQ==", | |
| 1198 | "license": "MIT" | |
| 1199 | }, | |
| 1200 | "node_modules/ready-resource": { | |
| 1201 | "version": "1.2.0", | |
| 1202 | "resolved": "https://registry.npmjs.org/ready-resource/-/ready-resource-1.2.0.tgz", | |
| 1203 | "integrity": "sha512-nfcco/8iAFV0M+2PYnmIc+/xY0iRb35d42HFHQ7AfjulbGEAFa+XWpByfwSyeVeiBoMLLFVMv1HixxNCqzSQ1g==", | |
| 1204 | "license": "MIT", | |
| 1205 | "dependencies": { | |
| 1206 | "bare-events": "^2.2.0" | |
| 1207 | } | |
| 1208 | }, | |
| 1209 | "node_modules/require-addon": { | |
| 1210 | "version": "1.2.0", | |
| 1211 | "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz", | |
| 1212 | "integrity": "sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA==", | |
| 1213 | "license": "Apache-2.0", | |
| 1214 | "dependencies": { | |
| 1215 | "bare-addon-resolve": "^1.3.0" | |
| 1216 | }, | |
| 1217 | "engines": { | |
| 1218 | "bare": ">=1.10.0" | |
| 1219 | } | |
| 1220 | }, | |
| 1221 | "node_modules/resolve-reject-promise": { | |
| 1222 | "version": "1.1.0", | |
| 1223 | "resolved": "https://registry.npmjs.org/resolve-reject-promise/-/resolve-reject-promise-1.1.0.tgz", | |
| 1224 | "integrity": "sha512-LWsTOA91AqzBTjSGgX79Tc130pwcBK6xjpJEO+qRT5IKZ6bGnHKcc8QL3upUBcWuU8OTIDzKK2VNSwmmlqvAVg==", | |
| 1225 | "license": "MIT" | |
| 1226 | }, | |
| 1227 | "node_modules/safety-catch": { | |
| 1228 | "version": "1.0.2", | |
| 1229 | "resolved": "https://registry.npmjs.org/safety-catch/-/safety-catch-1.0.2.tgz", | |
| 1230 | "integrity": "sha512-C1UYVZ4dtbBxEtvOcpjBaaD27nP8MlvyAQEp2fOTOEe6pfUpk1cDUxij6BR1jZup6rSyUTaBBplK7LanskrULA==", | |
| 1231 | "license": "MIT" | |
| 1232 | }, | |
| 1233 | "node_modules/same-object": { | |
| 1234 | "version": "1.0.2", | |
| 1235 | "resolved": "https://registry.npmjs.org/same-object/-/same-object-1.0.2.tgz", | |
| 1236 | "integrity": "sha512-csHWhvUsLbIOHDM/nP+KHWM+BLPsIzWkFa8HbzaI0G7BqKXgx+7FJpKTGgLXyz5amfdY2OVBcmXTqYOMEk04og==", | |
| 1237 | "dev": true, | |
| 1238 | "license": "MIT" | |
| 1239 | }, | |
| 1240 | "node_modules/signed-varint": { | |
| 1241 | "version": "2.0.1", | |
| 1242 | "resolved": "https://registry.npmjs.org/signed-varint/-/signed-varint-2.0.1.tgz", | |
| 1243 | "integrity": "sha512-abgDPg1106vuZZOvw7cFwdCABddfJRz5akcCcchzTbhyhYnsG31y4AlZEgp315T7W3nQq5P4xeOm186ZiPVFzw==", | |
| 1244 | "license": "MIT", | |
| 1245 | "dependencies": { | |
| 1246 | "varint": "~5.0.0" | |
| 1247 | } | |
| 1248 | }, | |
| 1249 | "node_modules/simdle-native": { | |
| 1250 | "version": "1.3.9", | |
| 1251 | "resolved": "https://registry.npmjs.org/simdle-native/-/simdle-native-1.3.9.tgz", | |
| 1252 | "integrity": "sha512-Isc8sP4OiiIU0mpslD4GHEnR0VQWvR/54WN7YtwEDkNdTJVWtpmvsSvsgRlw5BNGxdYXlVRegdnrSu10H/PhvA==", | |
| 1253 | "license": "Apache-2.0", | |
| 1254 | "optional": true, | |
| 1255 | "dependencies": { | |
| 1256 | "b4a": "^1.6.0", | |
| 1257 | "require-addon": "^1.1.0" | |
| 1258 | } | |
| 1259 | }, | |
| 1260 | "node_modules/simdle-universal": { | |
| 1261 | "version": "1.1.2", | |
| 1262 | "resolved": "https://registry.npmjs.org/simdle-universal/-/simdle-universal-1.1.2.tgz", | |
| 1263 | "integrity": "sha512-3n3w1bs+uwgHKQjt6arez83EywNlhZzYvNOhvAASTl/8KqNIcqr6aHyGt3JRlfuUC7iB0tomJRPlJ2cRGIpBzA==", | |
| 1264 | "license": "ISC", | |
| 1265 | "dependencies": { | |
| 1266 | "b4a": "^1.6.0" | |
| 1267 | }, | |
| 1268 | "optionalDependencies": { | |
| 1269 | "simdle-native": "^1.1.1" | |
| 1270 | } | |
| 1271 | }, | |
| 1272 | "node_modules/sodium-native": { | |
| 1273 | "version": "4.3.3", | |
| 1274 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.3.3.tgz", | |
| 1275 | "integrity": "sha512-OnxSlN3uyY8D0EsLHpmm2HOFmKddQVvEMmsakCrXUzSd8kjjbzL413t4ZNF3n0UxSwNgwTyUvkmZHTfuCeiYSw==", | |
| 1276 | "license": "MIT", | |
| 1277 | "dependencies": { | |
| 1278 | "require-addon": "^1.1.0" | |
| 1279 | } | |
| 1280 | }, | |
| 1281 | "node_modules/sodium-secretstream": { | |
| 1282 | "version": "1.2.0", | |
| 1283 | "resolved": "https://registry.npmjs.org/sodium-secretstream/-/sodium-secretstream-1.2.0.tgz", | |
| 1284 | "integrity": "sha512-q/DbraNFXm1KfCiiZvapmz5UC3OlpirYFIvBK2MhGaOFSb3gRyk8OXTi17UI9SGfshQNCpsVvlopogbzZNyW6Q==", | |
| 1285 | "license": "MIT", | |
| 1286 | "dependencies": { | |
| 1287 | "b4a": "^1.1.1", | |
| 1288 | "sodium-universal": "^5.0.0" | |
| 1289 | } | |
| 1290 | }, | |
| 1291 | "node_modules/sodium-secretstream/node_modules/sodium-native": { | |
| 1292 | "version": "5.0.10", | |
| 1293 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 1294 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 1295 | "license": "MIT", | |
| 1296 | "dependencies": { | |
| 1297 | "require-addon": "^1.1.0", | |
| 1298 | "which-runtime": "^1.2.1" | |
| 1299 | }, | |
| 1300 | "engines": { | |
| 1301 | "bare": ">=1.16.0" | |
| 1302 | } | |
| 1303 | }, | |
| 1304 | "node_modules/sodium-secretstream/node_modules/sodium-universal": { | |
| 1305 | "version": "5.0.1", | |
| 1306 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 1307 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 1308 | "license": "MIT", | |
| 1309 | "dependencies": { | |
| 1310 | "sodium-native": "^5.0.1" | |
| 1311 | }, | |
| 1312 | "peerDependencies": { | |
| 1313 | "sodium-javascript": "~0.8.0" | |
| 1314 | }, | |
| 1315 | "peerDependenciesMeta": { | |
| 1316 | "sodium-javascript": { | |
| 1317 | "optional": true | |
| 1318 | } | |
| 1319 | } | |
| 1320 | }, | |
| 1321 | "node_modules/sodium-universal": { | |
| 1322 | "version": "4.0.1", | |
| 1323 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-4.0.1.tgz", | |
| 1324 | "integrity": "sha512-sNp13PrxYLaUFHTGoDKkSDFvoEu51bfzE12RwGlqU1fcrkpAOK0NvizaJzOWV0Omtk9me2+Pnbjcf/l0efxuGQ==", | |
| 1325 | "license": "MIT", | |
| 1326 | "dependencies": { | |
| 1327 | "sodium-native": "^4.0.0" | |
| 1328 | }, | |
| 1329 | "peerDependencies": { | |
| 1330 | "sodium-javascript": "~0.8.0" | |
| 1331 | }, | |
| 1332 | "peerDependenciesMeta": { | |
| 1333 | "sodium-javascript": { | |
| 1334 | "optional": true | |
| 1335 | } | |
| 1336 | } | |
| 1337 | }, | |
| 1338 | "node_modules/stackframe": { | |
| 1339 | "version": "1.3.4", | |
| 1340 | "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", | |
| 1341 | "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", | |
| 1342 | "dev": true, | |
| 1343 | "license": "MIT" | |
| 1344 | }, | |
| 1345 | "node_modules/streamx": { | |
| 1346 | "version": "2.23.0", | |
| 1347 | "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", | |
| 1348 | "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", | |
| 1349 | "license": "MIT", | |
| 1350 | "dependencies": { | |
| 1351 | "events-universal": "^1.0.0", | |
| 1352 | "fast-fifo": "^1.3.2", | |
| 1353 | "text-decoder": "^1.1.0" | |
| 1354 | } | |
| 1355 | }, | |
| 1356 | "node_modules/test-tmp": { | |
| 1357 | "version": "1.4.0", | |
| 1358 | "resolved": "https://registry.npmjs.org/test-tmp/-/test-tmp-1.4.0.tgz", | |
| 1359 | "integrity": "sha512-GVggxGg+jXqP2Wbju50JVLo+9E+nIOPPyWqgr63EbOnNItIKu1cEbJpTWAJeflnyGqXOtcMI7ijHRp88GUkfDA==", | |
| 1360 | "dev": true, | |
| 1361 | "license": "MIT", | |
| 1362 | "dependencies": { | |
| 1363 | "bare-fs": "^4.0.1", | |
| 1364 | "bare-os": "^3.3.0", | |
| 1365 | "bare-path": "^3.0.0" | |
| 1366 | } | |
| 1367 | }, | |
| 1368 | "node_modules/text-decoder": { | |
| 1369 | "version": "1.2.6", | |
| 1370 | "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.6.tgz", | |
| 1371 | "integrity": "sha512-27FeW5GQFDfw0FpwMQhMagB7BztOOlmjcSRi97t2oplhKVTZtp0DZbSegSaXS5IIC6mxMvBG4AR1Sgc6BX3CQg==", | |
| 1372 | "license": "Apache-2.0", | |
| 1373 | "dependencies": { | |
| 1374 | "b4a": "^1.6.4" | |
| 1375 | } | |
| 1376 | }, | |
| 1377 | "node_modules/timeout-refresh": { | |
| 1378 | "version": "2.0.1", | |
| 1379 | "resolved": "https://registry.npmjs.org/timeout-refresh/-/timeout-refresh-2.0.1.tgz", | |
| 1380 | "integrity": "sha512-SVqEcMZBsZF9mA78rjzCrYrUs37LMJk3ShZ851ygZYW1cMeIjs9mL57KO6Iv5mmjSQnOe/29/VAfGXo+oRCiVw==", | |
| 1381 | "license": "MIT" | |
| 1382 | }, | |
| 1383 | "node_modules/tmatch": { | |
| 1384 | "version": "5.0.0", | |
| 1385 | "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-5.0.0.tgz", | |
| 1386 | "integrity": "sha512-Ib9OtBkpHn07tXP04SlN1SYRxFgTk6wSM2EBmjjxug4u5RXPRVLkdFJSS1PmrQidaSB8Lru9nRtViQBsbxzE5Q==", | |
| 1387 | "dev": true, | |
| 1388 | "license": "ISC", | |
| 1389 | "engines": { | |
| 1390 | "node": ">=8" | |
| 1391 | } | |
| 1392 | }, | |
| 1393 | "node_modules/unslab": { | |
| 1394 | "version": "1.3.0", | |
| 1395 | "resolved": "https://registry.npmjs.org/unslab/-/unslab-1.3.0.tgz", | |
| 1396 | "integrity": "sha512-YATkfKAFj47kTzmiQrWXMyRvaVrHsW6MEALa4bm+FhiA2YG4oira+Z3DXN6LrYOYn2Y8eO94Lwl9DOHjs1FpoQ==", | |
| 1397 | "license": "Apache-2.0", | |
| 1398 | "dependencies": { | |
| 1399 | "b4a": "^1.6.6" | |
| 1400 | } | |
| 1401 | }, | |
| 1402 | "node_modules/v8-to-istanbul": { | |
| 1403 | "version": "9.3.0", | |
| 1404 | "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", | |
| 1405 | "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", | |
| 1406 | "dev": true, | |
| 1407 | "license": "ISC", | |
| 1408 | "dependencies": { | |
| 1409 | "@jridgewell/trace-mapping": "^0.3.12", | |
| 1410 | "@types/istanbul-lib-coverage": "^2.0.1", | |
| 1411 | "convert-source-map": "^2.0.0" | |
| 1412 | }, | |
| 1413 | "engines": { | |
| 1414 | "node": ">=10.12.0" | |
| 1415 | } | |
| 1416 | }, | |
| 1417 | "node_modules/varint": { | |
| 1418 | "version": "5.0.0", | |
| 1419 | "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.0.tgz", | |
| 1420 | "integrity": "sha512-gC13b/bWrqQoKY2EmROCZ+AR0jitc6DnDGaQ6Ls9QpKmuSgJB1eQ7H3KETtQm7qSdMWMKCmsshyCmUwMLh3OAA==", | |
| 1421 | "license": "MIT" | |
| 1422 | }, | |
| 1423 | "node_modules/which-runtime": { | |
| 1424 | "version": "1.3.2", | |
| 1425 | "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.3.2.tgz", | |
| 1426 | "integrity": "sha512-5kwCfWml7+b2NO7KrLMhYihjRx0teKkd3yGp1Xk5Vaf2JGdSh+rgVhEALAD9c/59dP+YwJHXoEO7e8QPy7gOkw==", | |
| 1427 | "license": "Apache-2.0" | |
| 1428 | }, | |
| 1429 | "node_modules/xache": { | |
| 1430 | "version": "1.2.1", | |
| 1431 | "resolved": "https://registry.npmjs.org/xache/-/xache-1.2.1.tgz", | |
| 1432 | "integrity": "sha512-igRS6jPreJ54ABdzhh4mCDXcz+XMaWO2q1ABRV2yWYuk29jlp8VT7UBdCqNkX7rpYBbXsebVVKkwIuYZjyZNqA==", | |
| 1433 | "license": "MIT" | |
| 1434 | }, | |
| 1435 | "node_modules/z32": { | |
| 1436 | "version": "1.1.0", | |
| 1437 | "resolved": "https://registry.npmjs.org/z32/-/z32-1.1.0.tgz", | |
| 1438 | "integrity": "sha512-1WUHy+VS6d0HPNspDxvLssBbeQjXMjSnpv0vH82vRAUfg847NmX3OXozp/hRP5jPhxBbrVzrgvAt+UsGNzRFQQ==", | |
| 1439 | "license": "MIT", | |
| 1440 | "dependencies": { | |
| 1441 | "b4a": "^1.5.3" | |
| 1442 | } | |
| 1443 | } | |
| 1444 | } | |
| 1445 | } |
| … | … | @@ -0,0 +1,22 @@ |
| 1 | { | |
| 2 | "name": "bridge-vault", | |
| 3 | "version": "1.0.0", | |
| 4 | "description": "Encrypted P2P credential store for bridge configurations", | |
| 5 | "type": "module", | |
| 6 | "main": "index.js", | |
| 7 | "scripts": { | |
| 8 | "test": "brittle test/*.test.js" | |
| 9 | }, | |
| 10 | "dependencies": { | |
| 11 | "hyperbee": "^2.0.0", | |
| 12 | "hypercore": "^10.0.0", | |
| 13 | "corestore": "^6.0.0", | |
| 14 | "sodium-native": "^4.0.0", | |
| 15 | "b4a": "^1.0.0", | |
| 16 | "compact-encoding": "^2.0.0" | |
| 17 | }, | |
| 18 | "devDependencies": { | |
| 19 | "brittle": "^3.0.0" | |
| 20 | }, | |
| 21 | "license": "MIT" | |
| 22 | } |
| … | … | @@ -0,0 +1,214 @@ |
| 1 | import test from 'brittle' | |
| 2 | import { Vault, generateKey, deriveKey } from '../index.js' | |
| 3 | import b4a from 'b4a' | |
| 4 | import { tmpdir } from 'os' | |
| 5 | import { join } from 'path' | |
| 6 | import { randomBytes } from 'crypto' | |
| 7 | ||
| 8 | function tmpStorage () { | |
| 9 | return join(tmpdir(), 'bridge-vault-test-' + randomBytes(8).toString('hex')) | |
| 10 | } | |
| 11 | ||
| 12 | test('Vault - put and get credential', async (t) => { | |
| 13 | const vault = new Vault(tmpStorage()) | |
| 14 | await vault.ready() | |
| 15 | ||
| 16 | await vault.put('irc', 'libera', { | |
| 17 | server: 'irc.libera.chat', | |
| 18 | port: 6697, | |
| 19 | nick: 'testbot', | |
| 20 | password: 'secret123' | |
| 21 | }) | |
| 22 | ||
| 23 | const cred = await vault.get('irc', 'libera') | |
| 24 | t.is(cred.server, 'irc.libera.chat') | |
| 25 | t.is(cred.port, 6697) | |
| 26 | t.is(cred.nick, 'testbot') | |
| 27 | t.is(cred.password, 'secret123') | |
| 28 | ||
| 29 | await vault.close() | |
| 30 | }) | |
| 31 | ||
| 32 | test('Vault - get nonexistent returns null', async (t) => { | |
| 33 | const vault = new Vault(tmpStorage()) | |
| 34 | await vault.ready() | |
| 35 | ||
| 36 | const cred = await vault.get('irc', 'nonexistent') | |
| 37 | t.is(cred, null) | |
| 38 | ||
| 39 | await vault.close() | |
| 40 | }) | |
| 41 | ||
| 42 | test('Vault - delete credential', async (t) => { | |
| 43 | const vault = new Vault(tmpStorage()) | |
| 44 | await vault.ready() | |
| 45 | ||
| 46 | await vault.put('discord', 'mybot', { token: 'abc123' }) | |
| 47 | t.ok(await vault.get('discord', 'mybot')) | |
| 48 | ||
| 49 | await vault.del('discord', 'mybot') | |
| 50 | t.is(await vault.get('discord', 'mybot'), null) | |
| 51 | ||
| 52 | await vault.close() | |
| 53 | }) | |
| 54 | ||
| 55 | test('Vault - update credential', async (t) => { | |
| 56 | const vault = new Vault(tmpStorage()) | |
| 57 | await vault.ready() | |
| 58 | ||
| 59 | await vault.put('irc', 'server1', { nick: 'old' }) | |
| 60 | await vault.put('irc', 'server1', { nick: 'new' }) | |
| 61 | ||
| 62 | const cred = await vault.get('irc', 'server1') | |
| 63 | t.is(cred.nick, 'new') | |
| 64 | ||
| 65 | await vault.close() | |
| 66 | }) | |
| 67 | ||
| 68 | test('Vault - list credentials for platform', async (t) => { | |
| 69 | const vault = new Vault(tmpStorage()) | |
| 70 | await vault.ready() | |
| 71 | ||
| 72 | await vault.put('irc', 'libera', { nick: 'bot1' }) | |
| 73 | await vault.put('irc', 'oftc', { nick: 'bot2' }) | |
| 74 | await vault.put('discord', 'server1', { token: 'xyz' }) | |
| 75 | ||
| 76 | const ircCreds = await vault.list('irc') | |
| 77 | t.is(ircCreds.length, 2) | |
| 78 | t.ok(ircCreds.find(c => c.name === 'libera')) | |
| 79 | t.ok(ircCreds.find(c => c.name === 'oftc')) | |
| 80 | ||
| 81 | const discordCreds = await vault.list('discord') | |
| 82 | t.is(discordCreds.length, 1) | |
| 83 | t.is(discordCreds[0].name, 'server1') | |
| 84 | ||
| 85 | await vault.close() | |
| 86 | }) | |
| 87 | ||
| 88 | test('Vault - listPlatforms', async (t) => { | |
| 89 | const vault = new Vault(tmpStorage()) | |
| 90 | await vault.ready() | |
| 91 | ||
| 92 | await vault.put('irc', 'a', { x: 1 }) | |
| 93 | await vault.put('discord', 'b', { x: 2 }) | |
| 94 | await vault.put('matrix', 'c', { x: 3 }) | |
| 95 | ||
| 96 | const platforms = await vault.listPlatforms() | |
| 97 | t.is(platforms.length, 3) | |
| 98 | t.ok(platforms.includes('irc')) | |
| 99 | t.ok(platforms.includes('discord')) | |
| 100 | t.ok(platforms.includes('matrix')) | |
| 101 | ||
| 102 | await vault.close() | |
| 103 | }) | |
| 104 | ||
| 105 | test('Vault - encryption works (wrong key fails)', async (t) => { | |
| 106 | const storage = tmpStorage() | |
| 107 | const key1 = generateKey() | |
| 108 | const key2 = generateKey() | |
| 109 | ||
| 110 | const vault1 = new Vault(storage, { key: key1 }) | |
| 111 | await vault1.ready() | |
| 112 | await vault1.put('irc', 'test', { secret: 'hidden' }) | |
| 113 | await vault1.close() | |
| 114 | ||
| 115 | const vault2 = new Vault(storage, { key: key2 }) | |
| 116 | await vault2.ready() | |
| 117 | ||
| 118 | try { | |
| 119 | await vault2.get('irc', 'test') | |
| 120 | t.fail('should have thrown') | |
| 121 | } catch (err) { | |
| 122 | t.ok(err.message.includes('Decryption failed'), 'wrong key throws') | |
| 123 | } | |
| 124 | ||
| 125 | await vault2.close() | |
| 126 | }) | |
| 127 | ||
| 128 | test('Vault - same key reopens vault', async (t) => { | |
| 129 | const storage = tmpStorage() | |
| 130 | const key = generateKey() | |
| 131 | ||
| 132 | const vault1 = new Vault(storage, { key }) | |
| 133 | await vault1.ready() | |
| 134 | await vault1.put('irc', 'test', { value: 42 }) | |
| 135 | await vault1.close() | |
| 136 | ||
| 137 | const vault2 = new Vault(storage, { key }) | |
| 138 | await vault2.ready() | |
| 139 | const cred = await vault2.get('irc', 'test') | |
| 140 | t.is(cred.value, 42) | |
| 141 | await vault2.close() | |
| 142 | }) | |
| 143 | ||
| 144 | test('Vault - complex credential values', async (t) => { | |
| 145 | const vault = new Vault(tmpStorage()) | |
| 146 | await vault.ready() | |
| 147 | ||
| 148 | await vault.put('mastodon', 'instance1', { | |
| 149 | baseUrl: 'https://mastodon.social', | |
| 150 | accessToken: 'token123', | |
| 151 | scopes: ['read', 'write', 'push'], | |
| 152 | metadata: { | |
| 153 | createdAt: '2024-01-01', | |
| 154 | expiresAt: null, | |
| 155 | refreshToken: 'refresh456' | |
| 156 | } | |
| 157 | }) | |
| 158 | ||
| 159 | const cred = await vault.get('mastodon', 'instance1') | |
| 160 | t.is(cred.baseUrl, 'https://mastodon.social') | |
| 161 | t.alike(cred.scopes, ['read', 'write', 'push']) | |
| 162 | t.is(cred.metadata.refreshToken, 'refresh456') | |
| 163 | t.is(cred.metadata.expiresAt, null) | |
| 164 | ||
| 165 | await vault.close() | |
| 166 | }) | |
| 167 | ||
| 168 | test('generateKey - returns buffer of correct size', (t) => { | |
| 169 | const key = generateKey() | |
| 170 | t.ok(b4a.isBuffer(key)) | |
| 171 | t.is(key.length, 32) | |
| 172 | }) | |
| 173 | ||
| 174 | test('deriveKey - derives from password', (t) => { | |
| 175 | const { key, salt } = deriveKey('my-password') | |
| 176 | t.ok(b4a.isBuffer(key)) | |
| 177 | t.is(key.length, 32) | |
| 178 | t.ok(b4a.isBuffer(salt)) | |
| 179 | }) | |
| 180 | ||
| 181 | test('deriveKey - same password + salt gives same key', (t) => { | |
| 182 | const { key: key1, salt } = deriveKey('password') | |
| 183 | const { key: key2 } = deriveKey('password', salt) | |
| 184 | t.ok(b4a.equals(key1, key2)) | |
| 185 | }) | |
| 186 | ||
| 187 | test('deriveKey - different passwords give different keys', (t) => { | |
| 188 | const { key: key1, salt } = deriveKey('password1') | |
| 189 | const { key: key2 } = deriveKey('password2', salt) | |
| 190 | t.absent(b4a.equals(key1, key2)) | |
| 191 | }) | |
| 192 | ||
| 193 | test('Vault - empty list returns empty array', async (t) => { | |
| 194 | const vault = new Vault(tmpStorage()) | |
| 195 | await vault.ready() | |
| 196 | ||
| 197 | const creds = await vault.list('nonexistent') | |
| 198 | t.alike(creds, []) | |
| 199 | ||
| 200 | await vault.close() | |
| 201 | }) | |
| 202 | ||
| 203 | test('Vault - ready is idempotent', async (t) => { | |
| 204 | const vault = new Vault(tmpStorage()) | |
| 205 | await vault.ready() | |
| 206 | await vault.ready() | |
| 207 | await vault.ready() | |
| 208 | ||
| 209 | await vault.put('irc', 'test', { ok: true }) | |
| 210 | const cred = await vault.get('irc', 'test') | |
| 211 | t.is(cred.ok, true) | |
| 212 | ||
| 213 | await vault.close() | |
| 214 | }) |
| … | … | @@ -0,0 +1,185 @@ |
| 1 | # Architecture | |
| 2 | ||
| 3 | ## The Communication Graph | |
| 4 | ||
| 5 | The core idea: a P2P communication graph where each bridge is a **plane** connecting two (or more) spaces. There is no single bridge — there are many. The system is the graph itself. | |
| 6 | ||
| 7 | ``` | |
| 8 | ┌─────────┐ | |
| 9 | ┌────│ Keet │────┐ | |
| 10 | │ └─────────┘ │ | |
| 11 | │ │ │ | |
| 12 | ┌───┴───┐ ┌──┴──┐ ┌────┴────┐ | |
| 13 | │ IRC │ │Disc.│ │Mastodon │ | |
| 14 | │ plane │ │plane│ │ plane │ | |
| 15 | └───┬───┘ └──┬──┘ └────┬────┘ | |
| 16 | │ │ │ | |
| 17 | ┌───┴───┐ │ ┌────┴────┐ | |
| 18 | │Libera │ │ │Fediverse│ | |
| 19 | │ Chat │ │ └─────────┘ | |
| 20 | └───────┘ ┌─┴──────┐ | |
| 21 | │Discord │ | |
| 22 | │servers │ | |
| 23 | └────────┘ | |
| 24 | ``` | |
| 25 | ||
| 26 | Each bridge is a standalone process. Keet is the P2P hub (because it's already P2P), but any bridge can connect any two spaces. | |
| 27 | ||
| 28 | **No single point of failure.** Kill one bridge, others keep working. Add a new bridge, the graph grows. Run bridges on different machines — the graph is distributed. | |
| 29 | ||
| 30 | ## The Fracture Principle | |
| 31 | ||
| 32 | Any "universal bridge" eventually breaks into many bridges. Design for this from day one: | |
| 33 | ||
| 34 | - Each bridge is independently useful and independently deployable | |
| 35 | - Each panel is a standalone app that adds a capability | |
| 36 | - Each daemon is a standalone automation script | |
| 37 | - The system is the emergent property of all pieces running together | |
| 38 | - No central registry — bridges discover each other via Hyperswarm | |
| 39 | ||
| 40 | ## Five Tiers | |
| 41 | ||
| 42 | ### Tier 1: Foundation Libraries (npm packages) | |
| 43 | Small, focused packages that all other projects import. | |
| 44 | ||
| 45 | - **bridge-envelope** — Universal message schema + compact-encoding codec | |
| 46 | - **bridge-vault** — Encrypted P2P credential store (Hyperbee + keypair encryption) | |
| 47 | ||
| 48 | ### Tier 2: Platform Bridges (Pear terminal apps) | |
| 49 | Each bridge connects one external platform to Keet. Standalone. | |
| 50 | ||
| 51 | - **bridge-irc** — IRC channels <-> Keet rooms | |
| 52 | - **bridge-discord** — Discord channels <-> Keet rooms | |
| 53 | - **bridge-matrix** — Matrix rooms <-> Keet rooms | |
| 54 | - **bridge-mastodon** — Fediverse posts <-> Keet rooms | |
| 55 | - **bridge-email** — IMAP/SMTP inboxes <-> Keet rooms | |
| 56 | ||
| 57 | ### Tier 3: Workspace Panels (Pear apps) | |
| 58 | Each panel adds a social capability. Can attach to a Keet room or run standalone. | |
| 59 | ||
| 60 | - **hyper-wiki** — P2P wiki (Hyperdrive-backed, versioned) | |
| 61 | - **hyper-board** — P2P kanban board | |
| 62 | - **hyper-feed** — P2P social feed/timeline | |
| 63 | ||
| 64 | ### Tier 4: Automation (Pear apps) | |
| 65 | Event-driven automation attached to rooms. | |
| 66 | ||
| 67 | - **hyper-daemon** — Trigger-action runtime (like IFTTT but P2P) | |
| 68 | - daemon-crosspost — selective syndication across platforms | |
| 69 | - daemon-summarize — auto-summarize conversations into wiki pages | |
| 70 | ||
| 71 | ### Tier 5: Meta-App | |
| 72 | Composes everything into a unified experience. | |
| 73 | ||
| 74 | - **hyper-social** — unified hub with tiling WM layout for panels | |
| 75 | ||
| 76 | ## Dependency Graph | |
| 77 | ||
| 78 | ``` | |
| 79 | bridge-envelope (npm pkg, no deps) | |
| 80 | │ | |
| 81 | ├──────────────────────────────┐ | |
| 82 | │ │ | |
| 83 | bridge-vault (npm pkg) Each bridge/panel/daemon app | |
| 84 | │ | |
| 85 | └──── hyper-social (meta-app, composes all above) | |
| 86 | ``` | |
| 87 | ||
| 88 | **Dependency arrows go DOWN, not up.** Each project depends on at most `bridge-envelope` + `bridge-vault`. No circular dependencies. Each project is a leaf node except the meta-app. | |
| 89 | ||
| 90 | ## Hub API: WebSocket + JSON-RPC 2.0 | |
| 91 | ||
| 92 | All projects talk to keet-daemon via a single protocol: | |
| 93 | ||
| 94 | ``` | |
| 95 | ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────────┐ | |
| 96 | │ WebUI │ │ TUI │ │ GUI │ │ Godot │ | |
| 97 | │ (browser)│ │(terminal)│ │ (Pear) │ │ ┌───────┐ ┌──────┐ │ | |
| 98 | │ │ │ │ │ │ │ │ WS │ │ UDX │ │ | |
| 99 | │ │ │ │ │ │ │ │(social)│ │(game)│ │ | |
| 100 | └────┬─────┘ └────┬─────┘ └────┬─────┘ │ └───┬───┘ └──┬───┘ │ | |
| 101 | │ │ │ └──────┼────────┼─────┘ | |
| 102 | │ │ │ │ │ | |
| 103 | └──────┬──────┘──────┬──────┘───────────────┘ │ | |
| 104 | │ │ │ | |
| 105 | ┌──────┴─────────────┴───────────────┐ ┌─────────┴──────┐ | |
| 106 | │ keet-daemon WebSocket API │ │ Hyperswarm │ | |
| 107 | │ ws://localhost:3117/ws │ │ (direct P2P) │ | |
| 108 | │ JSON-RPC 2.0 │ │ via UDX/UDP │ | |
| 109 | └────────────────────────────────────┘ └────────────────┘ | |
| 110 | ``` | |
| 111 | ||
| 112 | See [NETWORKING.md](NETWORKING.md) for protocol details. | |
| 113 | See [GODOT.md](GODOT.md) for the two-tier Godot architecture. | |
| 114 | ||
| 115 | ## Panel Architecture (Modular Rooms) | |
| 116 | ||
| 117 | From the Keet conversation: "like a tiling WM but for different social networks." | |
| 118 | ||
| 119 | Each panel is a widget that can be composed into a room view: | |
| 120 | ||
| 121 | ``` | |
| 122 | ┌─────────────────────────────────────────────┐ | |
| 123 | │ Room: AI Town │ | |
| 124 | ├──────────────────┬──────────────────────────┤ | |
| 125 | │ │ │ | |
| 126 | │ Chat Panel │ Wiki Panel │ | |
| 127 | │ (real-time │ (collaborative │ | |
| 128 | │ messages) │ notes) │ | |
| 129 | │ │ │ | |
| 130 | ├──────────────────┼──────────────────────────┤ | |
| 131 | │ │ │ | |
| 132 | │ Feed Panel │ Board Panel │ | |
| 133 | │ (aggregated │ (kanban tasks) │ | |
| 134 | │ timeline) │ │ | |
| 135 | │ │ │ | |
| 136 | └──────────────────┴──────────────────────────┘ | |
| 137 | ``` | |
| 138 | ||
| 139 | Each panel type has: | |
| 140 | - A **data source** (hyper-wiki store, hyper-board store, hyper-feed store) | |
| 141 | - A **renderer** per interface type (HTML div, TUI box, Godot Control/Node2D/Node3D) | |
| 142 | - A **controller** that handles user input | |
| 143 | - An **API endpoint** on the hub | |
| 144 | ||
| 145 | The tiling layout is configurable per room: | |
| 146 | - Chat-only room = single panel | |
| 147 | - Wiki room = chat + wiki side by side | |
| 148 | - Project room = chat + board + wiki | |
| 149 | - Feed room = feed + chat | |
| 150 | ||
| 151 | ## Interface Types | |
| 152 | ||
| 153 | | Interface | Best for | Technology | | |
| 154 | |-----------|----------|------------| | |
| 155 | | WebUI | Dashboards, config, rich content | HTML/CSS/JS, browser | | |
| 156 | | TUI | Power users, SSH, quick status | Raw ANSI (like keet-tui) | | |
| 157 | | GUI | Always-on hub, system tray | Pear desktop app | | |
| 158 | | Godot 2D | Chat apps, feed readers, 2D games | GDScript + WebSocketPeer | | |
| 159 | | Godot 3D | Social spaces, 3D games | GDScript + GDExtension | | |
| 160 | ||
| 161 | All interfaces share the same backend via WebSocket + JSON-RPC 2.0. | |
| 162 | ||
| 163 | ## Holepunch Conventions | |
| 164 | ||
| 165 | Every project follows Holepunch's patterns: | |
| 166 | ||
| 167 | ``` | |
| 168 | project-name/ | |
| 169 | ├── package.json # "type": "module", pear config | |
| 170 | ├── index.js # Main export / entry point | |
| 171 | ├── lib/ # Implementation modules | |
| 172 | │ ├── feature-a.js # kebab-case filenames | |
| 173 | │ └── feature-b.js # PascalCase classes, camelCase functions | |
| 174 | ├── test/ # brittle tests | |
| 175 | │ └── basic.test.js | |
| 176 | └── README.md | |
| 177 | ``` | |
| 178 | ||
| 179 | **Naming:** | |
| 180 | - Bridges: `bridge-<platform>` (bridge-irc, bridge-discord) | |
| 181 | - Panels/tools: `hyper-<function>` (hyper-wiki, hyper-board) | |
| 182 | - Shared libs: `bridge-<function>` (bridge-envelope, bridge-vault) | |
| 183 | - Meta-app: `hyper-social` | |
| 184 | ||
| 185 | **Dependencies:** Each project symlinks `node_modules` from keet-cli (zero installs for Pear deps). Only platform-specific deps are new (e.g., `irc-framework` for bridge-irc). |
| … | … | @@ -0,0 +1,622 @@ |
| 1 | # Community Emergence, Governance, and Privacy | |
| 2 | ||
| 3 | How communities form, govern themselves, and maintain sovereignty in a decentralized social graph. Companion to [SOCIAL-GRAPH-NAVIGATION.md](SOCIAL-GRAPH-NAVIGATION.md) — that document covers the *what* (graph navigation, sheaf theory, parsing pipeline). This document covers the *how* and the *should we*. | |
| 4 | ||
| 5 | ## Case Study: Blacksky | |
| 6 | ||
| 7 | Before abstractions, a concrete example. **Blacksky** is the clearest demonstration of community emergence on a decentralized protocol. | |
| 8 | ||
| 9 | ### What It Is | |
| 10 | ||
| 11 | A BIPOC community infrastructure project founded by **Rudy Fraser** — a mutual aid organizer from Queens, NY who became a self-taught programmer. Built on AT Protocol (same protocol as Bluesky) but running fully independent infrastructure: | |
| 12 | ||
| 13 | | Component | Blacksky's Instance | | |
| 14 | |-----------|-------------------| | |
| 15 | | Personal Data Server | `blacksky.app` | | |
| 16 | | Full-network relay | `atproto.africa` (all 36M+ AT Protocol accounts) | | |
| 17 | | Moderation labeler | `@blackskyweb.xyz` | | |
| 18 | | Web client | `blacksky.community` | | |
| 19 | | Governance | People's Assembly (self-hosted Polis) | | |
| 20 | | Economic tools | Blacksky Cash, Papertree, GatherFor | | |
| 21 | ||
| 22 | **2.5 million feed users. 370K+ monthly active. $0 marketing. 100% organic growth.** Over 1,100 individual financial contributors through OpenCollective. | |
| 23 | ||
| 24 | The entire tech stack is called **rsky** (Rust AT Protocol implementation). It interoperates with Bluesky's infrastructure but is not dependent on it. | |
| 25 | ||
| 26 | Fraser's stated purpose: *"My purpose with Blacksky is not to be the next Black social network; it's to be the last one."* | |
| 27 | ||
| 28 | ### How It Actually Emerged | |
| 29 | ||
| 30 | **Stage 1 — Intentional Seeding.** A software engineer named Aveta personally invited ~1,000 Black users to Bluesky using invite codes, requiring each invitee to have someone **vouch for their character**. Before her work, the platform was overwhelmingly white. She didn't just invite people — she created a trust network. | |
| 31 | ||
| 32 | **Stage 2 — Graph Detection.** Fraser scraped Aveta's follow trains — the social graph itself — to build the Blacksky feed algorithm. He didn't ask people to self-identify. He detected community structure from **interaction patterns**. Same math as our MI/clustering pipeline, applied to follow graphs instead of room membership. | |
| 33 | ||
| 34 | **Stage 3 — Naming.** The feed was called "Blacksky." The statistical cluster got a label. This is the critical moment — more on this below. | |
| 35 | ||
| 36 | **Stage 4 — Identity Formation.** People started identifying AS Blacksky members. The label became identity. Behavior changed — people posted differently knowing they'd appear on the Blacksky feed. | |
| 37 | ||
| 38 | **Stage 5 — Crisis-Driven Crystallization.** A death threat against Aveta went insufficiently addressed by Bluesky. CEO offered "composable moderation" as a solution. Fraser's response: that's outsourcing responsibility. Then he built those community safety tools himself — but better than anything the platform would provide. The criticism and the construction coexist. Separately, Bluesky allowed racial slurs in usernames. Users staged a posting strike. | |
| 39 | ||
| 40 | **Stage 6 — Infrastructure Sovereignty.** Own PDS, own relay, own moderation, own governance (People's Assembly), own migration tool (Tektite — move your identity with one click), own web client, own economic tools. The only difference between Blacksky and Bluesky became **jurisdiction** — Terms of Service, moderation policy, governance. The protocol layer remained shared. | |
| 41 | ||
| 42 | ### Key Principles from Blacksky | |
| 43 | ||
| 44 | **Community = mutual accountability.** Fraser's definition: *"Community really means mutual accountability between the one and the many."* Not a member list. Not a set of rooms. A pattern of accountability that persists. | |
| 45 | ||
| 46 | **Moderation = shared context.** *"Context-aware, Black moderators moderating anti-Black content. Our moderators must have a shared understanding of anti-Blackness, white supremacy, internalized racism, and how online misogynoir can lead to violence in the real world."* Not rules. Context. | |
| 47 | ||
| 48 | **Safety = infrastructure.** *"An internet where marginalized users are safe is an internet where all users are safe."* Safety is not a policy layer — it's the infrastructure itself. | |
| 49 | ||
| 50 | **Economic sovereignty = community sovereignty.** Fraser built Papertree (mutual aid pooling) and GatherFor (rotating savings circles from West African and Caribbean traditions) BEFORE Blacksky. The economic infrastructure preceded the social infrastructure. Over 3.5 years, these tools facilitated over $250,000 in community funds. | |
| 51 | ||
| 52 | **Governance through deliberation, not voting.** The People's Assembly uses Polis — an open-source tool where members submit statements, vote on others' statements, and machine learning finds clusters of agreement. Not majority rule — consensus-finding. Day-to-day moderation is handled by the team using shared understanding, not rigid rules. | |
| 53 | ||
| 54 | ## The Phase Transition Model | |
| 55 | ||
| 56 | A community is not a static object. It's a phase transition in a social system. | |
| 57 | ||
| 58 | ### Supercooled Liquid → Crystal | |
| 59 | ||
| 60 | Below a certain density of interaction, there's no community — just people in rooms. Above that density, community crystallizes. The transition is not gradual. It's a phase change. | |
| 61 | ||
| 62 | ``` | |
| 63 | ┌──────────────── Phase transition ──────────────┐ | |
| 64 | │ │ | |
| 65 | People → Statistical → Named → Identity → Governance → Sovereignty | |
| 66 | in rooms pattern cluster formation emergence (infra control) | |
| 67 | │ ↑ │ | |
| 68 | │ │ │ | |
| 69 | │ NUCLEATION POINT │ | |
| 70 | │ (someone names the pattern) │ | |
| 71 | └─────────────────────────────────────────────────────────────┘ | |
| 72 | Feedback loop | |
| 73 | ``` | |
| 74 | ||
| 75 | **The nucleation point is the naming act.** Aveta created the conditions (high-MI cluster). Fraser provided the seed crystal (the name "Blacksky"). The community crystallized around the name. | |
| 76 | ||
| 77 | In physics: supercooled liquid has all the structure needed for crystallization but needs a nucleation point (a seed crystal, a scratch on the container wall). In social: the membership overlap has all the structure needed for community — but needs someone to name it. | |
| 78 | ||
| 79 | ### Implications for System Design | |
| 80 | ||
| 81 | **The graph navigation pipeline can detect supercooled liquid** — clusters of rooms with high MI, ready to crystallize. But the system should NOT automatically name clusters. Naming is a political act. | |
| 82 | ||
| 83 | ``` | |
| 84 | System detects: "These 12 rooms share 70% of their members" | |
| 85 | System surfaces: Visual cluster in graph view, proximity in sidebar | |
| 86 | System does NOT: Auto-label "Community #47" or suggest a name | |
| 87 | ||
| 88 | Human acts: Someone says "hey, we should have a registry for this" | |
| 89 | Human names: "Hypercore Dev" — the crystal forms | |
| 90 | Human governs: Registry governance room = community meta-room | |
| 91 | ``` | |
| 92 | ||
| 93 | **Emergence needs midwives, not managers.** The system creates conditions for nucleation. Humans decide when and whether to nucleate. | |
| 94 | ||
| 95 | ## Three Levels of Connector Theory | |
| 96 | ||
| 97 | Room-level connectors were covered in [SOCIAL-GRAPH-NAVIGATION.md](SOCIAL-GRAPH-NAVIGATION.md). The full picture has three levels, each a sheaf on the level below. | |
| 98 | ||
| 99 | ### Level 1: Room Connectors (Membership) | |
| 100 | ||
| 101 | ``` | |
| 102 | MI(Room_A, Room_B) = log2(P(A,B) / (P(A) × P(B))) | |
| 103 | ||
| 104 | Connector profile: pattern of MI with neighboring rooms | |
| 105 | A room's social "meaning" IS its connector set | |
| 106 | ``` | |
| 107 | ||
| 108 | Two rooms with high MI share members more than chance predicts. The connector profile captures a room's social role — a dev room connects differently than a social room. | |
| 109 | ||
| 110 | ### Level 2: Community Connectors (Room Overlap Between Clusters) | |
| 111 | ||
| 112 | ``` | |
| 113 | MI(Community_X, Community_Y) = f(shared rooms between clusters) | |
| 114 | ||
| 115 | Bridge rooms ARE the connectors between communities. | |
| 116 | A room in two communities IS the connection. | |
| 117 | ``` | |
| 118 | ||
| 119 | Communities have connector profiles — what kinds of communities they connect to and how. A tech community connects to other tech communities through shared tooling rooms. A geographic community connects through location-based rooms. | |
| 120 | ||
| 121 | ### Level 3: Meta-Community Connectors (Identity Overlap Across Platforms) | |
| 122 | ||
| 123 | ``` | |
| 124 | MI(MetaCom_on_Keet, MetaCom_on_Bluesky) = f(shared identities across bridges) | |
| 125 | ||
| 126 | Bridge USERS are the connectors at this level. | |
| 127 | People on both platforms ARE the cross-platform connection. | |
| 128 | ``` | |
| 129 | ||
| 130 | The same community existing on Keet and Bluesky is connected through the people who are on both. At Level 2, bridge rooms connect communities. At Level 3, bridge users connect meta-communities. | |
| 131 | ||
| 132 | ### Cohomological Obstructions at Each Level | |
| 133 | ||
| 134 | | Level | Obstruction | Example | | |
| 135 | |-------|-------------|---------| | |
| 136 | | 1 (Rooms) | Rooms that don't cluster cleanly | A room serving two distinct purposes | | |
| 137 | | 2 (Communities) | Incompatible governance at overlaps | Two registries with conflicting moderation | | |
| 138 | | 3 (Meta-communities) | Platform features that don't translate | Discord reactions can't map to IRC | | |
| 139 | ||
| 140 | These aren't bugs. They're structural information about the social space. | |
| 141 | ||
| 142 | ## Communities Are Living Languages | |
| 143 | ||
| 144 | This is not a metaphor. The mathematical structure is identical. | |
| 145 | ||
| 146 | | Language | Social | | |
| 147 | |----------|--------| | |
| 148 | | Word | Room | | |
| 149 | | Sentence | User's room list | | |
| 150 | | Grammar (descriptive) | Actual community norms | | |
| 151 | | Grammar (prescriptive) | Governance rules | | |
| 152 | | Speaker | Community member | | |
| 153 | | Dialect | Subgroup | | |
| 154 | | Language family | Community cluster | | |
| 155 | | Loanword | Norm imported from another community | | |
| 156 | | Pidgin | Bridge room norms (simplified cross-community) | | |
| 157 | | Creole | Mature bridge community (own norms, own identity) | | |
| 158 | | Compartment boundary | Community boundary | | |
| 159 | ||
| 160 | ### Prescriptive vs Descriptive Grammar | |
| 161 | ||
| 162 | Every living language has this tension: how people ACTUALLY speak (descriptive) vs how people SAY you should speak (prescriptive). Dictionaries describe. Style guides prescribe. The tension is productive — it drives language evolution. | |
| 163 | ||
| 164 | Every community has the same tension: how people ACTUALLY behave (descriptive norms) vs governance rules (prescriptive norms). The parsing pipeline extracts descriptive norms from interaction data. Governance deliberation (People's Assembly, community votes) produces prescriptive norms. The tension between them drives community evolution. | |
| 165 | ||
| 166 | **The pipeline can detect prescriptive norms from descriptive data** — if a community consistently sanctions behavior X, "don't do X" is a norm regardless of whether it's written down. The meta-pattern of enforcement IS the grammar. | |
| 167 | ||
| 168 | ### Pidgins and Creoles at Community Boundaries | |
| 169 | ||
| 170 | When two language communities meet, a pidgin forms — simplified rules for basic communication. If interaction continues, the pidgin becomes a creole — a full language with its own grammar, native speakers, literary tradition. | |
| 171 | ||
| 172 | **Blacksky is a social creole.** It emerged at the boundary between Black Twitter (community) and Bluesky (platform). The norms are neither Twitter norms nor Bluesky norms — they're Blacksky norms, born from the tension at the boundary. | |
| 173 | ||
| 174 | In hyper-social: **bridge rooms are pidgin spaces.** The norms are simplified — minimum needed for cross-community interaction. Over time, if interaction continues, bridge norms enrich. The bridge community becomes its own thing — a creole community with members from both sides but norms of its own. | |
| 175 | ||
| 176 | The IRC bridge to Keet will have pidgin norms initially. If the bridged communities continue interacting, those norms will develop into something richer. The bridge community (people active in both) will develop its own culture that is neither IRC culture nor Keet culture. | |
| 177 | ||
| 178 | ## What Data We're Counting | |
| 179 | ||
| 180 | ### Data That Exists (Per Node) | |
| 181 | ||
| 182 | Each node knows — and ONLY knows: | |
| 183 | ||
| 184 | ``` | |
| 185 | Your rooms → getRecentRooms() | |
| 186 | Members of YOUR rooms → getMemberList(roomId) | |
| 187 | Public room listings → discovery registries you follow | |
| 188 | ``` | |
| 189 | ||
| 190 | No node knows other people's room lists, membership of rooms they're not in, or the "full social graph." The global graph is a theoretical object that no one computes, sees, or stores. | |
| 191 | ||
| 192 | ### The Local Co-occurrence Matrix | |
| 193 | ||
| 194 | Each user computes from their own data: | |
| 195 | ||
| 196 | ``` | |
| 197 | For each pair of rooms (A, B) I'm in: | |
| 198 | overlap = members(A) ∩ members(B) | |
| 199 | count[A][B] = |overlap| | |
| 200 | ||
| 201 | P(A) = |members(A)| / total_unique_members_I_see | |
| 202 | P(B) = |members(B)| / total_unique_members_I_see | |
| 203 | P(A,B) = |overlap| / total_unique_members_I_see | |
| 204 | ||
| 205 | MI(A, B) = log2(P(A,B) / (P(A) × P(B))) | |
| 206 | ``` | |
| 207 | ||
| 208 | Your MI values are YOUR view. Someone in different rooms gets different MI. **That's the sheaf** — each user is a local section with no requirement for global consistency. | |
| 209 | ||
| 210 | ### Two Distinct Graphs | |
| 211 | ||
| 212 | **Room Topology Graph (Public)** | |
| 213 | - Which rooms exist in which discovery registries | |
| 214 | - Room metadata: name, topic, avatar (public by choice — rooms opt into registries) | |
| 215 | - Registry-to-room relationships, bridge rooms | |
| 216 | - Anyone following the registry can see this | |
| 217 | ||
| 218 | **Social/Membership Graph (Private, Local)** | |
| 219 | - Who's in what rooms (only YOUR rooms) | |
| 220 | - Membership overlap (only rooms YOU share) | |
| 221 | - MI, MST, clusters — all computed locally | |
| 222 | - Only YOU see YOUR graph | |
| 223 | ||
| 224 | ### What We Never Count | |
| 225 | ||
| 226 | - Message content (never) | |
| 227 | - Individual behavior patterns or posting frequency (never) | |
| 228 | - Cross-room user tracking beyond shared rooms (never) | |
| 229 | - Timing correlations between rooms (never) | |
| 230 | - Reading patterns or lurking behavior (never) | |
| 231 | ||
| 232 | ## Privacy Architecture | |
| 233 | ||
| 234 | ### Privacy Through Architecture, Not Policy | |
| 235 | ||
| 236 | ``` | |
| 237 | Discord: "We PROMISE not to read your DMs" → Policy (breakable) | |
| 238 | Keet: "We CAN'T read your DMs" → Architecture (unbreakable) | |
| 239 | ||
| 240 | Discord: "We PROMISE not to sell your graph" → Policy (breakable) | |
| 241 | Us: "No one HAS your social graph" → Architecture (unbreakable) | |
| 242 | ``` | |
| 243 | ||
| 244 | The P2P architecture NATURALLY provides the privacy properties we want. There's no central server to surveil. Each node has a limited view. The social graph is inherently fragmented across nodes. This isn't a limitation — it's the design. | |
| 245 | ||
| 246 | ### Inherent Information Leakage | |
| 247 | ||
| 248 | If you're in rooms A, B, C and user X is also in all three — you know X is in rooms A, B, C. This is unavoidable in any shared space. If you're at the same bar as someone, you know they're at that bar. | |
| 249 | ||
| 250 | ``` | |
| 251 | Level 0: I know X is in room A (we're both there) → Inherent | |
| 252 | Level 1: I know X is in rooms A and B (we share both) → Inherent | |
| 253 | Level 2: I can infer X is likely in similar rooms (MI) → Computed locally | |
| 254 | Level 3: I can model X's social neighborhood → Computed locally | |
| 255 | ``` | |
| 256 | ||
| 257 | Levels 0-1 are unavoidable in any social system. Levels 2-3 are what the computation enables — but only from data you already have. The computation doesn't create new information. It surfaces structure in existing information. | |
| 258 | ||
| 259 | ### The Aggregation Attack | |
| 260 | ||
| 261 | The dangerous scenario: | |
| 262 | ||
| 263 | ``` | |
| 264 | User A sees: X in rooms {1, 2, 3} | |
| 265 | User B sees: X in rooms {4, 5, 6} | |
| 266 | User C sees: X in rooms {7, 8, 9} | |
| 267 | ||
| 268 | If A, B, C share data → they reconstruct X's full room list {1-9} | |
| 269 | ``` | |
| 270 | ||
| 271 | **Mitigation: Never aggregate.** The system has NO mechanism for combining local graphs. No "share your graph" feature. No "global statistics" endpoint. No API for exporting membership data. Each user's computation stays local. | |
| 272 | ||
| 273 | ### The Observation Is an Intervention | |
| 274 | ||
| 275 | Detecting a community changes the community. Naming "Blacksky" changed how people self-identified and interacted. The feed algorithm changed behavior. Showing someone their social graph changes their social graph. | |
| 276 | ||
| 277 | The graph navigation system is not a neutral observation tool. It's a social intervention. The choice of what to surface, what to highlight, what to hide — these are design decisions that shape the social space. The tool participates in the dynamics it measures. | |
| 278 | ||
| 279 | **Design consequence:** Be explicit about what the tool shows and why. Don't pretend neutrality. Surface the choices. | |
| 280 | ||
| 281 | ## Governance | |
| 282 | ||
| 283 | ### What Governance Exists (Current System) | |
| 284 | ||
| 285 | | Layer | Governor | Scope | Power | | |
| 286 | |-------|----------|-------|-------| | |
| 287 | | Room | Room creator/admin | One room | Moderate content, manage members, set roles | | |
| 288 | | Registry | Autobase writers | Room listings | Add/remove rooms from discovery index | | |
| 289 | ||
| 290 | A room admin is a **bartender, not a surveillance state.** They see what happens in their bar. They don't follow you home. They don't know what other bars you go to. | |
| 291 | ||
| 292 | A registry admin has even LESS power — they curate listings (which rooms to show), but can't see room content, room membership, or anything inside the rooms they index. | |
| 293 | ||
| 294 | ### How Governance Emerges | |
| 295 | ||
| 296 | **Step 1: Interaction density crosses threshold.** People are in rooms. Membership overlap creates high-MI clusters. No governance yet — just statistical structure. | |
| 297 | ||
| 298 | **Step 2: Someone names the cluster.** A user or group creates a discovery registry. The cluster gets a label. This is the nucleation point. | |
| 299 | ||
| 300 | **Step 3: Naming creates identity.** People start identifying with the label. "I'm part of the Hypercore Dev community." Behavior shifts. | |
| 301 | ||
| 302 | **Step 4: Identity creates accountability.** "We don't do that here." Norms emerge from shared identity. Not written rules — behavioral patterns. The descriptive grammar of the community. | |
| 303 | ||
| 304 | **Step 5: Accountability creates governance.** Norms are formalized. Moderation policies are written. Deliberation processes are established. The prescriptive grammar of the community. | |
| 305 | ||
| 306 | **Step 6: Governance creates infrastructure.** The community builds or adopts moderation services, economic tools, communication infrastructure. Sovereignty follows from infrastructure control. | |
| 307 | ||
| 308 | This sequence can't be shortcut. You can't impose governance on a community that hasn't formed identity. You can't form identity around a cluster that hasn't been named. You can't name a cluster that doesn't exist in the data. The pipeline must run in order. | |
| 309 | ||
| 310 | ### Moderation as Local Knowledge | |
| 311 | ||
| 312 | Blacksky's moderation works because moderators share context that can't be reduced to rules. Anti-Blackness, misogynoir, internalized racism — these require understanding, not keyword matching. | |
| 313 | ||
| 314 | **Moderation is a section of the community sheaf.** Locally consistent within the community, but non-transferable. You can't export Blacksky's moderation to a gaming community. The context doesn't serialize. | |
| 315 | ||
| 316 | In the language analogy: you can't translate grammar rules between languages. French grammar doesn't apply to Japanese. Each community's governance is internally consistent but contextually bound. | |
| 317 | ||
| 318 | What DOES transfer across communities is **signal, not enforcement:** | |
| 319 | ||
| 320 | ``` | |
| 321 | Community A's moderation → bridge → Community B receives SIGNAL | |
| 322 | Community B decides what to do with signal | |
| 323 | ||
| 324 | Not: "User X is banned" (enforcement transfer) | |
| 325 | But: "User X was flagged for anti-Black harassment" (signal transfer) | |
| 326 | ||
| 327 | Community B applies its OWN context to the signal. | |
| 328 | ``` | |
| 329 | ||
| 330 | This is Blacksky's actual model — the labeler produces labels. Users and rooms decide independently whether to apply them (Off / Warn / Hide per label type). Moderation is a subscribable service, not an imposed authority. | |
| 331 | ||
| 332 | ### Moderation as a Subscribable Service | |
| 333 | ||
| 334 | ``` | |
| 335 | Moderation Service M | |
| 336 | ├── Scans content (automated + human review) | |
| 337 | ├── Produces labels (anti-Black harassment, misogynoir, etc.) | |
| 338 | └── Publishes labels to subscribers | |
| 339 | ||
| 340 | Room A subscribes to M → applies labels according to room policy | |
| 341 | Room B subscribes to M → applies different label settings | |
| 342 | Room C doesn't subscribe → doesn't see labels | |
| 343 | Room D subscribes to M and N → sees both sets of labels | |
| 344 | ``` | |
| 345 | ||
| 346 | In graph terms, moderation subscriptions are an **edge type** — a trust relationship between rooms and moderation services. Different rooms subscribe to different services. Users in overlapping rooms see the interaction of multiple moderation layers. | |
| 347 | ||
| 348 | ### The Recursive Self-Reference Problem | |
| 349 | ||
| 350 | The social graph contains its own governance. Governance modifies the graph (banning changes membership). The modified graph changes governance (different members → different norms). | |
| 351 | ||
| 352 | ``` | |
| 353 | Social graph → detection → communities → governance | |
| 354 | ↑ │ | |
| 355 | └────── enforcement modifies graph ←───┘ | |
| 356 | ``` | |
| 357 | ||
| 358 | This is a fixed-point problem. The language pipeline has the same structure: grammar extracted from corpus determines how new sentences are parsed, which feeds back into the corpus. Linas handles this with iterative refinement until convergence. | |
| 359 | ||
| 360 | In social governance: communities iteratively refine their norms through repeated interaction and deliberation. **Instability is a feature.** A community in stable equilibrium is dead or authoritarian. A community in unstable equilibrium is alive — norms shift, people push back, deliberation happens, norms adjust. The social grammar is never final. | |
| 361 | ||
| 362 | ## The Meta-Community Tension | |
| 363 | ||
| 364 | Hyper-social's vision: bridges connect platforms. A community can span Keet, IRC, Discord, Matrix, Mastodon, email. The community IS the people and their relationships — the platform is substrate. | |
| 365 | ||
| 366 | Blacksky demonstrates this partially — the community existed as "Black Twitter" before, on a different platform. The community migrated. The identity migrated. The social graph persisted because it's a property of the PEOPLE, not the platform. | |
| 367 | ||
| 368 | ### Three Competing Sovereignties | |
| 369 | ||
| 370 | ``` | |
| 371 | PROTOCOL SOVEREIGNTY Each platform has its own rules, moderation, identity | |
| 372 | COMMUNITY SOVEREIGNTY The community wants consistent norms regardless of platform | |
| 373 | INDIVIDUAL SOVEREIGNTY Each person wants control over their identity and expression | |
| 374 | ``` | |
| 375 | ||
| 376 | These conflict at boundaries: | |
| 377 | ||
| 378 | **At the bridge (protocol vs community):** A user banned by Blacksky's labeler on Bluesky — should they be banned in the bridged Keet room? Protocol sovereignty says Keet moderation is per-room. Community sovereignty says our moderation covers our community. | |
| 379 | ||
| 380 | **At the identity layer (community vs individual):** The community wants "same person across platforms" for accountability. The individual wants pseudonymity and context-dependent identity. The community needs to recognize Rudy across platforms. Rudy might want different presentation in different contexts. | |
| 381 | ||
| 382 | **At the governance layer (all three):** A meta-community's People's Assembly makes a decision: "no X-type content." On Bluesky, enforced via labeler. On Keet, via room admin. On IRC, via channel ops. Three different enforcement mechanisms for one governance decision. | |
| 383 | ||
| 384 | ### Resolution: Federation, Not Union | |
| 385 | ||
| 386 | The meta-community is a federation of platform-specific instances with local autonomy: | |
| 387 | ||
| 388 | 1. **Shared membership** — the same people, different handles | |
| 389 | 2. **Shared norms** — agreed through deliberation, enforced locally per platform | |
| 390 | 3. **Shared bridges** — message flow across platforms | |
| 391 | 4. **Shared moderation signals** — not enforcement, signal | |
| 392 | ||
| 393 | Governance is **advisory across bridges, binding within rooms.** The meta-community's People's Assembly says "we don't allow X." Within each platform, local governance enforces according to local mechanisms. The bridge translates intent, not mechanism. | |
| 394 | ||
| 395 | Identity mapping is **opt-in and partial.** You can link your Keet keypair to your Bluesky DID — but only if you choose to. You can be pseudonymous on one platform and named on another. The community sees "Rudy" regardless of platform, but the mapping is Rudy's choice. | |
| 396 | ||
| 397 | ### The Bridge Community as Creole | |
| 398 | ||
| 399 | People active on both sides of a bridge — in both the Keet rooms and the IRC channels — form their own community. Their norms are neither Keet norms nor IRC norms. They're bridge norms — a social creole. | |
| 400 | ||
| 401 | Over time, bridge communities develop their own culture, their own governance, their own identity. This is not planned — it emerges from sustained cross-platform interaction. The bridge room starts as pidgin (basic message passing). If interaction persists, it becomes creole (rich culture with its own grammar). | |
| 402 | ||
| 403 | ### The Fracture Principle Revisited | |
| 404 | ||
| 405 | From [ARCHITECTURE.md](ARCHITECTURE.md): *"The communication graph will fracture. Any 'universal bridge' eventually breaks into many bridges."* | |
| 406 | ||
| 407 | Blacksky validates this: started as a feed (one platform feature), fractured into own PDS + own relay + own moderation + own governance + own economic tools + own web client. Each piece independently useful. The system is the emergent property of all pieces running together. | |
| 408 | ||
| 409 | A meta-community spanning Keet + Bluesky + IRC + Matrix will similarly fracture into: | |
| 410 | - Bridge per platform pair (not one universal bridge) | |
| 411 | - Moderation service per community (not one universal moderator) | |
| 412 | - Governance per community (not one universal governance) | |
| 413 | - Identity mapping per person (not one universal identity) | |
| 414 | ||
| 415 | The fracture is not failure — it's the natural topology of social space. Non-trivial cohomology = real tensions that can't be flattened. | |
| 416 | ||
| 417 | ## Economic Sovereignty | |
| 418 | ||
| 419 | Fraser inverted the usual tech stack: | |
| 420 | ||
| 421 | ``` | |
| 422 | Usual tech model: Mutual aid model: | |
| 423 | Protocol Protocol | |
| 424 | Infrastructure Economic solidarity (mutual aid pooling) | |
| 425 | Social features Infrastructure (funded by community) | |
| 426 | Monetization (extract) Social features (emergent from funded infra) | |
| 427 | ``` | |
| 428 | ||
| 429 | The economic layer is not on top (monetization to extract value). It's near the bottom (foundation for sovereignty). Communities that fund their own infrastructure answer to no platform. | |
| 430 | ||
| 431 | In the language analogy: a language community that controls its own publishing (newspapers, schools, internet) maintains its language. A community dependent on another's infrastructure assimilates over time. Economic sovereignty IS cultural sovereignty IS community sovereignty. | |
| 432 | ||
| 433 | ### Economic Edges in the Graph | |
| 434 | ||
| 435 | Rooms with economic cooperation have a qualitatively different connection than rooms with only membership overlap. A community where members pool funds for infrastructure has a bond beyond shared interests. | |
| 436 | ||
| 437 | ``` | |
| 438 | Membership edge: "We share members" → statistical connection | |
| 439 | Governance edge: "We share moderation" → trust connection | |
| 440 | Economic edge: "We share treasury" → solidarity connection | |
| 441 | ``` | |
| 442 | ||
| 443 | Communities with all three edge types are the most robust. This maps to Fraser's mutual aid principle: financial solidarity creates the conditions for social infrastructure, which creates the conditions for community governance. | |
| 444 | ||
| 445 | ### Sustainability Without Extraction | |
| 446 | ||
| 447 | Blacksky's economic model: | |
| 448 | - 1,100+ individual contributors on OpenCollective | |
| 449 | - Papertree: mutual aid pooling (no applications, no proof of hardship — trust) | |
| 450 | - GatherFor: susu circles (rotating savings from West African/Caribbean traditions) | |
| 451 | - Blacksky Cash: peer-to-peer payments with group pooling | |
| 452 | ||
| 453 | Success metric: not transaction volume, but **ongoing participation, repeat contributions, collective decision-making around need.** The economic system measures community health, not profit. | |
| 454 | ||
| 455 | This is a pattern any decentralized community can follow: fund infrastructure collectively, own it collectively, govern it collectively. The alternative — depending on platform goodwill or venture capital — leads to the dynamics that made Black Twitter precarious on Twitter's infrastructure. | |
| 456 | ||
| 457 | ## Cyberspace Principles | |
| 458 | ||
| 459 | ### The Hacker Ethic Applied | |
| 460 | ||
| 461 | **"Access to computers should be unlimited and total."** | |
| 462 | Anyone can run a node. No account required. No approval process. No app store. `bare index.js` and you're in. | |
| 463 | ||
| 464 | **"All information should be free."** | |
| 465 | The PROTOCOL is free. The CODE is free. Room listings are free (public discovery). But people's social graphs are THEIR information. Freedom of information is not surveillance of people. | |
| 466 | ||
| 467 | **"Mistrust authority — promote decentralization."** | |
| 468 | No central graph authority. No aggregation server. Local computation only. The architecture makes centralization physically impossible. | |
| 469 | ||
| 470 | **"Hackers should be judged by their hacking."** | |
| 471 | Pseudonymous identity. Your key is your identity. Reputation from behavior in rooms, not from profiles managed by a corporation. | |
| 472 | ||
| 473 | ### The Sysadmin Scope | |
| 474 | ||
| 475 | ``` | |
| 476 | Traditional sysadmin: P2P room admin: | |
| 477 | Reads all messages ✓ Reads messages in THEIR room ✓ | |
| 478 | Sees all users ✓ Sees members of THEIR room ✓ | |
| 479 | Monitors all activity ✓ Monitors THEIR room activity ✓ | |
| 480 | Controls all access ✓ Controls THEIR room access ✓ | |
| 481 | Knows about other rooms ✗ | |
| 482 | Sees members' other activities ✗ | |
| 483 | Has cross-room surveillance ✗ | |
| 484 | ``` | |
| 485 | ||
| 486 | The room admin's scope is strictly bounded. No governance layer should see beyond its scope: | |
| 487 | - Room admin sees room data | |
| 488 | - Registry admin sees registry data (listings, not content or membership) | |
| 489 | - Nobody sees the full graph | |
| 490 | ||
| 491 | This is the **principle of least privilege** applied to social structure. | |
| 492 | ||
| 493 | ### Privacy Through Cryptography, Not Policy | |
| 494 | ||
| 495 | Build tools that empower, not surveil. Make the system transparent (open source, open protocol). Make it accessible (anyone can run a node, no gatekeepers). Resist centralization even when it's more convenient. | |
| 496 | ||
| 497 | The social graph navigation system should embody: | |
| 498 | 1. **No central authority** — local computation, no aggregation | |
| 499 | 2. **User sovereignty** — your graph is yours, you control what you see | |
| 500 | 3. **Pseudonymity** — graph reveals social structure, not identity | |
| 501 | 4. **Opt-in only** — discovery is consensual, rooms choose to be listed | |
| 502 | 5. **No surveillance** — no entity can see the full graph | |
| 503 | 6. **Interoperability** — bridges are sheaf morphisms, not walled gardens | |
| 504 | ||
| 505 | ## Can Social Structure Be Parsed? | |
| 506 | ||
| 507 | The deepest question. Language has structure because it's generated by finite rules. Is social structure similarly compressible? | |
| 508 | ||
| 509 | **Yes, with four caveats:** | |
| 510 | ||
| 511 | **1. Multiple valid parses.** "I saw the man with the telescope" has two parse trees. A social graph can have multiple valid community structures. The ambiguity is real, not a failure of the algorithm. | |
| 512 | ||
| 513 | **2. The observation is an intervention.** Showing the parse changes the thing being parsed. The pipeline has political implications. What you surface shapes what people see and how they behave. | |
| 514 | ||
| 515 | **3. Social grammar evolves fast.** Language grammar changes over decades. Social norms change over weeks. The pipeline must run continuously. | |
| 516 | ||
| 517 | **4. Power asymmetry.** In language, speakers contribute roughly equally to grammar. In social, admins and active members contribute disproportionately. The pipeline must either account for this (weighted MI) or deliberately not (equal weighting — a political choice). | |
| 518 | ||
| 519 | ## Implementation Phases | |
| 520 | ||
| 521 | ### Phase 1: Local-Only Graph (Safe) | |
| 522 | ||
| 523 | ``` | |
| 524 | Data source: keet-daemon API (your rooms, your members) | |
| 525 | Computation: local, in-process, never exported | |
| 526 | Storage: local only | |
| 527 | Sharing: none | |
| 528 | Risk: minimal — same data you already see, structured | |
| 529 | ``` | |
| 530 | ||
| 531 | ### Phase 2: Registry Graph (Safe — Already Public) | |
| 532 | ||
| 533 | ``` | |
| 534 | Data source: discovery registries (public by design) | |
| 535 | Computation: can be shared (public data) | |
| 536 | Storage: replicated via Autobase | |
| 537 | Sharing: inherent (registries are designed to be shared) | |
| 538 | Risk: none — this data is already public | |
| 539 | ``` | |
| 540 | ||
| 541 | ### Phase 3: Moderation Services (Careful — Opt-In) | |
| 542 | ||
| 543 | ``` | |
| 544 | Data source: content analysis (within subscribed rooms) | |
| 545 | Computation: by moderation service operators | |
| 546 | Sharing: labels published to subscribers (not content) | |
| 547 | Risk: moderate — moderation requires seeing content | |
| 548 | Mitigation: opt-in per room, transparent labeling, no content export | |
| 549 | ``` | |
| 550 | ||
| 551 | ### Phase 4: Deliberation Tools (Careful — Requires Identity) | |
| 552 | ||
| 553 | ``` | |
| 554 | Data source: community member opinions | |
| 555 | Computation: consensus-finding (Polis-style dimensionality reduction) | |
| 556 | Sharing: aggregated opinion clusters (not individual votes) | |
| 557 | Risk: moderate — opinion data is sensitive | |
| 558 | Mitigation: anonymous voting, only cluster-level results published | |
| 559 | ``` | |
| 560 | ||
| 561 | ### Phase 5: Economic Tools (Most Sensitive) | |
| 562 | ||
| 563 | ``` | |
| 564 | Data source: financial contributions | |
| 565 | Computation: treasury management, mutual aid distribution | |
| 566 | Sharing: treasury balances (not individual contributions) | |
| 567 | Risk: high — financial data requires strong privacy | |
| 568 | Mitigation: cryptographic privacy, no individual transaction visibility | |
| 569 | ``` | |
| 570 | ||
| 571 | **Skip global aggregate statistics entirely.** Local computation + public registries cover 90% of the value. Aggregate stats add marginal value with real privacy risk. | |
| 572 | ||
| 573 | ## Design Principles | |
| 574 | ||
| 575 | ``` | |
| 576 | 1. EMERGENCE NEEDS MIDWIVES, NOT MANAGERS | |
| 577 | Detect clusters. Let humans name them. Naming is political. | |
| 578 | ||
| 579 | 2. COMMUNITIES ARE LIVING LANGUAGES | |
| 580 | Parse them like language. Expect evolution. Expect dialects. | |
| 581 | Expect pidgins at boundaries. Expect creoles over time. | |
| 582 | ||
| 583 | 3. GOVERNANCE IS LOCAL KNOWLEDGE | |
| 584 | Can't be centralized without losing context. Each community | |
| 585 | moderates with shared understanding. Moderation crosses | |
| 586 | bridges as signal, not enforcement. | |
| 587 | ||
| 588 | 4. ECONOMIC SOVEREIGNTY IS THE FOUNDATION | |
| 589 | Communities that fund their own infrastructure are sovereign. | |
| 590 | Mutual aid precedes social infrastructure. | |
| 591 | ||
| 592 | 5. THE OBSERVATION IS AN INTERVENTION | |
| 593 | The graph view shapes the graph. Design choices are political. | |
| 594 | Don't pretend neutrality. Surface the choices. | |
| 595 | ||
| 596 | 6. META-COMMUNITIES ARE FEDERATIONS, NOT UNIONS | |
| 597 | Cross-platform communities have local autonomy per platform. | |
| 598 | Governance is advisory across bridges, binding within rooms. | |
| 599 | ||
| 600 | 7. THE FRACTURE IS THE TOPOLOGY | |
| 601 | Non-trivial cohomology = real social tensions. Don't resolve | |
| 602 | them. Surface them. Let communities negotiate. | |
| 603 | ||
| 604 | 8. INSTABILITY IS HEALTH | |
| 605 | Communities that stop evolving are dead or authoritarian. | |
| 606 | The grammar is never final. The parse is always provisional. | |
| 607 | ||
| 608 | 9. PRIVACY THROUGH ARCHITECTURE | |
| 609 | No aggregation. Local computation only. No God's-eye view. | |
| 610 | Make surveillance impossible, not just prohibited. | |
| 611 | ||
| 612 | 10. THREE SOVEREIGNTIES IN PRODUCTIVE TENSION | |
| 613 | Protocol (platform rules), Community (shared norms), | |
| 614 | Individual (personal freedom). The tension is the feature. | |
| 615 | ``` | |
| 616 | ||
| 617 | ## Connection to Other Documents | |
| 618 | ||
| 619 | - [SOCIAL-GRAPH-NAVIGATION.md](SOCIAL-GRAPH-NAVIGATION.md) — Graph navigation mechanics, sheaf theory, the parsing pipeline, dual navigation model | |
| 620 | - [ARCHITECTURE.md](ARCHITECTURE.md) — System overview, the fracture principle, bridge architecture | |
| 621 | - [NETWORKING.md](NETWORKING.md) — Protocol decisions for bridge communication | |
| 622 | - [PROJECTS.md](PROJECTS.md) — All standalone projects in the ecosystem |
| … | … | @@ -0,0 +1,351 @@ |
| 1 | # Godot 4 Integration | |
| 2 | ||
| 3 | Godot is the engine for all interactive interfaces — from simple 2D chat clients to immersive 3D multiplayer games. Not just 3D. Godot handles **2D apps, 2D games, 3D apps, AND 3D games**. | |
| 4 | ||
| 5 | ## Two-Tier Architecture | |
| 6 | ||
| 7 | ``` | |
| 8 | Tier 1 (Social/Apps -- WebSocket + JSON-RPC 2.0): | |
| 9 | Godot -> WebSocketPeer -> keet-daemon (ws://localhost:3117/ws) | |
| 10 | Good for: chat, feeds, wikis, boards, casual games | |
| 11 | Latency: ~1-5ms (WebSocket on localhost, bidirectional) | |
| 12 | Implementation: Pure GDScript. No compilation. No plugins to install. | |
| 13 | ||
| 14 | Tier 2 (Gaming -- Native UDP): | |
| 15 | Godot -> HyperswarmMultiplayerPeer (GDExtension) -> libudx -> UDP | |
| 16 | Good for: real-time multiplayer, competitive games, FPS, racing | |
| 17 | Latency: <5ms (direct UDP, no IPC, no serialization) | |
| 18 | Implementation: C++ GDExtension. Compile once per platform. | |
| 19 | ``` | |
| 20 | ||
| 21 | A game can use BOTH tiers simultaneously — Tier 1 for chat/social features, Tier 2 for gameplay networking. | |
| 22 | ||
| 23 | ## Use Cases by Category | |
| 24 | ||
| 25 | | Category | Examples | Tier | | |
| 26 | |----------|----------|------| | |
| 27 | | 2D apps | Chat client, feed reader, wiki browser, kanban board, notification dashboard | Tier 1 | | |
| 28 | | 2D games | Card games, board games, puzzle games — all P2P multiplayer via Keet rooms | Tier 1 | | |
| 29 | | 3D apps | Virtual rooms with avatars, spatial wikis, data visualization, 3D file browser | Tier 1 | | |
| 30 | | 3D games | FPS, MMO, sandbox, racing — full multiplayer via Hyperswarm/UDX | Tier 2 | | |
| 31 | ||
| 32 | ## Tier 1: keet-social Plugin (Pure GDScript) | |
| 33 | ||
| 34 | For social features and casual games. No compilation needed. | |
| 35 | ||
| 36 | ### Plugin Structure | |
| 37 | ||
| 38 | ``` | |
| 39 | addons/keet-social/ | |
| 40 | ├── plugin.cfg | |
| 41 | ├── keet_client.gd -- WebSocket client for hub API (JSON-RPC 2.0) | |
| 42 | ├── keet_chat.gd -- Chat panel (send/receive messages) | |
| 43 | ├── keet_room.gd -- Room management (join/leave/list) | |
| 44 | ├── keet_identity.gd -- Identity and profile management | |
| 45 | ├── keet_feed.gd -- Feed timeline display | |
| 46 | ├── keet_wiki.gd -- Wiki page viewer/editor | |
| 47 | ├── keet_board.gd -- Kanban board | |
| 48 | └── demo/ | |
| 49 | ├── chat_2d.tscn -- 2D chat app | |
| 50 | ├── feed_2d.tscn -- 2D feed reader | |
| 51 | ├── room_browser.tscn -- Room management UI | |
| 52 | ├── wiki_editor.tscn -- Wiki page editor | |
| 53 | ├── social_space_3d.tscn -- 3D social room with avatars | |
| 54 | └── dashboard.tscn -- Unified control panel | |
| 55 | ``` | |
| 56 | ||
| 57 | ### How It Works | |
| 58 | ||
| 59 | ```gdscript | |
| 60 | # keet_client.gd -- core WebSocket connection | |
| 61 | extends Node | |
| 62 | ||
| 63 | var ws := WebSocketPeer.new() | |
| 64 | var _next_id := 1 | |
| 65 | var _pending := {} # id -> callback | |
| 66 | ||
| 67 | func _ready(): | |
| 68 | ws.connect_to_url("ws://localhost:3117/ws") | |
| 69 | ||
| 70 | func _process(_delta): | |
| 71 | ws.poll() | |
| 72 | while ws.get_available_packet_count() > 0: | |
| 73 | var data = ws.get_packet().get_string_from_utf8() | |
| 74 | var msg = JSON.parse_string(data) | |
| 75 | if msg.has("id") and _pending.has(msg.id): | |
| 76 | _pending[msg.id].call(msg.get("result"), msg.get("error")) | |
| 77 | _pending.erase(msg.id) | |
| 78 | elif msg.has("method"): | |
| 79 | # Server push notification | |
| 80 | event_received.emit(msg.method, msg.params) | |
| 81 | ||
| 82 | func call_method(method: String, params: Dictionary, callback: Callable) -> void: | |
| 83 | var id = _next_id | |
| 84 | _next_id += 1 | |
| 85 | _pending[id] = callback | |
| 86 | var req = {"jsonrpc": "2.0", "id": id, "method": method, "params": params} | |
| 87 | ws.send_text(JSON.stringify(req)) | |
| 88 | ||
| 89 | func subscribe(room_id: String) -> void: | |
| 90 | call_method("subscribe", {"roomId": room_id}, func(_r, _e): pass) | |
| 91 | ||
| 92 | signal event_received(method: String, params: Dictionary) | |
| 93 | ``` | |
| 94 | ||
| 95 | ### GDScript Usage | |
| 96 | ||
| 97 | ```gdscript | |
| 98 | # Send a message | |
| 99 | keet_client.call_method("send", { | |
| 100 | "roomId": "ar5jd5...", | |
| 101 | "text": "Hello from Godot!" | |
| 102 | }, func(result, error): | |
| 103 | if error: | |
| 104 | push_error("Send failed: " + str(error)) | |
| 105 | ) | |
| 106 | ||
| 107 | # Subscribe to room and handle events | |
| 108 | keet_client.subscribe("ar5jd5...") | |
| 109 | keet_client.event_received.connect(func(method, params): | |
| 110 | if method == "message": | |
| 111 | add_chat_message(params.from, params.text) | |
| 112 | ) | |
| 113 | ||
| 114 | # List rooms | |
| 115 | keet_client.call_method("rooms.list", {}, func(result, _error): | |
| 116 | for room in result: | |
| 117 | print(room.name, " - ", room.id) | |
| 118 | ) | |
| 119 | ``` | |
| 120 | ||
| 121 | ## Tier 2: HyperswarmMultiplayerPeer (C++ GDExtension) | |
| 122 | ||
| 123 | For real-time multiplayer games. Compiles libudx into a native shared library that Godot loads directly. | |
| 124 | ||
| 125 | ### Why GDExtension, Not a Sidecar | |
| 126 | ||
| 127 | | Approach | Latency | Runtime Deps | Overhead | | |
| 128 | |----------|---------|-------------|----------| | |
| 129 | | HTTP to keet-daemon | ~50-200ms | keet-daemon running | HTTP headers per request | | |
| 130 | | JSON-RPC sidecar (Mauve v2) | ~10-50ms | hyper-sdk-rpc running | TCP + JSON parse | | |
| 131 | | GDExtension (native UDX) | <5ms | None -- compiled in | Zero IPC overhead | | |
| 132 | ||
| 133 | The GDExtension compiles libudx into a .so/.dylib/.dll that Godot loads at startup. No external processes, no IPC, no JSON serialization. One-time build cost, zero runtime overhead. | |
| 134 | ||
| 135 | ### How GDExtension Loading Works | |
| 136 | ||
| 137 | GDExtension is Godot's plugin system for native code. You compile your C/C++/Rust code into a platform-specific shared library, place it in the `addons/` folder with a manifest file, and Godot dynamically loads it. No custom Godot build required — works with any standard Godot binary. | |
| 138 | ||
| 139 | **The manifest (`.gdextension` file) maps platforms to libraries:** | |
| 140 | ||
| 141 | ```ini | |
| 142 | [configuration] | |
| 143 | entry_symbol = "hyperswarm_peer_init" | |
| 144 | compatibility_minimum = "4.3" | |
| 145 | ||
| 146 | [libraries] | |
| 147 | linux.x86_64 = "res://addons/hyperswarm_peer/bin/libhyperswarm_peer.linux.x86_64.so" | |
| 148 | macos.universal = "res://addons/hyperswarm_peer/bin/libhyperswarm_peer.macos.universal.dylib" | |
| 149 | windows.x86_64 = "res://addons/hyperswarm_peer/bin/libhyperswarm_peer.windows.x86_64.dll" | |
| 150 | ``` | |
| 151 | ||
| 152 | When Godot starts, it reads this file, finds the right library for the current platform, and `dlopen()`s it. Your native classes become available in GDScript just like built-in types. | |
| 153 | ||
| 154 | ### Plugin Structure | |
| 155 | ||
| 156 | ``` | |
| 157 | addons/hyperswarm_peer/ | |
| 158 | ├── hyperswarm_peer.gdextension # Extension manifest | |
| 159 | ├── bin/ | |
| 160 | │ ├── libhyperswarm_peer.linux.x86_64.so | |
| 161 | │ ├── libhyperswarm_peer.macos.universal.dylib | |
| 162 | │ └── libhyperswarm_peer.windows.x86_64.dll | |
| 163 | ├── src/ | |
| 164 | │ ├── register_types.cpp # GDExtension entry point | |
| 165 | │ ├── hyperswarm_multiplayer_peer.h | |
| 166 | │ ├── hyperswarm_multiplayer_peer.cpp | |
| 167 | │ ├── udx_transport.h # Wraps libudx | |
| 168 | │ ├── udx_transport.cpp | |
| 169 | │ ├── dht_discovery.h # Wraps libhyperdht | |
| 170 | │ └── dht_discovery.cpp | |
| 171 | └── thirdparty/ | |
| 172 | ├── libudx/ # C library (Holepunch) | |
| 173 | ├── libhyperdht/ # C library (if available) | |
| 174 | └── libsodium/ # For Noise encryption | |
| 175 | ``` | |
| 176 | ||
| 177 | ### GDScript Usage | |
| 178 | ||
| 179 | ```gdscript | |
| 180 | # Host a game | |
| 181 | var peer = HyperswarmMultiplayerPeer.new() | |
| 182 | peer.create_server("my-game-topic-as-hex-string") | |
| 183 | multiplayer.multiplayer_peer = peer | |
| 184 | ||
| 185 | # Join a game | |
| 186 | var peer = HyperswarmMultiplayerPeer.new() | |
| 187 | peer.create_client("my-game-topic-as-hex-string") | |
| 188 | multiplayer.multiplayer_peer = peer | |
| 189 | ||
| 190 | # P2P mesh (no server) | |
| 191 | var peer = HyperswarmMultiplayerPeer.new() | |
| 192 | peer.create_mesh(my_unique_id, "my-game-topic") | |
| 193 | multiplayer.multiplayer_peer = peer | |
| 194 | ||
| 195 | # All standard Godot multiplayer works: | |
| 196 | @rpc("any_peer", "unreliable", 1) | |
| 197 | func sync_position(pos: Vector3): | |
| 198 | position = pos | |
| 199 | ||
| 200 | @rpc("any_peer", "reliable") | |
| 201 | func fire_weapon(weapon_id: int): | |
| 202 | spawn_projectile(weapon_id) | |
| 203 | ``` | |
| 204 | ||
| 205 | ### Godot 4 Multiplayer Architecture | |
| 206 | ||
| 207 | Godot 4 has a modular, layered multiplayer system: | |
| 208 | ||
| 209 | ``` | |
| 210 | +--------------------------------------------------+ | |
| 211 | | Application Layer | | |
| 212 | | @rpc functions, MultiplayerSpawner, | | |
| 213 | | MultiplayerSynchronizer | | |
| 214 | +--------------------------------------------------+ | |
| 215 | | SceneMultiplayer (API layer) | | |
| 216 | | Authority, peer IDs, channels | | |
| 217 | +--------------------------------------------------+ | |
| 218 | | MultiplayerPeer (transport -- SWAPPABLE) | | |
| 219 | | ENet | WebSocket | WebRTC | Custom Extension | | |
| 220 | +--------------------------------------------------+ | |
| 221 | ``` | |
| 222 | ||
| 223 | **The transport layer is fully pluggable.** Godot provides `MultiplayerPeerExtension` with 23 virtual methods you override to plug in ANY transport. Everything above (RPCs, spawners, synchronizers) works unchanged. | |
| 224 | ||
| 225 | ### MultiplayerPeerExtension: 23 Virtual Methods | |
| 226 | ||
| 227 | **Packet I/O:** | |
| 228 | - `_get_packet_script()` -> PackedByteArray — get next incoming packet | |
| 229 | - `_put_packet_script(buffer)` -> Error — send packet | |
| 230 | - `_get_available_packet_count()` -> int | |
| 231 | - `_get_max_packet_size()` -> int | |
| 232 | ||
| 233 | **Peer Management:** | |
| 234 | - `_poll()` — poll for events (called each frame) | |
| 235 | - `_close()` — close connection | |
| 236 | - `_set_target_peer(id)` — 0=broadcast, negative=exclude | |
| 237 | - `_get_packet_peer()` -> sender ID | |
| 238 | - `_get_unique_id()` -> this peer's ID | |
| 239 | - `_is_server()` -> bool | |
| 240 | - `_get_connection_status()` -> disconnected/connecting/connected | |
| 241 | - `_disconnect_peer(id, force)` | |
| 242 | ||
| 243 | **Transfer Control:** | |
| 244 | - `_set_transfer_mode(mode)` — UNRELIABLE / UNRELIABLE_ORDERED / RELIABLE | |
| 245 | - `_get_transfer_mode()` -> mode | |
| 246 | - `_set_transfer_channel(ch)` / `_get_transfer_channel()` | |
| 247 | - `_get_packet_channel()` / `_get_packet_mode()` | |
| 248 | ||
| 249 | **Connection Control:** | |
| 250 | - `_set_refuse_new_connections(bool)` / `_is_refusing_new_connections()` | |
| 251 | - `_is_server_relay_supported()` -> bool | |
| 252 | ||
| 253 | ### ENet Mesh (P2P Reference) | |
| 254 | ||
| 255 | Godot 4 added `create_mesh(unique_id)` + `add_mesh_peer(id, connection)` for full P2P mesh networking where every peer connects directly. No central server. This maps well to Hyperswarm's topology. | |
| 256 | ||
| 257 | ### Key Multiplayer Nodes | |
| 258 | ||
| 259 | - **MultiplayerSynchronizer** — auto-sync properties across peers | |
| 260 | - `REPLICATION_MODE_ALWAYS` (periodic) vs `REPLICATION_MODE_ON_CHANGE` (delta) | |
| 261 | - Visibility filters for interest management | |
| 262 | - Configurable tick rate via `replication_interval` | |
| 263 | ||
| 264 | - **MultiplayerSpawner** — auto-replicate dynamically created scenes | |
| 265 | - Handles mid-game joins and reconnections | |
| 266 | ||
| 267 | - **@rpc annotation** — declarative RPC: `@rpc("any_peer", "unreliable", "call_local", 1)` | |
| 268 | - Modes: any_peer, authority | |
| 269 | - Sync: call_local, call_remote | |
| 270 | - Transfer: reliable, unreliable, unreliable_ordered | |
| 271 | - Channel: integer for multiplexing | |
| 272 | ||
| 273 | ### What Godot Does NOT Provide | |
| 274 | ||
| 275 | These must be implemented manually or via addons: | |
| 276 | - Client-side prediction | |
| 277 | - Server reconciliation | |
| 278 | - Network interpolation | |
| 279 | - Lag compensation | |
| 280 | - Snapshot system | |
| 281 | ||
| 282 | **Netfox addon** fills these gaps (prediction, rollback, interpolation). Consider bundling with Tier 2 games. | |
| 283 | ||
| 284 | ### Built-in Transports for Reference | |
| 285 | ||
| 286 | | Transport | Protocol | NAT | Best For | | |
| 287 | |-----------|----------|-----|----------| | |
| 288 | | ENetMultiplayerPeer | UDP (ENet) | No | Desktop games, low latency | | |
| 289 | | WebSocketMultiplayerPeer | TCP (WS) | Yes | Browser games | | |
| 290 | | WebRTCMultiplayerPeer | UDP (DTLS) | Yes | Browser P2P | | |
| 291 | | OfflineMultiplayerPeer | None | N/A | Single-player testing | | |
| 292 | ||
| 293 | ### Existing P2P Peer Implementations | |
| 294 | ||
| 295 | - **godot-iroh** (Rust GDExtension) — P2P networking via Iroh. Closest reference to our approach. Uses the same MultiplayerPeerExtension pattern. | |
| 296 | - **steam-multiplayer-peer** (C++ GDExtension) — Steam Sockets. Good C++ reference for GDExtension build patterns. | |
| 297 | ||
| 298 | ## HyperGodot: History and Context | |
| 299 | ||
| 300 | RangerMauve (Mauve Signweaver) created HyperGodot — the original P2P game engine integration. | |
| 301 | ||
| 302 | ### Three Generations | |
| 303 | ||
| 304 | **V1 (Godot 3) — Complete, working:** | |
| 305 | - HTTP sidecar pattern: hyper-gateway runs alongside Godot (port 4973) | |
| 306 | - SSE for events, extension messages for gossip, HyperRequest URL rewriting | |
| 307 | - ~35MB binary sidecar per platform | |
| 308 | - Demos: third-person shooter, witchy world (755MB) | |
| 309 | ||
| 310 | **Port (Godot 4.0) — Abandoned:** | |
| 311 | - Direct port of v1 by Attila772 | |
| 312 | - 62 commits of frustration | |
| 313 | - GDScript 2.0 broke signals, JSON, threads, File/Directory API all at once | |
| 314 | - Abandoned | |
| 315 | ||
| 316 | **V2 (Godot 4.3) — Proof of concept, 1 commit:** | |
| 317 | - JSON-RPC over TCP (hyper-sdk-rpc, port 4772) | |
| 318 | - StreamPeerTCP + newline-delimited JSON | |
| 319 | - Method chaining: `"sdk.getDrive(hostname).readdir(path)"` | |
| 320 | - Architecturally cleaner but barely started | |
| 321 | ||
| 322 | ### Our Approach: Beyond Sidecars | |
| 323 | ||
| 324 | Mauve's approaches all use the sidecar pattern — run a Node.js process alongside Godot. We improve on this: | |
| 325 | ||
| 326 | **Tier 1:** keet-daemon IS the sidecar. Already running, full API. No new process needed. But we use WebSocket instead of HTTP/SSE. | |
| 327 | ||
| 328 | **Tier 2:** Skip the sidecar entirely. GDExtension links directly against libudx. No external processes, no IPC, no serialization. Direct native code. | |
| 329 | ||
| 330 | ## Build Order | |
| 331 | ||
| 332 | 1. **Phase 4: keet-social plugin** (~600 LOC GDScript) | |
| 333 | - WebSocket client to keet-daemon | |
| 334 | - Chat, feed, wiki, board panels as Godot scenes | |
| 335 | - 2D demos: chat app, feed reader, dashboard | |
| 336 | - 3D demo: social room with avatars | |
| 337 | - Pure GDScript, no compilation | |
| 338 | ||
| 339 | 2. **Phase 5: HyperswarmMultiplayerPeer** (~2000 LOC C++) | |
| 340 | - C++ GDExtension wrapping libudx | |
| 341 | - All 23 MultiplayerPeerExtension virtual methods | |
| 342 | - DHT-based peer discovery | |
| 343 | - NAT holepunching | |
| 344 | - Three transfer modes mapped to UDX | |
| 345 | - Interactive mode for low-latency gaming | |
| 346 | - Demo: multiplayer game using standard @rpc | |
| 347 | ||
| 348 | 3. **Phase 7: Game demos** | |
| 349 | - 2D card game (P2P, no server) | |
| 350 | - 3D FPS arena (mesh networking, fast sync) | |
| 351 | - Social world (3D rooms with chat + wiki panels) |
| … | … | @@ -0,0 +1,171 @@ |
| 1 | # Networking: WebSocket + JSON-RPC 2.0 | |
| 2 | ||
| 3 | ## Why WebSocket, Not HTTP/SSE | |
| 4 | ||
| 5 | The original plan used HTTP requests + Server-Sent Events (SSE). Research showed this is inferior in every dimension. | |
| 6 | ||
| 7 | ### What SSE Is (and Why It's Bad Here) | |
| 8 | ||
| 9 | SSE (Server-Sent Events) is a one-way streaming protocol where the server pushes text events to the client over a kept-alive HTTP connection. The client can only listen — to send data back, it must make separate HTTP requests. This means two separate protocols for bidirectional communication. | |
| 10 | ||
| 11 | ### Protocol Comparison | |
| 12 | ||
| 13 | | Dimension | HTTP/SSE | WebSocket + JSON-RPC 2.0 | | |
| 14 | |-----------|----------|--------------------------| | |
| 15 | | Per-message overhead | ~400 bytes (HTTP headers) | 2-6 bytes (WS frame) | | |
| 16 | | Direction | Two protocols (HTTP up, SSE down) | Single bidirectional connection | | |
| 17 | | Browser connection limit | 6 per domain (SSE) | No practical limit | | |
| 18 | | Binary support | No (SSE is text-only) | Yes (binary frames) | | |
| 19 | | Godot support | HTTPRequest has 30s delay bugs | WebSocketPeer works cleanly | | |
| 20 | | Complexity | Two protocols to maintain | One protocol for everything | | |
| 21 | | Latency | ~50-200ms (HTTP round-trip) | ~1-5ms (persistent connection) | | |
| 22 | ||
| 23 | **Bottom line:** WebSocket is 100x less overhead, truly bidirectional, works everywhere, and has clean support in every client (browser, Godot, bare-ws, CLI tools). | |
| 24 | ||
| 25 | ### What We Use Instead | |
| 26 | ||
| 27 | ``` | |
| 28 | keet-daemon (:3117/ws) <-- single WebSocket endpoint | |
| 29 | | | |
| 30 | |-- Godot: WebSocketPeer (built-in, no plugins) | |
| 31 | |-- Browser: native WebSocket API | |
| 32 | |-- TUI: bare-ws | |
| 33 | |-- CLI: websocat (or HTTP fallback for curl) | |
| 34 | ``` | |
| 35 | ||
| 36 | ## JSON-RPC 2.0 Protocol | |
| 37 | ||
| 38 | All messages follow the JSON-RPC 2.0 specification over a single WebSocket connection. | |
| 39 | ||
| 40 | ### Message Types | |
| 41 | ||
| 42 | **Command (client -> daemon):** | |
| 43 | ```json | |
| 44 | {"jsonrpc":"2.0","id":1,"method":"send","params":{"roomId":"abc","text":"hello"}} | |
| 45 | ``` | |
| 46 | ||
| 47 | **Response (daemon -> client):** | |
| 48 | ```json | |
| 49 | {"jsonrpc":"2.0","id":1,"result":{"ok":true}} | |
| 50 | ``` | |
| 51 | ||
| 52 | **Event notification (daemon -> client, no id = server push):** | |
| 53 | ```json | |
| 54 | {"jsonrpc":"2.0","method":"message","params":{"roomId":"abc","text":"world","from":"alice"}} | |
| 55 | ``` | |
| 56 | ||
| 57 | **Subscribe to room events:** | |
| 58 | ```json | |
| 59 | {"jsonrpc":"2.0","id":2,"method":"subscribe","params":{"roomId":"abc"}} | |
| 60 | ``` | |
| 61 | ||
| 62 | **Unsubscribe:** | |
| 63 | ```json | |
| 64 | {"jsonrpc":"2.0","id":3,"method":"unsubscribe","params":{"roomId":"abc"}} | |
| 65 | ``` | |
| 66 | ||
| 67 | **Error:** | |
| 68 | ```json | |
| 69 | {"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"Method not found"}} | |
| 70 | ``` | |
| 71 | ||
| 72 | ### Available Methods | |
| 73 | ||
| 74 | **Chat:** | |
| 75 | - `send` — send a message to a room | |
| 76 | - `messages` — get message history | |
| 77 | - `subscribe` — subscribe to real-time room events (messages, joins, leaves) | |
| 78 | - `unsubscribe` — stop receiving events for a room | |
| 79 | ||
| 80 | **Room Management:** | |
| 81 | - `rooms.list` — list all joined rooms | |
| 82 | - `rooms.get` — get room details | |
| 83 | - `rooms.join` — join a room by invite | |
| 84 | ||
| 85 | **Service Control:** | |
| 86 | - `services.list` — list running bridges/panels/daemons | |
| 87 | - `services.start` — start a service | |
| 88 | - `services.stop` — stop a service | |
| 89 | ||
| 90 | **Panel APIs:** | |
| 91 | - `feed.list` / `feed.get` — timeline | |
| 92 | - `wiki.pages` / `wiki.get` / `wiki.put` — wiki | |
| 93 | - `board.cards` / `board.move` — kanban | |
| 94 | ||
| 95 | **Identity:** | |
| 96 | - `identity.get` — get identity info | |
| 97 | - `identity.graph` — cross-platform identity graph | |
| 98 | ||
| 99 | **Config:** | |
| 100 | - `config.get` / `config.set` — hub configuration | |
| 101 | ||
| 102 | ### HTTP Fallback | |
| 103 | ||
| 104 | A minimal HTTP endpoint remains for tools that don't support WebSocket: | |
| 105 | ||
| 106 | ``` | |
| 107 | GET /health -> {"ok":true,"version":"1.0.0"} | |
| 108 | GET /api/rooms -> room list (JSON) | |
| 109 | POST /api/send -> send message (JSON body) | |
| 110 | ``` | |
| 111 | ||
| 112 | This is for `curl`, health checks, and monitoring only. All real-time communication uses WebSocket. | |
| 113 | ||
| 114 | ## Downstream Effects | |
| 115 | ||
| 116 | Switching from HTTP/SSE to WebSocket affects all bridges and daemons. | |
| 117 | ||
| 118 | ### What Changes | |
| 119 | ||
| 120 | **1. keet-daemon (the server)** | |
| 121 | - Add WebSocket transport using `bare-ws` alongside existing HTTP | |
| 122 | - JSON-RPC 2.0 message dispatch (method routing, id tracking) | |
| 123 | - `subscribe`/`unsubscribe` for room-level event push | |
| 124 | ||
| 125 | **2. keet-client.js (shared across all bridges)** | |
| 126 | ||
| 127 | Before (HTTP polling): | |
| 128 | ```javascript | |
| 129 | // Poll every 2 seconds for new messages | |
| 130 | setInterval(async () => { | |
| 131 | const msgs = await fetch('http://localhost:3117/rpc/messages/' + roomId) | |
| 132 | // diff against last known messages... | |
| 133 | }, 2000) | |
| 134 | ``` | |
| 135 | ||
| 136 | After (WebSocket subscribe): | |
| 137 | ```javascript | |
| 138 | // Persistent connection, instant push | |
| 139 | const ws = new WebSocket('ws://localhost:3117/ws') | |
| 140 | ws.send(JSON.stringify({ | |
| 141 | jsonrpc: '2.0', id: 1, | |
| 142 | method: 'subscribe', | |
| 143 | params: { roomId } | |
| 144 | })) | |
| 145 | ws.on('message', (data) => { | |
| 146 | const msg = JSON.parse(data) | |
| 147 | if (msg.method === 'message') handleMessage(msg.params) | |
| 148 | }) | |
| 149 | ``` | |
| 150 | ||
| 151 | **3. Each bridge's relay loop** | |
| 152 | - Replace `_startKeetPoll()` / `setInterval` with WebSocket subscribe | |
| 153 | - The adapter/router logic stays identical — only the transport changes | |
| 154 | ||
| 155 | **4. hyper-daemon triggers** | |
| 156 | - Switch from HTTP polling to WebSocket subscribe for "new message" triggers | |
| 157 | ||
| 158 | ### What Does NOT Change | |
| 159 | ||
| 160 | - **bridge-envelope** — message schema is transport-agnostic | |
| 161 | - **bridge-vault** — credential storage, no networking | |
| 162 | - **Adapter logic** — IRC<->envelope<->Keet normalization stays identical | |
| 163 | - **Router logic** — dedup, routing rules unchanged | |
| 164 | - **Godot side** — already designed for WebSocket from the start | |
| 165 | ||
| 166 | ### Order of Implementation | |
| 167 | ||
| 168 | Changes cascade from server outward: | |
| 169 | 1. keet-daemon gets WebSocket transport (everything else depends on this) | |
| 170 | 2. keet-client.js rewritten (shared by all bridges) | |
| 171 | 3. Each bridge updated (mostly just swapping the import and removing polling) |
| … | … | @@ -0,0 +1,286 @@ |
| 1 | # Standalone Projects | |
| 2 | ||
| 3 | Each project is independently useful. Make one work well, then move on to the next. The system is the emergent property of all pieces running together. | |
| 4 | ||
| 5 | ## Project Map | |
| 6 | ||
| 7 | | # | Project | Type | What it does | ~LOC | | |
| 8 | |---|---------|------|-------------|------| | |
| 9 | | 1 | bridge-envelope | npm pkg | Universal message schema + compact-encoding codec | 200 | | |
| 10 | | 2 | bridge-vault | npm pkg | Encrypted P2P credential store | 300 | | |
| 11 | | 3 | bridge-irc | Pear app | IRC <-> Keet bridge | 500 | | |
| 12 | | 4 | bridge-discord | Pear app | Discord <-> Keet bridge | 600 | | |
| 13 | | 5 | bridge-matrix | Pear app | Matrix <-> Keet bridge | 600 | | |
| 14 | | 6 | bridge-mastodon | Pear app | Mastodon <-> Keet bridge | 700 | | |
| 15 | | 7 | bridge-email | Pear app | Email <-> Keet bridge | 800 | | |
| 16 | | 8 | hyper-wiki | Pear app | P2P wiki (Hyperdrive-backed) | 800 | | |
| 17 | | 9 | hyper-board | Pear app | P2P kanban board | 700 | | |
| 18 | | 10 | hyper-feed | Pear app | P2P social feed/timeline | 500 | | |
| 19 | | 11 | hyper-daemon | Pear app | P2P automation runtime | 600 | | |
| 20 | | 12 | hyper-social | Pear app | Meta-app composing everything | 1500 | | |
| 21 | ||
| 22 | **Plus interfaces:** | |
| 23 | ||
| 24 | | # | Interface | Type | What it does | ~LOC | | |
| 25 | |---|-----------|------|-------------|------| | |
| 26 | | 13 | Hub API Server | Pear app | WebSocket + JSON-RPC 2.0 + HTTP fallback | 300 | | |
| 27 | | 14 | WebUI | HTML/JS | Browser-based dashboard | 800 | | |
| 28 | | 15 | TUI | Pear app | Terminal interface | 400 | | |
| 29 | | 16 | keet-social plugin | GDScript | Godot Tier 1 (social/apps via WebSocket) | 600 | | |
| 30 | | 17 | HyperswarmMultiplayerPeer | C++ GDExtension | Godot Tier 2 (gaming via UDX/UDP) | 2000 | | |
| 31 | ||
| 32 | ## Foundation Libraries | |
| 33 | ||
| 34 | ### bridge-envelope (~200 LOC) | |
| 35 | ||
| 36 | Universal message schema with compact-encoding codec. Every bridge normalizes messages into this format before routing. | |
| 37 | ||
| 38 | ```javascript | |
| 39 | import { encode, decode, Envelope } from 'bridge-envelope' | |
| 40 | ||
| 41 | const msg = new Envelope({ | |
| 42 | type: 'chat', // chat | post | email | event | |
| 43 | platform: 'irc', // source platform | |
| 44 | author: { name: 'alice', id: 'alice@libera' }, | |
| 45 | content: { text: 'hello world' }, | |
| 46 | timestamp: Date.now(), | |
| 47 | hash: null // computed on encode | |
| 48 | }) | |
| 49 | ||
| 50 | const buf = encode(msg) // compact binary | |
| 51 | const decoded = decode(buf) // back to Envelope | |
| 52 | ``` | |
| 53 | ||
| 54 | **Done when:** any bridge can import and use it for message normalization. | |
| 55 | ||
| 56 | ### bridge-vault (~300 LOC) | |
| 57 | ||
| 58 | Encrypted credential store. Each bridge stores its API keys/tokens/passwords here, encrypted with the user's Keet keypair. | |
| 59 | ||
| 60 | ```javascript | |
| 61 | import { Vault } from 'bridge-vault' | |
| 62 | ||
| 63 | const vault = new Vault(corestoreInstance) | |
| 64 | await vault.ready() | |
| 65 | ||
| 66 | await vault.set('irc', { server: 'irc.libera.chat', nick: 'mybot' }) | |
| 67 | await vault.set('discord', { token: 'bot-token-here' }) | |
| 68 | ||
| 69 | const creds = await vault.get('irc') | |
| 70 | await vault.delete('discord') | |
| 71 | ``` | |
| 72 | ||
| 73 | Backed by Hyperbee. Replicates P2P (encrypted — only the key owner can decrypt). | |
| 74 | ||
| 75 | **Done when:** any bridge can securely store and retrieve credentials. | |
| 76 | ||
| 77 | ## Platform Bridges | |
| 78 | ||
| 79 | Each bridge is a standalone Pear terminal app connecting one platform to Keet. | |
| 80 | ||
| 81 | ### bridge-irc (~500 LOC) | |
| 82 | ||
| 83 | The first bridge. Proves the pattern. | |
| 84 | ||
| 85 | ``` | |
| 86 | bridge-irc/ | |
| 87 | ├── package.json # Pear terminal app | |
| 88 | ├── index.js # Entry: parse args, start bridge | |
| 89 | ├── lib/ | |
| 90 | │ ├── irc-client.js # IRC connection (irc-framework) | |
| 91 | │ ├── keet-client.js # WebSocket client to keet-daemon | |
| 92 | │ ├── adapter.js # IRC <-> Envelope normalization | |
| 93 | │ ├── router.js # Bidirectional relay + content-hash dedup | |
| 94 | │ └── config.js # Config loading | |
| 95 | └── test/ | |
| 96 | ``` | |
| 97 | ||
| 98 | **Usage:** | |
| 99 | ```bash | |
| 100 | pear run --dev . --server irc.libera.chat --channel "#test" --keet-room ar5jd5... | |
| 101 | ``` | |
| 102 | ||
| 103 | **What it does:** | |
| 104 | 1. Connects to an IRC server + channel | |
| 105 | 2. Connects to keet-daemon via WebSocket (ws://localhost:3117/ws) | |
| 106 | 3. Subscribes to Keet room events | |
| 107 | 4. IRC messages -> envelope -> send to Keet | |
| 108 | 5. Keet messages -> envelope -> send to IRC | |
| 109 | 6. Content-hash dedup prevents echo loops | |
| 110 | 7. Reconnects automatically on disconnect | |
| 111 | ||
| 112 | ### bridge-discord (~600 LOC) | |
| 113 | ||
| 114 | Second bridge. Validates archetype reuse. | |
| 115 | ||
| 116 | - Discord.js bot <-> keet-daemon WebSocket | |
| 117 | - Handles: embeds, reactions, threads, file uploads | |
| 118 | - Shares ~80% of adapter logic with bridge-irc | |
| 119 | - Platform-specific: Discord requires a bot token from Developer Portal | |
| 120 | ||
| 121 | ### bridge-matrix (~600 LOC) | |
| 122 | ||
| 123 | Matrix rooms <-> Keet rooms. | |
| 124 | ||
| 125 | - matrix-js-sdk or simple HTTP API | |
| 126 | - End-to-end encryption: Matrix has its own E2EE, Keet has its own — bridge decrypts/re-encrypts at the boundary | |
| 127 | - Platform-specific: Matrix homeserver URL + access token | |
| 128 | ||
| 129 | ### bridge-mastodon (~700 LOC) | |
| 130 | ||
| 131 | Mastodon/Fediverse <-> Keet. Different archetype from chat bridges. | |
| 132 | ||
| 133 | - Short posts, hashtags, boosts (not real-time chat) | |
| 134 | - ActivityPub protocol for federated communication | |
| 135 | - POSSE pattern: originate in Keet, syndicate to Fediverse | |
| 136 | - Platform-specific: Mastodon instance URL + API token | |
| 137 | ||
| 138 | ### bridge-email (~800 LOC) | |
| 139 | ||
| 140 | Email <-> Keet. Long-form archetype. | |
| 141 | ||
| 142 | - IMAP polling for incoming + SMTP for outgoing | |
| 143 | - Thread mapping: email threads <-> Keet threads | |
| 144 | - Attachment handling: files stored in Hyperdrive | |
| 145 | - Platform-specific: IMAP/SMTP credentials | |
| 146 | ||
| 147 | ## Workspace Panels | |
| 148 | ||
| 149 | Each panel adds a capability to a room. Can run standalone or compose into the meta-app. | |
| 150 | ||
| 151 | ### hyper-wiki (~800 LOC) | |
| 152 | ||
| 153 | P2P wiki. Like a local Notion but decentralized. | |
| 154 | ||
| 155 | ``` | |
| 156 | hyper-wiki/ | |
| 157 | ├── package.json | |
| 158 | ├── index.js | |
| 159 | ├── lib/ | |
| 160 | │ ├── wiki-store.js # Hyperdrive-backed page storage | |
| 161 | │ ├── versioning.js # Page history (Hyperdrive snapshots) | |
| 162 | │ ├── editor.js # Markdown editor | |
| 163 | │ ├── search.js # Full-text search (Hyperbee index) | |
| 164 | │ └── keet-hook.js # Optional: "pin to wiki" from Keet chat | |
| 165 | └── test/ | |
| 166 | ``` | |
| 167 | ||
| 168 | **Standalone features:** | |
| 169 | - Create/edit/delete wiki pages (Markdown) | |
| 170 | - Version history via Hyperdrive snapshots | |
| 171 | - Full-text search | |
| 172 | - P2P replication — share wiki with anyone via Hyperswarm | |
| 173 | ||
| 174 | ### hyper-board (~700 LOC) | |
| 175 | ||
| 176 | P2P kanban board. | |
| 177 | ||
| 178 | - Task cards with status lanes (todo / in-progress / done) | |
| 179 | - Assignments, labels, due dates | |
| 180 | - Keet integration: create tasks from chat messages | |
| 181 | - P2P sync: multiple users see the same board | |
| 182 | ||
| 183 | ### hyper-feed (~500 LOC) | |
| 184 | ||
| 185 | Aggregated social timeline. | |
| 186 | ||
| 187 | - Collects messages from all bridges into a unified feed | |
| 188 | - Filtering: by platform, room, author, content type | |
| 189 | - Chronological and threaded views | |
| 190 | - P2P: share your feed with followers | |
| 191 | ||
| 192 | ## Automation | |
| 193 | ||
| 194 | ### hyper-daemon (~600 LOC) | |
| 195 | ||
| 196 | P2P automation runtime — like IFTTT but decentralized. | |
| 197 | ||
| 198 | ``` | |
| 199 | hyper-daemon/ | |
| 200 | ├── package.json | |
| 201 | ├── index.js | |
| 202 | ├── lib/ | |
| 203 | │ ├── runtime.js # Script execution engine | |
| 204 | │ ├── triggers.js # Event matching | |
| 205 | │ ├── actions.js # Built-in actions | |
| 206 | │ └── store.js # Daemon state (Hyperbee) | |
| 207 | ├── daemons/ | |
| 208 | │ ├── crosspost.js # Syndicate to specific platforms | |
| 209 | │ ├── summarize.js # Auto-summarize chat to wiki | |
| 210 | │ └── notify.js # Push notifications | |
| 211 | └── test/ | |
| 212 | ``` | |
| 213 | ||
| 214 | **Triggers:** new Keet message, schedule (cron-like), file change, WebSocket event | |
| 215 | **Actions:** send Keet message, write to Hyperdrive, call HTTP endpoint | |
| 216 | **Scripts:** stored in Hyperbee, replicate P2P, users can fork/share | |
| 217 | ||
| 218 | ## Meta-App | |
| 219 | ||
| 220 | ### hyper-social (~1500 LOC) | |
| 221 | ||
| 222 | Composes all bridges + panels + daemons into a unified experience. | |
| 223 | ||
| 224 | **What's ONLY in the meta-app (not standalone):** | |
| 225 | - Routing configuration UI — composing multiple bridges | |
| 226 | - Modular room compositor — tiling WM layout | |
| 227 | - Panel docking system — how panels attach to rooms | |
| 228 | - Unified identity view — cross-platform identity graph | |
| 229 | - Service orchestration — start/stop/monitor all bridges | |
| 230 | ||
| 231 | ## Build Order | |
| 232 | ||
| 233 | One project at a time. Each complete before the next. | |
| 234 | ||
| 235 | ### Phase 1: Foundation | |
| 236 | 1. **bridge-envelope** — message schema + codec | |
| 237 | 2. **bridge-vault** — credential store | |
| 238 | ||
| 239 | ### Phase 2: First Bridges | |
| 240 | 3. **bridge-irc** — proves the pattern | |
| 241 | 4. **bridge-discord** — validates archetype reuse | |
| 242 | ||
| 243 | ### Phase 3: Panels + Automation | |
| 244 | 5. **hyper-wiki** — first panel | |
| 245 | 6. **hyper-daemon** — automation | |
| 246 | ||
| 247 | ### Phase 4: More Bridges | |
| 248 | 7. **bridge-mastodon** — social archetype | |
| 249 | 8. **bridge-email** — long-form archetype | |
| 250 | 9. **bridge-matrix** — chat archetype variant | |
| 251 | ||
| 252 | ### Phase 5: Board + Feed | |
| 253 | 10. **hyper-board** — kanban | |
| 254 | 11. **hyper-feed** — timeline | |
| 255 | ||
| 256 | ### Phase 6: Meta-App | |
| 257 | 12. **hyper-social** — composites everything | |
| 258 | ||
| 259 | ### Interface Build Order (parallel to above) | |
| 260 | ||
| 261 | See [GODOT.md](GODOT.md) for Godot-specific phases. | |
| 262 | ||
| 263 | 1. **Hub API Server** — WebSocket + JSON-RPC 2.0 (enables all interfaces) | |
| 264 | 2. **WebUI** — browser dashboard | |
| 265 | 3. **TUI** — terminal interface | |
| 266 | 4. **keet-social plugin** — Godot Tier 1 (GDScript) | |
| 267 | 5. **HyperswarmMultiplayerPeer** — Godot Tier 2 (C++ GDExtension) | |
| 268 | 6. **GUI** — Pear desktop app | |
| 269 | 7. **Game demos** — showcasing different patterns | |
| 270 | ||
| 271 | ## Verification Strategy | |
| 272 | ||
| 273 | | Project | How to verify | | |
| 274 | |---------|---------------| | |
| 275 | | bridge-envelope | `brittle test/` — encode/decode roundtrip | | |
| 276 | | bridge-vault | `brittle test/` — store/retrieve/encrypt/decrypt | | |
| 277 | | bridge-irc | Start keet-daemon + bridge-irc + join IRC -> verify bidirectional messages | | |
| 278 | | bridge-discord | Start keet-daemon + bridge-discord + Discord test server -> verify | | |
| 279 | | bridge-matrix | Start keet-daemon + bridge-matrix + Matrix test room -> verify | | |
| 280 | | hyper-wiki | Create page, edit, version history, replicate to second peer | | |
| 281 | | hyper-board | Create card, move lanes, assign, sync with second peer | | |
| 282 | | hyper-feed | Subscribe to room, see messages in feed, filter | | |
| 283 | | hyper-daemon | Create trigger, fire event, verify action executed | | |
| 284 | | hyper-social | Start with 2+ bridges, verify unified routing + tiling | | |
| 285 | | keet-social plugin | Run 2D chat demo in Godot, send/receive messages | | |
| 286 | | HyperswarmMultiplayerPeer | Two Godot instances discover via DHT, play multiplayer | |
| … | … | @@ -0,0 +1,62 @@ |
| 1 | # Hyper-Social Documentation | |
| 2 | ||
| 3 | P2P social layer built on Hypercore Protocol and Keet. A family of interoperable standalone projects — bridges, panels, daemons, interfaces — that together form a decentralized communication graph. | |
| 4 | ||
| 5 | ## Documentation Index | |
| 6 | ||
| 7 | | Document | What it covers | | |
| 8 | |----------|---------------| | |
| 9 | | [ARCHITECTURE.md](ARCHITECTURE.md) | System overview, communication graph, dependency graph, the "fracture" principle | | |
| 10 | | [PROJECTS.md](PROJECTS.md) | All 15 standalone projects — what they do, structure, build order | | |
| 11 | | [NETWORKING.md](NETWORKING.md) | Protocol decisions: WebSocket + JSON-RPC 2.0, why not HTTP/SSE, message format | | |
| 12 | | [GODOT.md](GODOT.md) | Godot 4 integration: two-tier architecture, keet-social plugin, GDExtension, libudx | | |
| 13 | | [UDX-GAMING.md](UDX-GAMING.md) | Deep dive: libudx for real-time gaming, packet overhead, NAT holepunching | | |
| 14 | | [SOCIAL-GRAPH-NAVIGATION.md](SOCIAL-GRAPH-NAVIGATION.md) | Graph-based navigation, sheaf theory, parsing social structure like language | | |
| 15 | | [COMMUNITY-EMERGENCE.md](COMMUNITY-EMERGENCE.md) | How communities form, govern themselves, privacy architecture, Blacksky case study | | |
| 16 | ||
| 17 | ## Quick Summary | |
| 18 | ||
| 19 | **What is this?** A P2P social ecosystem where each piece works standalone. Bridges connect platforms (IRC, Discord, Matrix, Mastodon, email) to Keet P2P rooms. Panels add capabilities (wiki, kanban, feed). Daemons automate actions. Interfaces let you use it all from browser, terminal, desktop, or Godot. | |
| 20 | ||
| 21 | **Core principle:** The communication graph will fracture. Any "universal bridge" eventually breaks into many bridges. Design for this from day one. Each bridge is independently useful. The system is the emergent property of all pieces running together. | |
| 22 | ||
| 23 | **This mirrors Holepunch's ecosystem:** | |
| 24 | - `hypercore` works alone (append-only logs) | |
| 25 | - `hyperbee` works alone (database on hypercore) | |
| 26 | - `hyperswarm` works alone (P2P networking) | |
| 27 | - **Keet composes them all** into a chat app | |
| 28 | ||
| 29 | We do the same: each project is independently useful. `hyper-social` is the whole family, not one app. | |
| 30 | ||
| 31 | ## Key Technologies | |
| 32 | ||
| 33 | | Technology | Role | | |
| 34 | |-----------|------| | |
| 35 | | Keet / keet-daemon | P2P chat hub (port 3117) | | |
| 36 | | WebSocket + JSON-RPC 2.0 | Hub API protocol (replaces HTTP/SSE) | | |
| 37 | | Hypercore | Append-only logs (data storage) | | |
| 38 | | Hyperbee | Key-value database on Hypercore | | |
| 39 | | Hyperdrive | P2P filesystem | | |
| 40 | | Hyperswarm | Peer discovery via DHT | | |
| 41 | | UDX (libudx) | Reliable UDP streams (gaming) | | |
| 42 | | Godot 4 | 2D/3D engine for apps and games | | |
| 43 | | Pear Runtime | P2P app runtime (deploys via `pear://` URLs) | | |
| 44 | ||
| 45 | ## Directory Layout | |
| 46 | ||
| 47 | ``` | |
| 48 | hyper-social/ | |
| 49 | ├── docs/ # This documentation | |
| 50 | ├── bridge-envelope/ # Universal message schema (npm pkg) | |
| 51 | ├── bridge-vault/ # Encrypted credential store (npm pkg) | |
| 52 | ├── bridge-irc/ # IRC <-> Keet bridge (Pear app) | |
| 53 | ├── bridge-discord/ # Discord <-> Keet bridge (Pear app) | |
| 54 | ├── bridge-matrix/ # Matrix <-> Keet bridge (Pear app) | |
| 55 | ├── bridge-mastodon/ # Mastodon <-> Keet bridge (Pear app) | |
| 56 | ├── bridge-email/ # Email <-> Keet bridge (Pear app) | |
| 57 | ├── hyper-wiki/ # P2P wiki (Pear app) | |
| 58 | ├── hyper-board/ # P2P kanban board (Pear app) | |
| 59 | ├── hyper-feed/ # P2P social feed (Pear app) | |
| 60 | ├── hyper-daemon/ # P2P automation runtime (Pear app) | |
| 61 | └── hyper-social/ # Meta-app composing everything (Pear app) | |
| 62 | ``` |
| … | … | @@ -0,0 +1,380 @@ |
| 1 | # Social Graph Navigation | |
| 2 | ||
| 3 | ## The Problem with Trees | |
| 4 | ||
| 5 | Discord's structure is a tree: | |
| 6 | ||
| 7 | ``` | |
| 8 | User | |
| 9 | ├── Folder (client-side) | |
| 10 | │ ├── Server | |
| 11 | │ │ ├── Category | |
| 12 | │ │ │ ├── #channel | |
| 13 | │ │ │ │ └── thread | |
| 14 | │ │ │ └── #channel | |
| 15 | │ │ └── Category | |
| 16 | │ └── Server | |
| 17 | └── Folder | |
| 18 | ``` | |
| 19 | ||
| 20 | Every node has exactly one parent. Simple to render, simple to navigate. But the tree is an artifact of client-server architecture — Discord has "servers" because there ARE servers. The tree mirrors the deployment topology. | |
| 21 | ||
| 22 | In P2P, there are no servers. There's no natural root for the tree. The underlying social reality was never a tree in the first place. | |
| 23 | ||
| 24 | ## What Actually Exists | |
| 25 | ||
| 26 | The real social structure is a graph — specifically a hypergraph: | |
| 27 | ||
| 28 | - **People** know people (social graph) | |
| 29 | - **People** are in rooms (bipartite graph: people ↔ rooms) | |
| 30 | - **Rooms** relate to topics (rooms ↔ topics) | |
| 31 | - **Rooms** can belong to multiple groups (rooms ↔ registries) | |
| 32 | - **Topics** overlap (topic graph) | |
| 33 | ||
| 34 | Discord forces this into a tree by bundling four orthogonal concerns into one hierarchy node (the "server"): | |
| 35 | ||
| 36 | 1. **Permission boundary** — who can see what | |
| 37 | 2. **Discovery scope** — what rooms are browsable together | |
| 38 | 3. **Social context** — this community of people | |
| 39 | 4. **Administrative domain** — who has mod power | |
| 40 | ||
| 41 | Keet already unbundles these: | |
| 42 | ||
| 43 | | Concern | Discord | Keet P2P | | |
| 44 | |---------|---------|----------| | |
| 45 | | Permissions | Per-server roles | Per-room (room admin, member roles) | | |
| 46 | | Discovery | Browse channels in a server | Per-registry (Autobase indexing rooms) | | |
| 47 | | Social context | Server membership | Per-room membership overlap | | |
| 48 | | Administration | Server owner/mods | Per-room creator/admins | | |
| 49 | ||
| 50 | Four orthogonal axes. No hierarchy. Already a graph. | |
| 51 | ||
| 52 | ## Graph vs Tree: What Changes | |
| 53 | ||
| 54 | **In a tree:** groups own rooms. Top-down. A channel belongs to exactly one server. | |
| 55 | ||
| 56 | **In a graph:** rooms exist independently. Groups are views/filters/sets. A room can be in multiple groups simultaneously. Groups can overlap. A room that bridges two groups is a first-class structural feature, not an edge case. | |
| 57 | ||
| 58 | ``` | |
| 59 | [Group A]───#general───[Group B] | |
| 60 | │ │ │ | |
| 61 | #dev-talk #announcements #art | |
| 62 | │ │ | |
| 63 | [Group C]──────────────────┘ | |
| 64 | ``` | |
| 65 | ||
| 66 | **What you gain:** | |
| 67 | - Rooms aren't siloed — a dev room can exist in both a "Hypercore" group and a "Bare Runtime" group | |
| 68 | - No duplicate rooms across communities | |
| 69 | - Bridge rooms (rooms in multiple groups) are natural, not special-cased | |
| 70 | - Natural for P2P — no server owner, just overlapping sets of peers | |
| 71 | ||
| 72 | **What you lose:** | |
| 73 | - Tree renders trivially as a sidebar. Graphs don't. | |
| 74 | - Navigation becomes "where am I" instead of "drill down" | |
| 75 | - Permissions get complex — who controls a room in 3 groups? | |
| 76 | ||
| 77 | ## Sheaf Theory (Linas Vepstas' Framework) | |
| 78 | ||
| 79 | A sheaf assigns data to open sets of a topological space with two axioms: | |
| 80 | ||
| 81 | 1. **Locality** — if two sections agree on every overlap, they're the same | |
| 82 | 2. **Gluing** — compatible local sections can be glued into a global section | |
| 83 | ||
| 84 | And the critical concept: **cohomology** — the obstruction to gluing. When local data can't be made globally consistent, you get non-trivial cohomology. That's not an error. It's structural information. | |
| 85 | ||
| 86 | ### Applied to the Social Graph | |
| 87 | ||
| 88 | **Each room is an open set.** It has local data — messages, members, roles, shared context, culture. Rooms overlap where they share members. The overlap is the set of people who see both rooms. | |
| 89 | ||
| 90 | **Identity is a section of the sheaf.** One keypair (global section), but context-dependent presentation (display name, avatar, what you share) that can vary locally. The sheaf condition: consistency is required on overlaps (shared members see the same you), freedom exists where contexts don't overlap. | |
| 91 | ||
| 92 | **Groups are an open cover.** A discovery registry covers a set of rooms. Multiple registries can cover the same room. The intersection of two registries is the rooms they share. Group-level data (name, governance, membership rules) must be compatible where registries overlap. | |
| 93 | ||
| 94 | **Discord's tree is a trivial covering.** Every channel belongs to exactly one server. No overlaps. Trivial topology. Trivial cohomology. Everything glues trivially. That's why it "just works" — and why it's limited. | |
| 95 | ||
| 96 | **A graph covering has non-trivial topology.** A room in two registries creates an overlap. Conflicting governance between registries is non-trivial cohomology — a real structural feature of the social space. The math surfaces real tensions that a tree architecture hides. | |
| 97 | ||
| 98 | ### Recursive Groups | |
| 99 | ||
| 100 | A discovery registry is an Autobase. An Autobase is writable by its members. Those members coordinate by chatting — in a room. So a group's governance room IS its registry, viewed differently: | |
| 101 | ||
| 102 | ``` | |
| 103 | "Holepunch Dev" = { | |
| 104 | as Registry: indexes [#hypercore, #bare, #pear-apps] | |
| 105 | as Room: where admins discuss which rooms to include | |
| 106 | same Autobase, different sections of the same sheaf | |
| 107 | } | |
| 108 | ``` | |
| 109 | ||
| 110 | A group can BE a room. The meta-room's members decide which rooms belong. This is what Discord's server admin channel is — but hardcoded. In a graph with sheaf structure, it's just another node with edges. | |
| 111 | ||
| 112 | ## Parsing the Social Graph Like Language | |
| 113 | ||
| 114 | ### The Language Learning Pipeline | |
| 115 | ||
| 116 | The OpenCog language learning pipeline (`opencog-kb/language-learning/mvp/`): | |
| 117 | ||
| 118 | ``` | |
| 119 | corpus text | |
| 120 | → pair counting (word A next to word B) | |
| 121 | → MI (which pairs are statistically significant) | |
| 122 | → MST parse (connect highest-MI pairs into trees) | |
| 123 | → clustering (words with similar connectors = same category) | |
| 124 | → grammar (rules extracted from clusters) | |
| 125 | ``` | |
| 126 | ||
| 127 | ### The Same Pipeline, Applied to Social | |
| 128 | ||
| 129 | | Language | Social | | |
| 130 | |----------|--------| | |
| 131 | | word | room | | |
| 132 | | sentence | a user's room list | | |
| 133 | | co-occurrence | two rooms share a member | | |
| 134 | | MI(word_A, word_B) | MI(room_A, room_B) | | |
| 135 | | parse tree | community structure | | |
| 136 | | grammatical category | group type | | |
| 137 | | connector | social affordance | | |
| 138 | ||
| 139 | **Step 1: Pair counting** | |
| 140 | ||
| 141 | A "sentence" is a user's room list. Each user generates one sentence. The "words" are the rooms they're in. | |
| 142 | ||
| 143 | ``` | |
| 144 | for each user U: | |
| 145 | rooms = U.rooms() | |
| 146 | for each pair (R1, R2) in rooms: | |
| 147 | count[R1][R2] += 1 | |
| 148 | ``` | |
| 149 | ||
| 150 | Two rooms co-occurring in the same user's room list = two words co-occurring in the same sentence. | |
| 151 | ||
| 152 | **Step 2: MI calculation** | |
| 153 | ||
| 154 | ``` | |
| 155 | MI(room_A, room_B) = log2(P(A,B) / (P(A) × P(B))) | |
| 156 | ``` | |
| 157 | ||
| 158 | - **High MI:** rooms that share members more than chance predicts. Meaningfully connected — the same people chose both. | |
| 159 | - **Low MI:** overlap is coincidental (both rooms are huge). | |
| 160 | - **Negative MI:** rooms that anti-correlate. People in room A are specifically NOT in room B. Community boundary. | |
| 161 | ||
| 162 | Same formula as `compute-mi.scm`. Same AtomSpace representation: | |
| 163 | ||
| 164 | ```scheme | |
| 165 | ; Language: | |
| 166 | (EvaluationLink (Predicate "word-pair") | |
| 167 | (List (Word "the") (Word "cat"))) | |
| 168 | ||
| 169 | ; Social: | |
| 170 | (EvaluationLink (Predicate "room-pair") | |
| 171 | (List (Room "The Garage") (Room "Hypercore Dev"))) | |
| 172 | ``` | |
| 173 | ||
| 174 | **Step 3: MST parsing** | |
| 175 | ||
| 176 | For each user's room neighborhood, connect rooms with the highest-MI edges. The tree reveals: which rooms are tightly coupled (same community), which are loosely connected (bridges), which are leaves (niche rooms off a hub). | |
| 177 | ||
| 178 | Critical parallel: you don't MST the entire vocabulary. You parse sentences — local contexts. Similarly, you parse local neighborhoods, not the entire global graph. Each user sees a local parse. The global structure emerges from consistent local parses. | |
| 179 | ||
| 180 | **That's the sheaf condition.** Local parses must agree on overlaps. | |
| 181 | ||
| 182 | **Step 4: Clustering** | |
| 183 | ||
| 184 | Words with similar connector profiles belong to the same grammatical category ("cat", "dog", "table" all take determiners → they're nouns). | |
| 185 | ||
| 186 | Rooms with similar membership profiles belong to the same group type: | |
| 187 | ||
| 188 | - Rooms where everyone is also in the Hypercore room → "ecosystem dev rooms" | |
| 189 | - Rooms where everyone is also in The Garage → "project rooms" | |
| 190 | - Rooms with no overlap → "isolated communities" | |
| 191 | ||
| 192 | The clusters ARE the groups. Not curated — emerged from the data. | |
| 193 | ||
| 194 | **Step 5: Grammar = Social Structure** | |
| 195 | ||
| 196 | Structural patterns extracted from clusters: | |
| 197 | ||
| 198 | - "Hub room → satellite rooms" (a community center with topic offshoots) | |
| 199 | - "Bridge room connects two communities" (shared between clusters) | |
| 200 | - "Governance room governs a set of topic rooms" (the recursive meta-room) | |
| 201 | ||
| 202 | These are the **social grammar** — the rules by which communities self-organize. | |
| 203 | ||
| 204 | ### Compartments = Communities | |
| 205 | ||
| 206 | In the language pipeline, compartments are regions where grammar is locally consistent. English in one compartment, French in another. The boundary is where rules change. | |
| 207 | ||
| 208 | In social, compartments are communities with locally consistent norms. The dev community has one set of norms, the gaming community has another. Detecting compartment boundaries = detecting community boundaries. Same algorithm. | |
| 209 | ||
| 210 | ### Connector Theory | |
| 211 | ||
| 212 | In Linas' framework, a word's meaning IS its connector set — what it can link to and how. | |
| 213 | ||
| 214 | A room's "meaning" IS its connector set — its pattern of MI with other rooms. The same room in a different graph neighborhood would be a different room, just like the same word in a different sentence has a different parse. | |
| 215 | ||
| 216 | Rooms have typed connectors (affordances): | |
| 217 | - A dev room: `[code+, discussion+, help-seeking+]` | |
| 218 | - A project room: `[team+, output+, planning+]` | |
| 219 | - A social room: `[casual+, off-topic+, bonding+]` | |
| 220 | ||
| 221 | When connectors match, links form. Groups emerge from the parse — from successful connector matching. Multiple valid parses = multiple valid groupings. Ambiguity is natural. | |
| 222 | ||
| 223 | ## The Dual Navigation Model | |
| 224 | ||
| 225 | Two views of the same underlying sheaf: | |
| 226 | ||
| 227 | ### 1D Sidebar (Local Section) | |
| 228 | ||
| 229 | The traditional sidebar — but computed, not fixed. It's the section of the sheaf at your current position: | |
| 230 | ||
| 231 | ``` | |
| 232 | 1. Current room (where you are) | |
| 233 | 2. Rooms with highest MI to current room (most connected) | |
| 234 | 3. Rooms where your contacts are active right now | |
| 235 | 4. Rooms at the boundary of your current cluster (doors out) | |
| 236 | 5. Rooms in other clusters you belong to (distant but yours) | |
| 237 | ``` | |
| 238 | ||
| 239 | Move to a different room, the slice recomputes. Join a new room, MI shifts, slices everywhere update. It's alive. | |
| 240 | ||
| 241 | Different orderings of the same slice: | |
| 242 | - **By group** — pick a registry, see its rooms (what godot-keet has now) | |
| 243 | - **By recency** — most active rooms float up, regardless of group | |
| 244 | - **By social** — rooms where your contacts are active right now | |
| 245 | - **By topology** — rooms that bridge groups are highlighted as connectors | |
| 246 | ||
| 247 | ### Graph View (Base Space) | |
| 248 | ||
| 249 | The topology around you. Rooms as nodes, connections as edges, clusters as groups: | |
| 250 | ||
| 251 | - Force-directed layout (nodes repel, edges attract, clusters emerge) | |
| 252 | - Nodes = rooms (sized by activity, colored by cluster) | |
| 253 | - Edges = connections (weighted by MI) | |
| 254 | - Your position = highlighted node | |
| 255 | - Contacts = dots on their current nodes | |
| 256 | ||
| 257 | Navigate by: | |
| 258 | - Clicking nodes (enter a room → 1D sidebar updates) | |
| 259 | - Zooming into clusters (see individual rooms) | |
| 260 | - Zooming out (clusters collapse into single nodes → macro structure) | |
| 261 | - Following edges (traverse connections) | |
| 262 | ||
| 263 | ### How They Work Together | |
| 264 | ||
| 265 | ``` | |
| 266 | 1D Sidebar ←→ Graph View | |
| 267 | Street view ←→ Map view | |
| 268 | Reading ←→ Navigating | |
| 269 | Section ←→ Base space | |
| 270 | Local ←→ Global | |
| 271 | ``` | |
| 272 | ||
| 273 | You're in the graph view. Three clusters visible — dev rooms, social rooms, project rooms. A room bridges two clusters. Click it. The 1D sidebar updates to show that bridge room's neighborhood. Enter the chat. The graph highlights your position. Your contacts' positions are visible. | |
| 274 | ||
| 275 | Toggle between views. Or split-screen: graph on left, chat on right. Click a node in the graph, chat loads, sidebar updates. | |
| 276 | ||
| 277 | ## The Dynamic Graph | |
| 278 | ||
| 279 | The graph isn't static. As people join rooms, send messages, create rooms: | |
| 280 | ||
| 281 | - MI values shift | |
| 282 | - MST re-parses | |
| 283 | - Clusters reorganize | |
| 284 | - The graph layout animates — communities drift apart or merge | |
| 285 | - New bridges form when someone joins rooms in two separate clusters | |
| 286 | ||
| 287 | Communities form and dissolve in real time. The same way conversation flows in chat, structure flows in the graph. | |
| 288 | ||
| 289 | ## Implementation Path | |
| 290 | ||
| 291 | ### Data Source | |
| 292 | ||
| 293 | keet-daemon API already provides: | |
| 294 | - Room lists (`getRecentRooms`) | |
| 295 | - Member lists (`getMemberList`) | |
| 296 | - Activity data (message events) | |
| 297 | - Discovery registries (existing group structure) | |
| 298 | ||
| 299 | ### Processing Pipeline | |
| 300 | ||
| 301 | ``` | |
| 302 | keet-daemon API | |
| 303 | → build co-occurrence matrix (rooms × rooms, weighted by shared members) | |
| 304 | → compute MI (same math as opencog-kb/language-learning/mvp/compute-mi.scm) | |
| 305 | → MST parse per-user neighborhoods | |
| 306 | → cluster rooms by connector profile | |
| 307 | → output: discovered groups, bridge rooms, community boundaries | |
| 308 | ``` | |
| 309 | ||
| 310 | Could run: | |
| 311 | - In AtomSpace/CogServer (existing infrastructure) | |
| 312 | - Periodically as membership changes | |
| 313 | - Per-user (each user's view parses differently — different sections of the sheaf) | |
| 314 | ||
| 315 | ### Godot UI | |
| 316 | ||
| 317 | Current scene tree: | |
| 318 | ``` | |
| 319 | Main → IdentityBar + ContentHBox[GroupBar | RoomList | Chat | Members] | |
| 320 | ``` | |
| 321 | ||
| 322 | Extended with graph view: | |
| 323 | ``` | |
| 324 | Main → IdentityBar + ContentHBox[ | |
| 325 | GraphView (2D force-directed, rooms as nodes) | |
| 326 | OR | |
| 327 | GroupBar + RoomList + Chat + Members (1D slice) | |
| 328 | ] | |
| 329 | ``` | |
| 330 | ||
| 331 | Force-directed layout in Godot: nodes as Node2D with physics, edges as Line2D, clusters colored by computed group. Click node → enter room → sidebar updates. | |
| 332 | ||
| 333 | ### Three Layers of Groups | |
| 334 | ||
| 335 | 1. **Curated groups** (discovery registries) — explicit, admin-managed | |
| 336 | 2. **Emergent groups** (MI/MST/clustering) — computed from membership data | |
| 337 | 3. **Personal groups** (client-side folders) — local-only, per-user | |
| 338 | ||
| 339 | All three coexist. Curated groups seed the structure. Emergent groups surface hidden patterns. Personal groups let users organize their own view. | |
| 340 | ||
| 341 | ## Connection to Hyper-Social Architecture | |
| 342 | ||
| 343 | Bridges are **sheaf morphisms** — they map local data from one space (IRC) to another (Keet) while preserving compatibility. Messages map to messages, users map to users, ordering is preserved. | |
| 344 | ||
| 345 | The bridge graph from [ARCHITECTURE.md](ARCHITECTURE.md) is the base space of a sheaf: | |
| 346 | ||
| 347 | ``` | |
| 348 | ┌─────────┐ | |
| 349 | ┌────│ Keet │────┐ | |
| 350 | │ └─────────┘ │ | |
| 351 | │ │ │ | |
| 352 | ┌───┴───┐ ┌──┴──┐ ┌────┴────┐ | |
| 353 | │ IRC │ │Disc.│ │Mastodon │ | |
| 354 | │ plane │ │plane│ │ plane │ | |
| 355 | └───┬───┘ └──┬──┘ └────┬────┘ | |
| 356 | ``` | |
| 357 | ||
| 358 | Each "plane" is an open set. The overlaps (bridged rooms) must have compatible data. The sheaf condition IS the bridge protocol — messages must be faithfully translated across the bridge. Cohomological obstructions (things that can't be bridged faithfully — Discord reactions, Matrix threads, IRC actions) are real structural features, not bugs. | |
| 359 | ||
| 360 | The fracture principle from the architecture doc is a topological statement: the communication graph has non-trivial topology, and any attempt to flatten it into a single universal bridge will fail because the cohomology is non-trivial. Many small bridges, each covering a local region, with compatibility on overlaps — that's a sheaf. | |
| 361 | ||
| 362 | ## What Makes This Novel | |
| 363 | ||
| 364 | No existing chat client: | |
| 365 | ||
| 366 | 1. **Discovers** group structure (instead of requiring manual creation) | |
| 367 | 2. **Navigates** topologically (instead of hierarchically) | |
| 368 | 3. **Computes** the sidebar as a local slice (instead of a fixed tree) | |
| 369 | 4. **Uses the same math** that parses language to parse social structure | |
| 370 | 5. **Surfaces cohomological tensions** (conflicting governance, incompatible bridges) as features rather than hiding them | |
| 371 | ||
| 372 | The technology stack already supports this: | |
| 373 | - Godot for spatial rendering | |
| 374 | - keet-daemon for data | |
| 375 | - AtomSpace for hypergraph reasoning | |
| 376 | - The language learning pipeline for MI/MST/clustering | |
| 377 | - Autobases for graph-native data | |
| 378 | - Sheaf theory for the mathematical framework | |
| 379 | ||
| 380 | The social graph and the linguistic graph are the same kind of object. The tools to navigate one are the tools to navigate the other. |
| … | … | @@ -0,0 +1,318 @@ |
| 1 | # UDX for Real-Time Gaming | |
| 2 | ||
| 3 | Deep dive into libudx — Holepunch's custom reliable UDP transport — and how it maps to Godot's multiplayer system for real-time P2P gaming. | |
| 4 | ||
| 5 | ## The Holepunch Networking Stack | |
| 6 | ||
| 7 | ``` | |
| 8 | Application (Keet, games, bridges) | |
| 9 | | | |
| 10 | v | |
| 11 | Protomux (protocol multiplexing over a single stream) | |
| 12 | | | |
| 13 | v | |
| 14 | Secret-Stream (Noise XX handshake, XChaCha20-Poly1305 encryption) | |
| 15 | | | |
| 16 | v | |
| 17 | UDX Stream (reliable, congestion-controlled streams over UDP) | |
| 18 | | | |
| 19 | v | |
| 20 | UDX Socket (raw UDP datagrams) | |
| 21 | | | |
| 22 | v | |
| 23 | UDP (kernel) | |
| 24 | ``` | |
| 25 | ||
| 26 | For gaming, we use the lower layers directly (UDX Stream + UDX Socket), bypassing Protomux and potentially Secret-Stream for maximum performance. | |
| 27 | ||
| 28 | ## UDX: Three Levels of Sending | |
| 29 | ||
| 30 | UDX provides three distinct ways to send data, each with different guarantees: | |
| 31 | ||
| 32 | ### 1. `stream.write(buffer)` — Reliable, Ordered | |
| 33 | - Like TCP but over UDP | |
| 34 | - Guaranteed delivery in order | |
| 35 | - Congestion-controlled | |
| 36 | - Use for: game events, chat, commands, file transfers | |
| 37 | ||
| 38 | ### 2. `stream.send(buffer)` — Unreliable, In-Stream | |
| 39 | - Fire within an established stream | |
| 40 | - No delivery guarantee, no ordering guarantee | |
| 41 | - Encrypted (shares the stream's Noise session) | |
| 42 | - Use for: position updates, velocity, rotation, state snapshots | |
| 43 | ||
| 44 | ### 3. `socket.send(buffer, port, host)` — Raw UDP | |
| 45 | - Bare UDP datagram, no stream, no encryption | |
| 46 | - Lowest possible overhead | |
| 47 | - Use for: voice chat, video, bulk transfers, custom protocols | |
| 48 | ||
| 49 | ```javascript | |
| 50 | const UDX = require('udx-native') | |
| 51 | const udx = new UDX() | |
| 52 | ||
| 53 | // Create socket and stream | |
| 54 | const socket = udx.createSocket() | |
| 55 | socket.bind(0) | |
| 56 | const stream = udx.createStream(streamId) | |
| 57 | stream.connect(socket, remoteStreamId, remotePort, remoteHost) | |
| 58 | ||
| 59 | // Reliable (game events) | |
| 60 | stream.write(Buffer.from('player_died:42')) | |
| 61 | ||
| 62 | // Unreliable (position) | |
| 63 | stream.send(Buffer.from(positionBuffer)) | |
| 64 | ||
| 65 | // Fire-and-forget (voice) | |
| 66 | stream.trySend(Buffer.from(voiceChunk)) | |
| 67 | ||
| 68 | // Raw UDP (custom protocol) | |
| 69 | socket.send(Buffer.from(rawData), remotePort, remoteHost) | |
| 70 | ``` | |
| 71 | ||
| 72 | ## Mapping to Godot TransferModes | |
| 73 | ||
| 74 | | Godot TransferMode | UDX Method | Use Case | | |
| 75 | |---------------------|------------|----------| | |
| 76 | | TRANSFER_MODE_RELIABLE | `stream.write()` | Commands, chat, game events, spawns | | |
| 77 | | TRANSFER_MODE_UNRELIABLE | `stream.send()` / `stream.trySend()` | Position, rotation, velocity | | |
| 78 | | TRANSFER_MODE_UNRELIABLE_ORDERED | `stream.send()` + client-side seq filter | Animation state, health bars | | |
| 79 | ||
| 80 | For UNRELIABLE_ORDERED, UDX doesn't provide native ordering on unreliable sends. The GDExtension must add a sequence number and drop stale packets client-side. This is a standard game networking pattern. | |
| 81 | ||
| 82 | ## Interactive Mode | |
| 83 | ||
| 84 | ```javascript | |
| 85 | stream.setInteractive(true) // mode 0 = interactive (low latency) | |
| 86 | // mode 1 = batch (high throughput, default) | |
| 87 | ``` | |
| 88 | ||
| 89 | Interactive mode changes UDX's behavior: | |
| 90 | - Reduces batching delay | |
| 91 | - Prioritizes latency over throughput | |
| 92 | - Essential for real-time games where every millisecond matters | |
| 93 | ||
| 94 | ## Congestion Control: BBR | |
| 95 | ||
| 96 | UDX uses **BBR** (Bottleneck Bandwidth and Round-trip propagation time) congestion control. | |
| 97 | ||
| 98 | **Note:** The udx-native skill file incorrectly states LEDBAT. The actual libudx C source code implements BBR. | |
| 99 | ||
| 100 | BBR characteristics relevant to gaming: | |
| 101 | - Probes for available bandwidth (doesn't just react to loss) | |
| 102 | - Better utilization of available bandwidth than loss-based algorithms | |
| 103 | - More stable throughput | |
| 104 | - Less latency inflation than TCP | |
| 105 | ||
| 106 | ## Packet Overhead | |
| 107 | ||
| 108 | Understanding exact byte budgets is critical for game networking. | |
| 109 | ||
| 110 | ### Header Sizes | |
| 111 | ||
| 112 | | Layer | Overhead | | |
| 113 | |-------|----------| | |
| 114 | | UDX header | 20 bytes | | |
| 115 | | IP header | 20 bytes | | |
| 116 | | UDP header | 8 bytes | | |
| 117 | | **Reliable total** | **67 bytes** (20 UDX + 20 IP + 8 UDP + 19 encryption) | | |
| 118 | | **Unreliable msg total** | **72 bytes** (20 UDX + 20 IP + 8 UDP + 24 encryption) | | |
| 119 | ||
| 120 | ### Encryption Overhead (Secret-Stream) | |
| 121 | ||
| 122 | | Mode | Overhead | | |
| 123 | |------|----------| | |
| 124 | | Ordered (stream.write) | 19 bytes | | |
| 125 | | Unordered (stream.send) | 24 bytes | | |
| 126 | ||
| 127 | ### Usable Payload | |
| 128 | ||
| 129 | At base MTU of 1200 bytes: | |
| 130 | ||
| 131 | | Mode | Payload | | |
| 132 | |------|---------| | |
| 133 | | Reliable | ~1133 bytes | | |
| 134 | | Unreliable message | ~1128 bytes | | |
| 135 | | Raw UDP (no encryption) | ~1152 bytes | | |
| 136 | ||
| 137 | MAX_PACKET = 2048 bytes per UDP datagram (but MTU usually limits to ~1200). | |
| 138 | ||
| 139 | ### What Fits in a Packet | |
| 140 | ||
| 141 | Typical game state per player: ~100-200 bytes (position, rotation, velocity, animation state, health). | |
| 142 | ||
| 143 | At 1128 bytes payload: **5-11 players' state per packet.** For most games, a single unreliable send per tick is enough. | |
| 144 | ||
| 145 | ## NAT Holepunching | |
| 146 | ||
| 147 | UDX inherits NAT traversal from Hyperswarm/HyperDHT. No port forwarding needed. | |
| 148 | ||
| 149 | ### How It Works | |
| 150 | ||
| 151 | 1. Peers register on the DHT (Distributed Hash Table) | |
| 152 | 2. When joining a game topic, the DHT provides peer addresses | |
| 153 | 3. HyperDHT coordinates NAT holepunching between peers | |
| 154 | 4. Once holepunched, UDX streams flow directly peer-to-peer | |
| 155 | ||
| 156 | ### Timing by NAT Type | |
| 157 | ||
| 158 | | NAT Combination | Holepunch Time | | |
| 159 | |----------------|----------------| | |
| 160 | | CONSISTENT + CONSISTENT | ~1-10 seconds | | |
| 161 | | CONSISTENT + RANDOM | ~5-20 seconds | | |
| 162 | | RANDOM + RANDOM | ~10-35 seconds | | |
| 163 | | Symmetric + Symmetric | Relay required | | |
| 164 | ||
| 165 | ### Relay Fallback | |
| 166 | ||
| 167 | For the hardest NAT scenarios (symmetric NATs), UDX provides: | |
| 168 | ||
| 169 | ```javascript | |
| 170 | stream.relayTo(relayStream) // Route through a relay peer | |
| 171 | ``` | |
| 172 | ||
| 173 | The relay peer forwards packets between peers that can't holepunch directly. Adds latency but ensures connectivity. | |
| 174 | ||
| 175 | ### IP Migration | |
| 176 | ||
| 177 | ```javascript | |
| 178 | stream.changeRemote(newPort, newHost) // Seamless handoff | |
| 179 | ``` | |
| 180 | ||
| 181 | If a player switches from WiFi to cellular (or vice versa), the stream migrates without disconnection. Critical for mobile gaming. | |
| 182 | ||
| 183 | ## Performance Metrics | |
| 184 | ||
| 185 | UDX exposes real-time network metrics useful for game UI and adaptive networking: | |
| 186 | ||
| 187 | ```javascript | |
| 188 | stream.rtt // Smoothed round-trip time (ms) | |
| 189 | stream.cwnd // Congestion window (bytes) | |
| 190 | stream.mtu // Maximum transmission unit | |
| 191 | stream.inflight // Packets currently in flight | |
| 192 | stream.retransmits // Total retransmissions | |
| 193 | stream.fastRecoveries // Fast recovery events | |
| 194 | stream.bytesTransmitted // Total bytes sent | |
| 195 | stream.packetsTransmitted // Total packets sent | |
| 196 | ``` | |
| 197 | ||
| 198 | ### Using Metrics in Games | |
| 199 | ||
| 200 | ```gdscript | |
| 201 | # Adaptive quality based on network conditions | |
| 202 | func _adjust_quality(): | |
| 203 | var rtt = peer.get_rtt() | |
| 204 | if rtt > 100: | |
| 205 | # High latency: reduce sync rate, increase prediction | |
| 206 | sync_rate = 10 # 10 Hz | |
| 207 | prediction_enabled = true | |
| 208 | elif rtt > 50: | |
| 209 | sync_rate = 20 # 20 Hz | |
| 210 | prediction_enabled = true | |
| 211 | else: | |
| 212 | sync_rate = 60 # 60 Hz | |
| 213 | prediction_enabled = false | |
| 214 | ``` | |
| 215 | ||
| 216 | ## Hyperswarm for Game Discovery | |
| 217 | ||
| 218 | Instead of IP:port, games use a **topic** (32-byte hash) for matchmaking: | |
| 219 | ||
| 220 | ```javascript | |
| 221 | const Hyperswarm = require('hyperswarm') | |
| 222 | const crypto = require('hypercore-crypto') | |
| 223 | ||
| 224 | // Create a game lobby topic | |
| 225 | const topic = crypto.hash(Buffer.from('my-awesome-game-v1-lobby')) | |
| 226 | ||
| 227 | const swarm = new Hyperswarm() | |
| 228 | swarm.join(topic) // Announce + lookup | |
| 229 | ||
| 230 | swarm.on('connection', (stream, peerInfo) => { | |
| 231 | // UDX stream arrives, encrypted, holepunched | |
| 232 | // Hand off to game networking... | |
| 233 | }) | |
| 234 | ``` | |
| 235 | ||
| 236 | ### Advantages Over Traditional Game Networking | |
| 237 | ||
| 238 | | Feature | Traditional (ENet/Steam) | Hyperswarm + UDX | | |
| 239 | |---------|--------------------------|------------------| | |
| 240 | | NAT traversal | Port forwarding or relay | DHT holepunching (automatic) | | |
| 241 | | Encryption | Optional, add-on | Always-on (Noise protocol) | | |
| 242 | | Server discovery | Lobby server required | Topic-based (no server) | | |
| 243 | | Relay | Dedicated relay servers | Any peer can relay | | |
| 244 | | Mobile handoff | Connection drops | `changeRemote()` (seamless) | | |
| 245 | | Identity | Platform-specific (Steam ID) | Cryptographic keypair | | |
| 246 | ||
| 247 | ## Building the GDExtension | |
| 248 | ||
| 249 | ### Dependencies | |
| 250 | ||
| 251 | - **godot-cpp** — C++ bindings for GDExtension API | |
| 252 | - **libudx** — Holepunch's UDP transport (C library) | |
| 253 | - **libsodium** — Noise encryption (C library) | |
| 254 | - **libhyperdht** — DHT discovery (if available as C library; may need to extract from JS) | |
| 255 | ||
| 256 | ### Build System | |
| 257 | ||
| 258 | ```bash | |
| 259 | # Clone godot-cpp | |
| 260 | git clone https://github.com/godotengine/godot-cpp | |
| 261 | cd godot-cpp && scons platform=linux | |
| 262 | ||
| 263 | # Build the extension | |
| 264 | cd addons/hyperswarm_peer/ | |
| 265 | scons platform=linux target=template_release | |
| 266 | ``` | |
| 267 | ||
| 268 | ### Challenge: libhyperdht | |
| 269 | ||
| 270 | libudx is a clean C library and wraps easily. The challenge is libhyperdht — the DHT discovery layer is currently JavaScript only. Options: | |
| 271 | ||
| 272 | 1. **Extract C core** — libhyperdht uses libudx internally; extract the holepunching logic | |
| 273 | 2. **Reimplement in C** — The DHT protocol is documented; reimplement the essential subset | |
| 274 | 3. **Sidecar for discovery only** — Use a small Node.js process just for DHT lookup, hand off the socket to the GDExtension | |
| 275 | 4. **Use existing C DHT** — If a C implementation of the Kademlia DHT exists | |
| 276 | ||
| 277 | Option 3 is the pragmatic first step: a tiny discovery sidecar that finds peers, then hands off the raw UDP socket to the GDExtension for all game traffic. | |
| 278 | ||
| 279 | ## Game Networking Patterns | |
| 280 | ||
| 281 | ### Authoritative Server (FPS, competitive) | |
| 282 | ||
| 283 | ``` | |
| 284 | Server Peer (authority) | |
| 285 | |-- validates all inputs | |
| 286 | |-- sends authoritative state | |
| 287 | | | |
| 288 | Client Peers | |
| 289 | |-- send inputs (reliable) | |
| 290 | |-- receive state (unreliable) | |
| 291 | |-- predict locally | |
| 292 | |-- reconcile on authoritative update | |
| 293 | ``` | |
| 294 | ||
| 295 | ### P2P Mesh (co-op, casual) | |
| 296 | ||
| 297 | ``` | |
| 298 | All Peers equal | |
| 299 | |-- each owns their player state | |
| 300 | |-- broadcast position/state (unreliable) | |
| 301 | |-- broadcast events (reliable) | |
| 302 | |-- no central authority | |
| 303 | ``` | |
| 304 | ||
| 305 | Using Godot's `create_mesh()` + Hyperswarm topics, this is the simplest pattern. Good for 2-8 player games. | |
| 306 | ||
| 307 | ### Relay for Large Lobbies | |
| 308 | ||
| 309 | For games with many players (>8), use a relay topology: | |
| 310 | ||
| 311 | ``` | |
| 312 | Relay Peer (optional dedicated) | |
| 313 | |-- forwards packets between peers | |
| 314 | |-- reduces connection count from O(n^2) to O(n) | |
| 315 | |-- adds latency but scales better | |
| 316 | ||
| 317 | UDX: stream.relayTo(relayStream) | |
| 318 | ``` |
| … | … | @@ -0,0 +1,4 @@ |
| 1 | import { Board } from './lib/board.js' | |
| 2 | import { BoardStore, DEFAULT_LANES } from './lib/board-store.js' | |
| 3 | ||
| 4 | export { Board, BoardStore, DEFAULT_LANES } |
| … | … | @@ -0,0 +1,199 @@ |
| 1 | import { randomBytes } from 'crypto' | |
| 2 | ||
| 3 | const DEFAULT_LANES = ['backlog', 'todo', 'in-progress', 'done'] | |
| 4 | ||
| 5 | class BoardStore { | |
| 6 | constructor (bee) { | |
| 7 | this.bee = bee | |
| 8 | } | |
| 9 | ||
| 10 | _cardKey (id) { | |
| 11 | return `card/${id}` | |
| 12 | } | |
| 13 | ||
| 14 | _laneKey (name) { | |
| 15 | return `lane/${name}` | |
| 16 | } | |
| 17 | ||
| 18 | _genId () { | |
| 19 | return randomBytes(8).toString('hex') | |
| 20 | } | |
| 21 | ||
| 22 | async ready () { | |
| 23 | await this.bee.ready() | |
| 24 | ||
| 25 | // Initialize default lanes if none exist | |
| 26 | const lanes = await this.listLanes() | |
| 27 | if (lanes.length === 0) { | |
| 28 | for (let i = 0; i < DEFAULT_LANES.length; i++) { | |
| 29 | await this.bee.put(this._laneKey(DEFAULT_LANES[i]), { | |
| 30 | name: DEFAULT_LANES[i], | |
| 31 | order: i, | |
| 32 | created: Date.now() | |
| 33 | }) | |
| 34 | } | |
| 35 | } | |
| 36 | } | |
| 37 | ||
| 38 | async createCard (opts = {}) { | |
| 39 | const id = opts.id || this._genId() | |
| 40 | const now = Date.now() | |
| 41 | ||
| 42 | const card = { | |
| 43 | id, | |
| 44 | title: opts.title || 'Untitled', | |
| 45 | description: opts.description || '', | |
| 46 | lane: opts.lane || 'backlog', | |
| 47 | assignee: opts.assignee || '', | |
| 48 | tags: opts.tags || [], | |
| 49 | priority: opts.priority || 'normal', // low, normal, high, urgent | |
| 50 | created: now, | |
| 51 | updated: now | |
| 52 | } | |
| 53 | ||
| 54 | await this.bee.put(this._cardKey(id), card) | |
| 55 | return card | |
| 56 | } | |
| 57 | ||
| 58 | async getCard (id) { | |
| 59 | const node = await this.bee.get(this._cardKey(id)) | |
| 60 | return node ? node.value : null | |
| 61 | } | |
| 62 | ||
| 63 | async updateCard (id, updates = {}) { | |
| 64 | const existing = await this.getCard(id) | |
| 65 | if (!existing) throw new Error(`Card not found: ${id}`) | |
| 66 | ||
| 67 | const card = { | |
| 68 | ...existing, | |
| 69 | ...updates, | |
| 70 | id, // never overwrite id | |
| 71 | created: existing.created, // never overwrite created | |
| 72 | updated: Date.now() | |
| 73 | } | |
| 74 | ||
| 75 | await this.bee.put(this._cardKey(id), card) | |
| 76 | return card | |
| 77 | } | |
| 78 | ||
| 79 | async deleteCard (id) { | |
| 80 | const existing = await this.getCard(id) | |
| 81 | if (!existing) return false | |
| 82 | ||
| 83 | await this.bee.del(this._cardKey(id)) | |
| 84 | return true | |
| 85 | } | |
| 86 | ||
| 87 | async moveCard (id, lane) { | |
| 88 | return this.updateCard(id, { lane }) | |
| 89 | } | |
| 90 | ||
| 91 | async listCards (opts = {}) { | |
| 92 | const cards = [] | |
| 93 | ||
| 94 | for await (const node of this.bee.createReadStream({ | |
| 95 | gte: 'card/', | |
| 96 | lte: 'card/~' | |
| 97 | })) { | |
| 98 | const card = node.value | |
| 99 | if (!card) continue | |
| 100 | ||
| 101 | // Filter by lane | |
| 102 | if (opts.lane && card.lane !== opts.lane) continue | |
| 103 | ||
| 104 | // Filter by assignee | |
| 105 | if (opts.assignee && card.assignee !== opts.assignee) continue | |
| 106 | ||
| 107 | // Filter by tag | |
| 108 | if (opts.tag && !card.tags?.includes(opts.tag)) continue | |
| 109 | ||
| 110 | // Filter by priority | |
| 111 | if (opts.priority && card.priority !== opts.priority) continue | |
| 112 | ||
| 113 | cards.push(card) | |
| 114 | } | |
| 115 | ||
| 116 | // Sort by priority then created date | |
| 117 | const priorityOrder = { urgent: 0, high: 1, normal: 2, low: 3 } | |
| 118 | cards.sort((a, b) => { | |
| 119 | const pa = priorityOrder[a.priority] ?? 2 | |
| 120 | const pb = priorityOrder[b.priority] ?? 2 | |
| 121 | if (pa !== pb) return pa - pb | |
| 122 | return a.created - b.created | |
| 123 | }) | |
| 124 | ||
| 125 | return cards | |
| 126 | } | |
| 127 | ||
| 128 | async addLane (name, order) { | |
| 129 | const existing = await this.bee.get(this._laneKey(name)) | |
| 130 | if (existing) throw new Error(`Lane already exists: ${name}`) | |
| 131 | ||
| 132 | if (order === undefined) { | |
| 133 | const lanes = await this.listLanes() | |
| 134 | order = lanes.length | |
| 135 | } | |
| 136 | ||
| 137 | const lane = { name, order, created: Date.now() } | |
| 138 | await this.bee.put(this._laneKey(name), lane) | |
| 139 | return lane | |
| 140 | } | |
| 141 | ||
| 142 | async removeLane (name) { | |
| 143 | // Don't allow removing lanes that have cards | |
| 144 | const cards = await this.listCards({ lane: name }) | |
| 145 | if (cards.length > 0) { | |
| 146 | throw new Error(`Cannot remove lane "${name}": ${cards.length} cards still in it`) | |
| 147 | } | |
| 148 | ||
| 149 | await this.bee.del(this._laneKey(name)) | |
| 150 | return true | |
| 151 | } | |
| 152 | ||
| 153 | async listLanes () { | |
| 154 | const lanes = [] | |
| 155 | ||
| 156 | for await (const node of this.bee.createReadStream({ | |
| 157 | gte: 'lane/', | |
| 158 | lte: 'lane/~' | |
| 159 | })) { | |
| 160 | if (node.value) lanes.push(node.value) | |
| 161 | } | |
| 162 | ||
| 163 | lanes.sort((a, b) => (a.order ?? 0) - (b.order ?? 0)) | |
| 164 | return lanes | |
| 165 | } | |
| 166 | ||
| 167 | async search (query) { | |
| 168 | const q = query.toLowerCase() | |
| 169 | const cards = await this.listCards() | |
| 170 | ||
| 171 | return cards.filter(card => { | |
| 172 | if (card.title.toLowerCase().includes(q)) return true | |
| 173 | if (card.description.toLowerCase().includes(q)) return true | |
| 174 | if (card.tags?.some(t => t.toLowerCase().includes(q))) return true | |
| 175 | return false | |
| 176 | }) | |
| 177 | } | |
| 178 | ||
| 179 | async stats () { | |
| 180 | const cards = await this.listCards() | |
| 181 | const lanes = await this.listLanes() | |
| 182 | ||
| 183 | const byLane = {} | |
| 184 | for (const lane of lanes) { | |
| 185 | byLane[lane.name] = 0 | |
| 186 | } | |
| 187 | for (const card of cards) { | |
| 188 | byLane[card.lane] = (byLane[card.lane] || 0) + 1 | |
| 189 | } | |
| 190 | ||
| 191 | return { | |
| 192 | totalCards: cards.length, | |
| 193 | totalLanes: lanes.length, | |
| 194 | byLane | |
| 195 | } | |
| 196 | } | |
| 197 | } | |
| 198 | ||
| 199 | export { BoardStore, DEFAULT_LANES } |
| … | … | @@ -0,0 +1,85 @@ |
| 1 | import Corestore from 'corestore' | |
| 2 | import Hyperbee from 'hyperbee' | |
| 3 | import { BoardStore } from './board-store.js' | |
| 4 | ||
| 5 | class Board { | |
| 6 | constructor (storage, opts = {}) { | |
| 7 | this.store = new Corestore(storage) | |
| 8 | this.bee = new Hyperbee(this.store.get({ name: 'board' }), { | |
| 9 | keyEncoding: 'utf-8', | |
| 10 | valueEncoding: 'json' | |
| 11 | }) | |
| 12 | this.boardStore = new BoardStore(this.bee) | |
| 13 | this.name = opts.name || 'Board' | |
| 14 | } | |
| 15 | ||
| 16 | async ready () { | |
| 17 | await this.store.ready() | |
| 18 | await this.boardStore.ready() | |
| 19 | } | |
| 20 | ||
| 21 | // Card CRUD | |
| 22 | async createCard (opts) { | |
| 23 | return this.boardStore.createCard(opts) | |
| 24 | } | |
| 25 | ||
| 26 | async getCard (id) { | |
| 27 | return this.boardStore.getCard(id) | |
| 28 | } | |
| 29 | ||
| 30 | async updateCard (id, updates) { | |
| 31 | return this.boardStore.updateCard(id, updates) | |
| 32 | } | |
| 33 | ||
| 34 | async deleteCard (id) { | |
| 35 | return this.boardStore.deleteCard(id) | |
| 36 | } | |
| 37 | ||
| 38 | async moveCard (id, lane) { | |
| 39 | return this.boardStore.moveCard(id, lane) | |
| 40 | } | |
| 41 | ||
| 42 | // Lane management | |
| 43 | async addLane (name, order) { | |
| 44 | return this.boardStore.addLane(name, order) | |
| 45 | } | |
| 46 | ||
| 47 | async removeLane (name) { | |
| 48 | return this.boardStore.removeLane(name) | |
| 49 | } | |
| 50 | ||
| 51 | async listLanes () { | |
| 52 | return this.boardStore.listLanes() | |
| 53 | } | |
| 54 | ||
| 55 | // Queries | |
| 56 | async listCards (opts) { | |
| 57 | return this.boardStore.listCards(opts) | |
| 58 | } | |
| 59 | ||
| 60 | async search (query) { | |
| 61 | return this.boardStore.search(query) | |
| 62 | } | |
| 63 | ||
| 64 | async stats () { | |
| 65 | return this.boardStore.stats() | |
| 66 | } | |
| 67 | ||
| 68 | // Get board view — all lanes with their cards | |
| 69 | async view () { | |
| 70 | const lanes = await this.listLanes() | |
| 71 | const allCards = await this.listCards() | |
| 72 | ||
| 73 | return lanes.map(lane => ({ | |
| 74 | name: lane.name, | |
| 75 | order: lane.order, | |
| 76 | cards: allCards.filter(c => c.lane === lane.name) | |
| 77 | })) | |
| 78 | } | |
| 79 | ||
| 80 | async close () { | |
| 81 | await this.store.close() | |
| 82 | } | |
| 83 | } | |
| 84 | ||
| 85 | export { Board } |
| … | … | @@ -0,0 +1,1443 @@ |
| 1 | { | |
| 2 | "name": "hyper-board", | |
| 3 | "version": "1.0.0", | |
| 4 | "lockfileVersion": 3, | |
| 5 | "requires": true, | |
| 6 | "packages": { | |
| 7 | "": { | |
| 8 | "name": "hyper-board", | |
| 9 | "version": "1.0.0", | |
| 10 | "license": "MIT", | |
| 11 | "dependencies": { | |
| 12 | "b4a": "^1.0.0", | |
| 13 | "compact-encoding": "^2.0.0", | |
| 14 | "corestore": "^6.0.0", | |
| 15 | "hyperbee": "^2.0.0" | |
| 16 | }, | |
| 17 | "devDependencies": { | |
| 18 | "brittle": "^3.0.0" | |
| 19 | } | |
| 20 | }, | |
| 21 | "node_modules/@hyperswarm/secret-stream": { | |
| 22 | "version": "6.9.1", | |
| 23 | "resolved": "https://registry.npmjs.org/@hyperswarm/secret-stream/-/secret-stream-6.9.1.tgz", | |
| 24 | "integrity": "sha512-xb0S5y3YJwBakD77JOGBHlBxdp63mHClZoXBYoLv+9wH8e054ESKlmQptWqjJK5dv5VMUIVYOJB4MaOpB0JdGw==", | |
| 25 | "license": "Apache-2.0", | |
| 26 | "dependencies": { | |
| 27 | "b4a": "^1.1.0", | |
| 28 | "hypercore-crypto": "^3.3.1", | |
| 29 | "noise-curve-ed": "^2.0.1", | |
| 30 | "noise-handshake": "^4.0.0", | |
| 31 | "sodium-secretstream": "^1.1.0", | |
| 32 | "sodium-universal": "^5.0.0", | |
| 33 | "streamx": "^2.14.0", | |
| 34 | "timeout-refresh": "^2.0.0", | |
| 35 | "unslab": "^1.3.0" | |
| 36 | } | |
| 37 | }, | |
| 38 | "node_modules/@hyperswarm/secret-stream/node_modules/sodium-native": { | |
| 39 | "version": "5.0.10", | |
| 40 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 41 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 42 | "license": "MIT", | |
| 43 | "dependencies": { | |
| 44 | "require-addon": "^1.1.0", | |
| 45 | "which-runtime": "^1.2.1" | |
| 46 | }, | |
| 47 | "engines": { | |
| 48 | "bare": ">=1.16.0" | |
| 49 | } | |
| 50 | }, | |
| 51 | "node_modules/@hyperswarm/secret-stream/node_modules/sodium-universal": { | |
| 52 | "version": "5.0.1", | |
| 53 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 54 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 55 | "license": "MIT", | |
| 56 | "dependencies": { | |
| 57 | "sodium-native": "^5.0.1" | |
| 58 | }, | |
| 59 | "peerDependencies": { | |
| 60 | "sodium-javascript": "~0.8.0" | |
| 61 | }, | |
| 62 | "peerDependenciesMeta": { | |
| 63 | "sodium-javascript": { | |
| 64 | "optional": true | |
| 65 | } | |
| 66 | } | |
| 67 | }, | |
| 68 | "node_modules/@jridgewell/resolve-uri": { | |
| 69 | "version": "3.1.2", | |
| 70 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", | |
| 71 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", | |
| 72 | "dev": true, | |
| 73 | "license": "MIT", | |
| 74 | "engines": { | |
| 75 | "node": ">=6.0.0" | |
| 76 | } | |
| 77 | }, | |
| 78 | "node_modules/@jridgewell/sourcemap-codec": { | |
| 79 | "version": "1.5.5", | |
| 80 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", | |
| 81 | "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", | |
| 82 | "dev": true, | |
| 83 | "license": "MIT" | |
| 84 | }, | |
| 85 | "node_modules/@jridgewell/trace-mapping": { | |
| 86 | "version": "0.3.31", | |
| 87 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", | |
| 88 | "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", | |
| 89 | "dev": true, | |
| 90 | "license": "MIT", | |
| 91 | "dependencies": { | |
| 92 | "@jridgewell/resolve-uri": "^3.1.0", | |
| 93 | "@jridgewell/sourcemap-codec": "^1.4.14" | |
| 94 | } | |
| 95 | }, | |
| 96 | "node_modules/@types/istanbul-lib-coverage": { | |
| 97 | "version": "2.0.6", | |
| 98 | "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", | |
| 99 | "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", | |
| 100 | "dev": true, | |
| 101 | "license": "MIT" | |
| 102 | }, | |
| 103 | "node_modules/b4a": { | |
| 104 | "version": "1.7.4", | |
| 105 | "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.4.tgz", | |
| 106 | "integrity": "sha512-u20zJLDaSWpxaZ+zaAkEIB2dZZ1o+DF4T/MRbmsvGp9nletHOyiai19OzX1fF8xUBYsO1bPXxODvcd0978pnug==", | |
| 107 | "license": "Apache-2.0", | |
| 108 | "peerDependencies": { | |
| 109 | "react-native-b4a": "*" | |
| 110 | }, | |
| 111 | "peerDependenciesMeta": { | |
| 112 | "react-native-b4a": { | |
| 113 | "optional": true | |
| 114 | } | |
| 115 | } | |
| 116 | }, | |
| 117 | "node_modules/bare-addon-resolve": { | |
| 118 | "version": "1.10.0", | |
| 119 | "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.0.tgz", | |
| 120 | "integrity": "sha512-sSd0jieRJlDaODOzj0oe0RjFVC1QI0ZIjGIdPkbrTXsdVVtENg14c+lHHAhHwmWCZ2nQlMhy8jA3Y5LYPc/isA==", | |
| 121 | "license": "Apache-2.0", | |
| 122 | "dependencies": { | |
| 123 | "bare-module-resolve": "^1.10.0", | |
| 124 | "bare-semver": "^1.0.0" | |
| 125 | }, | |
| 126 | "peerDependencies": { | |
| 127 | "bare-url": "*" | |
| 128 | }, | |
| 129 | "peerDependenciesMeta": { | |
| 130 | "bare-url": { | |
| 131 | "optional": true | |
| 132 | } | |
| 133 | } | |
| 134 | }, | |
| 135 | "node_modules/bare-ansi-escapes": { | |
| 136 | "version": "2.2.3", | |
| 137 | "resolved": "https://registry.npmjs.org/bare-ansi-escapes/-/bare-ansi-escapes-2.2.3.tgz", | |
| 138 | "integrity": "sha512-02ES4/E2RbrtZSnHJ9LntBhYkLA6lPpSEeP8iqS3MccBIVhVBlEmruF1I7HZqx5Q8aiTeYfQVeqmrU9YO2yYoQ==", | |
| 139 | "dev": true, | |
| 140 | "license": "Apache-2.0", | |
| 141 | "dependencies": { | |
| 142 | "bare-stream": "^2.6.5" | |
| 143 | }, | |
| 144 | "peerDependencies": { | |
| 145 | "bare-buffer": "*" | |
| 146 | }, | |
| 147 | "peerDependenciesMeta": { | |
| 148 | "bare-buffer": { | |
| 149 | "optional": true | |
| 150 | } | |
| 151 | } | |
| 152 | }, | |
| 153 | "node_modules/bare-assert": { | |
| 154 | "version": "1.2.0", | |
| 155 | "resolved": "https://registry.npmjs.org/bare-assert/-/bare-assert-1.2.0.tgz", | |
| 156 | "integrity": "sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==", | |
| 157 | "dev": true, | |
| 158 | "license": "Apache-2.0", | |
| 159 | "dependencies": { | |
| 160 | "bare-inspect": "^3.1.2" | |
| 161 | } | |
| 162 | }, | |
| 163 | "node_modules/bare-bundle": { | |
| 164 | "version": "1.10.0", | |
| 165 | "resolved": "https://registry.npmjs.org/bare-bundle/-/bare-bundle-1.10.0.tgz", | |
| 166 | "integrity": "sha512-4LVlnJAHr00Hh6Vu6ZUJS38rcEtJT3b3vChXSsBsJ2mk1TN0lQ+gzd+Dw5L0aV7uqDZv84smuwW+O02X7PfDlw==", | |
| 167 | "dev": true, | |
| 168 | "license": "Apache-2.0", | |
| 169 | "peerDependencies": { | |
| 170 | "bare-buffer": "*", | |
| 171 | "bare-url": "*" | |
| 172 | }, | |
| 173 | "peerDependenciesMeta": { | |
| 174 | "bare-buffer": { | |
| 175 | "optional": true | |
| 176 | }, | |
| 177 | "bare-url": { | |
| 178 | "optional": true | |
| 179 | } | |
| 180 | } | |
| 181 | }, | |
| 182 | "node_modules/bare-cov": { | |
| 183 | "version": "1.2.1", | |
| 184 | "resolved": "https://registry.npmjs.org/bare-cov/-/bare-cov-1.2.1.tgz", | |
| 185 | "integrity": "sha512-SHNqb/WG9udUEj8m5qGSehoUQ9o0Xzv8nrf4HIdmG+KNU/XdhPLhTP5VPrHnSAMAXhAzEB4Q+2IYZ0x9qPzN1g==", | |
| 186 | "dev": true, | |
| 187 | "license": "Apache-2.0", | |
| 188 | "dependencies": { | |
| 189 | "bare-fs": "^4.1.2", | |
| 190 | "bare-inspector": "^4.0.1", | |
| 191 | "bare-path": "^3.0.0", | |
| 192 | "bare-process": "^4.2.1", | |
| 193 | "bare-url": "^2.1.5", | |
| 194 | "bare-v8-to-istanbul": "^1.0.2", | |
| 195 | "picomatch": "^4.0.2", | |
| 196 | "which-runtime": "^1.2.1" | |
| 197 | } | |
| 198 | }, | |
| 199 | "node_modules/bare-crypto": { | |
| 200 | "version": "1.13.0", | |
| 201 | "resolved": "https://registry.npmjs.org/bare-crypto/-/bare-crypto-1.13.0.tgz", | |
| 202 | "integrity": "sha512-RQl13yD+YTACWUZHMMck0C6LNjXgGgRyhVC557ag0xIrkxXf4IQwpnc8WOB58f4k5kSzKhIjy05oW3HUBrFpSQ==", | |
| 203 | "dev": true, | |
| 204 | "license": "Apache-2.0", | |
| 205 | "dependencies": { | |
| 206 | "bare-stream": "^2.6.3" | |
| 207 | }, | |
| 208 | "peerDependencies": { | |
| 209 | "bare-buffer": "*" | |
| 210 | }, | |
| 211 | "peerDependenciesMeta": { | |
| 212 | "bare-buffer": { | |
| 213 | "optional": true | |
| 214 | } | |
| 215 | } | |
| 216 | }, | |
| 217 | "node_modules/bare-debug-log": { | |
| 218 | "version": "2.0.0", | |
| 219 | "resolved": "https://registry.npmjs.org/bare-debug-log/-/bare-debug-log-2.0.0.tgz", | |
| 220 | "integrity": "sha512-Vi42PkMQsNV9PUpx2Gl1hikshx5O9FzMJ6o9Nnopseg7qLBBK7Nl31d0RHcfwLEAfmcPApytpc0ZFfq68u22FQ==", | |
| 221 | "dev": true, | |
| 222 | "license": "Apache-2.0", | |
| 223 | "dependencies": { | |
| 224 | "bare-os": "^3.0.1" | |
| 225 | } | |
| 226 | }, | |
| 227 | "node_modules/bare-dns": { | |
| 228 | "version": "2.1.4", | |
| 229 | "resolved": "https://registry.npmjs.org/bare-dns/-/bare-dns-2.1.4.tgz", | |
| 230 | "integrity": "sha512-abwjHmpWqSRNB7V5615QxPH92L71AVzFm/kKTs8VYiNTAi2xVdonpv0BjJ0hwXLwomoW+xsSOPjW6PZPO14asg==", | |
| 231 | "dev": true, | |
| 232 | "license": "Apache-2.0", | |
| 233 | "engines": { | |
| 234 | "bare": ">=1.7.0" | |
| 235 | } | |
| 236 | }, | |
| 237 | "node_modules/bare-encoding": { | |
| 238 | "version": "1.0.3", | |
| 239 | "resolved": "https://registry.npmjs.org/bare-encoding/-/bare-encoding-1.0.3.tgz", | |
| 240 | "integrity": "sha512-Kqf+t/azs13lUeyK4Tb7ha4wdLRXKWCXQ8w1rVmt7KtoPCPdHD/Xwt7LBIsCSwwGglrcmblo5VOLa5avkJqULA==", | |
| 241 | "dev": true, | |
| 242 | "license": "Apache-2.0", | |
| 243 | "peerDependencies": { | |
| 244 | "bare-buffer": "*" | |
| 245 | }, | |
| 246 | "peerDependenciesMeta": { | |
| 247 | "bare-buffer": { | |
| 248 | "optional": true | |
| 249 | } | |
| 250 | } | |
| 251 | }, | |
| 252 | "node_modules/bare-env": { | |
| 253 | "version": "3.0.0", | |
| 254 | "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.0.tgz", | |
| 255 | "integrity": "sha512-0u964P5ZLAxTi+lW4Kjp7YRJQ5gZr9ycYOtjLxsSrupgMz3sn5Z9n4SH/JIifHwvadsf1brA2JAjP+9IOWwTiw==", | |
| 256 | "dev": true, | |
| 257 | "license": "Apache-2.0", | |
| 258 | "dependencies": { | |
| 259 | "bare-os": "^3.0.1" | |
| 260 | } | |
| 261 | }, | |
| 262 | "node_modules/bare-events": { | |
| 263 | "version": "2.8.2", | |
| 264 | "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", | |
| 265 | "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", | |
| 266 | "license": "Apache-2.0", | |
| 267 | "peerDependencies": { | |
| 268 | "bare-abort-controller": "*" | |
| 269 | }, | |
| 270 | "peerDependenciesMeta": { | |
| 271 | "bare-abort-controller": { | |
| 272 | "optional": true | |
| 273 | } | |
| 274 | } | |
| 275 | }, | |
| 276 | "node_modules/bare-format": { | |
| 277 | "version": "1.0.1", | |
| 278 | "resolved": "https://registry.npmjs.org/bare-format/-/bare-format-1.0.1.tgz", | |
| 279 | "integrity": "sha512-1oS+LZrWK6tnYnvNSHDGljc2MPunRxwhpFriuCgzNF+oklrnwmBKD91tS0yt+jpl2j3UgcSDzBIMiVTvLs9A8w==", | |
| 280 | "dev": true, | |
| 281 | "license": "Apache-2.0", | |
| 282 | "dependencies": { | |
| 283 | "bare-inspect": "^3.0.0" | |
| 284 | } | |
| 285 | }, | |
| 286 | "node_modules/bare-fs": { | |
| 287 | "version": "4.5.4", | |
| 288 | "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.4.tgz", | |
| 289 | "integrity": "sha512-POK4oplfA7P7gqvetNmCs4CNtm9fNsx+IAh7jH7GgU0OJdge2rso0R20TNWVq6VoWcCvsTdlNDaleLHGaKx8CA==", | |
| 290 | "license": "Apache-2.0", | |
| 291 | "dependencies": { | |
| 292 | "bare-events": "^2.5.4", | |
| 293 | "bare-path": "^3.0.0", | |
| 294 | "bare-stream": "^2.6.4", | |
| 295 | "bare-url": "^2.2.2", | |
| 296 | "fast-fifo": "^1.3.2" | |
| 297 | }, | |
| 298 | "engines": { | |
| 299 | "bare": ">=1.16.0" | |
| 300 | }, | |
| 301 | "peerDependencies": { | |
| 302 | "bare-buffer": "*" | |
| 303 | }, | |
| 304 | "peerDependenciesMeta": { | |
| 305 | "bare-buffer": { | |
| 306 | "optional": true | |
| 307 | } | |
| 308 | } | |
| 309 | }, | |
| 310 | "node_modules/bare-hrtime": { | |
| 311 | "version": "2.1.1", | |
| 312 | "resolved": "https://registry.npmjs.org/bare-hrtime/-/bare-hrtime-2.1.1.tgz", | |
| 313 | "integrity": "sha512-VMb3tHo05gsnbu3OXTmkDiwTjMlOsbQmKoysKqKEyR09m77TuDrYFbj3Q5GGk10dAKsUHrnXmwCaeJqzVpB5ZA==", | |
| 314 | "dev": true, | |
| 315 | "license": "Apache-2.0" | |
| 316 | }, | |
| 317 | "node_modules/bare-http-parser": { | |
| 318 | "version": "1.0.1", | |
| 319 | "resolved": "https://registry.npmjs.org/bare-http-parser/-/bare-http-parser-1.0.1.tgz", | |
| 320 | "integrity": "sha512-A3LTDTcELcmNJ3g5liIaS038v/BQxOhA9cjhBESn7eoV7QCuMoIRBKLDadDe08flxyLbxI2f+1l2MZ/5+HnKPA==", | |
| 321 | "dev": true, | |
| 322 | "license": "Apache-2.0" | |
| 323 | }, | |
| 324 | "node_modules/bare-http1": { | |
| 325 | "version": "4.2.3", | |
| 326 | "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.2.3.tgz", | |
| 327 | "integrity": "sha512-zyvHF3xzvBJNpuw0QydE9etTn2AyC/TRVDWTo8Ed3LQEEGaFOkv6KHhaJPlf4eWEeTekk41Fowie+fTsXoNxkA==", | |
| 328 | "dev": true, | |
| 329 | "license": "Apache-2.0", | |
| 330 | "dependencies": { | |
| 331 | "bare-events": "^2.6.0", | |
| 332 | "bare-http-parser": "^1.0.0", | |
| 333 | "bare-stream": "^2.3.0", | |
| 334 | "bare-tcp": "^2.2.0" | |
| 335 | }, | |
| 336 | "peerDependencies": { | |
| 337 | "bare-buffer": "*", | |
| 338 | "bare-url": "*" | |
| 339 | }, | |
| 340 | "peerDependenciesMeta": { | |
| 341 | "bare-buffer": { | |
| 342 | "optional": true | |
| 343 | }, | |
| 344 | "bare-url": { | |
| 345 | "optional": true | |
| 346 | } | |
| 347 | } | |
| 348 | }, | |
| 349 | "node_modules/bare-https": { | |
| 350 | "version": "2.1.2", | |
| 351 | "resolved": "https://registry.npmjs.org/bare-https/-/bare-https-2.1.2.tgz", | |
| 352 | "integrity": "sha512-Q+TTydUDsuKQJvh8dX2dvOXCR9fM3xR5TBmKaFrs5p7Lj7XbKX7v4vIUJ36H0SXg2xCOQxXKIbjwrLg5tfJNYg==", | |
| 353 | "dev": true, | |
| 354 | "license": "Apache-2.0", | |
| 355 | "dependencies": { | |
| 356 | "bare-http1": "^4.0.0", | |
| 357 | "bare-tcp": "^2.2.0", | |
| 358 | "bare-tls": "^2.0.0" | |
| 359 | } | |
| 360 | }, | |
| 361 | "node_modules/bare-inspect": { | |
| 362 | "version": "3.1.4", | |
| 363 | "resolved": "https://registry.npmjs.org/bare-inspect/-/bare-inspect-3.1.4.tgz", | |
| 364 | "integrity": "sha512-jfW5KRA84o3REpI6Vr4nbvMn+hqVAw8GU1mMdRwUsY5yJovQamxYeKGVKGqdzs+8ZbG4jRzGUXP/3Ji/DnqfPg==", | |
| 365 | "dev": true, | |
| 366 | "license": "Apache-2.0", | |
| 367 | "dependencies": { | |
| 368 | "bare-ansi-escapes": "^2.1.0", | |
| 369 | "bare-type": "^1.0.0" | |
| 370 | }, | |
| 371 | "engines": { | |
| 372 | "bare": ">=1.18.0" | |
| 373 | } | |
| 374 | }, | |
| 375 | "node_modules/bare-inspector": { | |
| 376 | "version": "4.0.3", | |
| 377 | "resolved": "https://registry.npmjs.org/bare-inspector/-/bare-inspector-4.0.3.tgz", | |
| 378 | "integrity": "sha512-yYk6+7tTVKJKHOja5+QBn989ETv8oVsmwoH0nCM+QYC6w5MC03GC4aQNK+lipAZSz1EaAj42WZ8Wewjpy0L9TA==", | |
| 379 | "dev": true, | |
| 380 | "license": "Apache-2.0", | |
| 381 | "dependencies": { | |
| 382 | "bare-events": "^2.1.0", | |
| 383 | "bare-http1": "^4.0.0", | |
| 384 | "bare-stream": "^2.0.0", | |
| 385 | "bare-url": "^2.0.0", | |
| 386 | "bare-ws": "^2.0.0" | |
| 387 | }, | |
| 388 | "engines": { | |
| 389 | "bare": ">=1.2.0" | |
| 390 | }, | |
| 391 | "peerDependencies": { | |
| 392 | "bare-tcp": "*" | |
| 393 | }, | |
| 394 | "peerDependenciesMeta": { | |
| 395 | "bare-tcp": { | |
| 396 | "optional": true | |
| 397 | } | |
| 398 | } | |
| 399 | }, | |
| 400 | "node_modules/bare-module": { | |
| 401 | "version": "6.1.3", | |
| 402 | "resolved": "https://registry.npmjs.org/bare-module/-/bare-module-6.1.3.tgz", | |
| 403 | "integrity": "sha512-5XWsVHsvtWMH4tK4DQWgpNTV0t/sg3ZrAaQLIxrwjrS5+u8Q9vEgc/zQ4QaDPWDse/y/5h+d+YG1Q0JfSMt0zA==", | |
| 404 | "dev": true, | |
| 405 | "license": "Apache-2.0", | |
| 406 | "dependencies": { | |
| 407 | "bare-bundle": "^1.3.0", | |
| 408 | "bare-module-lexer": "^1.0.0", | |
| 409 | "bare-module-resolve": "^1.8.0", | |
| 410 | "bare-path": "^3.0.0", | |
| 411 | "bare-url": "^2.0.1" | |
| 412 | }, | |
| 413 | "engines": { | |
| 414 | "bare": ">=1.23.0" | |
| 415 | }, | |
| 416 | "peerDependencies": { | |
| 417 | "bare-buffer": "*" | |
| 418 | }, | |
| 419 | "peerDependenciesMeta": { | |
| 420 | "bare-buffer": { | |
| 421 | "optional": true | |
| 422 | } | |
| 423 | } | |
| 424 | }, | |
| 425 | "node_modules/bare-module-lexer": { | |
| 426 | "version": "1.4.7", | |
| 427 | "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.4.7.tgz", | |
| 428 | "integrity": "sha512-0klU4eMsjh/wcxi8FdHmNom2j2F4kmkXOhyJFL9qTaSFp2lE3m6BtbKgMHY8R5miqC9r8/IfA8wzXnC5Os14WA==", | |
| 429 | "dev": true, | |
| 430 | "license": "Apache-2.0", | |
| 431 | "dependencies": { | |
| 432 | "require-addon": "^1.0.2" | |
| 433 | }, | |
| 434 | "peerDependencies": { | |
| 435 | "bare-buffer": "*" | |
| 436 | }, | |
| 437 | "peerDependenciesMeta": { | |
| 438 | "bare-buffer": { | |
| 439 | "optional": true | |
| 440 | } | |
| 441 | } | |
| 442 | }, | |
| 443 | "node_modules/bare-module-resolve": { | |
| 444 | "version": "1.12.1", | |
| 445 | "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.1.tgz", | |
| 446 | "integrity": "sha512-hbmAPyFpEq8FoZMd5sFO3u6MC5feluWoGE8YKlA8fCrl6mNtx68Wjg4DTiDJcqRJaovTvOYKfYngoBUnbaT7eg==", | |
| 447 | "license": "Apache-2.0", | |
| 448 | "dependencies": { | |
| 449 | "bare-semver": "^1.0.0" | |
| 450 | }, | |
| 451 | "peerDependencies": { | |
| 452 | "bare-url": "*" | |
| 453 | }, | |
| 454 | "peerDependenciesMeta": { | |
| 455 | "bare-url": { | |
| 456 | "optional": true | |
| 457 | } | |
| 458 | } | |
| 459 | }, | |
| 460 | "node_modules/bare-net": { | |
| 461 | "version": "2.2.0", | |
| 462 | "resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.2.0.tgz", | |
| 463 | "integrity": "sha512-UF7cAbHsGE+H6uEqWF5IULBow1x58chZz4g3ALgHtv7wZsFcCbRDt0JKWEumf5Oma3QWS1Q6aLi0Rpll8RElMg==", | |
| 464 | "dev": true, | |
| 465 | "license": "Apache-2.0", | |
| 466 | "dependencies": { | |
| 467 | "bare-events": "^2.2.2", | |
| 468 | "bare-pipe": "^4.0.0", | |
| 469 | "bare-stream": "^2.0.0", | |
| 470 | "bare-tcp": "^2.0.0" | |
| 471 | } | |
| 472 | }, | |
| 473 | "node_modules/bare-os": { | |
| 474 | "version": "3.6.2", | |
| 475 | "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", | |
| 476 | "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", | |
| 477 | "license": "Apache-2.0", | |
| 478 | "engines": { | |
| 479 | "bare": ">=1.14.0" | |
| 480 | } | |
| 481 | }, | |
| 482 | "node_modules/bare-path": { | |
| 483 | "version": "3.0.0", | |
| 484 | "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", | |
| 485 | "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", | |
| 486 | "license": "Apache-2.0", | |
| 487 | "dependencies": { | |
| 488 | "bare-os": "^3.0.1" | |
| 489 | } | |
| 490 | }, | |
| 491 | "node_modules/bare-pipe": { | |
| 492 | "version": "4.1.3", | |
| 493 | "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.1.3.tgz", | |
| 494 | "integrity": "sha512-DqQsx93rAzre6yJ9T6l/Vgh+X+bntkVMB1X5ggtXjXtqtMmF2Y2RVlCzxxy/09R6yeR9FSWBEUIaMYJL1/5VDA==", | |
| 495 | "dev": true, | |
| 496 | "license": "Apache-2.0", | |
| 497 | "dependencies": { | |
| 498 | "bare-events": "^2.0.0", | |
| 499 | "bare-stream": "^2.0.0" | |
| 500 | }, | |
| 501 | "engines": { | |
| 502 | "bare": ">=1.16.0" | |
| 503 | } | |
| 504 | }, | |
| 505 | "node_modules/bare-process": { | |
| 506 | "version": "4.2.2", | |
| 507 | "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.2.2.tgz", | |
| 508 | "integrity": "sha512-ikzEw+HGLB+2lS/WLVEsqi8BXBwzleG3n7DYI0v6/YNklvNabOIGlLd1dof+7Jw5ob4jsBRPtyflweVahY7rFA==", | |
| 509 | "dev": true, | |
| 510 | "license": "Apache-2.0", | |
| 511 | "dependencies": { | |
| 512 | "bare-env": "^3.0.0", | |
| 513 | "bare-events": "^2.3.1", | |
| 514 | "bare-hrtime": "^2.0.0", | |
| 515 | "bare-os": "^3.5.0", | |
| 516 | "bare-pipe": "^4.0.0", | |
| 517 | "bare-signals": "^4.0.0", | |
| 518 | "bare-tty": "^5.0.0" | |
| 519 | } | |
| 520 | }, | |
| 521 | "node_modules/bare-semver": { | |
| 522 | "version": "1.0.2", | |
| 523 | "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.0.2.tgz", | |
| 524 | "integrity": "sha512-ESVaN2nzWhcI5tf3Zzcq9aqCZ676VWzqw07eEZ0qxAcEOAFYBa0pWq8sK34OQeHLY3JsfKXZS9mDyzyxGjeLzA==", | |
| 525 | "license": "Apache-2.0" | |
| 526 | }, | |
| 527 | "node_modules/bare-signals": { | |
| 528 | "version": "4.2.0", | |
| 529 | "resolved": "https://registry.npmjs.org/bare-signals/-/bare-signals-4.2.0.tgz", | |
| 530 | "integrity": "sha512-fNHMOdQIlYuTvMB3Oh9Apk99hLKn351+Ir8vz+khiPTcOqIyGG4uWWjdLTzxWdYGsA0eT+We3y0K74hjj2nq7A==", | |
| 531 | "dev": true, | |
| 532 | "license": "Apache-2.0", | |
| 533 | "dependencies": { | |
| 534 | "bare-events": "^2.5.3", | |
| 535 | "bare-os": "^3.3.1" | |
| 536 | }, | |
| 537 | "engines": { | |
| 538 | "bare": ">=1.7.0" | |
| 539 | } | |
| 540 | }, | |
| 541 | "node_modules/bare-stream": { | |
| 542 | "version": "2.7.0", | |
| 543 | "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", | |
| 544 | "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", | |
| 545 | "license": "Apache-2.0", | |
| 546 | "dependencies": { | |
| 547 | "streamx": "^2.21.0" | |
| 548 | }, | |
| 549 | "peerDependencies": { | |
| 550 | "bare-buffer": "*", | |
| 551 | "bare-events": "*" | |
| 552 | }, | |
| 553 | "peerDependenciesMeta": { | |
| 554 | "bare-buffer": { | |
| 555 | "optional": true | |
| 556 | }, | |
| 557 | "bare-events": { | |
| 558 | "optional": true | |
| 559 | } | |
| 560 | } | |
| 561 | }, | |
| 562 | "node_modules/bare-subprocess": { | |
| 563 | "version": "5.2.2", | |
| 564 | "resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.2.tgz", | |
| 565 | "integrity": "sha512-L6oXgQ1aWs25RtG5Ky0bDD06p3RAcVVrDDMWb1DfXpHtyEWxamcyIWUbSykxMTWrpLlmSj6ytbb6yoKehGFfmw==", | |
| 566 | "dev": true, | |
| 567 | "license": "Apache-2.0", | |
| 568 | "dependencies": { | |
| 569 | "bare-env": "^3.0.0", | |
| 570 | "bare-events": "^2.5.4", | |
| 571 | "bare-os": "^3.0.1", | |
| 572 | "bare-pipe": "^4.0.0", | |
| 573 | "bare-url": "^2.2.2" | |
| 574 | }, | |
| 575 | "engines": { | |
| 576 | "bare": ">=1.7.0" | |
| 577 | }, | |
| 578 | "peerDependencies": { | |
| 579 | "bare-buffer": "*" | |
| 580 | }, | |
| 581 | "peerDependenciesMeta": { | |
| 582 | "bare-buffer": { | |
| 583 | "optional": true | |
| 584 | } | |
| 585 | } | |
| 586 | }, | |
| 587 | "node_modules/bare-tcp": { | |
| 588 | "version": "2.2.4", | |
| 589 | "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.2.4.tgz", | |
| 590 | "integrity": "sha512-vGjfMkyhS1lxp+Y4+gXRDR1ijZMK2cKx0RBwtYKUuZbD90ARCoM6bAiTa1HBztumCIC+NXb8eXdMFXyuYwlPyw==", | |
| 591 | "dev": true, | |
| 592 | "license": "Apache-2.0", | |
| 593 | "dependencies": { | |
| 594 | "bare-dns": "^2.0.4", | |
| 595 | "bare-events": "^2.5.4", | |
| 596 | "bare-stream": "^2.6.4" | |
| 597 | }, | |
| 598 | "engines": { | |
| 599 | "bare": ">=1.16.0" | |
| 600 | } | |
| 601 | }, | |
| 602 | "node_modules/bare-tls": { | |
| 603 | "version": "2.1.7", | |
| 604 | "resolved": "https://registry.npmjs.org/bare-tls/-/bare-tls-2.1.7.tgz", | |
| 605 | "integrity": "sha512-h6wcNXQdBeTX7fed9tjPp0/9cA/QfcBTv3ItgjnbUk4rWAU8bEFalZCZnUDdCK/t9zrNfJ+yvcPx4D/1Y6biyA==", | |
| 606 | "dev": true, | |
| 607 | "license": "Apache-2.0", | |
| 608 | "dependencies": { | |
| 609 | "bare-net": "^2.0.1", | |
| 610 | "bare-stream": "^2.6.4" | |
| 611 | }, | |
| 612 | "engines": { | |
| 613 | "bare": ">=1.7.0" | |
| 614 | } | |
| 615 | }, | |
| 616 | "node_modules/bare-tty": { | |
| 617 | "version": "5.0.3", | |
| 618 | "resolved": "https://registry.npmjs.org/bare-tty/-/bare-tty-5.0.3.tgz", | |
| 619 | "integrity": "sha512-jW24RBWRZOMHuSEWC9mh8wUKO5WUNl0UWUTNPn3yZ8qkOqwa8vaV2dR3a+BvblONPk7V35wuvK9P8508+judAg==", | |
| 620 | "dev": true, | |
| 621 | "license": "Apache-2.0", | |
| 622 | "dependencies": { | |
| 623 | "bare-events": "^2.2.0", | |
| 624 | "bare-signals": "^4.0.0", | |
| 625 | "bare-stream": "^2.0.0" | |
| 626 | }, | |
| 627 | "engines": { | |
| 628 | "bare": ">=1.16.0" | |
| 629 | } | |
| 630 | }, | |
| 631 | "node_modules/bare-type": { | |
| 632 | "version": "1.1.0", | |
| 633 | "resolved": "https://registry.npmjs.org/bare-type/-/bare-type-1.1.0.tgz", | |
| 634 | "integrity": "sha512-LdtnnEEYldOc87Dr4GpsKnStStZk3zfgoEMXy8yvEZkXrcCv9RtYDrUYWFsBQHtaB0s1EUWmcvS6XmEZYIj3Bw==", | |
| 635 | "dev": true, | |
| 636 | "license": "Apache-2.0", | |
| 637 | "engines": { | |
| 638 | "bare": ">=1.2.0" | |
| 639 | } | |
| 640 | }, | |
| 641 | "node_modules/bare-url": { | |
| 642 | "version": "2.3.2", | |
| 643 | "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", | |
| 644 | "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", | |
| 645 | "license": "Apache-2.0", | |
| 646 | "peer": true, | |
| 647 | "dependencies": { | |
| 648 | "bare-path": "^3.0.0" | |
| 649 | } | |
| 650 | }, | |
| 651 | "node_modules/bare-utils": { | |
| 652 | "version": "1.5.1", | |
| 653 | "resolved": "https://registry.npmjs.org/bare-utils/-/bare-utils-1.5.1.tgz", | |
| 654 | "integrity": "sha512-mxCkFvmDU3mlD/mb+pT64kKXOsx2KMsWLQbngN1LB+NOXfhfnRnyvpy3VZc6m7gzQxe57Bsi+aTCBqA4/S3elQ==", | |
| 655 | "dev": true, | |
| 656 | "license": "Apache-2.0", | |
| 657 | "dependencies": { | |
| 658 | "bare-debug-log": "^2.0.0", | |
| 659 | "bare-encoding": "^1.0.0", | |
| 660 | "bare-format": "^1.0.0", | |
| 661 | "bare-inspect": "^3.0.0", | |
| 662 | "bare-type": "^1.0.6" | |
| 663 | } | |
| 664 | }, | |
| 665 | "node_modules/bare-v8-to-istanbul": { | |
| 666 | "version": "1.0.2", | |
| 667 | "resolved": "https://registry.npmjs.org/bare-v8-to-istanbul/-/bare-v8-to-istanbul-1.0.2.tgz", | |
| 668 | "integrity": "sha512-CM0wPgyQtXTQ7h4q8NjbTZ/FH5he0Jkiwgpq0lFZZA5tkVZzmQ/9ul4R7E+cNlGZJIZlQZ9v4CwbgVaFG3zOAA==", | |
| 669 | "dev": true, | |
| 670 | "license": "Apache-2.0", | |
| 671 | "dependencies": { | |
| 672 | "bare-assert": "^1.0.2", | |
| 673 | "bare-fs": "^4.1.2", | |
| 674 | "bare-module": "^6.1.2", | |
| 675 | "bare-path": "^3.0.0", | |
| 676 | "bare-process": "^4.2.0", | |
| 677 | "bare-url": "^2.1.5", | |
| 678 | "bare-utils": "^1.2.0", | |
| 679 | "v8-to-istanbul": "^9.3.0", | |
| 680 | "which-runtime": "^1.2.1" | |
| 681 | } | |
| 682 | }, | |
| 683 | "node_modules/bare-ws": { | |
| 684 | "version": "2.0.4", | |
| 685 | "resolved": "https://registry.npmjs.org/bare-ws/-/bare-ws-2.0.4.tgz", | |
| 686 | "integrity": "sha512-SQMXzBYna9dRj57Dz1/ag+VWHCRXbfCjMHgyfM2F2lhkVLzMjnVSZP72aVeFWPFqe494Rd70Kzhe2JElGwFlJQ==", | |
| 687 | "dev": true, | |
| 688 | "license": "Apache-2.0", | |
| 689 | "dependencies": { | |
| 690 | "bare-crypto": "^1.2.0", | |
| 691 | "bare-events": "^2.3.1", | |
| 692 | "bare-http1": "^4.0.0", | |
| 693 | "bare-https": "^2.0.0", | |
| 694 | "bare-stream": "^2.1.2" | |
| 695 | }, | |
| 696 | "peerDependencies": { | |
| 697 | "bare-buffer": "*", | |
| 698 | "bare-url": "*" | |
| 699 | }, | |
| 700 | "peerDependenciesMeta": { | |
| 701 | "bare-buffer": { | |
| 702 | "optional": true | |
| 703 | }, | |
| 704 | "bare-url": { | |
| 705 | "optional": true | |
| 706 | } | |
| 707 | } | |
| 708 | }, | |
| 709 | "node_modules/big-sparse-array": { | |
| 710 | "version": "1.0.3", | |
| 711 | "resolved": "https://registry.npmjs.org/big-sparse-array/-/big-sparse-array-1.0.3.tgz", | |
| 712 | "integrity": "sha512-6RjV/3mSZORlMdpUaQ6rUSpG637cZm0//E54YYGtQg1c1O+AbZP8UTdJ/TchsDZcTVLmyWZcseBfp2HBeXUXOQ==", | |
| 713 | "license": "MIT" | |
| 714 | }, | |
| 715 | "node_modules/brittle": { | |
| 716 | "version": "3.19.1", | |
| 717 | "resolved": "https://registry.npmjs.org/brittle/-/brittle-3.19.1.tgz", | |
| 718 | "integrity": "sha512-4Ted1Mt9o9B6oIA6ImJJCtB/Fv++ZO3IDNQgRcHOXWSYGRUidgxchaGrUBaRn+4mlneXrgInPkCPzi0jO8qKbA==", | |
| 719 | "dev": true, | |
| 720 | "license": "Apache-2.0", | |
| 721 | "dependencies": { | |
| 722 | "b4a": "^1.6.0", | |
| 723 | "bare-assert": "^1.0.2", | |
| 724 | "bare-cov": "^1.1.0", | |
| 725 | "bare-fs": "^4.1.6", | |
| 726 | "bare-os": "^3.6.1", | |
| 727 | "bare-path": "^3.0.0", | |
| 728 | "bare-process": "^4.2.1", | |
| 729 | "bare-subprocess": "^5.0.0", | |
| 730 | "bare-url": "^2.1.6", | |
| 731 | "error-stack-parser": "^2.1.4", | |
| 732 | "globbie": "^1.0.2", | |
| 733 | "paparam": "^1.6.2", | |
| 734 | "same-object": "^1.0.2", | |
| 735 | "test-tmp": "^1.4.0", | |
| 736 | "tmatch": "^5.0.0" | |
| 737 | }, | |
| 738 | "bin": { | |
| 739 | "brittle": "bin/node.js", | |
| 740 | "brittle-bare": "bin/bare.js", | |
| 741 | "brittle-node": "bin/node.js", | |
| 742 | "brittle-pear": "bin/pear.js" | |
| 743 | } | |
| 744 | }, | |
| 745 | "node_modules/codecs": { | |
| 746 | "version": "3.1.0", | |
| 747 | "resolved": "https://registry.npmjs.org/codecs/-/codecs-3.1.0.tgz", | |
| 748 | "integrity": "sha512-Dqx8NwvBvnMeuPQdVKy/XEF71igjR5apxBvCGeV0pP1tXadOiaLvDTXt7xh+/5wI1ASB195mXQGJbw3Ml4YDWQ==", | |
| 749 | "license": "MIT", | |
| 750 | "dependencies": { | |
| 751 | "b4a": "^1.6.3" | |
| 752 | } | |
| 753 | }, | |
| 754 | "node_modules/compact-encoding": { | |
| 755 | "version": "2.18.0", | |
| 756 | "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-2.18.0.tgz", | |
| 757 | "integrity": "sha512-goACAOlhMI2xo5jGOMUDfOLnGdRE1jGfyZ+zie8N5114nHrbPIqf6GLUtzbLof6DSyrERlYRm3EcBplte5LcQw==", | |
| 758 | "license": "Apache-2.0", | |
| 759 | "dependencies": { | |
| 760 | "b4a": "^1.3.0" | |
| 761 | } | |
| 762 | }, | |
| 763 | "node_modules/convert-source-map": { | |
| 764 | "version": "2.0.0", | |
| 765 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", | |
| 766 | "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", | |
| 767 | "dev": true, | |
| 768 | "license": "MIT" | |
| 769 | }, | |
| 770 | "node_modules/corestore": { | |
| 771 | "version": "6.18.4", | |
| 772 | "resolved": "https://registry.npmjs.org/corestore/-/corestore-6.18.4.tgz", | |
| 773 | "integrity": "sha512-KL1cOUyj3DFRNzeVRekGPRiGN4sIfGo7BsncSJQQBT3Rh2y1nQ32MOR9X5DlOMlWIT4txwqBzhzam6+JXtwFVw==", | |
| 774 | "license": "MIT", | |
| 775 | "dependencies": { | |
| 776 | "b4a": "^1.6.4", | |
| 777 | "hypercore": "^10.37.10", | |
| 778 | "hypercore-crypto": "^3.4.0", | |
| 779 | "hypercore-id-encoding": "^1.2.0", | |
| 780 | "read-write-mutexify": "^2.1.0", | |
| 781 | "ready-resource": "^1.0.0", | |
| 782 | "safety-catch": "^1.0.1", | |
| 783 | "sodium-universal": "^4.0.0", | |
| 784 | "xache": "^1.1.0" | |
| 785 | } | |
| 786 | }, | |
| 787 | "node_modules/crc-native": { | |
| 788 | "version": "1.1.8", | |
| 789 | "resolved": "https://registry.npmjs.org/crc-native/-/crc-native-1.1.8.tgz", | |
| 790 | "integrity": "sha512-R/RYbD8lhG5M1k0iLX49tJdwHM0ipevJPpZw2Wk7b4EShyvx2P0uaq79xyRpcknjiVGiUfjFA8Gyy0mOlhABvw==", | |
| 791 | "license": "Apache-2.0", | |
| 792 | "optional": true, | |
| 793 | "dependencies": { | |
| 794 | "require-addon": "^1.1.0" | |
| 795 | } | |
| 796 | }, | |
| 797 | "node_modules/crc-universal": { | |
| 798 | "version": "1.0.4", | |
| 799 | "resolved": "https://registry.npmjs.org/crc-universal/-/crc-universal-1.0.4.tgz", | |
| 800 | "integrity": "sha512-CE9xWEI6Gd5V0Bdmj5NoWH3d7+EIe4zUpy1sv6uvKYznzsDP1vhiWsTBmL6q9IrH2P6RRshp+8AkhR4CMtY5Hg==", | |
| 801 | "license": "ISC", | |
| 802 | "optionalDependencies": { | |
| 803 | "crc-native": "^1.0.3" | |
| 804 | } | |
| 805 | }, | |
| 806 | "node_modules/debounceify": { | |
| 807 | "version": "1.1.0", | |
| 808 | "resolved": "https://registry.npmjs.org/debounceify/-/debounceify-1.1.0.tgz", | |
| 809 | "integrity": "sha512-eKuHDVfJVg+u/0nPy8P+fhnLgbyuTgVxuCRrS/R7EpDSMMkBDgSes41MJtSAY1F1hcqfHz3Zy/qpqHHIp/EhdA==", | |
| 810 | "license": "MIT" | |
| 811 | }, | |
| 812 | "node_modules/error-stack-parser": { | |
| 813 | "version": "2.1.4", | |
| 814 | "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", | |
| 815 | "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", | |
| 816 | "dev": true, | |
| 817 | "license": "MIT", | |
| 818 | "dependencies": { | |
| 819 | "stackframe": "^1.3.4" | |
| 820 | } | |
| 821 | }, | |
| 822 | "node_modules/events-universal": { | |
| 823 | "version": "1.0.1", | |
| 824 | "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", | |
| 825 | "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", | |
| 826 | "license": "Apache-2.0", | |
| 827 | "dependencies": { | |
| 828 | "bare-events": "^2.7.0" | |
| 829 | } | |
| 830 | }, | |
| 831 | "node_modules/fast-fifo": { | |
| 832 | "version": "1.3.2", | |
| 833 | "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", | |
| 834 | "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", | |
| 835 | "license": "MIT" | |
| 836 | }, | |
| 837 | "node_modules/flat-tree": { | |
| 838 | "version": "1.13.0", | |
| 839 | "resolved": "https://registry.npmjs.org/flat-tree/-/flat-tree-1.13.0.tgz", | |
| 840 | "integrity": "sha512-fT3HIuCPwHhFgJ20QYzDHgUG0zMmFg5cHvFiFo5h+QMSJ28TihsEVY0f8HGliuO+pOzmvjMx1odToeaEWkTnyQ==", | |
| 841 | "license": "MIT" | |
| 842 | }, | |
| 843 | "node_modules/fs-native-extensions": { | |
| 844 | "version": "1.4.5", | |
| 845 | "resolved": "https://registry.npmjs.org/fs-native-extensions/-/fs-native-extensions-1.4.5.tgz", | |
| 846 | "integrity": "sha512-ekV0T//iDm4AvhOcuPaHpxub4DI7HvY5ucLJVDvi7T2J+NZkQ9S6MuvgP0yeQvoqNUaAGyLjVYb1905BF9bpmg==", | |
| 847 | "license": "Apache-2.0", | |
| 848 | "optional": true, | |
| 849 | "dependencies": { | |
| 850 | "require-addon": "^1.1.0", | |
| 851 | "which-runtime": "^1.2.0" | |
| 852 | } | |
| 853 | }, | |
| 854 | "node_modules/globbie": { | |
| 855 | "version": "1.0.3", | |
| 856 | "resolved": "https://registry.npmjs.org/globbie/-/globbie-1.0.3.tgz", | |
| 857 | "integrity": "sha512-hcryJmKcftf82xfBWTWxuUITWIIoYO3ec104V17SMHGFl6VLbm1d1Ju9LX9L+jyJTwICpemX/dmPI/HGYoKr1A==", | |
| 858 | "dev": true, | |
| 859 | "license": "Apache-2.0", | |
| 860 | "dependencies": { | |
| 861 | "bare-fs": "^4.1.2", | |
| 862 | "bare-path": "^3.0.0", | |
| 863 | "bare-process": "^4.2.1", | |
| 864 | "picomatch": "^4.0.2" | |
| 865 | } | |
| 866 | }, | |
| 867 | "node_modules/hyperbee": { | |
| 868 | "version": "2.27.3", | |
| 869 | "resolved": "https://registry.npmjs.org/hyperbee/-/hyperbee-2.27.3.tgz", | |
| 870 | "integrity": "sha512-PXURH2U4juUZyJRKHTrY5z1zX851pmI1Q0jfv5F/hCIErDt/ND8jOZuxc3hfOLM9f0W3qJEDTMlV5AJBkVPy8w==", | |
| 871 | "license": "MIT", | |
| 872 | "dependencies": { | |
| 873 | "b4a": "^1.6.0", | |
| 874 | "codecs": "^3.0.0", | |
| 875 | "debounceify": "^1.0.0", | |
| 876 | "hypercore-errors": "^1.0.0", | |
| 877 | "mutexify": "^1.4.0", | |
| 878 | "protocol-buffers-encodings": "^1.2.0", | |
| 879 | "rache": "^1.0.0", | |
| 880 | "ready-resource": "^1.0.0", | |
| 881 | "resolve-reject-promise": "^1.1.0", | |
| 882 | "safety-catch": "^1.0.2", | |
| 883 | "streamx": "^2.12.4", | |
| 884 | "unslab": "^1.2.0" | |
| 885 | } | |
| 886 | }, | |
| 887 | "node_modules/hypercore": { | |
| 888 | "version": "10.38.2", | |
| 889 | "resolved": "https://registry.npmjs.org/hypercore/-/hypercore-10.38.2.tgz", | |
| 890 | "integrity": "sha512-2vx42g2RKdP/o2XzlxKfvgEaZ3+h/B855gCR8iscUDnxUoOZej7vXZ2buex0sweDUEW5aEfMxFR6o5hncio3Nw==", | |
| 891 | "license": "MIT", | |
| 892 | "dependencies": { | |
| 893 | "@hyperswarm/secret-stream": "^6.0.0", | |
| 894 | "b4a": "^1.1.0", | |
| 895 | "bare-events": "^2.2.0", | |
| 896 | "big-sparse-array": "^1.0.3", | |
| 897 | "compact-encoding": "^2.11.0", | |
| 898 | "crc-universal": "^1.0.2", | |
| 899 | "fast-fifo": "^1.3.0", | |
| 900 | "flat-tree": "^1.9.0", | |
| 901 | "hypercore-crypto": "^3.2.1", | |
| 902 | "hypercore-errors": "^1.2.0", | |
| 903 | "hypercore-id-encoding": "^1.2.0", | |
| 904 | "is-options": "^1.0.1", | |
| 905 | "protomux": "^3.5.0", | |
| 906 | "quickbit-universal": "^2.2.0", | |
| 907 | "random-access-file": "^4.0.0", | |
| 908 | "random-array-iterator": "^1.0.0", | |
| 909 | "safety-catch": "^1.0.1", | |
| 910 | "sodium-universal": "^4.0.0", | |
| 911 | "streamx": "^2.12.4", | |
| 912 | "unslab": "^1.3.0", | |
| 913 | "xache": "^1.1.0", | |
| 914 | "z32": "^1.0.0" | |
| 915 | } | |
| 916 | }, | |
| 917 | "node_modules/hypercore-crypto": { | |
| 918 | "version": "3.6.1", | |
| 919 | "resolved": "https://registry.npmjs.org/hypercore-crypto/-/hypercore-crypto-3.6.1.tgz", | |
| 920 | "integrity": "sha512-ltIz2uDwy9pO/ZGTvqcjzyBkvt6O4cVm4r/nNxh0GFs/RbQtqP/i4wCvLEdmU7ptgtnw7fI67WYD1aHPuv4OVA==", | |
| 921 | "license": "MIT", | |
| 922 | "dependencies": { | |
| 923 | "b4a": "^1.6.6", | |
| 924 | "compact-encoding": "^2.15.0", | |
| 925 | "sodium-universal": "^5.0.0" | |
| 926 | } | |
| 927 | }, | |
| 928 | "node_modules/hypercore-crypto/node_modules/sodium-native": { | |
| 929 | "version": "5.0.10", | |
| 930 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 931 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 932 | "license": "MIT", | |
| 933 | "dependencies": { | |
| 934 | "require-addon": "^1.1.0", | |
| 935 | "which-runtime": "^1.2.1" | |
| 936 | }, | |
| 937 | "engines": { | |
| 938 | "bare": ">=1.16.0" | |
| 939 | } | |
| 940 | }, | |
| 941 | "node_modules/hypercore-crypto/node_modules/sodium-universal": { | |
| 942 | "version": "5.0.1", | |
| 943 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 944 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 945 | "license": "MIT", | |
| 946 | "dependencies": { | |
| 947 | "sodium-native": "^5.0.1" | |
| 948 | }, | |
| 949 | "peerDependencies": { | |
| 950 | "sodium-javascript": "~0.8.0" | |
| 951 | }, | |
| 952 | "peerDependenciesMeta": { | |
| 953 | "sodium-javascript": { | |
| 954 | "optional": true | |
| 955 | } | |
| 956 | } | |
| 957 | }, | |
| 958 | "node_modules/hypercore-errors": { | |
| 959 | "version": "1.5.0", | |
| 960 | "resolved": "https://registry.npmjs.org/hypercore-errors/-/hypercore-errors-1.5.0.tgz", | |
| 961 | "integrity": "sha512-5KQ/SuDxsvet+7qWA35Ay6zdD9WyAHQoyWHGcPUTbmJBd300gvNIJoi3oma7kp4TTCSzii6qYumNZe/s0j/saQ==", | |
| 962 | "license": "Apache-2.0", | |
| 963 | "dependencies": { | |
| 964 | "hypercore-id-encoding": "^1.3.0" | |
| 965 | } | |
| 966 | }, | |
| 967 | "node_modules/hypercore-id-encoding": { | |
| 968 | "version": "1.3.0", | |
| 969 | "resolved": "https://registry.npmjs.org/hypercore-id-encoding/-/hypercore-id-encoding-1.3.0.tgz", | |
| 970 | "integrity": "sha512-W6sHdGo5h7LXEsoWfKf/KfuROZmZRQDlGqJF2EPHW+noCK66Vvr0+zE6cL0vqQi18s0kQPeN7Sq3QyR0Ytc2VQ==", | |
| 971 | "license": "Apache-2.0", | |
| 972 | "dependencies": { | |
| 973 | "b4a": "^1.5.3", | |
| 974 | "z32": "^1.0.0" | |
| 975 | } | |
| 976 | }, | |
| 977 | "node_modules/is-options": { | |
| 978 | "version": "1.0.2", | |
| 979 | "resolved": "https://registry.npmjs.org/is-options/-/is-options-1.0.2.tgz", | |
| 980 | "integrity": "sha512-u+Ai74c8Q74aS8BuHwPdI1jptGOT1FQXgCq8/zv0xRuE+wRgSMEJLj8lVO8Zp9BeGb29BXY6AsNPinfqjkr7Fg==", | |
| 981 | "license": "MIT", | |
| 982 | "dependencies": { | |
| 983 | "b4a": "^1.1.1" | |
| 984 | } | |
| 985 | }, | |
| 986 | "node_modules/mutexify": { | |
| 987 | "version": "1.4.0", | |
| 988 | "resolved": "https://registry.npmjs.org/mutexify/-/mutexify-1.4.0.tgz", | |
| 989 | "integrity": "sha512-pbYSsOrSB/AKN5h/WzzLRMFgZhClWccf2XIB4RSMC8JbquiB0e0/SH5AIfdQMdyHmYtv4seU7yV/TvAwPLJ1Yg==", | |
| 990 | "license": "MIT", | |
| 991 | "dependencies": { | |
| 992 | "queue-tick": "^1.0.0" | |
| 993 | } | |
| 994 | }, | |
| 995 | "node_modules/nanoassert": { | |
| 996 | "version": "2.0.0", | |
| 997 | "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz", | |
| 998 | "integrity": "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==", | |
| 999 | "license": "ISC" | |
| 1000 | }, | |
| 1001 | "node_modules/noise-curve-ed": { | |
| 1002 | "version": "2.1.0", | |
| 1003 | "resolved": "https://registry.npmjs.org/noise-curve-ed/-/noise-curve-ed-2.1.0.tgz", | |
| 1004 | "integrity": "sha512-zAzJx+VwZM3w6EA1hTmDhJfvAnCeBQn/1FAeZ0LtGxCcCtlAK/uJXQVF/eDVUOaAZ286lHlx77WJ+qj9SmsRRg==", | |
| 1005 | "license": "ISC", | |
| 1006 | "dependencies": { | |
| 1007 | "b4a": "^1.1.0", | |
| 1008 | "nanoassert": "^2.0.0", | |
| 1009 | "sodium-universal": "^5.0.0" | |
| 1010 | } | |
| 1011 | }, | |
| 1012 | "node_modules/noise-curve-ed/node_modules/sodium-native": { | |
| 1013 | "version": "5.0.10", | |
| 1014 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 1015 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 1016 | "license": "MIT", | |
| 1017 | "dependencies": { | |
| 1018 | "require-addon": "^1.1.0", | |
| 1019 | "which-runtime": "^1.2.1" | |
| 1020 | }, | |
| 1021 | "engines": { | |
| 1022 | "bare": ">=1.16.0" | |
| 1023 | } | |
| 1024 | }, | |
| 1025 | "node_modules/noise-curve-ed/node_modules/sodium-universal": { | |
| 1026 | "version": "5.0.1", | |
| 1027 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 1028 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 1029 | "license": "MIT", | |
| 1030 | "dependencies": { | |
| 1031 | "sodium-native": "^5.0.1" | |
| 1032 | }, | |
| 1033 | "peerDependencies": { | |
| 1034 | "sodium-javascript": "~0.8.0" | |
| 1035 | }, | |
| 1036 | "peerDependenciesMeta": { | |
| 1037 | "sodium-javascript": { | |
| 1038 | "optional": true | |
| 1039 | } | |
| 1040 | } | |
| 1041 | }, | |
| 1042 | "node_modules/noise-handshake": { | |
| 1043 | "version": "4.2.0", | |
| 1044 | "resolved": "https://registry.npmjs.org/noise-handshake/-/noise-handshake-4.2.0.tgz", | |
| 1045 | "integrity": "sha512-9O/VTNX/E2/AToyMTTDU0J/4WhaXMTdqc2DHs9vf+snoZ0cenSBq0dNYTVV1snYYEkmo6QeRrYMxtqtoYnY+LA==", | |
| 1046 | "license": "Apache-2.0", | |
| 1047 | "dependencies": { | |
| 1048 | "b4a": "^1.1.0", | |
| 1049 | "nanoassert": "^2.0.0", | |
| 1050 | "sodium-universal": "^5.0.0" | |
| 1051 | } | |
| 1052 | }, | |
| 1053 | "node_modules/noise-handshake/node_modules/sodium-native": { | |
| 1054 | "version": "5.0.10", | |
| 1055 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 1056 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 1057 | "license": "MIT", | |
| 1058 | "dependencies": { | |
| 1059 | "require-addon": "^1.1.0", | |
| 1060 | "which-runtime": "^1.2.1" | |
| 1061 | }, | |
| 1062 | "engines": { | |
| 1063 | "bare": ">=1.16.0" | |
| 1064 | } | |
| 1065 | }, | |
| 1066 | "node_modules/noise-handshake/node_modules/sodium-universal": { | |
| 1067 | "version": "5.0.1", | |
| 1068 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 1069 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 1070 | "license": "MIT", | |
| 1071 | "dependencies": { | |
| 1072 | "sodium-native": "^5.0.1" | |
| 1073 | }, | |
| 1074 | "peerDependencies": { | |
| 1075 | "sodium-javascript": "~0.8.0" | |
| 1076 | }, | |
| 1077 | "peerDependenciesMeta": { | |
| 1078 | "sodium-javascript": { | |
| 1079 | "optional": true | |
| 1080 | } | |
| 1081 | } | |
| 1082 | }, | |
| 1083 | "node_modules/paparam": { | |
| 1084 | "version": "1.10.0", | |
| 1085 | "resolved": "https://registry.npmjs.org/paparam/-/paparam-1.10.0.tgz", | |
| 1086 | "integrity": "sha512-p36QQrwU3X6fj5d2JN20B4lcZI/O3fxMVUHc1xD7MNe5bcOf3jIgE0CIK5Zv/s5YAUGlobTJ/kKXrh1FJVrs3w==", | |
| 1087 | "dev": true, | |
| 1088 | "license": "Apache-2.0" | |
| 1089 | }, | |
| 1090 | "node_modules/picomatch": { | |
| 1091 | "version": "4.0.3", | |
| 1092 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", | |
| 1093 | "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", | |
| 1094 | "dev": true, | |
| 1095 | "license": "MIT", | |
| 1096 | "engines": { | |
| 1097 | "node": ">=12" | |
| 1098 | }, | |
| 1099 | "funding": { | |
| 1100 | "url": "https://github.com/sponsors/jonschlinkert" | |
| 1101 | } | |
| 1102 | }, | |
| 1103 | "node_modules/protocol-buffers-encodings": { | |
| 1104 | "version": "1.2.0", | |
| 1105 | "resolved": "https://registry.npmjs.org/protocol-buffers-encodings/-/protocol-buffers-encodings-1.2.0.tgz", | |
| 1106 | "integrity": "sha512-daeNPuKh1NlLD1uDfbLpD+xyUTc07nEtfHwmBZmt/vH0B7VOM+JOCOpDcx9ZRpqHjAiIkGqyTDi+wfGSl17R9w==", | |
| 1107 | "license": "MIT", | |
| 1108 | "dependencies": { | |
| 1109 | "b4a": "^1.6.0", | |
| 1110 | "signed-varint": "^2.0.1", | |
| 1111 | "varint": "5.0.0" | |
| 1112 | } | |
| 1113 | }, | |
| 1114 | "node_modules/protomux": { | |
| 1115 | "version": "3.10.1", | |
| 1116 | "resolved": "https://registry.npmjs.org/protomux/-/protomux-3.10.1.tgz", | |
| 1117 | "integrity": "sha512-jgBqx8ZyaBWea/DFG4eOu1scOaeBwcnagiRC1XFVrjeGt7oAb0Pk5udPpBUpJ4DJBRjra50jD6YcZiQQTRqaaA==", | |
| 1118 | "license": "MIT", | |
| 1119 | "dependencies": { | |
| 1120 | "b4a": "^1.3.1", | |
| 1121 | "compact-encoding": "^2.5.1", | |
| 1122 | "queue-tick": "^1.0.0", | |
| 1123 | "safety-catch": "^1.0.1", | |
| 1124 | "unslab": "^1.3.0" | |
| 1125 | } | |
| 1126 | }, | |
| 1127 | "node_modules/queue-tick": { | |
| 1128 | "version": "1.0.1", | |
| 1129 | "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", | |
| 1130 | "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", | |
| 1131 | "license": "MIT" | |
| 1132 | }, | |
| 1133 | "node_modules/quickbit-native": { | |
| 1134 | "version": "2.4.8", | |
| 1135 | "resolved": "https://registry.npmjs.org/quickbit-native/-/quickbit-native-2.4.8.tgz", | |
| 1136 | "integrity": "sha512-FcCcqI+nIAWGknqhtrYT5TSD7t/N+Xd8ctM+2PrIIBuwOi5hx0SxAvuPtzLIEMfT/2h9+fhBakUe2uALOHX6yw==", | |
| 1137 | "license": "Apache-2.0", | |
| 1138 | "optional": true, | |
| 1139 | "dependencies": { | |
| 1140 | "require-addon": "^1.1.0" | |
| 1141 | } | |
| 1142 | }, | |
| 1143 | "node_modules/quickbit-universal": { | |
| 1144 | "version": "2.2.0", | |
| 1145 | "resolved": "https://registry.npmjs.org/quickbit-universal/-/quickbit-universal-2.2.0.tgz", | |
| 1146 | "integrity": "sha512-w02i1R8n7+6pEKTud8DfF8zbFY9o7RtPlUc3jWbtCkDKvhbx/AvV7oNnz4/TcmsPGpSJS+fq5Ud6RH6+YPvSGg==", | |
| 1147 | "license": "ISC", | |
| 1148 | "dependencies": { | |
| 1149 | "b4a": "^1.6.0", | |
| 1150 | "simdle-universal": "^1.1.0" | |
| 1151 | }, | |
| 1152 | "optionalDependencies": { | |
| 1153 | "quickbit-native": "^2.2.0" | |
| 1154 | } | |
| 1155 | }, | |
| 1156 | "node_modules/rache": { | |
| 1157 | "version": "1.0.0", | |
| 1158 | "resolved": "https://registry.npmjs.org/rache/-/rache-1.0.0.tgz", | |
| 1159 | "integrity": "sha512-e0k0g0w/8jOCB+7YqCIlOa+OJ38k0wrYS4x18pMSmqOvLKoyhmMhmQyCcvfY6VaP8D75cqkEnlakXs+RYYLqNg==", | |
| 1160 | "license": "Apache-2.0" | |
| 1161 | }, | |
| 1162 | "node_modules/random-access-file": { | |
| 1163 | "version": "4.1.2", | |
| 1164 | "resolved": "https://registry.npmjs.org/random-access-file/-/random-access-file-4.1.2.tgz", | |
| 1165 | "integrity": "sha512-GQM6R78DceZDcQod8KxlDFwXIiUvlvuy1EOzxTDsjuDjW5NlnlZi0MOk6iI4itAj/2vcvdqcEExYbVpC/dJcEw==", | |
| 1166 | "license": "MIT", | |
| 1167 | "dependencies": { | |
| 1168 | "bare-fs": "^4.0.1", | |
| 1169 | "bare-path": "^3.0.0", | |
| 1170 | "random-access-storage": "^3.0.0" | |
| 1171 | }, | |
| 1172 | "optionalDependencies": { | |
| 1173 | "fs-native-extensions": "^1.3.1" | |
| 1174 | } | |
| 1175 | }, | |
| 1176 | "node_modules/random-access-storage": { | |
| 1177 | "version": "3.0.2", | |
| 1178 | "resolved": "https://registry.npmjs.org/random-access-storage/-/random-access-storage-3.0.2.tgz", | |
| 1179 | "integrity": "sha512-Es9maUyWdJXWKckKy9s1+vT+DEgAt+PBb9lxPaake/0EDUsHehloKGv9v1zimS2V3gpFAcQXubvc1Rgci2sDPQ==", | |
| 1180 | "license": "MIT", | |
| 1181 | "dependencies": { | |
| 1182 | "bare-events": "^2.2.0", | |
| 1183 | "queue-tick": "^1.0.0" | |
| 1184 | } | |
| 1185 | }, | |
| 1186 | "node_modules/random-array-iterator": { | |
| 1187 | "version": "1.0.0", | |
| 1188 | "resolved": "https://registry.npmjs.org/random-array-iterator/-/random-array-iterator-1.0.0.tgz", | |
| 1189 | "integrity": "sha512-u7xCM93XqKEvPTP6xZp2ehttcAemKnh73oKNf1FvzuVCfpt6dILDt1Kxl1LeBjm2iNIeR49VGFhy4Iz3yOun+Q==", | |
| 1190 | "license": "MIT" | |
| 1191 | }, | |
| 1192 | "node_modules/read-write-mutexify": { | |
| 1193 | "version": "2.1.0", | |
| 1194 | "resolved": "https://registry.npmjs.org/read-write-mutexify/-/read-write-mutexify-2.1.0.tgz", | |
| 1195 | "integrity": "sha512-fDw/p5/acI1ytVY1UbxEDma/ej1yJH/n9NcjS9YNzcE6sPBPWdlru3ydRa/UBowUg4zqOvNMD5SOGYJrlQ6MzQ==", | |
| 1196 | "license": "MIT" | |
| 1197 | }, | |
| 1198 | "node_modules/ready-resource": { | |
| 1199 | "version": "1.2.0", | |
| 1200 | "resolved": "https://registry.npmjs.org/ready-resource/-/ready-resource-1.2.0.tgz", | |
| 1201 | "integrity": "sha512-nfcco/8iAFV0M+2PYnmIc+/xY0iRb35d42HFHQ7AfjulbGEAFa+XWpByfwSyeVeiBoMLLFVMv1HixxNCqzSQ1g==", | |
| 1202 | "license": "MIT", | |
| 1203 | "dependencies": { | |
| 1204 | "bare-events": "^2.2.0" | |
| 1205 | } | |
| 1206 | }, | |
| 1207 | "node_modules/require-addon": { | |
| 1208 | "version": "1.2.0", | |
| 1209 | "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz", | |
| 1210 | "integrity": "sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA==", | |
| 1211 | "license": "Apache-2.0", | |
| 1212 | "dependencies": { | |
| 1213 | "bare-addon-resolve": "^1.3.0" | |
| 1214 | }, | |
| 1215 | "engines": { | |
| 1216 | "bare": ">=1.10.0" | |
| 1217 | } | |
| 1218 | }, | |
| 1219 | "node_modules/resolve-reject-promise": { | |
| 1220 | "version": "1.1.0", | |
| 1221 | "resolved": "https://registry.npmjs.org/resolve-reject-promise/-/resolve-reject-promise-1.1.0.tgz", | |
| 1222 | "integrity": "sha512-LWsTOA91AqzBTjSGgX79Tc130pwcBK6xjpJEO+qRT5IKZ6bGnHKcc8QL3upUBcWuU8OTIDzKK2VNSwmmlqvAVg==", | |
| 1223 | "license": "MIT" | |
| 1224 | }, | |
| 1225 | "node_modules/safety-catch": { | |
| 1226 | "version": "1.0.2", | |
| 1227 | "resolved": "https://registry.npmjs.org/safety-catch/-/safety-catch-1.0.2.tgz", | |
| 1228 | "integrity": "sha512-C1UYVZ4dtbBxEtvOcpjBaaD27nP8MlvyAQEp2fOTOEe6pfUpk1cDUxij6BR1jZup6rSyUTaBBplK7LanskrULA==", | |
| 1229 | "license": "MIT" | |
| 1230 | }, | |
| 1231 | "node_modules/same-object": { | |
| 1232 | "version": "1.0.2", | |
| 1233 | "resolved": "https://registry.npmjs.org/same-object/-/same-object-1.0.2.tgz", | |
| 1234 | "integrity": "sha512-csHWhvUsLbIOHDM/nP+KHWM+BLPsIzWkFa8HbzaI0G7BqKXgx+7FJpKTGgLXyz5amfdY2OVBcmXTqYOMEk04og==", | |
| 1235 | "dev": true, | |
| 1236 | "license": "MIT" | |
| 1237 | }, | |
| 1238 | "node_modules/signed-varint": { | |
| 1239 | "version": "2.0.1", | |
| 1240 | "resolved": "https://registry.npmjs.org/signed-varint/-/signed-varint-2.0.1.tgz", | |
| 1241 | "integrity": "sha512-abgDPg1106vuZZOvw7cFwdCABddfJRz5akcCcchzTbhyhYnsG31y4AlZEgp315T7W3nQq5P4xeOm186ZiPVFzw==", | |
| 1242 | "license": "MIT", | |
| 1243 | "dependencies": { | |
| 1244 | "varint": "~5.0.0" | |
| 1245 | } | |
| 1246 | }, | |
| 1247 | "node_modules/simdle-native": { | |
| 1248 | "version": "1.3.9", | |
| 1249 | "resolved": "https://registry.npmjs.org/simdle-native/-/simdle-native-1.3.9.tgz", | |
| 1250 | "integrity": "sha512-Isc8sP4OiiIU0mpslD4GHEnR0VQWvR/54WN7YtwEDkNdTJVWtpmvsSvsgRlw5BNGxdYXlVRegdnrSu10H/PhvA==", | |
| 1251 | "license": "Apache-2.0", | |
| 1252 | "optional": true, | |
| 1253 | "dependencies": { | |
| 1254 | "b4a": "^1.6.0", | |
| 1255 | "require-addon": "^1.1.0" | |
| 1256 | } | |
| 1257 | }, | |
| 1258 | "node_modules/simdle-universal": { | |
| 1259 | "version": "1.1.2", | |
| 1260 | "resolved": "https://registry.npmjs.org/simdle-universal/-/simdle-universal-1.1.2.tgz", | |
| 1261 | "integrity": "sha512-3n3w1bs+uwgHKQjt6arez83EywNlhZzYvNOhvAASTl/8KqNIcqr6aHyGt3JRlfuUC7iB0tomJRPlJ2cRGIpBzA==", | |
| 1262 | "license": "ISC", | |
| 1263 | "dependencies": { | |
| 1264 | "b4a": "^1.6.0" | |
| 1265 | }, | |
| 1266 | "optionalDependencies": { | |
| 1267 | "simdle-native": "^1.1.1" | |
| 1268 | } | |
| 1269 | }, | |
| 1270 | "node_modules/sodium-native": { | |
| 1271 | "version": "4.3.3", | |
| 1272 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.3.3.tgz", | |
| 1273 | "integrity": "sha512-OnxSlN3uyY8D0EsLHpmm2HOFmKddQVvEMmsakCrXUzSd8kjjbzL413t4ZNF3n0UxSwNgwTyUvkmZHTfuCeiYSw==", | |
| 1274 | "license": "MIT", | |
| 1275 | "dependencies": { | |
| 1276 | "require-addon": "^1.1.0" | |
| 1277 | } | |
| 1278 | }, | |
| 1279 | "node_modules/sodium-secretstream": { | |
| 1280 | "version": "1.2.0", | |
| 1281 | "resolved": "https://registry.npmjs.org/sodium-secretstream/-/sodium-secretstream-1.2.0.tgz", | |
| 1282 | "integrity": "sha512-q/DbraNFXm1KfCiiZvapmz5UC3OlpirYFIvBK2MhGaOFSb3gRyk8OXTi17UI9SGfshQNCpsVvlopogbzZNyW6Q==", | |
| 1283 | "license": "MIT", | |
| 1284 | "dependencies": { | |
| 1285 | "b4a": "^1.1.1", | |
| 1286 | "sodium-universal": "^5.0.0" | |
| 1287 | } | |
| 1288 | }, | |
| 1289 | "node_modules/sodium-secretstream/node_modules/sodium-native": { | |
| 1290 | "version": "5.0.10", | |
| 1291 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 1292 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 1293 | "license": "MIT", | |
| 1294 | "dependencies": { | |
| 1295 | "require-addon": "^1.1.0", | |
| 1296 | "which-runtime": "^1.2.1" | |
| 1297 | }, | |
| 1298 | "engines": { | |
| 1299 | "bare": ">=1.16.0" | |
| 1300 | } | |
| 1301 | }, | |
| 1302 | "node_modules/sodium-secretstream/node_modules/sodium-universal": { | |
| 1303 | "version": "5.0.1", | |
| 1304 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 1305 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 1306 | "license": "MIT", | |
| 1307 | "dependencies": { | |
| 1308 | "sodium-native": "^5.0.1" | |
| 1309 | }, | |
| 1310 | "peerDependencies": { | |
| 1311 | "sodium-javascript": "~0.8.0" | |
| 1312 | }, | |
| 1313 | "peerDependenciesMeta": { | |
| 1314 | "sodium-javascript": { | |
| 1315 | "optional": true | |
| 1316 | } | |
| 1317 | } | |
| 1318 | }, | |
| 1319 | "node_modules/sodium-universal": { | |
| 1320 | "version": "4.0.1", | |
| 1321 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-4.0.1.tgz", | |
| 1322 | "integrity": "sha512-sNp13PrxYLaUFHTGoDKkSDFvoEu51bfzE12RwGlqU1fcrkpAOK0NvizaJzOWV0Omtk9me2+Pnbjcf/l0efxuGQ==", | |
| 1323 | "license": "MIT", | |
| 1324 | "dependencies": { | |
| 1325 | "sodium-native": "^4.0.0" | |
| 1326 | }, | |
| 1327 | "peerDependencies": { | |
| 1328 | "sodium-javascript": "~0.8.0" | |
| 1329 | }, | |
| 1330 | "peerDependenciesMeta": { | |
| 1331 | "sodium-javascript": { | |
| 1332 | "optional": true | |
| 1333 | } | |
| 1334 | } | |
| 1335 | }, | |
| 1336 | "node_modules/stackframe": { | |
| 1337 | "version": "1.3.4", | |
| 1338 | "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", | |
| 1339 | "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", | |
| 1340 | "dev": true, | |
| 1341 | "license": "MIT" | |
| 1342 | }, | |
| 1343 | "node_modules/streamx": { | |
| 1344 | "version": "2.23.0", | |
| 1345 | "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", | |
| 1346 | "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", | |
| 1347 | "license": "MIT", | |
| 1348 | "dependencies": { | |
| 1349 | "events-universal": "^1.0.0", | |
| 1350 | "fast-fifo": "^1.3.2", | |
| 1351 | "text-decoder": "^1.1.0" | |
| 1352 | } | |
| 1353 | }, | |
| 1354 | "node_modules/test-tmp": { | |
| 1355 | "version": "1.4.0", | |
| 1356 | "resolved": "https://registry.npmjs.org/test-tmp/-/test-tmp-1.4.0.tgz", | |
| 1357 | "integrity": "sha512-GVggxGg+jXqP2Wbju50JVLo+9E+nIOPPyWqgr63EbOnNItIKu1cEbJpTWAJeflnyGqXOtcMI7ijHRp88GUkfDA==", | |
| 1358 | "dev": true, | |
| 1359 | "license": "MIT", | |
| 1360 | "dependencies": { | |
| 1361 | "bare-fs": "^4.0.1", | |
| 1362 | "bare-os": "^3.3.0", | |
| 1363 | "bare-path": "^3.0.0" | |
| 1364 | } | |
| 1365 | }, | |
| 1366 | "node_modules/text-decoder": { | |
| 1367 | "version": "1.2.6", | |
| 1368 | "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.6.tgz", | |
| 1369 | "integrity": "sha512-27FeW5GQFDfw0FpwMQhMagB7BztOOlmjcSRi97t2oplhKVTZtp0DZbSegSaXS5IIC6mxMvBG4AR1Sgc6BX3CQg==", | |
| 1370 | "license": "Apache-2.0", | |
| 1371 | "dependencies": { | |
| 1372 | "b4a": "^1.6.4" | |
| 1373 | } | |
| 1374 | }, | |
| 1375 | "node_modules/timeout-refresh": { | |
| 1376 | "version": "2.0.1", | |
| 1377 | "resolved": "https://registry.npmjs.org/timeout-refresh/-/timeout-refresh-2.0.1.tgz", | |
| 1378 | "integrity": "sha512-SVqEcMZBsZF9mA78rjzCrYrUs37LMJk3ShZ851ygZYW1cMeIjs9mL57KO6Iv5mmjSQnOe/29/VAfGXo+oRCiVw==", | |
| 1379 | "license": "MIT" | |
| 1380 | }, | |
| 1381 | "node_modules/tmatch": { | |
| 1382 | "version": "5.0.0", | |
| 1383 | "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-5.0.0.tgz", | |
| 1384 | "integrity": "sha512-Ib9OtBkpHn07tXP04SlN1SYRxFgTk6wSM2EBmjjxug4u5RXPRVLkdFJSS1PmrQidaSB8Lru9nRtViQBsbxzE5Q==", | |
| 1385 | "dev": true, | |
| 1386 | "license": "ISC", | |
| 1387 | "engines": { | |
| 1388 | "node": ">=8" | |
| 1389 | } | |
| 1390 | }, | |
| 1391 | "node_modules/unslab": { | |
| 1392 | "version": "1.3.0", | |
| 1393 | "resolved": "https://registry.npmjs.org/unslab/-/unslab-1.3.0.tgz", | |
| 1394 | "integrity": "sha512-YATkfKAFj47kTzmiQrWXMyRvaVrHsW6MEALa4bm+FhiA2YG4oira+Z3DXN6LrYOYn2Y8eO94Lwl9DOHjs1FpoQ==", | |
| 1395 | "license": "Apache-2.0", | |
| 1396 | "dependencies": { | |
| 1397 | "b4a": "^1.6.6" | |
| 1398 | } | |
| 1399 | }, | |
| 1400 | "node_modules/v8-to-istanbul": { | |
| 1401 | "version": "9.3.0", | |
| 1402 | "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", | |
| 1403 | "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", | |
| 1404 | "dev": true, | |
| 1405 | "license": "ISC", | |
| 1406 | "dependencies": { | |
| 1407 | "@jridgewell/trace-mapping": "^0.3.12", | |
| 1408 | "@types/istanbul-lib-coverage": "^2.0.1", | |
| 1409 | "convert-source-map": "^2.0.0" | |
| 1410 | }, | |
| 1411 | "engines": { | |
| 1412 | "node": ">=10.12.0" | |
| 1413 | } | |
| 1414 | }, | |
| 1415 | "node_modules/varint": { | |
| 1416 | "version": "5.0.0", | |
| 1417 | "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.0.tgz", | |
| 1418 | "integrity": "sha512-gC13b/bWrqQoKY2EmROCZ+AR0jitc6DnDGaQ6Ls9QpKmuSgJB1eQ7H3KETtQm7qSdMWMKCmsshyCmUwMLh3OAA==", | |
| 1419 | "license": "MIT" | |
| 1420 | }, | |
| 1421 | "node_modules/which-runtime": { | |
| 1422 | "version": "1.3.2", | |
| 1423 | "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.3.2.tgz", | |
| 1424 | "integrity": "sha512-5kwCfWml7+b2NO7KrLMhYihjRx0teKkd3yGp1Xk5Vaf2JGdSh+rgVhEALAD9c/59dP+YwJHXoEO7e8QPy7gOkw==", | |
| 1425 | "license": "Apache-2.0" | |
| 1426 | }, | |
| 1427 | "node_modules/xache": { | |
| 1428 | "version": "1.2.1", | |
| 1429 | "resolved": "https://registry.npmjs.org/xache/-/xache-1.2.1.tgz", | |
| 1430 | "integrity": "sha512-igRS6jPreJ54ABdzhh4mCDXcz+XMaWO2q1ABRV2yWYuk29jlp8VT7UBdCqNkX7rpYBbXsebVVKkwIuYZjyZNqA==", | |
| 1431 | "license": "MIT" | |
| 1432 | }, | |
| 1433 | "node_modules/z32": { | |
| 1434 | "version": "1.1.0", | |
| 1435 | "resolved": "https://registry.npmjs.org/z32/-/z32-1.1.0.tgz", | |
| 1436 | "integrity": "sha512-1WUHy+VS6d0HPNspDxvLssBbeQjXMjSnpv0vH82vRAUfg847NmX3OXozp/hRP5jPhxBbrVzrgvAt+UsGNzRFQQ==", | |
| 1437 | "license": "MIT", | |
| 1438 | "dependencies": { | |
| 1439 | "b4a": "^1.5.3" | |
| 1440 | } | |
| 1441 | } | |
| 1442 | } | |
| 1443 | } |
| … | … | @@ -0,0 +1,24 @@ |
| 1 | { | |
| 2 | "name": "hyper-board", | |
| 3 | "version": "1.0.0", | |
| 4 | "description": "P2P kanban board backed by Hyperbee", | |
| 5 | "type": "module", | |
| 6 | "main": "index.js", | |
| 7 | "pear": { | |
| 8 | "name": "hyper-board", | |
| 9 | "type": "terminal" | |
| 10 | }, | |
| 11 | "scripts": { | |
| 12 | "test": "brittle test/*.test.js" | |
| 13 | }, | |
| 14 | "dependencies": { | |
| 15 | "hyperbee": "^2.0.0", | |
| 16 | "corestore": "^6.0.0", | |
| 17 | "b4a": "^1.0.0", | |
| 18 | "compact-encoding": "^2.0.0" | |
| 19 | }, | |
| 20 | "devDependencies": { | |
| 21 | "brittle": "^3.0.0" | |
| 22 | }, | |
| 23 | "license": "MIT" | |
| 24 | } |
| … | … | @@ -0,0 +1,328 @@ |
| 1 | import test from 'brittle' | |
| 2 | import { Board } from '../lib/board.js' | |
| 3 | import { tmpdir } from 'os' | |
| 4 | import { join } from 'path' | |
| 5 | import { randomBytes } from 'crypto' | |
| 6 | ||
| 7 | function tmpStorage () { | |
| 8 | return join(tmpdir(), 'hyper-board-test-' + randomBytes(8).toString('hex')) | |
| 9 | } | |
| 10 | ||
| 11 | test('Board - ready creates default lanes', async (t) => { | |
| 12 | const board = new Board(tmpStorage()) | |
| 13 | await board.ready() | |
| 14 | ||
| 15 | const lanes = await board.listLanes() | |
| 16 | t.is(lanes.length, 4) | |
| 17 | t.is(lanes[0].name, 'backlog') | |
| 18 | t.is(lanes[1].name, 'todo') | |
| 19 | t.is(lanes[2].name, 'in-progress') | |
| 20 | t.is(lanes[3].name, 'done') | |
| 21 | ||
| 22 | await board.close() | |
| 23 | }) | |
| 24 | ||
| 25 | test('Board - create and get card', async (t) => { | |
| 26 | const board = new Board(tmpStorage()) | |
| 27 | await board.ready() | |
| 28 | ||
| 29 | const card = await board.createCard({ | |
| 30 | title: 'Fix bug', | |
| 31 | description: 'Critical production bug', | |
| 32 | lane: 'todo', | |
| 33 | priority: 'high', | |
| 34 | tags: ['bug', 'production'] | |
| 35 | }) | |
| 36 | ||
| 37 | t.ok(card.id) | |
| 38 | t.is(card.title, 'Fix bug') | |
| 39 | t.is(card.lane, 'todo') | |
| 40 | t.is(card.priority, 'high') | |
| 41 | t.alike(card.tags, ['bug', 'production']) | |
| 42 | t.ok(card.created > 0) | |
| 43 | ||
| 44 | const fetched = await board.getCard(card.id) | |
| 45 | t.is(fetched.title, 'Fix bug') | |
| 46 | ||
| 47 | await board.close() | |
| 48 | }) | |
| 49 | ||
| 50 | test('Board - update card', async (t) => { | |
| 51 | const board = new Board(tmpStorage()) | |
| 52 | await board.ready() | |
| 53 | ||
| 54 | const card = await board.createCard({ title: 'Task A' }) | |
| 55 | const updated = await board.updateCard(card.id, { | |
| 56 | title: 'Task A (updated)', | |
| 57 | description: 'Now with details', | |
| 58 | assignee: 'fleeky' | |
| 59 | }) | |
| 60 | ||
| 61 | t.is(updated.title, 'Task A (updated)') | |
| 62 | t.is(updated.assignee, 'fleeky') | |
| 63 | t.is(updated.id, card.id) | |
| 64 | t.is(updated.created, card.created) | |
| 65 | t.ok(updated.updated >= card.updated) | |
| 66 | ||
| 67 | await board.close() | |
| 68 | }) | |
| 69 | ||
| 70 | test('Board - delete card', async (t) => { | |
| 71 | const board = new Board(tmpStorage()) | |
| 72 | await board.ready() | |
| 73 | ||
| 74 | const card = await board.createCard({ title: 'Delete me' }) | |
| 75 | t.ok(await board.getCard(card.id)) | |
| 76 | ||
| 77 | const ok = await board.deleteCard(card.id) | |
| 78 | t.ok(ok) | |
| 79 | t.is(await board.getCard(card.id), null) | |
| 80 | ||
| 81 | // Deleting non-existent returns false | |
| 82 | const nope = await board.deleteCard('nonexistent') | |
| 83 | t.is(nope, false) | |
| 84 | ||
| 85 | await board.close() | |
| 86 | }) | |
| 87 | ||
| 88 | test('Board - move card between lanes', async (t) => { | |
| 89 | const board = new Board(tmpStorage()) | |
| 90 | await board.ready() | |
| 91 | ||
| 92 | const card = await board.createCard({ title: 'Move me', lane: 'backlog' }) | |
| 93 | t.is(card.lane, 'backlog') | |
| 94 | ||
| 95 | const moved = await board.moveCard(card.id, 'in-progress') | |
| 96 | t.is(moved.lane, 'in-progress') | |
| 97 | ||
| 98 | const fetched = await board.getCard(card.id) | |
| 99 | t.is(fetched.lane, 'in-progress') | |
| 100 | ||
| 101 | await board.close() | |
| 102 | }) | |
| 103 | ||
| 104 | test('Board - list cards with lane filter', async (t) => { | |
| 105 | const board = new Board(tmpStorage()) | |
| 106 | await board.ready() | |
| 107 | ||
| 108 | await board.createCard({ title: 'A', lane: 'todo' }) | |
| 109 | await board.createCard({ title: 'B', lane: 'todo' }) | |
| 110 | await board.createCard({ title: 'C', lane: 'done' }) | |
| 111 | ||
| 112 | const todo = await board.listCards({ lane: 'todo' }) | |
| 113 | t.is(todo.length, 2) | |
| 114 | ||
| 115 | const done = await board.listCards({ lane: 'done' }) | |
| 116 | t.is(done.length, 1) | |
| 117 | ||
| 118 | const all = await board.listCards() | |
| 119 | t.is(all.length, 3) | |
| 120 | ||
| 121 | await board.close() | |
| 122 | }) | |
| 123 | ||
| 124 | test('Board - list cards with assignee filter', async (t) => { | |
| 125 | const board = new Board(tmpStorage()) | |
| 126 | await board.ready() | |
| 127 | ||
| 128 | await board.createCard({ title: 'A', assignee: 'alice' }) | |
| 129 | await board.createCard({ title: 'B', assignee: 'bob' }) | |
| 130 | await board.createCard({ title: 'C', assignee: 'alice' }) | |
| 131 | ||
| 132 | const alice = await board.listCards({ assignee: 'alice' }) | |
| 133 | t.is(alice.length, 2) | |
| 134 | ||
| 135 | await board.close() | |
| 136 | }) | |
| 137 | ||
| 138 | test('Board - list cards with tag filter', async (t) => { | |
| 139 | const board = new Board(tmpStorage()) | |
| 140 | await board.ready() | |
| 141 | ||
| 142 | await board.createCard({ title: 'A', tags: ['bug'] }) | |
| 143 | await board.createCard({ title: 'B', tags: ['feature'] }) | |
| 144 | await board.createCard({ title: 'C', tags: ['bug', 'urgent'] }) | |
| 145 | ||
| 146 | const bugs = await board.listCards({ tag: 'bug' }) | |
| 147 | t.is(bugs.length, 2) | |
| 148 | ||
| 149 | await board.close() | |
| 150 | }) | |
| 151 | ||
| 152 | test('Board - priority sorting', async (t) => { | |
| 153 | const board = new Board(tmpStorage()) | |
| 154 | await board.ready() | |
| 155 | ||
| 156 | await board.createCard({ title: 'Normal', priority: 'normal' }) | |
| 157 | await board.createCard({ title: 'Urgent', priority: 'urgent' }) | |
| 158 | await board.createCard({ title: 'Low', priority: 'low' }) | |
| 159 | await board.createCard({ title: 'High', priority: 'high' }) | |
| 160 | ||
| 161 | const cards = await board.listCards() | |
| 162 | t.is(cards[0].title, 'Urgent') | |
| 163 | t.is(cards[1].title, 'High') | |
| 164 | t.is(cards[2].title, 'Normal') | |
| 165 | t.is(cards[3].title, 'Low') | |
| 166 | ||
| 167 | await board.close() | |
| 168 | }) | |
| 169 | ||
| 170 | test('Board - add and remove lane', async (t) => { | |
| 171 | const board = new Board(tmpStorage()) | |
| 172 | await board.ready() | |
| 173 | ||
| 174 | await board.addLane('review', 2) | |
| 175 | const lanes = await board.listLanes() | |
| 176 | t.ok(lanes.find(l => l.name === 'review')) | |
| 177 | ||
| 178 | await board.removeLane('review') | |
| 179 | const lanes2 = await board.listLanes() | |
| 180 | t.absent(lanes2.find(l => l.name === 'review')) | |
| 181 | ||
| 182 | await board.close() | |
| 183 | }) | |
| 184 | ||
| 185 | test('Board - cannot remove lane with cards', async (t) => { | |
| 186 | const board = new Board(tmpStorage()) | |
| 187 | await board.ready() | |
| 188 | ||
| 189 | await board.createCard({ title: 'Blocker', lane: 'todo' }) | |
| 190 | ||
| 191 | try { | |
| 192 | await board.removeLane('todo') | |
| 193 | t.fail('Should have thrown') | |
| 194 | } catch (err) { | |
| 195 | t.ok(err.message.includes('Cannot remove')) | |
| 196 | t.ok(err.message.includes('1 cards')) | |
| 197 | } | |
| 198 | ||
| 199 | await board.close() | |
| 200 | }) | |
| 201 | ||
| 202 | test('Board - duplicate lane name rejected', async (t) => { | |
| 203 | const board = new Board(tmpStorage()) | |
| 204 | await board.ready() | |
| 205 | ||
| 206 | try { | |
| 207 | await board.addLane('todo') | |
| 208 | t.fail('Should have thrown') | |
| 209 | } catch (err) { | |
| 210 | t.ok(err.message.includes('already exists')) | |
| 211 | } | |
| 212 | ||
| 213 | await board.close() | |
| 214 | }) | |
| 215 | ||
| 216 | test('Board - search by title', async (t) => { | |
| 217 | const board = new Board(tmpStorage()) | |
| 218 | await board.ready() | |
| 219 | ||
| 220 | await board.createCard({ title: 'Fix authentication bug' }) | |
| 221 | await board.createCard({ title: 'Add new feature' }) | |
| 222 | await board.createCard({ title: 'Bug in login page' }) | |
| 223 | ||
| 224 | const results = await board.search('bug') | |
| 225 | t.is(results.length, 2) | |
| 226 | ||
| 227 | await board.close() | |
| 228 | }) | |
| 229 | ||
| 230 | test('Board - search by description', async (t) => { | |
| 231 | const board = new Board(tmpStorage()) | |
| 232 | await board.ready() | |
| 233 | ||
| 234 | await board.createCard({ title: 'Task', description: 'needs performance optimization' }) | |
| 235 | await board.createCard({ title: 'Other' }) | |
| 236 | ||
| 237 | const results = await board.search('performance') | |
| 238 | t.is(results.length, 1) | |
| 239 | t.is(results[0].title, 'Task') | |
| 240 | ||
| 241 | await board.close() | |
| 242 | }) | |
| 243 | ||
| 244 | test('Board - search by tag', async (t) => { | |
| 245 | const board = new Board(tmpStorage()) | |
| 246 | await board.ready() | |
| 247 | ||
| 248 | await board.createCard({ title: 'A', tags: ['frontend'] }) | |
| 249 | await board.createCard({ title: 'B', tags: ['backend'] }) | |
| 250 | ||
| 251 | const results = await board.search('frontend') | |
| 252 | t.is(results.length, 1) | |
| 253 | ||
| 254 | await board.close() | |
| 255 | }) | |
| 256 | ||
| 257 | test('Board - stats', async (t) => { | |
| 258 | const board = new Board(tmpStorage()) | |
| 259 | await board.ready() | |
| 260 | ||
| 261 | await board.createCard({ title: 'A', lane: 'todo' }) | |
| 262 | await board.createCard({ title: 'B', lane: 'todo' }) | |
| 263 | await board.createCard({ title: 'C', lane: 'done' }) | |
| 264 | ||
| 265 | const stats = await board.stats() | |
| 266 | t.is(stats.totalCards, 3) | |
| 267 | t.is(stats.totalLanes, 4) | |
| 268 | t.is(stats.byLane.todo, 2) | |
| 269 | t.is(stats.byLane.done, 1) | |
| 270 | t.is(stats.byLane.backlog, 0) | |
| 271 | ||
| 272 | await board.close() | |
| 273 | }) | |
| 274 | ||
| 275 | test('Board - view returns lanes with cards', async (t) => { | |
| 276 | const board = new Board(tmpStorage()) | |
| 277 | await board.ready() | |
| 278 | ||
| 279 | await board.createCard({ title: 'A', lane: 'todo' }) | |
| 280 | await board.createCard({ title: 'B', lane: 'done' }) | |
| 281 | ||
| 282 | const view = await board.view() | |
| 283 | t.is(view.length, 4) // all 4 default lanes | |
| 284 | ||
| 285 | const todoView = view.find(l => l.name === 'todo') | |
| 286 | t.is(todoView.cards.length, 1) | |
| 287 | t.is(todoView.cards[0].title, 'A') | |
| 288 | ||
| 289 | const doneView = view.find(l => l.name === 'done') | |
| 290 | t.is(doneView.cards.length, 1) | |
| 291 | ||
| 292 | const backlogView = view.find(l => l.name === 'backlog') | |
| 293 | t.is(backlogView.cards.length, 0) | |
| 294 | ||
| 295 | await board.close() | |
| 296 | }) | |
| 297 | ||
| 298 | test('Board - persistence', async (t) => { | |
| 299 | const storage = tmpStorage() | |
| 300 | ||
| 301 | // Create and populate | |
| 302 | const b1 = new Board(storage) | |
| 303 | await b1.ready() | |
| 304 | await b1.createCard({ title: 'Persistent', lane: 'todo' }) | |
| 305 | await b1.close() | |
| 306 | ||
| 307 | // Reopen | |
| 308 | const b2 = new Board(storage) | |
| 309 | await b2.ready() | |
| 310 | const cards = await b2.listCards() | |
| 311 | t.is(cards.length, 1) | |
| 312 | t.is(cards[0].title, 'Persistent') | |
| 313 | await b2.close() | |
| 314 | }) | |
| 315 | ||
| 316 | test('Board - update nonexistent card throws', async (t) => { | |
| 317 | const board = new Board(tmpStorage()) | |
| 318 | await board.ready() | |
| 319 | ||
| 320 | try { | |
| 321 | await board.updateCard('nonexistent', { title: 'X' }) | |
| 322 | t.fail('Should have thrown') | |
| 323 | } catch (err) { | |
| 324 | t.ok(err.message.includes('not found')) | |
| 325 | } | |
| 326 | ||
| 327 | await board.close() | |
| 328 | }) |
| … | … | @@ -0,0 +1,180 @@ |
| 1 | // Crosspost daemon — selective content syndication | |
| 2 | // Routes messages between platforms based on configurable rules | |
| 3 | ||
| 4 | const PLATFORM_LIMITS = { | |
| 5 | mastodon: 500, | |
| 6 | keet: 0, // no limit | |
| 7 | irc: 450, | |
| 8 | discord: 2000, | |
| 9 | matrix: 0 // no limit | |
| 10 | } | |
| 11 | ||
| 12 | const DEFAULT_RULES = [ | |
| 13 | // keet messages tagged #public → mastodon | |
| 14 | { from: 'keet', to: 'mastodon', filter: { tags: ['public'] } }, | |
| 15 | // all mastodon posts → keet | |
| 16 | { from: 'mastodon', to: 'keet', filter: {} } | |
| 17 | ] | |
| 18 | ||
| 19 | // Check if a message matches a filter | |
| 20 | function matchFilter (msg, filter) { | |
| 21 | if (!filter || Object.keys(filter).length === 0) return true | |
| 22 | ||
| 23 | // Tag filter — at least one tag must appear in the text | |
| 24 | if (filter.tags && filter.tags.length > 0) { | |
| 25 | const text = (msg.text || '').toLowerCase() | |
| 26 | const hasTag = filter.tags.some(tag => { | |
| 27 | const normalized = tag.toLowerCase() | |
| 28 | // Match #tag or plain tag | |
| 29 | return text.includes('#' + normalized) || text.includes(normalized) | |
| 30 | }) | |
| 31 | if (!hasTag) return false | |
| 32 | } | |
| 33 | ||
| 34 | // Author filter — exact match | |
| 35 | if (filter.author && msg.author !== filter.author) return false | |
| 36 | ||
| 37 | // Type filter — message type (text, image, link, etc.) | |
| 38 | if (filter.type && msg.type !== filter.type) return false | |
| 39 | ||
| 40 | // Text regex pattern | |
| 41 | if (filter.pattern) { | |
| 42 | try { | |
| 43 | const regex = new RegExp(filter.pattern, 'i') | |
| 44 | if (!regex.test(msg.text || '')) return false | |
| 45 | } catch { | |
| 46 | return false | |
| 47 | } | |
| 48 | } | |
| 49 | ||
| 50 | // Contains — simple substring | |
| 51 | if (filter.contains) { | |
| 52 | if (!(msg.text || '').includes(filter.contains)) return false | |
| 53 | } | |
| 54 | ||
| 55 | return true | |
| 56 | } | |
| 57 | ||
| 58 | // Strip hashtags used for routing (e.g., #public) to keep output clean | |
| 59 | function stripRoutingTags (text, tags) { | |
| 60 | if (!tags || !tags.length) return text | |
| 61 | let result = text | |
| 62 | for (const tag of tags) { | |
| 63 | // Remove #tag variants (case-insensitive) | |
| 64 | result = result.replace(new RegExp('#' + tag.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi'), '') | |
| 65 | } | |
| 66 | return result.replace(/\s{2,}/g, ' ').trim() | |
| 67 | } | |
| 68 | ||
| 69 | // Adapt message text for target platform | |
| 70 | function formatForTarget (msg, target, rule) { | |
| 71 | const author = msg.author || 'unknown' | |
| 72 | const source = msg.source || 'unknown' | |
| 73 | let text = msg.text || '' | |
| 74 | ||
| 75 | // Strip routing tags from output | |
| 76 | if (rule && rule.filter && rule.filter.tags) { | |
| 77 | text = stripRoutingTags(text, rule.filter.tags) | |
| 78 | } | |
| 79 | ||
| 80 | const attribution = `[via ${source}/${author}]` | |
| 81 | ||
| 82 | switch (target) { | |
| 83 | case 'mastodon': { | |
| 84 | const limit = PLATFORM_LIMITS.mastodon | |
| 85 | const prefix = attribution + ' ' | |
| 86 | const available = limit - prefix.length | |
| 87 | if (text.length > available) { | |
| 88 | text = text.slice(0, available - 3) + '...' | |
| 89 | } | |
| 90 | return prefix + text | |
| 91 | } | |
| 92 | ||
| 93 | case 'keet': { | |
| 94 | return `${attribution} ${text}` | |
| 95 | } | |
| 96 | ||
| 97 | case 'irc': { | |
| 98 | const limit = PLATFORM_LIMITS.irc | |
| 99 | const prefix = `<${author}@${source}> ` | |
| 100 | const available = limit - prefix.length | |
| 101 | if (text.length > available) { | |
| 102 | text = text.slice(0, available - 3) + '...' | |
| 103 | } | |
| 104 | return prefix + text | |
| 105 | } | |
| 106 | ||
| 107 | case 'discord': { | |
| 108 | return `**${author}** (from ${source}):\n${text}` | |
| 109 | } | |
| 110 | ||
| 111 | case 'matrix': { | |
| 112 | return `[${source}] <${author}> ${text}` | |
| 113 | } | |
| 114 | ||
| 115 | default: { | |
| 116 | return `${attribution} ${text}` | |
| 117 | } | |
| 118 | } | |
| 119 | } | |
| 120 | ||
| 121 | const crosspost = { | |
| 122 | name: 'crosspost', | |
| 123 | description: 'Selective syndication -- route content to specific platforms', | |
| 124 | ||
| 125 | defaultConfig: { | |
| 126 | rules: DEFAULT_RULES | |
| 127 | }, | |
| 128 | ||
| 129 | // Process a message through all rules | |
| 130 | // msg: { source, text, tags, author, type, timestamp } | |
| 131 | // config: { rules: [...] } | |
| 132 | // Returns: [{ target, text, metadata }] | |
| 133 | process (msg, config) { | |
| 134 | const rules = (config && config.rules) || DEFAULT_RULES | |
| 135 | const actions = [] | |
| 136 | ||
| 137 | for (const rule of rules) { | |
| 138 | // Source platform must match | |
| 139 | if (msg.source !== rule.from) continue | |
| 140 | ||
| 141 | // Filter must match | |
| 142 | if (!matchFilter(msg, rule.filter)) continue | |
| 143 | ||
| 144 | actions.push({ | |
| 145 | target: rule.to, | |
| 146 | text: formatForTarget(msg, rule.to, rule), | |
| 147 | metadata: { | |
| 148 | crosspostedFrom: msg.source, | |
| 149 | originalAuthor: msg.author, | |
| 150 | originalTimestamp: msg.timestamp || Date.now(), | |
| 151 | rule: { from: rule.from, to: rule.to } | |
| 152 | } | |
| 153 | }) | |
| 154 | } | |
| 155 | ||
| 156 | return actions | |
| 157 | }, | |
| 158 | ||
| 159 | // Generate a daemon script for the DaemonRuntime | |
| 160 | // Wraps process() into the trigger/action format the runtime expects | |
| 161 | toDaemon (config, opts = {}) { | |
| 162 | const targetRoom = opts.targetRoom | |
| 163 | const sourceRoom = opts.sourceRoom | |
| 164 | ||
| 165 | return { | |
| 166 | trigger: { | |
| 167 | type: 'message', | |
| 168 | filter: {} | |
| 169 | }, | |
| 170 | actions: [{ | |
| 171 | type: 'send-message', | |
| 172 | roomId: targetRoom, | |
| 173 | text: `[crosspost from ${(sourceRoom || '').slice(0, 8) || 'source'}] {author}: {text}` | |
| 174 | }], | |
| 175 | enabled: true | |
| 176 | } | |
| 177 | } | |
| 178 | } | |
| 179 | ||
| 180 | export { crosspost, matchFilter, formatForTarget, stripRoutingTags, PLATFORM_LIMITS } |
| … | … | @@ -0,0 +1,23 @@ |
| 1 | // Notify daemon — alert on keyword mentions | |
| 2 | // Sends a notification message when a keyword is mentioned | |
| 3 | ||
| 4 | function notify (opts = {}) { | |
| 5 | const keywords = opts.keywords || [] | |
| 6 | const notifyRoom = opts.notifyRoom | |
| 7 | const pattern = keywords.map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|') | |
| 8 | ||
| 9 | return { | |
| 10 | trigger: { | |
| 11 | type: 'message', | |
| 12 | filter: { pattern } | |
| 13 | }, | |
| 14 | actions: [{ | |
| 15 | type: 'send-message', | |
| 16 | roomId: notifyRoom, | |
| 17 | text: `🔔 Keyword match from {author}: {text}` | |
| 18 | }], | |
| 19 | enabled: true | |
| 20 | } | |
| 21 | } | |
| 22 | ||
| 23 | export { notify } |
| … | … | @@ -0,0 +1,300 @@ |
| 1 | // Summarize daemon — extractive conversation summarizer | |
| 2 | // Collects chat messages and produces summaries without LLM | |
| 3 | ||
| 4 | // Common English stop words to filter from topic extraction | |
| 5 | const STOP_WORDS = new Set([ | |
| 6 | 'the', 'be', 'to', 'of', 'and', 'a', 'in', 'that', 'have', 'i', | |
| 7 | 'it', 'for', 'not', 'on', 'with', 'he', 'as', 'you', 'do', 'at', | |
| 8 | 'this', 'but', 'his', 'by', 'from', 'they', 'we', 'say', 'her', | |
| 9 | 'she', 'or', 'an', 'will', 'my', 'one', 'all', 'would', 'there', | |
| 10 | 'their', 'what', 'so', 'up', 'out', 'if', 'about', 'who', 'get', | |
| 11 | 'which', 'go', 'me', 'when', 'make', 'can', 'like', 'time', 'no', | |
| 12 | 'just', 'him', 'know', 'take', 'people', 'into', 'year', 'your', | |
| 13 | 'good', 'some', 'could', 'them', 'see', 'other', 'than', 'then', | |
| 14 | 'now', 'look', 'only', 'come', 'its', 'over', 'think', 'also', | |
| 15 | 'back', 'after', 'use', 'two', 'how', 'our', 'work', 'first', | |
| 16 | 'well', 'way', 'even', 'new', 'want', 'because', 'any', 'these', | |
| 17 | 'give', 'day', 'most', 'us', 'is', 'am', 'are', 'was', 'were', | |
| 18 | 'been', 'being', 'has', 'had', 'having', 'does', 'did', 'doing', | |
| 19 | 'should', 'more', 'very', 'too', 'here', 'where', 'why', 'yes', | |
| 20 | 'yeah', 'ok', 'okay', 'sure', 'right', 'oh', 'hi', 'hey', 'lol', | |
| 21 | 'haha', 'thanks', 'thank', 'im', 'dont', 'thats', 'gonna', 'got', | |
| 22 | 'really', 'thing', 'things', 'much', 'still', 'let', 'need', | |
| 23 | 'been', 'going', 'did', 'made', 'said', 'each', 'tell', 'does', | |
| 24 | 'set', 'three', 'own', 'point', 'end', 'while', 'same' | |
| 25 | ]) | |
| 26 | ||
| 27 | // Tokenize text into lowercase words, stripping punctuation | |
| 28 | function tokenize (text) { | |
| 29 | if (!text) return [] | |
| 30 | return text | |
| 31 | .toLowerCase() | |
| 32 | .replace(/https?:\/\/\S+/g, '') // strip URLs | |
| 33 | .replace(/[^a-z0-9\s'-]/g, ' ') | |
| 34 | .split(/\s+/) | |
| 35 | .filter(w => w.length > 2 && !STOP_WORDS.has(w)) | |
| 36 | } | |
| 37 | ||
| 38 | // Extract most frequent non-stopword terms (3+ occurrences by default) | |
| 39 | function extractTopics (messages, opts = {}) { | |
| 40 | const minCount = opts.minCount || 3 | |
| 41 | const maxTopics = opts.maxTopics || 10 | |
| 42 | const freq = new Map() | |
| 43 | ||
| 44 | for (const msg of messages) { | |
| 45 | const words = tokenize(msg.text) | |
| 46 | // Count unique words per message to avoid spam-inflation | |
| 47 | const unique = new Set(words) | |
| 48 | for (const word of unique) { | |
| 49 | freq.set(word, (freq.get(word) || 0) + 1) | |
| 50 | } | |
| 51 | } | |
| 52 | ||
| 53 | // Filter by minimum count, sort by frequency descending | |
| 54 | return Array.from(freq.entries()) | |
| 55 | .filter(([, count]) => count >= minCount) | |
| 56 | .sort((a, b) => b[1] - a[1]) | |
| 57 | .slice(0, maxTopics) | |
| 58 | .map(([word, count]) => ({ word, count })) | |
| 59 | } | |
| 60 | ||
| 61 | // Identify key messages: questions, links, or high-engagement content | |
| 62 | function extractKeyMessages (messages, opts = {}) { | |
| 63 | const maxKey = opts.maxKeyMessages || 5 | |
| 64 | const scored = [] | |
| 65 | ||
| 66 | for (const msg of messages) { | |
| 67 | const text = msg.text || '' | |
| 68 | let score = 0 | |
| 69 | ||
| 70 | // Questions get high priority | |
| 71 | if (text.includes('?')) score += 3 | |
| 72 | ||
| 73 | // Links are informational | |
| 74 | if (/https?:\/\/\S+/.test(text)) score += 2 | |
| 75 | ||
| 76 | // Code blocks indicate technical discussion | |
| 77 | if (text.includes('```') || text.includes('`')) score += 1 | |
| 78 | ||
| 79 | // Longer messages tend to be more substantive | |
| 80 | if (text.length > 100) score += 1 | |
| 81 | if (text.length > 200) score += 1 | |
| 82 | ||
| 83 | // Exclamation marks indicate emphasis | |
| 84 | if (text.includes('!')) score += 1 | |
| 85 | ||
| 86 | if (score > 0) { | |
| 87 | scored.push({ msg, score }) | |
| 88 | } | |
| 89 | } | |
| 90 | ||
| 91 | return scored | |
| 92 | .sort((a, b) => b.score - a.score) | |
| 93 | .slice(0, maxKey) | |
| 94 | .map(s => s.msg) | |
| 95 | } | |
| 96 | ||
| 97 | // Get unique participants | |
| 98 | function extractParticipants (messages) { | |
| 99 | const participants = new Map() | |
| 100 | ||
| 101 | for (const msg of messages) { | |
| 102 | const author = msg.author || 'unknown' | |
| 103 | participants.set(author, (participants.get(author) || 0) + 1) | |
| 104 | } | |
| 105 | ||
| 106 | return Array.from(participants.entries()) | |
| 107 | .sort((a, b) => b[1] - a[1]) | |
| 108 | .map(([name, count]) => ({ name, count })) | |
| 109 | } | |
| 110 | ||
| 111 | // Calculate time range of the conversation | |
| 112 | function getTimeRange (messages) { | |
| 113 | if (!messages.length) return { start: 0, end: 0, durationMs: 0 } | |
| 114 | ||
| 115 | const timestamps = messages | |
| 116 | .map(m => m.timestamp || 0) | |
| 117 | .filter(t => t > 0) | |
| 118 | ||
| 119 | if (!timestamps.length) return { start: 0, end: 0, durationMs: 0 } | |
| 120 | ||
| 121 | const start = Math.min(...timestamps) | |
| 122 | const end = Math.max(...timestamps) | |
| 123 | ||
| 124 | return { | |
| 125 | start, | |
| 126 | end, | |
| 127 | durationMs: end - start | |
| 128 | } | |
| 129 | } | |
| 130 | ||
| 131 | // Format duration in human-readable form | |
| 132 | function formatDuration (ms) { | |
| 133 | if (ms < 60000) return 'less than a minute' | |
| 134 | const minutes = Math.floor(ms / 60000) | |
| 135 | if (minutes < 60) return `${minutes} minute${minutes !== 1 ? 's' : ''}` | |
| 136 | const hours = Math.floor(minutes / 60) | |
| 137 | const remaining = minutes % 60 | |
| 138 | if (hours < 24) { | |
| 139 | if (remaining === 0) return `${hours} hour${hours !== 1 ? 's' : ''}` | |
| 140 | return `${hours}h ${remaining}m` | |
| 141 | } | |
| 142 | const days = Math.floor(hours / 24) | |
| 143 | return `${days} day${days !== 1 ? 's' : ''}` | |
| 144 | } | |
| 145 | ||
| 146 | // Build the markdown summary | |
| 147 | function formatSummary (participants, topics, keyMessages, timeRange, messageCount) { | |
| 148 | const lines = [] | |
| 149 | ||
| 150 | lines.push(`## Conversation Summary`) | |
| 151 | lines.push('') | |
| 152 | ||
| 153 | // Stats line | |
| 154 | const parts = [`${messageCount} messages`] | |
| 155 | if (participants.length > 0) { | |
| 156 | parts.push(`${participants.length} participant${participants.length !== 1 ? 's' : ''}`) | |
| 157 | } | |
| 158 | if (timeRange.durationMs > 0) { | |
| 159 | parts.push(`over ${formatDuration(timeRange.durationMs)}`) | |
| 160 | } | |
| 161 | lines.push(parts.join(', ')) | |
| 162 | lines.push('') | |
| 163 | ||
| 164 | // Participants | |
| 165 | if (participants.length > 0) { | |
| 166 | lines.push('### Participants') | |
| 167 | for (const p of participants) { | |
| 168 | lines.push(`- **${p.name}** (${p.count} message${p.count !== 1 ? 's' : ''})`) | |
| 169 | } | |
| 170 | lines.push('') | |
| 171 | } | |
| 172 | ||
| 173 | // Topics | |
| 174 | if (topics.length > 0) { | |
| 175 | lines.push('### Topics') | |
| 176 | for (const t of topics) { | |
| 177 | lines.push(`- ${t.word} (${t.count}x)`) | |
| 178 | } | |
| 179 | lines.push('') | |
| 180 | } | |
| 181 | ||
| 182 | // Key messages | |
| 183 | if (keyMessages.length > 0) { | |
| 184 | lines.push('### Key Messages') | |
| 185 | for (const msg of keyMessages) { | |
| 186 | const author = msg.author || 'unknown' | |
| 187 | const text = (msg.text || '').slice(0, 150) | |
| 188 | const ellipsis = (msg.text || '').length > 150 ? '...' : '' | |
| 189 | lines.push(`- **${author}**: ${text}${ellipsis}`) | |
| 190 | } | |
| 191 | lines.push('') | |
| 192 | } | |
| 193 | ||
| 194 | return lines.join('\n') | |
| 195 | } | |
| 196 | ||
| 197 | const summarize = { | |
| 198 | name: 'summarize', | |
| 199 | description: 'Auto-summarize conversations into digestible format', | |
| 200 | ||
| 201 | defaultConfig: { | |
| 202 | windowSize: 50, | |
| 203 | minMessages: 10, | |
| 204 | includeParticipants: true, | |
| 205 | includeTopics: true, | |
| 206 | topicMinCount: 3, | |
| 207 | maxTopics: 10, | |
| 208 | maxKeyMessages: 5 | |
| 209 | }, | |
| 210 | ||
| 211 | // Process a batch of messages and produce a summary | |
| 212 | // messages: [{ text, author, timestamp, type }] | |
| 213 | // config: { windowSize, minMessages, ... } | |
| 214 | // Returns: { summary, participants, topics, keyMessages, messageCount, timeRange } | |
| 215 | process (messages, config) { | |
| 216 | const cfg = { ...summarize.defaultConfig, ...(config || {}) } | |
| 217 | ||
| 218 | if (!messages || !messages.length) { | |
| 219 | return { | |
| 220 | summary: '', | |
| 221 | participants: [], | |
| 222 | topics: [], | |
| 223 | keyMessages: [], | |
| 224 | messageCount: 0, | |
| 225 | timeRange: { start: 0, end: 0, durationMs: 0 } | |
| 226 | } | |
| 227 | } | |
| 228 | ||
| 229 | // Enforce minimum threshold | |
| 230 | if (messages.length < cfg.minMessages) { | |
| 231 | return { | |
| 232 | summary: '', | |
| 233 | participants: extractParticipants(messages), | |
| 234 | topics: [], | |
| 235 | keyMessages: [], | |
| 236 | messageCount: messages.length, | |
| 237 | timeRange: getTimeRange(messages), | |
| 238 | belowThreshold: true | |
| 239 | } | |
| 240 | } | |
| 241 | ||
| 242 | // Window: take only the last N messages | |
| 243 | const window = messages.length > cfg.windowSize | |
| 244 | ? messages.slice(messages.length - cfg.windowSize) | |
| 245 | : messages | |
| 246 | ||
| 247 | const participants = cfg.includeParticipants | |
| 248 | ? extractParticipants(window) | |
| 249 | : [] | |
| 250 | ||
| 251 | const topics = cfg.includeTopics | |
| 252 | ? extractTopics(window, { minCount: cfg.topicMinCount, maxTopics: cfg.maxTopics }) | |
| 253 | : [] | |
| 254 | ||
| 255 | const keyMessages = extractKeyMessages(window, { maxKeyMessages: cfg.maxKeyMessages }) | |
| 256 | const timeRange = getTimeRange(window) | |
| 257 | const summary = formatSummary(participants, topics, keyMessages, timeRange, window.length) | |
| 258 | ||
| 259 | return { | |
| 260 | summary, | |
| 261 | participants, | |
| 262 | topics, | |
| 263 | keyMessages, | |
| 264 | messageCount: window.length, | |
| 265 | timeRange | |
| 266 | } | |
| 267 | }, | |
| 268 | ||
| 269 | // Generate a daemon script for the DaemonRuntime | |
| 270 | // Collects messages in a buffer and summarizes at threshold | |
| 271 | toDaemon (config, opts = {}) { | |
| 272 | const targetRoom = opts.targetRoom | |
| 273 | ||
| 274 | return { | |
| 275 | trigger: { | |
| 276 | type: 'message', | |
| 277 | filter: {} | |
| 278 | }, | |
| 279 | actions: [{ | |
| 280 | type: 'log', | |
| 281 | message: 'Message buffered for summary' | |
| 282 | }], | |
| 283 | enabled: true, | |
| 284 | _targetRoom: targetRoom, | |
| 285 | _threshold: (config && config.windowSize) || 50 | |
| 286 | } | |
| 287 | } | |
| 288 | } | |
| 289 | ||
| 290 | export { | |
| 291 | summarize, | |
| 292 | extractTopics, | |
| 293 | extractKeyMessages, | |
| 294 | extractParticipants, | |
| 295 | getTimeRange, | |
| 296 | formatSummary, | |
| 297 | formatDuration, | |
| 298 | tokenize, | |
| 299 | STOP_WORDS | |
| 300 | } |
| … | … | @@ -0,0 +1,5 @@ |
| 1 | import { DaemonRuntime } from './lib/runtime.js' | |
| 2 | import { TriggerEngine } from './lib/triggers.js' | |
| 3 | import { ActionRunner } from './lib/actions.js' | |
| 4 | ||
| 5 | export { DaemonRuntime, TriggerEngine, ActionRunner } |
| … | … | @@ -0,0 +1,103 @@ |
| 1 | import http from 'http' | |
| 2 | import net from 'net' | |
| 3 | ||
| 4 | class ActionRunner { | |
| 5 | constructor (opts = {}) { | |
| 6 | this.keetHost = opts.keetHost || 'localhost' | |
| 7 | this.keetPort = opts.keetPort || 3117 | |
| 8 | this._log = opts.onLog || console.log | |
| 9 | } | |
| 10 | ||
| 11 | async run (action, data) { | |
| 12 | switch (action.type) { | |
| 13 | case 'send-message': | |
| 14 | return this._sendMessage(action, data) | |
| 15 | case 'http-call': | |
| 16 | return this._httpCall(action, data) | |
| 17 | case 'log': | |
| 18 | return this._log(action, data) | |
| 19 | default: | |
| 20 | throw new Error(`Unknown action type: ${action.type}`) | |
| 21 | } | |
| 22 | } | |
| 23 | ||
| 24 | async _sendMessage (action, data) { | |
| 25 | const roomId = action.roomId || data.roomId | |
| 26 | let text = action.text || '' | |
| 27 | ||
| 28 | // Template substitution | |
| 29 | text = text | |
| 30 | .replace(/\{author\}/g, data.author || data.name || '') | |
| 31 | .replace(/\{text\}/g, data.text || '') | |
| 32 | .replace(/\{channel\}/g, data.channel || '') | |
| 33 | .replace(/\{timestamp\}/g, new Date().toISOString()) | |
| 34 | ||
| 35 | if (!roomId || !text) return { sent: false, reason: 'missing roomId or text' } | |
| 36 | ||
| 37 | await this._keetRpc('send', { roomId, text }) | |
| 38 | return { sent: true, roomId, text } | |
| 39 | } | |
| 40 | ||
| 41 | async _httpCall (action) { | |
| 42 | const url = new URL(action.url) | |
| 43 | return new Promise((resolve, reject) => { | |
| 44 | const req = http.request({ | |
| 45 | hostname: url.hostname, | |
| 46 | port: url.port || 80, | |
| 47 | path: url.pathname + url.search, | |
| 48 | method: action.method || 'GET', | |
| 49 | headers: action.headers || {} | |
| 50 | }, (res) => { | |
| 51 | let body = '' | |
| 52 | res.on('data', (c) => { body += c.toString() }) | |
| 53 | res.on('end', () => resolve({ status: res.statusCode, body })) | |
| 54 | }) | |
| 55 | req.on('error', reject) | |
| 56 | req.setTimeout(10000, () => req.destroy(new Error('timeout'))) | |
| 57 | if (action.body) req.write(action.body) | |
| 58 | req.end() | |
| 59 | }) | |
| 60 | } | |
| 61 | ||
| 62 | _logAction (action, data) { | |
| 63 | const msg = action.message || JSON.stringify(data) | |
| 64 | this._log(`[daemon] ${msg}`) | |
| 65 | return { logged: true } | |
| 66 | } | |
| 67 | ||
| 68 | // Raw TCP for keet-daemon (duplicate Content-Length workaround) | |
| 69 | _keetRpc (method, params) { | |
| 70 | return new Promise((resolve, reject) => { | |
| 71 | const socket = net.connect(this.keetPort, this.keetHost) | |
| 72 | const payload = JSON.stringify({ method, params }) | |
| 73 | ||
| 74 | socket.on('connect', () => { | |
| 75 | let req = `POST /rpc HTTP/1.1\r\n` | |
| 76 | req += `Host: ${this.keetHost}:${this.keetPort}\r\n` | |
| 77 | req += `Content-Type: application/json\r\n` | |
| 78 | req += `Content-Length: ${Buffer.byteLength(payload)}\r\n` | |
| 79 | req += `Connection: close\r\n\r\n` | |
| 80 | req += payload | |
| 81 | socket.write(req) | |
| 82 | }) | |
| 83 | ||
| 84 | let data = '' | |
| 85 | socket.on('data', (chunk) => { data += chunk.toString() }) | |
| 86 | socket.on('end', () => { | |
| 87 | const idx = data.indexOf('\r\n\r\n') | |
| 88 | if (idx === -1) return reject(new Error('No body')) | |
| 89 | try { | |
| 90 | const parsed = JSON.parse(data.slice(idx + 4)) | |
| 91 | if (!parsed.ok) reject(new Error(parsed.error || 'RPC error')) | |
| 92 | else resolve(parsed.data) | |
| 93 | } catch { | |
| 94 | reject(new Error('Invalid response')) | |
| 95 | } | |
| 96 | }) | |
| 97 | socket.on('error', reject) | |
| 98 | socket.setTimeout(10000, () => socket.destroy(new Error('timeout'))) | |
| 99 | }) | |
| 100 | } | |
| 101 | } | |
| 102 | ||
| 103 | export { ActionRunner } |
| … | … | @@ -0,0 +1,95 @@ |
| 1 | import { TriggerEngine } from './triggers.js' | |
| 2 | import { ActionRunner } from './actions.js' | |
| 3 | import { DaemonStore } from './store.js' | |
| 4 | ||
| 5 | class DaemonRuntime { | |
| 6 | constructor (storage, opts = {}) { | |
| 7 | this._storage = storage | |
| 8 | this._triggers = new TriggerEngine() | |
| 9 | this._actions = new ActionRunner({ | |
| 10 | keetHost: opts.keetHost || 'localhost', | |
| 11 | keetPort: opts.keetPort || 3117, | |
| 12 | onLog: opts.onLog || console.log | |
| 13 | }) | |
| 14 | this._store = new DaemonStore(storage) | |
| 15 | this._ready = false | |
| 16 | this._onLog = opts.onLog || console.log | |
| 17 | } | |
| 18 | ||
| 19 | async ready () { | |
| 20 | if (this._ready) return | |
| 21 | await this._store.ready() | |
| 22 | this._ready = true | |
| 23 | } | |
| 24 | ||
| 25 | async load (id, script) { | |
| 26 | await this.ready() | |
| 27 | ||
| 28 | // Register trigger | |
| 29 | this._triggers.register(id, { | |
| 30 | type: script.trigger.type, | |
| 31 | filter: script.trigger.filter || {}, | |
| 32 | enabled: script.enabled ?? true, | |
| 33 | action: async (data) => { | |
| 34 | const results = [] | |
| 35 | for (const action of script.actions) { | |
| 36 | const result = await this._actions.run(action, data) | |
| 37 | results.push(result) | |
| 38 | } | |
| 39 | ||
| 40 | // Log execution | |
| 41 | await this._store.logEvent(id, { | |
| 42 | timestamp: Date.now(), | |
| 43 | trigger: script.trigger.type, | |
| 44 | results | |
| 45 | }) | |
| 46 | ||
| 47 | return results | |
| 48 | } | |
| 49 | }) | |
| 50 | ||
| 51 | // Save to persistent store | |
| 52 | await this._store.saveScript(id, script) | |
| 53 | ||
| 54 | this._onLog(`[daemon] Loaded script: ${id}`) | |
| 55 | } | |
| 56 | ||
| 57 | async unload (id) { | |
| 58 | this._triggers.unregister(id) | |
| 59 | await this._store.deleteScript(id) | |
| 60 | this._onLog(`[daemon] Unloaded script: ${id}`) | |
| 61 | } | |
| 62 | ||
| 63 | async loadAll () { | |
| 64 | await this.ready() | |
| 65 | const scripts = await this._store.listScripts() | |
| 66 | for (const script of scripts) { | |
| 67 | await this.load(script.id, script) | |
| 68 | } | |
| 69 | return scripts.length | |
| 70 | } | |
| 71 | ||
| 72 | enable (id) { this._triggers.enable(id) } | |
| 73 | disable (id) { this._triggers.disable(id) } | |
| 74 | ||
| 75 | // Fire an event through the trigger engine | |
| 76 | async fire (eventType, data) { | |
| 77 | return this._triggers.fire(eventType, data) | |
| 78 | } | |
| 79 | ||
| 80 | list () { | |
| 81 | return this._triggers.list() | |
| 82 | } | |
| 83 | ||
| 84 | async getLog (id, limit = 20) { | |
| 85 | return this._store.getLog(id, limit) | |
| 86 | } | |
| 87 | ||
| 88 | async close () { | |
| 89 | this._triggers.destroy() | |
| 90 | await this._store.close() | |
| 91 | this._ready = false | |
| 92 | } | |
| 93 | } | |
| 94 | ||
| 95 | export { DaemonRuntime } |
| … | … | @@ -0,0 +1,89 @@ |
| 1 | import Corestore from 'corestore' | |
| 2 | import Hyperbee from 'hyperbee' | |
| 3 | ||
| 4 | class DaemonStore { | |
| 5 | constructor (storage) { | |
| 6 | this._storage = storage | |
| 7 | this._store = null | |
| 8 | this._db = null | |
| 9 | this._ready = false | |
| 10 | } | |
| 11 | ||
| 12 | async ready () { | |
| 13 | if (this._ready) return | |
| 14 | ||
| 15 | this._store = new Corestore(this._storage) | |
| 16 | const core = this._store.get({ name: 'hyper-daemon' }) | |
| 17 | this._db = new Hyperbee(core, { | |
| 18 | keyEncoding: 'utf-8', | |
| 19 | valueEncoding: 'utf-8' | |
| 20 | }) | |
| 21 | await this._db.ready() | |
| 22 | this._ready = true | |
| 23 | } | |
| 24 | ||
| 25 | async saveScript (id, script) { | |
| 26 | await this.ready() | |
| 27 | await this._db.put(`script/${id}`, JSON.stringify(script)) | |
| 28 | } | |
| 29 | ||
| 30 | async getScript (id) { | |
| 31 | await this.ready() | |
| 32 | const node = await this._db.get(`script/${id}`) | |
| 33 | if (!node) return null | |
| 34 | try { | |
| 35 | return JSON.parse(node.value) | |
| 36 | } catch { | |
| 37 | return null | |
| 38 | } | |
| 39 | } | |
| 40 | ||
| 41 | async deleteScript (id) { | |
| 42 | await this.ready() | |
| 43 | await this._db.del(`script/${id}`) | |
| 44 | } | |
| 45 | ||
| 46 | async listScripts () { | |
| 47 | await this.ready() | |
| 48 | const scripts = [] | |
| 49 | for await (const node of this._db.createReadStream({ | |
| 50 | gte: 'script/', | |
| 51 | lt: 'script/\xff' | |
| 52 | })) { | |
| 53 | const id = node.key.slice('script/'.length) | |
| 54 | try { | |
| 55 | scripts.push({ id, ...JSON.parse(node.value) }) | |
| 56 | } catch {} | |
| 57 | } | |
| 58 | return scripts | |
| 59 | } | |
| 60 | ||
| 61 | async logEvent (id, event) { | |
| 62 | await this.ready() | |
| 63 | const key = `log/${id}/${Date.now()}` | |
| 64 | await this._db.put(key, JSON.stringify(event)) | |
| 65 | } | |
| 66 | ||
| 67 | async getLog (id, limit = 20) { | |
| 68 | await this.ready() | |
| 69 | const events = [] | |
| 70 | for await (const node of this._db.createReadStream({ | |
| 71 | gte: `log/${id}/`, | |
| 72 | lt: `log/${id}/\xff`, | |
| 73 | reverse: true, | |
| 74 | limit | |
| 75 | })) { | |
| 76 | try { | |
| 77 | events.push(JSON.parse(node.value)) | |
| 78 | } catch {} | |
| 79 | } | |
| 80 | return events | |
| 81 | } | |
| 82 | ||
| 83 | async close () { | |
| 84 | if (this._store) await this._store.close() | |
| 85 | this._ready = false | |
| 86 | } | |
| 87 | } | |
| 88 | ||
| 89 | export { DaemonStore } |
| … | … | @@ -0,0 +1,128 @@ |
| 1 | class TriggerEngine { | |
| 2 | constructor () { | |
| 3 | this._triggers = new Map() | |
| 4 | this._timers = new Map() | |
| 5 | } | |
| 6 | ||
| 7 | register (id, trigger) { | |
| 8 | this._triggers.set(id, { | |
| 9 | id, | |
| 10 | type: trigger.type, | |
| 11 | filter: trigger.filter || {}, | |
| 12 | action: trigger.action, | |
| 13 | enabled: trigger.enabled ?? true | |
| 14 | }) | |
| 15 | } | |
| 16 | ||
| 17 | unregister (id) { | |
| 18 | this._triggers.delete(id) | |
| 19 | if (this._timers.has(id)) { | |
| 20 | clearInterval(this._timers.get(id)) | |
| 21 | this._timers.delete(id) | |
| 22 | } | |
| 23 | } | |
| 24 | ||
| 25 | enable (id) { | |
| 26 | const t = this._triggers.get(id) | |
| 27 | if (t) t.enabled = true | |
| 28 | } | |
| 29 | ||
| 30 | disable (id) { | |
| 31 | const t = this._triggers.get(id) | |
| 32 | if (t) t.enabled = false | |
| 33 | } | |
| 34 | ||
| 35 | // Fire event and run matching trigger actions | |
| 36 | async fire (eventType, data) { | |
| 37 | const results = [] | |
| 38 | ||
| 39 | for (const [id, trigger] of this._triggers) { | |
| 40 | if (!trigger.enabled) continue | |
| 41 | if (trigger.type !== eventType) continue | |
| 42 | ||
| 43 | if (this._matchesFilter(trigger.filter, data)) { | |
| 44 | try { | |
| 45 | const result = await trigger.action(data) | |
| 46 | results.push({ id, ok: true, result }) | |
| 47 | } catch (err) { | |
| 48 | results.push({ id, ok: false, error: err.message }) | |
| 49 | } | |
| 50 | } | |
| 51 | } | |
| 52 | ||
| 53 | return results | |
| 54 | } | |
| 55 | ||
| 56 | _matchesFilter (filter, data) { | |
| 57 | if (!filter || Object.keys(filter).length === 0) return true | |
| 58 | ||
| 59 | // Channel filter | |
| 60 | if (filter.channel && data.channel !== filter.channel) return false | |
| 61 | ||
| 62 | // Author filter | |
| 63 | if (filter.author && data.author !== filter.author) return false | |
| 64 | ||
| 65 | // Content contains filter | |
| 66 | if (filter.contains && !data.text?.includes(filter.contains)) return false | |
| 67 | ||
| 68 | // Regex pattern filter | |
| 69 | if (filter.pattern) { | |
| 70 | const regex = new RegExp(filter.pattern) | |
| 71 | if (!regex.test(data.text || '')) return false | |
| 72 | } | |
| 73 | ||
| 74 | // Tag filter | |
| 75 | if (filter.tags) { | |
| 76 | const tags = filter.tags | |
| 77 | const text = (data.text || '').toLowerCase() | |
| 78 | if (!tags.some(tag => text.includes(tag.toLowerCase()))) return false | |
| 79 | } | |
| 80 | ||
| 81 | return true | |
| 82 | } | |
| 83 | ||
| 84 | // Start a scheduled trigger (cron-like) | |
| 85 | startSchedule (id, intervalMs, action) { | |
| 86 | this.register(id, { | |
| 87 | type: 'schedule', | |
| 88 | action | |
| 89 | }) | |
| 90 | ||
| 91 | const timer = setInterval(async () => { | |
| 92 | const trigger = this._triggers.get(id) | |
| 93 | if (!trigger || !trigger.enabled) return | |
| 94 | try { | |
| 95 | await trigger.action({ timestamp: Date.now(), triggerId: id }) | |
| 96 | } catch {} | |
| 97 | }, intervalMs) | |
| 98 | ||
| 99 | this._timers.set(id, timer) | |
| 100 | } | |
| 101 | ||
| 102 | list () { | |
| 103 | const result = [] | |
| 104 | for (const [id, t] of this._triggers) { | |
| 105 | result.push({ | |
| 106 | id, | |
| 107 | type: t.type, | |
| 108 | enabled: t.enabled, | |
| 109 | hasFilter: Object.keys(t.filter).length > 0 | |
| 110 | }) | |
| 111 | } | |
| 112 | return result | |
| 113 | } | |
| 114 | ||
| 115 | get count () { | |
| 116 | return this._triggers.size | |
| 117 | } | |
| 118 | ||
| 119 | destroy () { | |
| 120 | for (const timer of this._timers.values()) { | |
| 121 | clearInterval(timer) | |
| 122 | } | |
| 123 | this._timers.clear() | |
| 124 | this._triggers.clear() | |
| 125 | } | |
| 126 | } | |
| 127 | ||
| 128 | export { TriggerEngine } |
| … | … | @@ -0,0 +1,1443 @@ |
| 1 | { | |
| 2 | "name": "hyper-daemon", | |
| 3 | "version": "1.0.0", | |
| 4 | "lockfileVersion": 3, | |
| 5 | "requires": true, | |
| 6 | "packages": { | |
| 7 | "": { | |
| 8 | "name": "hyper-daemon", | |
| 9 | "version": "1.0.0", | |
| 10 | "license": "MIT", | |
| 11 | "dependencies": { | |
| 12 | "b4a": "^1.0.0", | |
| 13 | "corestore": "^6.0.0", | |
| 14 | "hyperbee": "^2.0.0", | |
| 15 | "hypercore": "^10.0.0" | |
| 16 | }, | |
| 17 | "devDependencies": { | |
| 18 | "brittle": "^3.0.0" | |
| 19 | } | |
| 20 | }, | |
| 21 | "node_modules/@hyperswarm/secret-stream": { | |
| 22 | "version": "6.9.1", | |
| 23 | "resolved": "https://registry.npmjs.org/@hyperswarm/secret-stream/-/secret-stream-6.9.1.tgz", | |
| 24 | "integrity": "sha512-xb0S5y3YJwBakD77JOGBHlBxdp63mHClZoXBYoLv+9wH8e054ESKlmQptWqjJK5dv5VMUIVYOJB4MaOpB0JdGw==", | |
| 25 | "license": "Apache-2.0", | |
| 26 | "dependencies": { | |
| 27 | "b4a": "^1.1.0", | |
| 28 | "hypercore-crypto": "^3.3.1", | |
| 29 | "noise-curve-ed": "^2.0.1", | |
| 30 | "noise-handshake": "^4.0.0", | |
| 31 | "sodium-secretstream": "^1.1.0", | |
| 32 | "sodium-universal": "^5.0.0", | |
| 33 | "streamx": "^2.14.0", | |
| 34 | "timeout-refresh": "^2.0.0", | |
| 35 | "unslab": "^1.3.0" | |
| 36 | } | |
| 37 | }, | |
| 38 | "node_modules/@hyperswarm/secret-stream/node_modules/sodium-native": { | |
| 39 | "version": "5.0.10", | |
| 40 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 41 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 42 | "license": "MIT", | |
| 43 | "dependencies": { | |
| 44 | "require-addon": "^1.1.0", | |
| 45 | "which-runtime": "^1.2.1" | |
| 46 | }, | |
| 47 | "engines": { | |
| 48 | "bare": ">=1.16.0" | |
| 49 | } | |
| 50 | }, | |
| 51 | "node_modules/@hyperswarm/secret-stream/node_modules/sodium-universal": { | |
| 52 | "version": "5.0.1", | |
| 53 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 54 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 55 | "license": "MIT", | |
| 56 | "dependencies": { | |
| 57 | "sodium-native": "^5.0.1" | |
| 58 | }, | |
| 59 | "peerDependencies": { | |
| 60 | "sodium-javascript": "~0.8.0" | |
| 61 | }, | |
| 62 | "peerDependenciesMeta": { | |
| 63 | "sodium-javascript": { | |
| 64 | "optional": true | |
| 65 | } | |
| 66 | } | |
| 67 | }, | |
| 68 | "node_modules/@jridgewell/resolve-uri": { | |
| 69 | "version": "3.1.2", | |
| 70 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", | |
| 71 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", | |
| 72 | "dev": true, | |
| 73 | "license": "MIT", | |
| 74 | "engines": { | |
| 75 | "node": ">=6.0.0" | |
| 76 | } | |
| 77 | }, | |
| 78 | "node_modules/@jridgewell/sourcemap-codec": { | |
| 79 | "version": "1.5.5", | |
| 80 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", | |
| 81 | "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", | |
| 82 | "dev": true, | |
| 83 | "license": "MIT" | |
| 84 | }, | |
| 85 | "node_modules/@jridgewell/trace-mapping": { | |
| 86 | "version": "0.3.31", | |
| 87 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", | |
| 88 | "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", | |
| 89 | "dev": true, | |
| 90 | "license": "MIT", | |
| 91 | "dependencies": { | |
| 92 | "@jridgewell/resolve-uri": "^3.1.0", | |
| 93 | "@jridgewell/sourcemap-codec": "^1.4.14" | |
| 94 | } | |
| 95 | }, | |
| 96 | "node_modules/@types/istanbul-lib-coverage": { | |
| 97 | "version": "2.0.6", | |
| 98 | "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", | |
| 99 | "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", | |
| 100 | "dev": true, | |
| 101 | "license": "MIT" | |
| 102 | }, | |
| 103 | "node_modules/b4a": { | |
| 104 | "version": "1.7.4", | |
| 105 | "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.4.tgz", | |
| 106 | "integrity": "sha512-u20zJLDaSWpxaZ+zaAkEIB2dZZ1o+DF4T/MRbmsvGp9nletHOyiai19OzX1fF8xUBYsO1bPXxODvcd0978pnug==", | |
| 107 | "license": "Apache-2.0", | |
| 108 | "peerDependencies": { | |
| 109 | "react-native-b4a": "*" | |
| 110 | }, | |
| 111 | "peerDependenciesMeta": { | |
| 112 | "react-native-b4a": { | |
| 113 | "optional": true | |
| 114 | } | |
| 115 | } | |
| 116 | }, | |
| 117 | "node_modules/bare-addon-resolve": { | |
| 118 | "version": "1.10.0", | |
| 119 | "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.0.tgz", | |
| 120 | "integrity": "sha512-sSd0jieRJlDaODOzj0oe0RjFVC1QI0ZIjGIdPkbrTXsdVVtENg14c+lHHAhHwmWCZ2nQlMhy8jA3Y5LYPc/isA==", | |
| 121 | "license": "Apache-2.0", | |
| 122 | "dependencies": { | |
| 123 | "bare-module-resolve": "^1.10.0", | |
| 124 | "bare-semver": "^1.0.0" | |
| 125 | }, | |
| 126 | "peerDependencies": { | |
| 127 | "bare-url": "*" | |
| 128 | }, | |
| 129 | "peerDependenciesMeta": { | |
| 130 | "bare-url": { | |
| 131 | "optional": true | |
| 132 | } | |
| 133 | } | |
| 134 | }, | |
| 135 | "node_modules/bare-ansi-escapes": { | |
| 136 | "version": "2.2.3", | |
| 137 | "resolved": "https://registry.npmjs.org/bare-ansi-escapes/-/bare-ansi-escapes-2.2.3.tgz", | |
| 138 | "integrity": "sha512-02ES4/E2RbrtZSnHJ9LntBhYkLA6lPpSEeP8iqS3MccBIVhVBlEmruF1I7HZqx5Q8aiTeYfQVeqmrU9YO2yYoQ==", | |
| 139 | "dev": true, | |
| 140 | "license": "Apache-2.0", | |
| 141 | "dependencies": { | |
| 142 | "bare-stream": "^2.6.5" | |
| 143 | }, | |
| 144 | "peerDependencies": { | |
| 145 | "bare-buffer": "*" | |
| 146 | }, | |
| 147 | "peerDependenciesMeta": { | |
| 148 | "bare-buffer": { | |
| 149 | "optional": true | |
| 150 | } | |
| 151 | } | |
| 152 | }, | |
| 153 | "node_modules/bare-assert": { | |
| 154 | "version": "1.2.0", | |
| 155 | "resolved": "https://registry.npmjs.org/bare-assert/-/bare-assert-1.2.0.tgz", | |
| 156 | "integrity": "sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==", | |
| 157 | "dev": true, | |
| 158 | "license": "Apache-2.0", | |
| 159 | "dependencies": { | |
| 160 | "bare-inspect": "^3.1.2" | |
| 161 | } | |
| 162 | }, | |
| 163 | "node_modules/bare-bundle": { | |
| 164 | "version": "1.10.0", | |
| 165 | "resolved": "https://registry.npmjs.org/bare-bundle/-/bare-bundle-1.10.0.tgz", | |
| 166 | "integrity": "sha512-4LVlnJAHr00Hh6Vu6ZUJS38rcEtJT3b3vChXSsBsJ2mk1TN0lQ+gzd+Dw5L0aV7uqDZv84smuwW+O02X7PfDlw==", | |
| 167 | "dev": true, | |
| 168 | "license": "Apache-2.0", | |
| 169 | "peerDependencies": { | |
| 170 | "bare-buffer": "*", | |
| 171 | "bare-url": "*" | |
| 172 | }, | |
| 173 | "peerDependenciesMeta": { | |
| 174 | "bare-buffer": { | |
| 175 | "optional": true | |
| 176 | }, | |
| 177 | "bare-url": { | |
| 178 | "optional": true | |
| 179 | } | |
| 180 | } | |
| 181 | }, | |
| 182 | "node_modules/bare-cov": { | |
| 183 | "version": "1.2.1", | |
| 184 | "resolved": "https://registry.npmjs.org/bare-cov/-/bare-cov-1.2.1.tgz", | |
| 185 | "integrity": "sha512-SHNqb/WG9udUEj8m5qGSehoUQ9o0Xzv8nrf4HIdmG+KNU/XdhPLhTP5VPrHnSAMAXhAzEB4Q+2IYZ0x9qPzN1g==", | |
| 186 | "dev": true, | |
| 187 | "license": "Apache-2.0", | |
| 188 | "dependencies": { | |
| 189 | "bare-fs": "^4.1.2", | |
| 190 | "bare-inspector": "^4.0.1", | |
| 191 | "bare-path": "^3.0.0", | |
| 192 | "bare-process": "^4.2.1", | |
| 193 | "bare-url": "^2.1.5", | |
| 194 | "bare-v8-to-istanbul": "^1.0.2", | |
| 195 | "picomatch": "^4.0.2", | |
| 196 | "which-runtime": "^1.2.1" | |
| 197 | } | |
| 198 | }, | |
| 199 | "node_modules/bare-crypto": { | |
| 200 | "version": "1.13.0", | |
| 201 | "resolved": "https://registry.npmjs.org/bare-crypto/-/bare-crypto-1.13.0.tgz", | |
| 202 | "integrity": "sha512-RQl13yD+YTACWUZHMMck0C6LNjXgGgRyhVC557ag0xIrkxXf4IQwpnc8WOB58f4k5kSzKhIjy05oW3HUBrFpSQ==", | |
| 203 | "dev": true, | |
| 204 | "license": "Apache-2.0", | |
| 205 | "dependencies": { | |
| 206 | "bare-stream": "^2.6.3" | |
| 207 | }, | |
| 208 | "peerDependencies": { | |
| 209 | "bare-buffer": "*" | |
| 210 | }, | |
| 211 | "peerDependenciesMeta": { | |
| 212 | "bare-buffer": { | |
| 213 | "optional": true | |
| 214 | } | |
| 215 | } | |
| 216 | }, | |
| 217 | "node_modules/bare-debug-log": { | |
| 218 | "version": "2.0.0", | |
| 219 | "resolved": "https://registry.npmjs.org/bare-debug-log/-/bare-debug-log-2.0.0.tgz", | |
| 220 | "integrity": "sha512-Vi42PkMQsNV9PUpx2Gl1hikshx5O9FzMJ6o9Nnopseg7qLBBK7Nl31d0RHcfwLEAfmcPApytpc0ZFfq68u22FQ==", | |
| 221 | "dev": true, | |
| 222 | "license": "Apache-2.0", | |
| 223 | "dependencies": { | |
| 224 | "bare-os": "^3.0.1" | |
| 225 | } | |
| 226 | }, | |
| 227 | "node_modules/bare-dns": { | |
| 228 | "version": "2.1.4", | |
| 229 | "resolved": "https://registry.npmjs.org/bare-dns/-/bare-dns-2.1.4.tgz", | |
| 230 | "integrity": "sha512-abwjHmpWqSRNB7V5615QxPH92L71AVzFm/kKTs8VYiNTAi2xVdonpv0BjJ0hwXLwomoW+xsSOPjW6PZPO14asg==", | |
| 231 | "dev": true, | |
| 232 | "license": "Apache-2.0", | |
| 233 | "engines": { | |
| 234 | "bare": ">=1.7.0" | |
| 235 | } | |
| 236 | }, | |
| 237 | "node_modules/bare-encoding": { | |
| 238 | "version": "1.0.3", | |
| 239 | "resolved": "https://registry.npmjs.org/bare-encoding/-/bare-encoding-1.0.3.tgz", | |
| 240 | "integrity": "sha512-Kqf+t/azs13lUeyK4Tb7ha4wdLRXKWCXQ8w1rVmt7KtoPCPdHD/Xwt7LBIsCSwwGglrcmblo5VOLa5avkJqULA==", | |
| 241 | "dev": true, | |
| 242 | "license": "Apache-2.0", | |
| 243 | "peerDependencies": { | |
| 244 | "bare-buffer": "*" | |
| 245 | }, | |
| 246 | "peerDependenciesMeta": { | |
| 247 | "bare-buffer": { | |
| 248 | "optional": true | |
| 249 | } | |
| 250 | } | |
| 251 | }, | |
| 252 | "node_modules/bare-env": { | |
| 253 | "version": "3.0.0", | |
| 254 | "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.0.tgz", | |
| 255 | "integrity": "sha512-0u964P5ZLAxTi+lW4Kjp7YRJQ5gZr9ycYOtjLxsSrupgMz3sn5Z9n4SH/JIifHwvadsf1brA2JAjP+9IOWwTiw==", | |
| 256 | "dev": true, | |
| 257 | "license": "Apache-2.0", | |
| 258 | "dependencies": { | |
| 259 | "bare-os": "^3.0.1" | |
| 260 | } | |
| 261 | }, | |
| 262 | "node_modules/bare-events": { | |
| 263 | "version": "2.8.2", | |
| 264 | "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", | |
| 265 | "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", | |
| 266 | "license": "Apache-2.0", | |
| 267 | "peerDependencies": { | |
| 268 | "bare-abort-controller": "*" | |
| 269 | }, | |
| 270 | "peerDependenciesMeta": { | |
| 271 | "bare-abort-controller": { | |
| 272 | "optional": true | |
| 273 | } | |
| 274 | } | |
| 275 | }, | |
| 276 | "node_modules/bare-format": { | |
| 277 | "version": "1.0.1", | |
| 278 | "resolved": "https://registry.npmjs.org/bare-format/-/bare-format-1.0.1.tgz", | |
| 279 | "integrity": "sha512-1oS+LZrWK6tnYnvNSHDGljc2MPunRxwhpFriuCgzNF+oklrnwmBKD91tS0yt+jpl2j3UgcSDzBIMiVTvLs9A8w==", | |
| 280 | "dev": true, | |
| 281 | "license": "Apache-2.0", | |
| 282 | "dependencies": { | |
| 283 | "bare-inspect": "^3.0.0" | |
| 284 | } | |
| 285 | }, | |
| 286 | "node_modules/bare-fs": { | |
| 287 | "version": "4.5.4", | |
| 288 | "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.4.tgz", | |
| 289 | "integrity": "sha512-POK4oplfA7P7gqvetNmCs4CNtm9fNsx+IAh7jH7GgU0OJdge2rso0R20TNWVq6VoWcCvsTdlNDaleLHGaKx8CA==", | |
| 290 | "license": "Apache-2.0", | |
| 291 | "dependencies": { | |
| 292 | "bare-events": "^2.5.4", | |
| 293 | "bare-path": "^3.0.0", | |
| 294 | "bare-stream": "^2.6.4", | |
| 295 | "bare-url": "^2.2.2", | |
| 296 | "fast-fifo": "^1.3.2" | |
| 297 | }, | |
| 298 | "engines": { | |
| 299 | "bare": ">=1.16.0" | |
| 300 | }, | |
| 301 | "peerDependencies": { | |
| 302 | "bare-buffer": "*" | |
| 303 | }, | |
| 304 | "peerDependenciesMeta": { | |
| 305 | "bare-buffer": { | |
| 306 | "optional": true | |
| 307 | } | |
| 308 | } | |
| 309 | }, | |
| 310 | "node_modules/bare-hrtime": { | |
| 311 | "version": "2.1.1", | |
| 312 | "resolved": "https://registry.npmjs.org/bare-hrtime/-/bare-hrtime-2.1.1.tgz", | |
| 313 | "integrity": "sha512-VMb3tHo05gsnbu3OXTmkDiwTjMlOsbQmKoysKqKEyR09m77TuDrYFbj3Q5GGk10dAKsUHrnXmwCaeJqzVpB5ZA==", | |
| 314 | "dev": true, | |
| 315 | "license": "Apache-2.0" | |
| 316 | }, | |
| 317 | "node_modules/bare-http-parser": { | |
| 318 | "version": "1.0.1", | |
| 319 | "resolved": "https://registry.npmjs.org/bare-http-parser/-/bare-http-parser-1.0.1.tgz", | |
| 320 | "integrity": "sha512-A3LTDTcELcmNJ3g5liIaS038v/BQxOhA9cjhBESn7eoV7QCuMoIRBKLDadDe08flxyLbxI2f+1l2MZ/5+HnKPA==", | |
| 321 | "dev": true, | |
| 322 | "license": "Apache-2.0" | |
| 323 | }, | |
| 324 | "node_modules/bare-http1": { | |
| 325 | "version": "4.2.3", | |
| 326 | "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.2.3.tgz", | |
| 327 | "integrity": "sha512-zyvHF3xzvBJNpuw0QydE9etTn2AyC/TRVDWTo8Ed3LQEEGaFOkv6KHhaJPlf4eWEeTekk41Fowie+fTsXoNxkA==", | |
| 328 | "dev": true, | |
| 329 | "license": "Apache-2.0", | |
| 330 | "dependencies": { | |
| 331 | "bare-events": "^2.6.0", | |
| 332 | "bare-http-parser": "^1.0.0", | |
| 333 | "bare-stream": "^2.3.0", | |
| 334 | "bare-tcp": "^2.2.0" | |
| 335 | }, | |
| 336 | "peerDependencies": { | |
| 337 | "bare-buffer": "*", | |
| 338 | "bare-url": "*" | |
| 339 | }, | |
| 340 | "peerDependenciesMeta": { | |
| 341 | "bare-buffer": { | |
| 342 | "optional": true | |
| 343 | }, | |
| 344 | "bare-url": { | |
| 345 | "optional": true | |
| 346 | } | |
| 347 | } | |
| 348 | }, | |
| 349 | "node_modules/bare-https": { | |
| 350 | "version": "2.1.2", | |
| 351 | "resolved": "https://registry.npmjs.org/bare-https/-/bare-https-2.1.2.tgz", | |
| 352 | "integrity": "sha512-Q+TTydUDsuKQJvh8dX2dvOXCR9fM3xR5TBmKaFrs5p7Lj7XbKX7v4vIUJ36H0SXg2xCOQxXKIbjwrLg5tfJNYg==", | |
| 353 | "dev": true, | |
| 354 | "license": "Apache-2.0", | |
| 355 | "dependencies": { | |
| 356 | "bare-http1": "^4.0.0", | |
| 357 | "bare-tcp": "^2.2.0", | |
| 358 | "bare-tls": "^2.0.0" | |
| 359 | } | |
| 360 | }, | |
| 361 | "node_modules/bare-inspect": { | |
| 362 | "version": "3.1.4", | |
| 363 | "resolved": "https://registry.npmjs.org/bare-inspect/-/bare-inspect-3.1.4.tgz", | |
| 364 | "integrity": "sha512-jfW5KRA84o3REpI6Vr4nbvMn+hqVAw8GU1mMdRwUsY5yJovQamxYeKGVKGqdzs+8ZbG4jRzGUXP/3Ji/DnqfPg==", | |
| 365 | "dev": true, | |
| 366 | "license": "Apache-2.0", | |
| 367 | "dependencies": { | |
| 368 | "bare-ansi-escapes": "^2.1.0", | |
| 369 | "bare-type": "^1.0.0" | |
| 370 | }, | |
| 371 | "engines": { | |
| 372 | "bare": ">=1.18.0" | |
| 373 | } | |
| 374 | }, | |
| 375 | "node_modules/bare-inspector": { | |
| 376 | "version": "4.0.3", | |
| 377 | "resolved": "https://registry.npmjs.org/bare-inspector/-/bare-inspector-4.0.3.tgz", | |
| 378 | "integrity": "sha512-yYk6+7tTVKJKHOja5+QBn989ETv8oVsmwoH0nCM+QYC6w5MC03GC4aQNK+lipAZSz1EaAj42WZ8Wewjpy0L9TA==", | |
| 379 | "dev": true, | |
| 380 | "license": "Apache-2.0", | |
| 381 | "dependencies": { | |
| 382 | "bare-events": "^2.1.0", | |
| 383 | "bare-http1": "^4.0.0", | |
| 384 | "bare-stream": "^2.0.0", | |
| 385 | "bare-url": "^2.0.0", | |
| 386 | "bare-ws": "^2.0.0" | |
| 387 | }, | |
| 388 | "engines": { | |
| 389 | "bare": ">=1.2.0" | |
| 390 | }, | |
| 391 | "peerDependencies": { | |
| 392 | "bare-tcp": "*" | |
| 393 | }, | |
| 394 | "peerDependenciesMeta": { | |
| 395 | "bare-tcp": { | |
| 396 | "optional": true | |
| 397 | } | |
| 398 | } | |
| 399 | }, | |
| 400 | "node_modules/bare-module": { | |
| 401 | "version": "6.1.3", | |
| 402 | "resolved": "https://registry.npmjs.org/bare-module/-/bare-module-6.1.3.tgz", | |
| 403 | "integrity": "sha512-5XWsVHsvtWMH4tK4DQWgpNTV0t/sg3ZrAaQLIxrwjrS5+u8Q9vEgc/zQ4QaDPWDse/y/5h+d+YG1Q0JfSMt0zA==", | |
| 404 | "dev": true, | |
| 405 | "license": "Apache-2.0", | |
| 406 | "dependencies": { | |
| 407 | "bare-bundle": "^1.3.0", | |
| 408 | "bare-module-lexer": "^1.0.0", | |
| 409 | "bare-module-resolve": "^1.8.0", | |
| 410 | "bare-path": "^3.0.0", | |
| 411 | "bare-url": "^2.0.1" | |
| 412 | }, | |
| 413 | "engines": { | |
| 414 | "bare": ">=1.23.0" | |
| 415 | }, | |
| 416 | "peerDependencies": { | |
| 417 | "bare-buffer": "*" | |
| 418 | }, | |
| 419 | "peerDependenciesMeta": { | |
| 420 | "bare-buffer": { | |
| 421 | "optional": true | |
| 422 | } | |
| 423 | } | |
| 424 | }, | |
| 425 | "node_modules/bare-module-lexer": { | |
| 426 | "version": "1.4.7", | |
| 427 | "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.4.7.tgz", | |
| 428 | "integrity": "sha512-0klU4eMsjh/wcxi8FdHmNom2j2F4kmkXOhyJFL9qTaSFp2lE3m6BtbKgMHY8R5miqC9r8/IfA8wzXnC5Os14WA==", | |
| 429 | "dev": true, | |
| 430 | "license": "Apache-2.0", | |
| 431 | "dependencies": { | |
| 432 | "require-addon": "^1.0.2" | |
| 433 | }, | |
| 434 | "peerDependencies": { | |
| 435 | "bare-buffer": "*" | |
| 436 | }, | |
| 437 | "peerDependenciesMeta": { | |
| 438 | "bare-buffer": { | |
| 439 | "optional": true | |
| 440 | } | |
| 441 | } | |
| 442 | }, | |
| 443 | "node_modules/bare-module-resolve": { | |
| 444 | "version": "1.12.1", | |
| 445 | "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.1.tgz", | |
| 446 | "integrity": "sha512-hbmAPyFpEq8FoZMd5sFO3u6MC5feluWoGE8YKlA8fCrl6mNtx68Wjg4DTiDJcqRJaovTvOYKfYngoBUnbaT7eg==", | |
| 447 | "license": "Apache-2.0", | |
| 448 | "dependencies": { | |
| 449 | "bare-semver": "^1.0.0" | |
| 450 | }, | |
| 451 | "peerDependencies": { | |
| 452 | "bare-url": "*" | |
| 453 | }, | |
| 454 | "peerDependenciesMeta": { | |
| 455 | "bare-url": { | |
| 456 | "optional": true | |
| 457 | } | |
| 458 | } | |
| 459 | }, | |
| 460 | "node_modules/bare-net": { | |
| 461 | "version": "2.2.0", | |
| 462 | "resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.2.0.tgz", | |
| 463 | "integrity": "sha512-UF7cAbHsGE+H6uEqWF5IULBow1x58chZz4g3ALgHtv7wZsFcCbRDt0JKWEumf5Oma3QWS1Q6aLi0Rpll8RElMg==", | |
| 464 | "dev": true, | |
| 465 | "license": "Apache-2.0", | |
| 466 | "dependencies": { | |
| 467 | "bare-events": "^2.2.2", | |
| 468 | "bare-pipe": "^4.0.0", | |
| 469 | "bare-stream": "^2.0.0", | |
| 470 | "bare-tcp": "^2.0.0" | |
| 471 | } | |
| 472 | }, | |
| 473 | "node_modules/bare-os": { | |
| 474 | "version": "3.6.2", | |
| 475 | "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", | |
| 476 | "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", | |
| 477 | "license": "Apache-2.0", | |
| 478 | "engines": { | |
| 479 | "bare": ">=1.14.0" | |
| 480 | } | |
| 481 | }, | |
| 482 | "node_modules/bare-path": { | |
| 483 | "version": "3.0.0", | |
| 484 | "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", | |
| 485 | "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", | |
| 486 | "license": "Apache-2.0", | |
| 487 | "dependencies": { | |
| 488 | "bare-os": "^3.0.1" | |
| 489 | } | |
| 490 | }, | |
| 491 | "node_modules/bare-pipe": { | |
| 492 | "version": "4.1.3", | |
| 493 | "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.1.3.tgz", | |
| 494 | "integrity": "sha512-DqQsx93rAzre6yJ9T6l/Vgh+X+bntkVMB1X5ggtXjXtqtMmF2Y2RVlCzxxy/09R6yeR9FSWBEUIaMYJL1/5VDA==", | |
| 495 | "dev": true, | |
| 496 | "license": "Apache-2.0", | |
| 497 | "dependencies": { | |
| 498 | "bare-events": "^2.0.0", | |
| 499 | "bare-stream": "^2.0.0" | |
| 500 | }, | |
| 501 | "engines": { | |
| 502 | "bare": ">=1.16.0" | |
| 503 | } | |
| 504 | }, | |
| 505 | "node_modules/bare-process": { | |
| 506 | "version": "4.2.2", | |
| 507 | "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.2.2.tgz", | |
| 508 | "integrity": "sha512-ikzEw+HGLB+2lS/WLVEsqi8BXBwzleG3n7DYI0v6/YNklvNabOIGlLd1dof+7Jw5ob4jsBRPtyflweVahY7rFA==", | |
| 509 | "dev": true, | |
| 510 | "license": "Apache-2.0", | |
| 511 | "dependencies": { | |
| 512 | "bare-env": "^3.0.0", | |
| 513 | "bare-events": "^2.3.1", | |
| 514 | "bare-hrtime": "^2.0.0", | |
| 515 | "bare-os": "^3.5.0", | |
| 516 | "bare-pipe": "^4.0.0", | |
| 517 | "bare-signals": "^4.0.0", | |
| 518 | "bare-tty": "^5.0.0" | |
| 519 | } | |
| 520 | }, | |
| 521 | "node_modules/bare-semver": { | |
| 522 | "version": "1.0.2", | |
| 523 | "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.0.2.tgz", | |
| 524 | "integrity": "sha512-ESVaN2nzWhcI5tf3Zzcq9aqCZ676VWzqw07eEZ0qxAcEOAFYBa0pWq8sK34OQeHLY3JsfKXZS9mDyzyxGjeLzA==", | |
| 525 | "license": "Apache-2.0" | |
| 526 | }, | |
| 527 | "node_modules/bare-signals": { | |
| 528 | "version": "4.2.0", | |
| 529 | "resolved": "https://registry.npmjs.org/bare-signals/-/bare-signals-4.2.0.tgz", | |
| 530 | "integrity": "sha512-fNHMOdQIlYuTvMB3Oh9Apk99hLKn351+Ir8vz+khiPTcOqIyGG4uWWjdLTzxWdYGsA0eT+We3y0K74hjj2nq7A==", | |
| 531 | "dev": true, | |
| 532 | "license": "Apache-2.0", | |
| 533 | "dependencies": { | |
| 534 | "bare-events": "^2.5.3", | |
| 535 | "bare-os": "^3.3.1" | |
| 536 | }, | |
| 537 | "engines": { | |
| 538 | "bare": ">=1.7.0" | |
| 539 | } | |
| 540 | }, | |
| 541 | "node_modules/bare-stream": { | |
| 542 | "version": "2.7.0", | |
| 543 | "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", | |
| 544 | "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", | |
| 545 | "license": "Apache-2.0", | |
| 546 | "dependencies": { | |
| 547 | "streamx": "^2.21.0" | |
| 548 | }, | |
| 549 | "peerDependencies": { | |
| 550 | "bare-buffer": "*", | |
| 551 | "bare-events": "*" | |
| 552 | }, | |
| 553 | "peerDependenciesMeta": { | |
| 554 | "bare-buffer": { | |
| 555 | "optional": true | |
| 556 | }, | |
| 557 | "bare-events": { | |
| 558 | "optional": true | |
| 559 | } | |
| 560 | } | |
| 561 | }, | |
| 562 | "node_modules/bare-subprocess": { | |
| 563 | "version": "5.2.2", | |
| 564 | "resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.2.tgz", | |
| 565 | "integrity": "sha512-L6oXgQ1aWs25RtG5Ky0bDD06p3RAcVVrDDMWb1DfXpHtyEWxamcyIWUbSykxMTWrpLlmSj6ytbb6yoKehGFfmw==", | |
| 566 | "dev": true, | |
| 567 | "license": "Apache-2.0", | |
| 568 | "dependencies": { | |
| 569 | "bare-env": "^3.0.0", | |
| 570 | "bare-events": "^2.5.4", | |
| 571 | "bare-os": "^3.0.1", | |
| 572 | "bare-pipe": "^4.0.0", | |
| 573 | "bare-url": "^2.2.2" | |
| 574 | }, | |
| 575 | "engines": { | |
| 576 | "bare": ">=1.7.0" | |
| 577 | }, | |
| 578 | "peerDependencies": { | |
| 579 | "bare-buffer": "*" | |
| 580 | }, | |
| 581 | "peerDependenciesMeta": { | |
| 582 | "bare-buffer": { | |
| 583 | "optional": true | |
| 584 | } | |
| 585 | } | |
| 586 | }, | |
| 587 | "node_modules/bare-tcp": { | |
| 588 | "version": "2.2.4", | |
| 589 | "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.2.4.tgz", | |
| 590 | "integrity": "sha512-vGjfMkyhS1lxp+Y4+gXRDR1ijZMK2cKx0RBwtYKUuZbD90ARCoM6bAiTa1HBztumCIC+NXb8eXdMFXyuYwlPyw==", | |
| 591 | "dev": true, | |
| 592 | "license": "Apache-2.0", | |
| 593 | "dependencies": { | |
| 594 | "bare-dns": "^2.0.4", | |
| 595 | "bare-events": "^2.5.4", | |
| 596 | "bare-stream": "^2.6.4" | |
| 597 | }, | |
| 598 | "engines": { | |
| 599 | "bare": ">=1.16.0" | |
| 600 | } | |
| 601 | }, | |
| 602 | "node_modules/bare-tls": { | |
| 603 | "version": "2.1.7", | |
| 604 | "resolved": "https://registry.npmjs.org/bare-tls/-/bare-tls-2.1.7.tgz", | |
| 605 | "integrity": "sha512-h6wcNXQdBeTX7fed9tjPp0/9cA/QfcBTv3ItgjnbUk4rWAU8bEFalZCZnUDdCK/t9zrNfJ+yvcPx4D/1Y6biyA==", | |
| 606 | "dev": true, | |
| 607 | "license": "Apache-2.0", | |
| 608 | "dependencies": { | |
| 609 | "bare-net": "^2.0.1", | |
| 610 | "bare-stream": "^2.6.4" | |
| 611 | }, | |
| 612 | "engines": { | |
| 613 | "bare": ">=1.7.0" | |
| 614 | } | |
| 615 | }, | |
| 616 | "node_modules/bare-tty": { | |
| 617 | "version": "5.0.3", | |
| 618 | "resolved": "https://registry.npmjs.org/bare-tty/-/bare-tty-5.0.3.tgz", | |
| 619 | "integrity": "sha512-jW24RBWRZOMHuSEWC9mh8wUKO5WUNl0UWUTNPn3yZ8qkOqwa8vaV2dR3a+BvblONPk7V35wuvK9P8508+judAg==", | |
| 620 | "dev": true, | |
| 621 | "license": "Apache-2.0", | |
| 622 | "dependencies": { | |
| 623 | "bare-events": "^2.2.0", | |
| 624 | "bare-signals": "^4.0.0", | |
| 625 | "bare-stream": "^2.0.0" | |
| 626 | }, | |
| 627 | "engines": { | |
| 628 | "bare": ">=1.16.0" | |
| 629 | } | |
| 630 | }, | |
| 631 | "node_modules/bare-type": { | |
| 632 | "version": "1.1.0", | |
| 633 | "resolved": "https://registry.npmjs.org/bare-type/-/bare-type-1.1.0.tgz", | |
| 634 | "integrity": "sha512-LdtnnEEYldOc87Dr4GpsKnStStZk3zfgoEMXy8yvEZkXrcCv9RtYDrUYWFsBQHtaB0s1EUWmcvS6XmEZYIj3Bw==", | |
| 635 | "dev": true, | |
| 636 | "license": "Apache-2.0", | |
| 637 | "engines": { | |
| 638 | "bare": ">=1.2.0" | |
| 639 | } | |
| 640 | }, | |
| 641 | "node_modules/bare-url": { | |
| 642 | "version": "2.3.2", | |
| 643 | "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", | |
| 644 | "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", | |
| 645 | "license": "Apache-2.0", | |
| 646 | "peer": true, | |
| 647 | "dependencies": { | |
| 648 | "bare-path": "^3.0.0" | |
| 649 | } | |
| 650 | }, | |
| 651 | "node_modules/bare-utils": { | |
| 652 | "version": "1.5.1", | |
| 653 | "resolved": "https://registry.npmjs.org/bare-utils/-/bare-utils-1.5.1.tgz", | |
| 654 | "integrity": "sha512-mxCkFvmDU3mlD/mb+pT64kKXOsx2KMsWLQbngN1LB+NOXfhfnRnyvpy3VZc6m7gzQxe57Bsi+aTCBqA4/S3elQ==", | |
| 655 | "dev": true, | |
| 656 | "license": "Apache-2.0", | |
| 657 | "dependencies": { | |
| 658 | "bare-debug-log": "^2.0.0", | |
| 659 | "bare-encoding": "^1.0.0", | |
| 660 | "bare-format": "^1.0.0", | |
| 661 | "bare-inspect": "^3.0.0", | |
| 662 | "bare-type": "^1.0.6" | |
| 663 | } | |
| 664 | }, | |
| 665 | "node_modules/bare-v8-to-istanbul": { | |
| 666 | "version": "1.0.2", | |
| 667 | "resolved": "https://registry.npmjs.org/bare-v8-to-istanbul/-/bare-v8-to-istanbul-1.0.2.tgz", | |
| 668 | "integrity": "sha512-CM0wPgyQtXTQ7h4q8NjbTZ/FH5he0Jkiwgpq0lFZZA5tkVZzmQ/9ul4R7E+cNlGZJIZlQZ9v4CwbgVaFG3zOAA==", | |
| 669 | "dev": true, | |
| 670 | "license": "Apache-2.0", | |
| 671 | "dependencies": { | |
| 672 | "bare-assert": "^1.0.2", | |
| 673 | "bare-fs": "^4.1.2", | |
| 674 | "bare-module": "^6.1.2", | |
| 675 | "bare-path": "^3.0.0", | |
| 676 | "bare-process": "^4.2.0", | |
| 677 | "bare-url": "^2.1.5", | |
| 678 | "bare-utils": "^1.2.0", | |
| 679 | "v8-to-istanbul": "^9.3.0", | |
| 680 | "which-runtime": "^1.2.1" | |
| 681 | } | |
| 682 | }, | |
| 683 | "node_modules/bare-ws": { | |
| 684 | "version": "2.0.4", | |
| 685 | "resolved": "https://registry.npmjs.org/bare-ws/-/bare-ws-2.0.4.tgz", | |
| 686 | "integrity": "sha512-SQMXzBYna9dRj57Dz1/ag+VWHCRXbfCjMHgyfM2F2lhkVLzMjnVSZP72aVeFWPFqe494Rd70Kzhe2JElGwFlJQ==", | |
| 687 | "dev": true, | |
| 688 | "license": "Apache-2.0", | |
| 689 | "dependencies": { | |
| 690 | "bare-crypto": "^1.2.0", | |
| 691 | "bare-events": "^2.3.1", | |
| 692 | "bare-http1": "^4.0.0", | |
| 693 | "bare-https": "^2.0.0", | |
| 694 | "bare-stream": "^2.1.2" | |
| 695 | }, | |
| 696 | "peerDependencies": { | |
| 697 | "bare-buffer": "*", | |
| 698 | "bare-url": "*" | |
| 699 | }, | |
| 700 | "peerDependenciesMeta": { | |
| 701 | "bare-buffer": { | |
| 702 | "optional": true | |
| 703 | }, | |
| 704 | "bare-url": { | |
| 705 | "optional": true | |
| 706 | } | |
| 707 | } | |
| 708 | }, | |
| 709 | "node_modules/big-sparse-array": { | |
| 710 | "version": "1.0.3", | |
| 711 | "resolved": "https://registry.npmjs.org/big-sparse-array/-/big-sparse-array-1.0.3.tgz", | |
| 712 | "integrity": "sha512-6RjV/3mSZORlMdpUaQ6rUSpG637cZm0//E54YYGtQg1c1O+AbZP8UTdJ/TchsDZcTVLmyWZcseBfp2HBeXUXOQ==", | |
| 713 | "license": "MIT" | |
| 714 | }, | |
| 715 | "node_modules/brittle": { | |
| 716 | "version": "3.19.1", | |
| 717 | "resolved": "https://registry.npmjs.org/brittle/-/brittle-3.19.1.tgz", | |
| 718 | "integrity": "sha512-4Ted1Mt9o9B6oIA6ImJJCtB/Fv++ZO3IDNQgRcHOXWSYGRUidgxchaGrUBaRn+4mlneXrgInPkCPzi0jO8qKbA==", | |
| 719 | "dev": true, | |
| 720 | "license": "Apache-2.0", | |
| 721 | "dependencies": { | |
| 722 | "b4a": "^1.6.0", | |
| 723 | "bare-assert": "^1.0.2", | |
| 724 | "bare-cov": "^1.1.0", | |
| 725 | "bare-fs": "^4.1.6", | |
| 726 | "bare-os": "^3.6.1", | |
| 727 | "bare-path": "^3.0.0", | |
| 728 | "bare-process": "^4.2.1", | |
| 729 | "bare-subprocess": "^5.0.0", | |
| 730 | "bare-url": "^2.1.6", | |
| 731 | "error-stack-parser": "^2.1.4", | |
| 732 | "globbie": "^1.0.2", | |
| 733 | "paparam": "^1.6.2", | |
| 734 | "same-object": "^1.0.2", | |
| 735 | "test-tmp": "^1.4.0", | |
| 736 | "tmatch": "^5.0.0" | |
| 737 | }, | |
| 738 | "bin": { | |
| 739 | "brittle": "bin/node.js", | |
| 740 | "brittle-bare": "bin/bare.js", | |
| 741 | "brittle-node": "bin/node.js", | |
| 742 | "brittle-pear": "bin/pear.js" | |
| 743 | } | |
| 744 | }, | |
| 745 | "node_modules/codecs": { | |
| 746 | "version": "3.1.0", | |
| 747 | "resolved": "https://registry.npmjs.org/codecs/-/codecs-3.1.0.tgz", | |
| 748 | "integrity": "sha512-Dqx8NwvBvnMeuPQdVKy/XEF71igjR5apxBvCGeV0pP1tXadOiaLvDTXt7xh+/5wI1ASB195mXQGJbw3Ml4YDWQ==", | |
| 749 | "license": "MIT", | |
| 750 | "dependencies": { | |
| 751 | "b4a": "^1.6.3" | |
| 752 | } | |
| 753 | }, | |
| 754 | "node_modules/compact-encoding": { | |
| 755 | "version": "2.18.0", | |
| 756 | "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-2.18.0.tgz", | |
| 757 | "integrity": "sha512-goACAOlhMI2xo5jGOMUDfOLnGdRE1jGfyZ+zie8N5114nHrbPIqf6GLUtzbLof6DSyrERlYRm3EcBplte5LcQw==", | |
| 758 | "license": "Apache-2.0", | |
| 759 | "dependencies": { | |
| 760 | "b4a": "^1.3.0" | |
| 761 | } | |
| 762 | }, | |
| 763 | "node_modules/convert-source-map": { | |
| 764 | "version": "2.0.0", | |
| 765 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", | |
| 766 | "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", | |
| 767 | "dev": true, | |
| 768 | "license": "MIT" | |
| 769 | }, | |
| 770 | "node_modules/corestore": { | |
| 771 | "version": "6.18.4", | |
| 772 | "resolved": "https://registry.npmjs.org/corestore/-/corestore-6.18.4.tgz", | |
| 773 | "integrity": "sha512-KL1cOUyj3DFRNzeVRekGPRiGN4sIfGo7BsncSJQQBT3Rh2y1nQ32MOR9X5DlOMlWIT4txwqBzhzam6+JXtwFVw==", | |
| 774 | "license": "MIT", | |
| 775 | "dependencies": { | |
| 776 | "b4a": "^1.6.4", | |
| 777 | "hypercore": "^10.37.10", | |
| 778 | "hypercore-crypto": "^3.4.0", | |
| 779 | "hypercore-id-encoding": "^1.2.0", | |
| 780 | "read-write-mutexify": "^2.1.0", | |
| 781 | "ready-resource": "^1.0.0", | |
| 782 | "safety-catch": "^1.0.1", | |
| 783 | "sodium-universal": "^4.0.0", | |
| 784 | "xache": "^1.1.0" | |
| 785 | } | |
| 786 | }, | |
| 787 | "node_modules/crc-native": { | |
| 788 | "version": "1.1.8", | |
| 789 | "resolved": "https://registry.npmjs.org/crc-native/-/crc-native-1.1.8.tgz", | |
| 790 | "integrity": "sha512-R/RYbD8lhG5M1k0iLX49tJdwHM0ipevJPpZw2Wk7b4EShyvx2P0uaq79xyRpcknjiVGiUfjFA8Gyy0mOlhABvw==", | |
| 791 | "license": "Apache-2.0", | |
| 792 | "optional": true, | |
| 793 | "dependencies": { | |
| 794 | "require-addon": "^1.1.0" | |
| 795 | } | |
| 796 | }, | |
| 797 | "node_modules/crc-universal": { | |
| 798 | "version": "1.0.4", | |
| 799 | "resolved": "https://registry.npmjs.org/crc-universal/-/crc-universal-1.0.4.tgz", | |
| 800 | "integrity": "sha512-CE9xWEI6Gd5V0Bdmj5NoWH3d7+EIe4zUpy1sv6uvKYznzsDP1vhiWsTBmL6q9IrH2P6RRshp+8AkhR4CMtY5Hg==", | |
| 801 | "license": "ISC", | |
| 802 | "optionalDependencies": { | |
| 803 | "crc-native": "^1.0.3" | |
| 804 | } | |
| 805 | }, | |
| 806 | "node_modules/debounceify": { | |
| 807 | "version": "1.1.0", | |
| 808 | "resolved": "https://registry.npmjs.org/debounceify/-/debounceify-1.1.0.tgz", | |
| 809 | "integrity": "sha512-eKuHDVfJVg+u/0nPy8P+fhnLgbyuTgVxuCRrS/R7EpDSMMkBDgSes41MJtSAY1F1hcqfHz3Zy/qpqHHIp/EhdA==", | |
| 810 | "license": "MIT" | |
| 811 | }, | |
| 812 | "node_modules/error-stack-parser": { | |
| 813 | "version": "2.1.4", | |
| 814 | "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", | |
| 815 | "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", | |
| 816 | "dev": true, | |
| 817 | "license": "MIT", | |
| 818 | "dependencies": { | |
| 819 | "stackframe": "^1.3.4" | |
| 820 | } | |
| 821 | }, | |
| 822 | "node_modules/events-universal": { | |
| 823 | "version": "1.0.1", | |
| 824 | "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", | |
| 825 | "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", | |
| 826 | "license": "Apache-2.0", | |
| 827 | "dependencies": { | |
| 828 | "bare-events": "^2.7.0" | |
| 829 | } | |
| 830 | }, | |
| 831 | "node_modules/fast-fifo": { | |
| 832 | "version": "1.3.2", | |
| 833 | "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", | |
| 834 | "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", | |
| 835 | "license": "MIT" | |
| 836 | }, | |
| 837 | "node_modules/flat-tree": { | |
| 838 | "version": "1.13.0", | |
| 839 | "resolved": "https://registry.npmjs.org/flat-tree/-/flat-tree-1.13.0.tgz", | |
| 840 | "integrity": "sha512-fT3HIuCPwHhFgJ20QYzDHgUG0zMmFg5cHvFiFo5h+QMSJ28TihsEVY0f8HGliuO+pOzmvjMx1odToeaEWkTnyQ==", | |
| 841 | "license": "MIT" | |
| 842 | }, | |
| 843 | "node_modules/fs-native-extensions": { | |
| 844 | "version": "1.4.5", | |
| 845 | "resolved": "https://registry.npmjs.org/fs-native-extensions/-/fs-native-extensions-1.4.5.tgz", | |
| 846 | "integrity": "sha512-ekV0T//iDm4AvhOcuPaHpxub4DI7HvY5ucLJVDvi7T2J+NZkQ9S6MuvgP0yeQvoqNUaAGyLjVYb1905BF9bpmg==", | |
| 847 | "license": "Apache-2.0", | |
| 848 | "optional": true, | |
| 849 | "dependencies": { | |
| 850 | "require-addon": "^1.1.0", | |
| 851 | "which-runtime": "^1.2.0" | |
| 852 | } | |
| 853 | }, | |
| 854 | "node_modules/globbie": { | |
| 855 | "version": "1.0.3", | |
| 856 | "resolved": "https://registry.npmjs.org/globbie/-/globbie-1.0.3.tgz", | |
| 857 | "integrity": "sha512-hcryJmKcftf82xfBWTWxuUITWIIoYO3ec104V17SMHGFl6VLbm1d1Ju9LX9L+jyJTwICpemX/dmPI/HGYoKr1A==", | |
| 858 | "dev": true, | |
| 859 | "license": "Apache-2.0", | |
| 860 | "dependencies": { | |
| 861 | "bare-fs": "^4.1.2", | |
| 862 | "bare-path": "^3.0.0", | |
| 863 | "bare-process": "^4.2.1", | |
| 864 | "picomatch": "^4.0.2" | |
| 865 | } | |
| 866 | }, | |
| 867 | "node_modules/hyperbee": { | |
| 868 | "version": "2.27.3", | |
| 869 | "resolved": "https://registry.npmjs.org/hyperbee/-/hyperbee-2.27.3.tgz", | |
| 870 | "integrity": "sha512-PXURH2U4juUZyJRKHTrY5z1zX851pmI1Q0jfv5F/hCIErDt/ND8jOZuxc3hfOLM9f0W3qJEDTMlV5AJBkVPy8w==", | |
| 871 | "license": "MIT", | |
| 872 | "dependencies": { | |
| 873 | "b4a": "^1.6.0", | |
| 874 | "codecs": "^3.0.0", | |
| 875 | "debounceify": "^1.0.0", | |
| 876 | "hypercore-errors": "^1.0.0", | |
| 877 | "mutexify": "^1.4.0", | |
| 878 | "protocol-buffers-encodings": "^1.2.0", | |
| 879 | "rache": "^1.0.0", | |
| 880 | "ready-resource": "^1.0.0", | |
| 881 | "resolve-reject-promise": "^1.1.0", | |
| 882 | "safety-catch": "^1.0.2", | |
| 883 | "streamx": "^2.12.4", | |
| 884 | "unslab": "^1.2.0" | |
| 885 | } | |
| 886 | }, | |
| 887 | "node_modules/hypercore": { | |
| 888 | "version": "10.38.2", | |
| 889 | "resolved": "https://registry.npmjs.org/hypercore/-/hypercore-10.38.2.tgz", | |
| 890 | "integrity": "sha512-2vx42g2RKdP/o2XzlxKfvgEaZ3+h/B855gCR8iscUDnxUoOZej7vXZ2buex0sweDUEW5aEfMxFR6o5hncio3Nw==", | |
| 891 | "license": "MIT", | |
| 892 | "dependencies": { | |
| 893 | "@hyperswarm/secret-stream": "^6.0.0", | |
| 894 | "b4a": "^1.1.0", | |
| 895 | "bare-events": "^2.2.0", | |
| 896 | "big-sparse-array": "^1.0.3", | |
| 897 | "compact-encoding": "^2.11.0", | |
| 898 | "crc-universal": "^1.0.2", | |
| 899 | "fast-fifo": "^1.3.0", | |
| 900 | "flat-tree": "^1.9.0", | |
| 901 | "hypercore-crypto": "^3.2.1", | |
| 902 | "hypercore-errors": "^1.2.0", | |
| 903 | "hypercore-id-encoding": "^1.2.0", | |
| 904 | "is-options": "^1.0.1", | |
| 905 | "protomux": "^3.5.0", | |
| 906 | "quickbit-universal": "^2.2.0", | |
| 907 | "random-access-file": "^4.0.0", | |
| 908 | "random-array-iterator": "^1.0.0", | |
| 909 | "safety-catch": "^1.0.1", | |
| 910 | "sodium-universal": "^4.0.0", | |
| 911 | "streamx": "^2.12.4", | |
| 912 | "unslab": "^1.3.0", | |
| 913 | "xache": "^1.1.0", | |
| 914 | "z32": "^1.0.0" | |
| 915 | } | |
| 916 | }, | |
| 917 | "node_modules/hypercore-crypto": { | |
| 918 | "version": "3.6.1", | |
| 919 | "resolved": "https://registry.npmjs.org/hypercore-crypto/-/hypercore-crypto-3.6.1.tgz", | |
| 920 | "integrity": "sha512-ltIz2uDwy9pO/ZGTvqcjzyBkvt6O4cVm4r/nNxh0GFs/RbQtqP/i4wCvLEdmU7ptgtnw7fI67WYD1aHPuv4OVA==", | |
| 921 | "license": "MIT", | |
| 922 | "dependencies": { | |
| 923 | "b4a": "^1.6.6", | |
| 924 | "compact-encoding": "^2.15.0", | |
| 925 | "sodium-universal": "^5.0.0" | |
| 926 | } | |
| 927 | }, | |
| 928 | "node_modules/hypercore-crypto/node_modules/sodium-native": { | |
| 929 | "version": "5.0.10", | |
| 930 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 931 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 932 | "license": "MIT", | |
| 933 | "dependencies": { | |
| 934 | "require-addon": "^1.1.0", | |
| 935 | "which-runtime": "^1.2.1" | |
| 936 | }, | |
| 937 | "engines": { | |
| 938 | "bare": ">=1.16.0" | |
| 939 | } | |
| 940 | }, | |
| 941 | "node_modules/hypercore-crypto/node_modules/sodium-universal": { | |
| 942 | "version": "5.0.1", | |
| 943 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 944 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 945 | "license": "MIT", | |
| 946 | "dependencies": { | |
| 947 | "sodium-native": "^5.0.1" | |
| 948 | }, | |
| 949 | "peerDependencies": { | |
| 950 | "sodium-javascript": "~0.8.0" | |
| 951 | }, | |
| 952 | "peerDependenciesMeta": { | |
| 953 | "sodium-javascript": { | |
| 954 | "optional": true | |
| 955 | } | |
| 956 | } | |
| 957 | }, | |
| 958 | "node_modules/hypercore-errors": { | |
| 959 | "version": "1.5.0", | |
| 960 | "resolved": "https://registry.npmjs.org/hypercore-errors/-/hypercore-errors-1.5.0.tgz", | |
| 961 | "integrity": "sha512-5KQ/SuDxsvet+7qWA35Ay6zdD9WyAHQoyWHGcPUTbmJBd300gvNIJoi3oma7kp4TTCSzii6qYumNZe/s0j/saQ==", | |
| 962 | "license": "Apache-2.0", | |
| 963 | "dependencies": { | |
| 964 | "hypercore-id-encoding": "^1.3.0" | |
| 965 | } | |
| 966 | }, | |
| 967 | "node_modules/hypercore-id-encoding": { | |
| 968 | "version": "1.3.0", | |
| 969 | "resolved": "https://registry.npmjs.org/hypercore-id-encoding/-/hypercore-id-encoding-1.3.0.tgz", | |
| 970 | "integrity": "sha512-W6sHdGo5h7LXEsoWfKf/KfuROZmZRQDlGqJF2EPHW+noCK66Vvr0+zE6cL0vqQi18s0kQPeN7Sq3QyR0Ytc2VQ==", | |
| 971 | "license": "Apache-2.0", | |
| 972 | "dependencies": { | |
| 973 | "b4a": "^1.5.3", | |
| 974 | "z32": "^1.0.0" | |
| 975 | } | |
| 976 | }, | |
| 977 | "node_modules/is-options": { | |
| 978 | "version": "1.0.2", | |
| 979 | "resolved": "https://registry.npmjs.org/is-options/-/is-options-1.0.2.tgz", | |
| 980 | "integrity": "sha512-u+Ai74c8Q74aS8BuHwPdI1jptGOT1FQXgCq8/zv0xRuE+wRgSMEJLj8lVO8Zp9BeGb29BXY6AsNPinfqjkr7Fg==", | |
| 981 | "license": "MIT", | |
| 982 | "dependencies": { | |
| 983 | "b4a": "^1.1.1" | |
| 984 | } | |
| 985 | }, | |
| 986 | "node_modules/mutexify": { | |
| 987 | "version": "1.4.0", | |
| 988 | "resolved": "https://registry.npmjs.org/mutexify/-/mutexify-1.4.0.tgz", | |
| 989 | "integrity": "sha512-pbYSsOrSB/AKN5h/WzzLRMFgZhClWccf2XIB4RSMC8JbquiB0e0/SH5AIfdQMdyHmYtv4seU7yV/TvAwPLJ1Yg==", | |
| 990 | "license": "MIT", | |
| 991 | "dependencies": { | |
| 992 | "queue-tick": "^1.0.0" | |
| 993 | } | |
| 994 | }, | |
| 995 | "node_modules/nanoassert": { | |
| 996 | "version": "2.0.0", | |
| 997 | "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz", | |
| 998 | "integrity": "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==", | |
| 999 | "license": "ISC" | |
| 1000 | }, | |
| 1001 | "node_modules/noise-curve-ed": { | |
| 1002 | "version": "2.1.0", | |
| 1003 | "resolved": "https://registry.npmjs.org/noise-curve-ed/-/noise-curve-ed-2.1.0.tgz", | |
| 1004 | "integrity": "sha512-zAzJx+VwZM3w6EA1hTmDhJfvAnCeBQn/1FAeZ0LtGxCcCtlAK/uJXQVF/eDVUOaAZ286lHlx77WJ+qj9SmsRRg==", | |
| 1005 | "license": "ISC", | |
| 1006 | "dependencies": { | |
| 1007 | "b4a": "^1.1.0", | |
| 1008 | "nanoassert": "^2.0.0", | |
| 1009 | "sodium-universal": "^5.0.0" | |
| 1010 | } | |
| 1011 | }, | |
| 1012 | "node_modules/noise-curve-ed/node_modules/sodium-native": { | |
| 1013 | "version": "5.0.10", | |
| 1014 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 1015 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 1016 | "license": "MIT", | |
| 1017 | "dependencies": { | |
| 1018 | "require-addon": "^1.1.0", | |
| 1019 | "which-runtime": "^1.2.1" | |
| 1020 | }, | |
| 1021 | "engines": { | |
| 1022 | "bare": ">=1.16.0" | |
| 1023 | } | |
| 1024 | }, | |
| 1025 | "node_modules/noise-curve-ed/node_modules/sodium-universal": { | |
| 1026 | "version": "5.0.1", | |
| 1027 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 1028 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 1029 | "license": "MIT", | |
| 1030 | "dependencies": { | |
| 1031 | "sodium-native": "^5.0.1" | |
| 1032 | }, | |
| 1033 | "peerDependencies": { | |
| 1034 | "sodium-javascript": "~0.8.0" | |
| 1035 | }, | |
| 1036 | "peerDependenciesMeta": { | |
| 1037 | "sodium-javascript": { | |
| 1038 | "optional": true | |
| 1039 | } | |
| 1040 | } | |
| 1041 | }, | |
| 1042 | "node_modules/noise-handshake": { | |
| 1043 | "version": "4.2.0", | |
| 1044 | "resolved": "https://registry.npmjs.org/noise-handshake/-/noise-handshake-4.2.0.tgz", | |
| 1045 | "integrity": "sha512-9O/VTNX/E2/AToyMTTDU0J/4WhaXMTdqc2DHs9vf+snoZ0cenSBq0dNYTVV1snYYEkmo6QeRrYMxtqtoYnY+LA==", | |
| 1046 | "license": "Apache-2.0", | |
| 1047 | "dependencies": { | |
| 1048 | "b4a": "^1.1.0", | |
| 1049 | "nanoassert": "^2.0.0", | |
| 1050 | "sodium-universal": "^5.0.0" | |
| 1051 | } | |
| 1052 | }, | |
| 1053 | "node_modules/noise-handshake/node_modules/sodium-native": { | |
| 1054 | "version": "5.0.10", | |
| 1055 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 1056 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 1057 | "license": "MIT", | |
| 1058 | "dependencies": { | |
| 1059 | "require-addon": "^1.1.0", | |
| 1060 | "which-runtime": "^1.2.1" | |
| 1061 | }, | |
| 1062 | "engines": { | |
| 1063 | "bare": ">=1.16.0" | |
| 1064 | } | |
| 1065 | }, | |
| 1066 | "node_modules/noise-handshake/node_modules/sodium-universal": { | |
| 1067 | "version": "5.0.1", | |
| 1068 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 1069 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 1070 | "license": "MIT", | |
| 1071 | "dependencies": { | |
| 1072 | "sodium-native": "^5.0.1" | |
| 1073 | }, | |
| 1074 | "peerDependencies": { | |
| 1075 | "sodium-javascript": "~0.8.0" | |
| 1076 | }, | |
| 1077 | "peerDependenciesMeta": { | |
| 1078 | "sodium-javascript": { | |
| 1079 | "optional": true | |
| 1080 | } | |
| 1081 | } | |
| 1082 | }, | |
| 1083 | "node_modules/paparam": { | |
| 1084 | "version": "1.10.0", | |
| 1085 | "resolved": "https://registry.npmjs.org/paparam/-/paparam-1.10.0.tgz", | |
| 1086 | "integrity": "sha512-p36QQrwU3X6fj5d2JN20B4lcZI/O3fxMVUHc1xD7MNe5bcOf3jIgE0CIK5Zv/s5YAUGlobTJ/kKXrh1FJVrs3w==", | |
| 1087 | "dev": true, | |
| 1088 | "license": "Apache-2.0" | |
| 1089 | }, | |
| 1090 | "node_modules/picomatch": { | |
| 1091 | "version": "4.0.3", | |
| 1092 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", | |
| 1093 | "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", | |
| 1094 | "dev": true, | |
| 1095 | "license": "MIT", | |
| 1096 | "engines": { | |
| 1097 | "node": ">=12" | |
| 1098 | }, | |
| 1099 | "funding": { | |
| 1100 | "url": "https://github.com/sponsors/jonschlinkert" | |
| 1101 | } | |
| 1102 | }, | |
| 1103 | "node_modules/protocol-buffers-encodings": { | |
| 1104 | "version": "1.2.0", | |
| 1105 | "resolved": "https://registry.npmjs.org/protocol-buffers-encodings/-/protocol-buffers-encodings-1.2.0.tgz", | |
| 1106 | "integrity": "sha512-daeNPuKh1NlLD1uDfbLpD+xyUTc07nEtfHwmBZmt/vH0B7VOM+JOCOpDcx9ZRpqHjAiIkGqyTDi+wfGSl17R9w==", | |
| 1107 | "license": "MIT", | |
| 1108 | "dependencies": { | |
| 1109 | "b4a": "^1.6.0", | |
| 1110 | "signed-varint": "^2.0.1", | |
| 1111 | "varint": "5.0.0" | |
| 1112 | } | |
| 1113 | }, | |
| 1114 | "node_modules/protomux": { | |
| 1115 | "version": "3.10.1", | |
| 1116 | "resolved": "https://registry.npmjs.org/protomux/-/protomux-3.10.1.tgz", | |
| 1117 | "integrity": "sha512-jgBqx8ZyaBWea/DFG4eOu1scOaeBwcnagiRC1XFVrjeGt7oAb0Pk5udPpBUpJ4DJBRjra50jD6YcZiQQTRqaaA==", | |
| 1118 | "license": "MIT", | |
| 1119 | "dependencies": { | |
| 1120 | "b4a": "^1.3.1", | |
| 1121 | "compact-encoding": "^2.5.1", | |
| 1122 | "queue-tick": "^1.0.0", | |
| 1123 | "safety-catch": "^1.0.1", | |
| 1124 | "unslab": "^1.3.0" | |
| 1125 | } | |
| 1126 | }, | |
| 1127 | "node_modules/queue-tick": { | |
| 1128 | "version": "1.0.1", | |
| 1129 | "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", | |
| 1130 | "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", | |
| 1131 | "license": "MIT" | |
| 1132 | }, | |
| 1133 | "node_modules/quickbit-native": { | |
| 1134 | "version": "2.4.8", | |
| 1135 | "resolved": "https://registry.npmjs.org/quickbit-native/-/quickbit-native-2.4.8.tgz", | |
| 1136 | "integrity": "sha512-FcCcqI+nIAWGknqhtrYT5TSD7t/N+Xd8ctM+2PrIIBuwOi5hx0SxAvuPtzLIEMfT/2h9+fhBakUe2uALOHX6yw==", | |
| 1137 | "license": "Apache-2.0", | |
| 1138 | "optional": true, | |
| 1139 | "dependencies": { | |
| 1140 | "require-addon": "^1.1.0" | |
| 1141 | } | |
| 1142 | }, | |
| 1143 | "node_modules/quickbit-universal": { | |
| 1144 | "version": "2.2.0", | |
| 1145 | "resolved": "https://registry.npmjs.org/quickbit-universal/-/quickbit-universal-2.2.0.tgz", | |
| 1146 | "integrity": "sha512-w02i1R8n7+6pEKTud8DfF8zbFY9o7RtPlUc3jWbtCkDKvhbx/AvV7oNnz4/TcmsPGpSJS+fq5Ud6RH6+YPvSGg==", | |
| 1147 | "license": "ISC", | |
| 1148 | "dependencies": { | |
| 1149 | "b4a": "^1.6.0", | |
| 1150 | "simdle-universal": "^1.1.0" | |
| 1151 | }, | |
| 1152 | "optionalDependencies": { | |
| 1153 | "quickbit-native": "^2.2.0" | |
| 1154 | } | |
| 1155 | }, | |
| 1156 | "node_modules/rache": { | |
| 1157 | "version": "1.0.0", | |
| 1158 | "resolved": "https://registry.npmjs.org/rache/-/rache-1.0.0.tgz", | |
| 1159 | "integrity": "sha512-e0k0g0w/8jOCB+7YqCIlOa+OJ38k0wrYS4x18pMSmqOvLKoyhmMhmQyCcvfY6VaP8D75cqkEnlakXs+RYYLqNg==", | |
| 1160 | "license": "Apache-2.0" | |
| 1161 | }, | |
| 1162 | "node_modules/random-access-file": { | |
| 1163 | "version": "4.1.2", | |
| 1164 | "resolved": "https://registry.npmjs.org/random-access-file/-/random-access-file-4.1.2.tgz", | |
| 1165 | "integrity": "sha512-GQM6R78DceZDcQod8KxlDFwXIiUvlvuy1EOzxTDsjuDjW5NlnlZi0MOk6iI4itAj/2vcvdqcEExYbVpC/dJcEw==", | |
| 1166 | "license": "MIT", | |
| 1167 | "dependencies": { | |
| 1168 | "bare-fs": "^4.0.1", | |
| 1169 | "bare-path": "^3.0.0", | |
| 1170 | "random-access-storage": "^3.0.0" | |
| 1171 | }, | |
| 1172 | "optionalDependencies": { | |
| 1173 | "fs-native-extensions": "^1.3.1" | |
| 1174 | } | |
| 1175 | }, | |
| 1176 | "node_modules/random-access-storage": { | |
| 1177 | "version": "3.0.2", | |
| 1178 | "resolved": "https://registry.npmjs.org/random-access-storage/-/random-access-storage-3.0.2.tgz", | |
| 1179 | "integrity": "sha512-Es9maUyWdJXWKckKy9s1+vT+DEgAt+PBb9lxPaake/0EDUsHehloKGv9v1zimS2V3gpFAcQXubvc1Rgci2sDPQ==", | |
| 1180 | "license": "MIT", | |
| 1181 | "dependencies": { | |
| 1182 | "bare-events": "^2.2.0", | |
| 1183 | "queue-tick": "^1.0.0" | |
| 1184 | } | |
| 1185 | }, | |
| 1186 | "node_modules/random-array-iterator": { | |
| 1187 | "version": "1.0.0", | |
| 1188 | "resolved": "https://registry.npmjs.org/random-array-iterator/-/random-array-iterator-1.0.0.tgz", | |
| 1189 | "integrity": "sha512-u7xCM93XqKEvPTP6xZp2ehttcAemKnh73oKNf1FvzuVCfpt6dILDt1Kxl1LeBjm2iNIeR49VGFhy4Iz3yOun+Q==", | |
| 1190 | "license": "MIT" | |
| 1191 | }, | |
| 1192 | "node_modules/read-write-mutexify": { | |
| 1193 | "version": "2.1.0", | |
| 1194 | "resolved": "https://registry.npmjs.org/read-write-mutexify/-/read-write-mutexify-2.1.0.tgz", | |
| 1195 | "integrity": "sha512-fDw/p5/acI1ytVY1UbxEDma/ej1yJH/n9NcjS9YNzcE6sPBPWdlru3ydRa/UBowUg4zqOvNMD5SOGYJrlQ6MzQ==", | |
| 1196 | "license": "MIT" | |
| 1197 | }, | |
| 1198 | "node_modules/ready-resource": { | |
| 1199 | "version": "1.2.0", | |
| 1200 | "resolved": "https://registry.npmjs.org/ready-resource/-/ready-resource-1.2.0.tgz", | |
| 1201 | "integrity": "sha512-nfcco/8iAFV0M+2PYnmIc+/xY0iRb35d42HFHQ7AfjulbGEAFa+XWpByfwSyeVeiBoMLLFVMv1HixxNCqzSQ1g==", | |
| 1202 | "license": "MIT", | |
| 1203 | "dependencies": { | |
| 1204 | "bare-events": "^2.2.0" | |
| 1205 | } | |
| 1206 | }, | |
| 1207 | "node_modules/require-addon": { | |
| 1208 | "version": "1.2.0", | |
| 1209 | "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz", | |
| 1210 | "integrity": "sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA==", | |
| 1211 | "license": "Apache-2.0", | |
| 1212 | "dependencies": { | |
| 1213 | "bare-addon-resolve": "^1.3.0" | |
| 1214 | }, | |
| 1215 | "engines": { | |
| 1216 | "bare": ">=1.10.0" | |
| 1217 | } | |
| 1218 | }, | |
| 1219 | "node_modules/resolve-reject-promise": { | |
| 1220 | "version": "1.1.0", | |
| 1221 | "resolved": "https://registry.npmjs.org/resolve-reject-promise/-/resolve-reject-promise-1.1.0.tgz", | |
| 1222 | "integrity": "sha512-LWsTOA91AqzBTjSGgX79Tc130pwcBK6xjpJEO+qRT5IKZ6bGnHKcc8QL3upUBcWuU8OTIDzKK2VNSwmmlqvAVg==", | |
| 1223 | "license": "MIT" | |
| 1224 | }, | |
| 1225 | "node_modules/safety-catch": { | |
| 1226 | "version": "1.0.2", | |
| 1227 | "resolved": "https://registry.npmjs.org/safety-catch/-/safety-catch-1.0.2.tgz", | |
| 1228 | "integrity": "sha512-C1UYVZ4dtbBxEtvOcpjBaaD27nP8MlvyAQEp2fOTOEe6pfUpk1cDUxij6BR1jZup6rSyUTaBBplK7LanskrULA==", | |
| 1229 | "license": "MIT" | |
| 1230 | }, | |
| 1231 | "node_modules/same-object": { | |
| 1232 | "version": "1.0.2", | |
| 1233 | "resolved": "https://registry.npmjs.org/same-object/-/same-object-1.0.2.tgz", | |
| 1234 | "integrity": "sha512-csHWhvUsLbIOHDM/nP+KHWM+BLPsIzWkFa8HbzaI0G7BqKXgx+7FJpKTGgLXyz5amfdY2OVBcmXTqYOMEk04og==", | |
| 1235 | "dev": true, | |
| 1236 | "license": "MIT" | |
| 1237 | }, | |
| 1238 | "node_modules/signed-varint": { | |
| 1239 | "version": "2.0.1", | |
| 1240 | "resolved": "https://registry.npmjs.org/signed-varint/-/signed-varint-2.0.1.tgz", | |
| 1241 | "integrity": "sha512-abgDPg1106vuZZOvw7cFwdCABddfJRz5akcCcchzTbhyhYnsG31y4AlZEgp315T7W3nQq5P4xeOm186ZiPVFzw==", | |
| 1242 | "license": "MIT", | |
| 1243 | "dependencies": { | |
| 1244 | "varint": "~5.0.0" | |
| 1245 | } | |
| 1246 | }, | |
| 1247 | "node_modules/simdle-native": { | |
| 1248 | "version": "1.3.9", | |
| 1249 | "resolved": "https://registry.npmjs.org/simdle-native/-/simdle-native-1.3.9.tgz", | |
| 1250 | "integrity": "sha512-Isc8sP4OiiIU0mpslD4GHEnR0VQWvR/54WN7YtwEDkNdTJVWtpmvsSvsgRlw5BNGxdYXlVRegdnrSu10H/PhvA==", | |
| 1251 | "license": "Apache-2.0", | |
| 1252 | "optional": true, | |
| 1253 | "dependencies": { | |
| 1254 | "b4a": "^1.6.0", | |
| 1255 | "require-addon": "^1.1.0" | |
| 1256 | } | |
| 1257 | }, | |
| 1258 | "node_modules/simdle-universal": { | |
| 1259 | "version": "1.1.2", | |
| 1260 | "resolved": "https://registry.npmjs.org/simdle-universal/-/simdle-universal-1.1.2.tgz", | |
| 1261 | "integrity": "sha512-3n3w1bs+uwgHKQjt6arez83EywNlhZzYvNOhvAASTl/8KqNIcqr6aHyGt3JRlfuUC7iB0tomJRPlJ2cRGIpBzA==", | |
| 1262 | "license": "ISC", | |
| 1263 | "dependencies": { | |
| 1264 | "b4a": "^1.6.0" | |
| 1265 | }, | |
| 1266 | "optionalDependencies": { | |
| 1267 | "simdle-native": "^1.1.1" | |
| 1268 | } | |
| 1269 | }, | |
| 1270 | "node_modules/sodium-native": { | |
| 1271 | "version": "4.3.3", | |
| 1272 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.3.3.tgz", | |
| 1273 | "integrity": "sha512-OnxSlN3uyY8D0EsLHpmm2HOFmKddQVvEMmsakCrXUzSd8kjjbzL413t4ZNF3n0UxSwNgwTyUvkmZHTfuCeiYSw==", | |
| 1274 | "license": "MIT", | |
| 1275 | "dependencies": { | |
| 1276 | "require-addon": "^1.1.0" | |
| 1277 | } | |
| 1278 | }, | |
| 1279 | "node_modules/sodium-secretstream": { | |
| 1280 | "version": "1.2.0", | |
| 1281 | "resolved": "https://registry.npmjs.org/sodium-secretstream/-/sodium-secretstream-1.2.0.tgz", | |
| 1282 | "integrity": "sha512-q/DbraNFXm1KfCiiZvapmz5UC3OlpirYFIvBK2MhGaOFSb3gRyk8OXTi17UI9SGfshQNCpsVvlopogbzZNyW6Q==", | |
| 1283 | "license": "MIT", | |
| 1284 | "dependencies": { | |
| 1285 | "b4a": "^1.1.1", | |
| 1286 | "sodium-universal": "^5.0.0" | |
| 1287 | } | |
| 1288 | }, | |
| 1289 | "node_modules/sodium-secretstream/node_modules/sodium-native": { | |
| 1290 | "version": "5.0.10", | |
| 1291 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 1292 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 1293 | "license": "MIT", | |
| 1294 | "dependencies": { | |
| 1295 | "require-addon": "^1.1.0", | |
| 1296 | "which-runtime": "^1.2.1" | |
| 1297 | }, | |
| 1298 | "engines": { | |
| 1299 | "bare": ">=1.16.0" | |
| 1300 | } | |
| 1301 | }, | |
| 1302 | "node_modules/sodium-secretstream/node_modules/sodium-universal": { | |
| 1303 | "version": "5.0.1", | |
| 1304 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 1305 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 1306 | "license": "MIT", | |
| 1307 | "dependencies": { | |
| 1308 | "sodium-native": "^5.0.1" | |
| 1309 | }, | |
| 1310 | "peerDependencies": { | |
| 1311 | "sodium-javascript": "~0.8.0" | |
| 1312 | }, | |
| 1313 | "peerDependenciesMeta": { | |
| 1314 | "sodium-javascript": { | |
| 1315 | "optional": true | |
| 1316 | } | |
| 1317 | } | |
| 1318 | }, | |
| 1319 | "node_modules/sodium-universal": { | |
| 1320 | "version": "4.0.1", | |
| 1321 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-4.0.1.tgz", | |
| 1322 | "integrity": "sha512-sNp13PrxYLaUFHTGoDKkSDFvoEu51bfzE12RwGlqU1fcrkpAOK0NvizaJzOWV0Omtk9me2+Pnbjcf/l0efxuGQ==", | |
| 1323 | "license": "MIT", | |
| 1324 | "dependencies": { | |
| 1325 | "sodium-native": "^4.0.0" | |
| 1326 | }, | |
| 1327 | "peerDependencies": { | |
| 1328 | "sodium-javascript": "~0.8.0" | |
| 1329 | }, | |
| 1330 | "peerDependenciesMeta": { | |
| 1331 | "sodium-javascript": { | |
| 1332 | "optional": true | |
| 1333 | } | |
| 1334 | } | |
| 1335 | }, | |
| 1336 | "node_modules/stackframe": { | |
| 1337 | "version": "1.3.4", | |
| 1338 | "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", | |
| 1339 | "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", | |
| 1340 | "dev": true, | |
| 1341 | "license": "MIT" | |
| 1342 | }, | |
| 1343 | "node_modules/streamx": { | |
| 1344 | "version": "2.23.0", | |
| 1345 | "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", | |
| 1346 | "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", | |
| 1347 | "license": "MIT", | |
| 1348 | "dependencies": { | |
| 1349 | "events-universal": "^1.0.0", | |
| 1350 | "fast-fifo": "^1.3.2", | |
| 1351 | "text-decoder": "^1.1.0" | |
| 1352 | } | |
| 1353 | }, | |
| 1354 | "node_modules/test-tmp": { | |
| 1355 | "version": "1.4.0", | |
| 1356 | "resolved": "https://registry.npmjs.org/test-tmp/-/test-tmp-1.4.0.tgz", | |
| 1357 | "integrity": "sha512-GVggxGg+jXqP2Wbju50JVLo+9E+nIOPPyWqgr63EbOnNItIKu1cEbJpTWAJeflnyGqXOtcMI7ijHRp88GUkfDA==", | |
| 1358 | "dev": true, | |
| 1359 | "license": "MIT", | |
| 1360 | "dependencies": { | |
| 1361 | "bare-fs": "^4.0.1", | |
| 1362 | "bare-os": "^3.3.0", | |
| 1363 | "bare-path": "^3.0.0" | |
| 1364 | } | |
| 1365 | }, | |
| 1366 | "node_modules/text-decoder": { | |
| 1367 | "version": "1.2.6", | |
| 1368 | "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.6.tgz", | |
| 1369 | "integrity": "sha512-27FeW5GQFDfw0FpwMQhMagB7BztOOlmjcSRi97t2oplhKVTZtp0DZbSegSaXS5IIC6mxMvBG4AR1Sgc6BX3CQg==", | |
| 1370 | "license": "Apache-2.0", | |
| 1371 | "dependencies": { | |
| 1372 | "b4a": "^1.6.4" | |
| 1373 | } | |
| 1374 | }, | |
| 1375 | "node_modules/timeout-refresh": { | |
| 1376 | "version": "2.0.1", | |
| 1377 | "resolved": "https://registry.npmjs.org/timeout-refresh/-/timeout-refresh-2.0.1.tgz", | |
| 1378 | "integrity": "sha512-SVqEcMZBsZF9mA78rjzCrYrUs37LMJk3ShZ851ygZYW1cMeIjs9mL57KO6Iv5mmjSQnOe/29/VAfGXo+oRCiVw==", | |
| 1379 | "license": "MIT" | |
| 1380 | }, | |
| 1381 | "node_modules/tmatch": { | |
| 1382 | "version": "5.0.0", | |
| 1383 | "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-5.0.0.tgz", | |
| 1384 | "integrity": "sha512-Ib9OtBkpHn07tXP04SlN1SYRxFgTk6wSM2EBmjjxug4u5RXPRVLkdFJSS1PmrQidaSB8Lru9nRtViQBsbxzE5Q==", | |
| 1385 | "dev": true, | |
| 1386 | "license": "ISC", | |
| 1387 | "engines": { | |
| 1388 | "node": ">=8" | |
| 1389 | } | |
| 1390 | }, | |
| 1391 | "node_modules/unslab": { | |
| 1392 | "version": "1.3.0", | |
| 1393 | "resolved": "https://registry.npmjs.org/unslab/-/unslab-1.3.0.tgz", | |
| 1394 | "integrity": "sha512-YATkfKAFj47kTzmiQrWXMyRvaVrHsW6MEALa4bm+FhiA2YG4oira+Z3DXN6LrYOYn2Y8eO94Lwl9DOHjs1FpoQ==", | |
| 1395 | "license": "Apache-2.0", | |
| 1396 | "dependencies": { | |
| 1397 | "b4a": "^1.6.6" | |
| 1398 | } | |
| 1399 | }, | |
| 1400 | "node_modules/v8-to-istanbul": { | |
| 1401 | "version": "9.3.0", | |
| 1402 | "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", | |
| 1403 | "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", | |
| 1404 | "dev": true, | |
| 1405 | "license": "ISC", | |
| 1406 | "dependencies": { | |
| 1407 | "@jridgewell/trace-mapping": "^0.3.12", | |
| 1408 | "@types/istanbul-lib-coverage": "^2.0.1", | |
| 1409 | "convert-source-map": "^2.0.0" | |
| 1410 | }, | |
| 1411 | "engines": { | |
| 1412 | "node": ">=10.12.0" | |
| 1413 | } | |
| 1414 | }, | |
| 1415 | "node_modules/varint": { | |
| 1416 | "version": "5.0.0", | |
| 1417 | "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.0.tgz", | |
| 1418 | "integrity": "sha512-gC13b/bWrqQoKY2EmROCZ+AR0jitc6DnDGaQ6Ls9QpKmuSgJB1eQ7H3KETtQm7qSdMWMKCmsshyCmUwMLh3OAA==", | |
| 1419 | "license": "MIT" | |
| 1420 | }, | |
| 1421 | "node_modules/which-runtime": { | |
| 1422 | "version": "1.3.2", | |
| 1423 | "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.3.2.tgz", | |
| 1424 | "integrity": "sha512-5kwCfWml7+b2NO7KrLMhYihjRx0teKkd3yGp1Xk5Vaf2JGdSh+rgVhEALAD9c/59dP+YwJHXoEO7e8QPy7gOkw==", | |
| 1425 | "license": "Apache-2.0" | |
| 1426 | }, | |
| 1427 | "node_modules/xache": { | |
| 1428 | "version": "1.2.1", | |
| 1429 | "resolved": "https://registry.npmjs.org/xache/-/xache-1.2.1.tgz", | |
| 1430 | "integrity": "sha512-igRS6jPreJ54ABdzhh4mCDXcz+XMaWO2q1ABRV2yWYuk29jlp8VT7UBdCqNkX7rpYBbXsebVVKkwIuYZjyZNqA==", | |
| 1431 | "license": "MIT" | |
| 1432 | }, | |
| 1433 | "node_modules/z32": { | |
| 1434 | "version": "1.1.0", | |
| 1435 | "resolved": "https://registry.npmjs.org/z32/-/z32-1.1.0.tgz", | |
| 1436 | "integrity": "sha512-1WUHy+VS6d0HPNspDxvLssBbeQjXMjSnpv0vH82vRAUfg847NmX3OXozp/hRP5jPhxBbrVzrgvAt+UsGNzRFQQ==", | |
| 1437 | "license": "MIT", | |
| 1438 | "dependencies": { | |
| 1439 | "b4a": "^1.5.3" | |
| 1440 | } | |
| 1441 | } | |
| 1442 | } | |
| 1443 | } |
| … | … | @@ -0,0 +1,24 @@ |
| 1 | { | |
| 2 | "name": "hyper-daemon", | |
| 3 | "version": "1.0.0", | |
| 4 | "description": "P2P automation runtime — trigger-action scripts for Keet rooms", | |
| 5 | "type": "module", | |
| 6 | "main": "index.js", | |
| 7 | "pear": { | |
| 8 | "name": "hyper-daemon", | |
| 9 | "type": "terminal" | |
| 10 | }, | |
| 11 | "scripts": { | |
| 12 | "test": "brittle test/*.test.js" | |
| 13 | }, | |
| 14 | "dependencies": { | |
| 15 | "hyperbee": "^2.0.0", | |
| 16 | "hypercore": "^10.0.0", | |
| 17 | "corestore": "^6.0.0", | |
| 18 | "b4a": "^1.0.0" | |
| 19 | }, | |
| 20 | "devDependencies": { | |
| 21 | "brittle": "^3.0.0" | |
| 22 | }, | |
| 23 | "license": "MIT" | |
| 24 | } |
| … | … | @@ -0,0 +1,422 @@ |
| 1 | import test from 'brittle' | |
| 2 | import { crosspost, matchFilter, formatForTarget, stripRoutingTags, PLATFORM_LIMITS } from '../daemons/crosspost.js' | |
| 3 | import { | |
| 4 | summarize, extractTopics, extractKeyMessages, extractParticipants, | |
| 5 | getTimeRange, formatSummary, formatDuration, tokenize | |
| 6 | } from '../daemons/summarize.js' | |
| 7 | ||
| 8 | // --------------------------------------------------------------------------- | |
| 9 | // Crosspost tests | |
| 10 | // --------------------------------------------------------------------------- | |
| 11 | ||
| 12 | test('crosspost - match by source platform', (t) => { | |
| 13 | const msg = { source: 'keet', text: 'Hello #public world', author: 'alice' } | |
| 14 | const config = { | |
| 15 | rules: [ | |
| 16 | { from: 'keet', to: 'mastodon', filter: { tags: ['public'] } } | |
| 17 | ] | |
| 18 | } | |
| 19 | ||
| 20 | const actions = crosspost.process(msg, config) | |
| 21 | t.is(actions.length, 1) | |
| 22 | t.is(actions[0].target, 'mastodon') | |
| 23 | }) | |
| 24 | ||
| 25 | test('crosspost - no match when source differs', (t) => { | |
| 26 | const msg = { source: 'discord', text: 'Hello #public world', author: 'alice' } | |
| 27 | const config = { | |
| 28 | rules: [ | |
| 29 | { from: 'keet', to: 'mastodon', filter: { tags: ['public'] } } | |
| 30 | ] | |
| 31 | } | |
| 32 | ||
| 33 | const actions = crosspost.process(msg, config) | |
| 34 | t.is(actions.length, 0) | |
| 35 | }) | |
| 36 | ||
| 37 | test('crosspost - match by tag filter', (t) => { | |
| 38 | const msg = { source: 'keet', text: 'Check this out #public', author: 'bob' } | |
| 39 | const result = matchFilter(msg, { tags: ['public'] }) | |
| 40 | t.ok(result) | |
| 41 | }) | |
| 42 | ||
| 43 | test('crosspost - no match when tag absent', (t) => { | |
| 44 | const msg = { source: 'keet', text: 'This is private stuff', author: 'bob' } | |
| 45 | const result = matchFilter(msg, { tags: ['public'] }) | |
| 46 | t.absent(result) | |
| 47 | }) | |
| 48 | ||
| 49 | test('crosspost - match by type filter', (t) => { | |
| 50 | const msg = { source: 'keet', text: 'Image post', type: 'image', author: 'alice' } | |
| 51 | const result = matchFilter(msg, { type: 'image' }) | |
| 52 | t.ok(result) | |
| 53 | }) | |
| 54 | ||
| 55 | test('crosspost - no match when type differs', (t) => { | |
| 56 | const msg = { source: 'keet', text: 'Text post', type: 'text', author: 'alice' } | |
| 57 | const result = matchFilter(msg, { type: 'image' }) | |
| 58 | t.absent(result) | |
| 59 | }) | |
| 60 | ||
| 61 | test('crosspost - match by author filter', (t) => { | |
| 62 | const msg = { source: 'keet', text: 'Hello', author: 'alice' } | |
| 63 | const result = matchFilter(msg, { author: 'alice' }) | |
| 64 | t.ok(result) | |
| 65 | }) | |
| 66 | ||
| 67 | test('crosspost - no match when author differs', (t) => { | |
| 68 | const msg = { source: 'keet', text: 'Hello', author: 'bob' } | |
| 69 | const result = matchFilter(msg, { author: 'alice' }) | |
| 70 | t.absent(result) | |
| 71 | }) | |
| 72 | ||
| 73 | test('crosspost - match by pattern filter', (t) => { | |
| 74 | const msg = { source: 'keet', text: 'Check https://example.com for details', author: 'alice' } | |
| 75 | const result = matchFilter(msg, { pattern: 'https?://\\S+' }) | |
| 76 | t.ok(result) | |
| 77 | }) | |
| 78 | ||
| 79 | test('crosspost - empty filter matches everything', (t) => { | |
| 80 | const msg = { source: 'mastodon', text: 'Anything goes', author: 'carol' } | |
| 81 | const result = matchFilter(msg, {}) | |
| 82 | t.ok(result) | |
| 83 | }) | |
| 84 | ||
| 85 | test('crosspost - format for mastodon trims to 500 chars', (t) => { | |
| 86 | const longText = 'A'.repeat(600) | |
| 87 | const msg = { source: 'keet', text: longText, author: 'alice' } | |
| 88 | const formatted = formatForTarget(msg, 'mastodon') | |
| 89 | t.ok(formatted.length <= PLATFORM_LIMITS.mastodon) | |
| 90 | t.ok(formatted.includes('...')) | |
| 91 | t.ok(formatted.includes('[via keet/alice]')) | |
| 92 | }) | |
| 93 | ||
| 94 | test('crosspost - format for mastodon with short text', (t) => { | |
| 95 | const msg = { source: 'keet', text: 'Short post', author: 'alice' } | |
| 96 | const formatted = formatForTarget(msg, 'mastodon') | |
| 97 | t.ok(formatted.length <= PLATFORM_LIMITS.mastodon) | |
| 98 | t.ok(formatted.includes('[via keet/alice]')) | |
| 99 | t.ok(formatted.includes('Short post')) | |
| 100 | }) | |
| 101 | ||
| 102 | test('crosspost - format for keet adds attribution', (t) => { | |
| 103 | const msg = { source: 'mastodon', text: 'Hello from the fediverse', author: 'bob' } | |
| 104 | const formatted = formatForTarget(msg, 'keet') | |
| 105 | t.ok(formatted.includes('[via mastodon/bob]')) | |
| 106 | t.ok(formatted.includes('Hello from the fediverse')) | |
| 107 | }) | |
| 108 | ||
| 109 | test('crosspost - format for irc trims and uses angle brackets', (t) => { | |
| 110 | const msg = { source: 'keet', text: 'Hello IRC', author: 'alice' } | |
| 111 | const formatted = formatForTarget(msg, 'irc') | |
| 112 | t.ok(formatted.includes('<alice@keet>')) | |
| 113 | t.ok(formatted.includes('Hello IRC')) | |
| 114 | }) | |
| 115 | ||
| 116 | test('crosspost - format for discord uses markdown bold', (t) => { | |
| 117 | const msg = { source: 'keet', text: 'Hey discord', author: 'alice' } | |
| 118 | const formatted = formatForTarget(msg, 'discord') | |
| 119 | t.ok(formatted.includes('**alice**')) | |
| 120 | t.ok(formatted.includes('Hey discord')) | |
| 121 | }) | |
| 122 | ||
| 123 | test('crosspost - format for matrix uses brackets', (t) => { | |
| 124 | const msg = { source: 'keet', text: 'Hello matrix', author: 'alice' } | |
| 125 | const formatted = formatForTarget(msg, 'matrix') | |
| 126 | t.ok(formatted.includes('[keet]')) | |
| 127 | t.ok(formatted.includes('<alice>')) | |
| 128 | }) | |
| 129 | ||
| 130 | test('crosspost - multiple rules produce multiple actions', (t) => { | |
| 131 | const msg = { source: 'keet', text: 'Broadcast #public', author: 'alice' } | |
| 132 | const config = { | |
| 133 | rules: [ | |
| 134 | { from: 'keet', to: 'mastodon', filter: { tags: ['public'] } }, | |
| 135 | { from: 'keet', to: 'discord', filter: { tags: ['public'] } }, | |
| 136 | { from: 'keet', to: 'matrix', filter: { tags: ['public'] } } | |
| 137 | ] | |
| 138 | } | |
| 139 | ||
| 140 | const actions = crosspost.process(msg, config) | |
| 141 | t.is(actions.length, 3) | |
| 142 | t.is(actions[0].target, 'mastodon') | |
| 143 | t.is(actions[1].target, 'discord') | |
| 144 | t.is(actions[2].target, 'matrix') | |
| 145 | }) | |
| 146 | ||
| 147 | test('crosspost - empty rules produce no actions', (t) => { | |
| 148 | const msg = { source: 'keet', text: 'Hello', author: 'alice' } | |
| 149 | const config = { rules: [] } | |
| 150 | ||
| 151 | const actions = crosspost.process(msg, config) | |
| 152 | t.is(actions.length, 0) | |
| 153 | }) | |
| 154 | ||
| 155 | test('crosspost - metadata includes crosspost info', (t) => { | |
| 156 | const msg = { source: 'keet', text: 'Cross me #public', author: 'alice', timestamp: 12345 } | |
| 157 | const config = { | |
| 158 | rules: [ | |
| 159 | { from: 'keet', to: 'mastodon', filter: { tags: ['public'] } } | |
| 160 | ] | |
| 161 | } | |
| 162 | ||
| 163 | const actions = crosspost.process(msg, config) | |
| 164 | t.is(actions[0].metadata.crosspostedFrom, 'keet') | |
| 165 | t.is(actions[0].metadata.originalAuthor, 'alice') | |
| 166 | t.is(actions[0].metadata.originalTimestamp, 12345) | |
| 167 | t.alike(actions[0].metadata.rule, { from: 'keet', to: 'mastodon' }) | |
| 168 | }) | |
| 169 | ||
| 170 | test('crosspost - stripRoutingTags removes #public from output', (t) => { | |
| 171 | const result = stripRoutingTags('Hello #public world', ['public']) | |
| 172 | t.is(result, 'Hello world') | |
| 173 | }) | |
| 174 | ||
| 175 | test('crosspost - routing tags stripped in formatted output', (t) => { | |
| 176 | const msg = { source: 'keet', text: 'Important #public announcement', author: 'alice' } | |
| 177 | const rule = { from: 'keet', to: 'keet', filter: { tags: ['public'] } } | |
| 178 | const formatted = formatForTarget(msg, 'keet', rule) | |
| 179 | t.absent(formatted.includes('#public')) | |
| 180 | t.ok(formatted.includes('Important')) | |
| 181 | t.ok(formatted.includes('announcement')) | |
| 182 | }) | |
| 183 | ||
| 184 | test('crosspost - contains filter works', (t) => { | |
| 185 | const msg = { source: 'keet', text: 'secret word: banana', author: 'alice' } | |
| 186 | t.ok(matchFilter(msg, { contains: 'banana' })) | |
| 187 | t.absent(matchFilter(msg, { contains: 'orange' })) | |
| 188 | }) | |
| 189 | ||
| 190 | // --------------------------------------------------------------------------- | |
| 191 | // Summarize tests | |
| 192 | // --------------------------------------------------------------------------- | |
| 193 | ||
| 194 | function makeMessages (count, opts = {}) { | |
| 195 | const messages = [] | |
| 196 | const authors = opts.authors || ['alice', 'bob', 'carol'] | |
| 197 | const baseTime = opts.baseTime || 1700000000000 | |
| 198 | ||
| 199 | for (let i = 0; i < count; i++) { | |
| 200 | messages.push({ | |
| 201 | text: opts.textFn ? opts.textFn(i) : `Message number ${i} about hypercore and distributed systems`, | |
| 202 | author: authors[i % authors.length], | |
| 203 | timestamp: baseTime + (i * 60000), // 1 min apart | |
| 204 | type: 'text' | |
| 205 | }) | |
| 206 | } | |
| 207 | return messages | |
| 208 | } | |
| 209 | ||
| 210 | test('summarize - extract topics from messages', (t) => { | |
| 211 | const messages = [ | |
| 212 | { text: 'hypercore is great for distributed data', author: 'alice' }, | |
| 213 | { text: 'I love hypercore and hyperbee', author: 'bob' }, | |
| 214 | { text: 'hypercore makes P2P easy', author: 'carol' }, | |
| 215 | { text: 'hyperbee is built on hypercore', author: 'alice' }, | |
| 216 | { text: 'distributed systems with hypercore', author: 'bob' } | |
| 217 | ] | |
| 218 | ||
| 219 | const topics = extractTopics(messages, { minCount: 2 }) | |
| 220 | t.ok(topics.length > 0) | |
| 221 | ||
| 222 | // hypercore should be the top topic (appears in all 5) | |
| 223 | t.is(topics[0].word, 'hypercore') | |
| 224 | t.is(topics[0].count, 5) | |
| 225 | }) | |
| 226 | ||
| 227 | test('summarize - extract key messages with questions', (t) => { | |
| 228 | const messages = [ | |
| 229 | { text: 'Hello', author: 'alice' }, | |
| 230 | { text: 'How does hypercore replication work?', author: 'bob' }, | |
| 231 | { text: 'ok', author: 'carol' }, | |
| 232 | { text: 'Check https://docs.holepunch.to for details', author: 'alice' }, | |
| 233 | { text: 'cool', author: 'bob' } | |
| 234 | ] | |
| 235 | ||
| 236 | const keyMsgs = extractKeyMessages(messages) | |
| 237 | t.ok(keyMsgs.length >= 2) | |
| 238 | ||
| 239 | // The question and the link should be picked up | |
| 240 | const texts = keyMsgs.map(m => m.text) | |
| 241 | t.ok(texts.some(t => t.includes('?'))) | |
| 242 | t.ok(texts.some(t => t.includes('https://'))) | |
| 243 | }) | |
| 244 | ||
| 245 | test('summarize - extract key messages with links', (t) => { | |
| 246 | const messages = [ | |
| 247 | { text: 'hi', author: 'alice' }, | |
| 248 | { text: 'See https://example.com/docs', author: 'bob' }, | |
| 249 | { text: 'thanks', author: 'carol' } | |
| 250 | ] | |
| 251 | ||
| 252 | const keyMsgs = extractKeyMessages(messages) | |
| 253 | t.ok(keyMsgs.length >= 1) | |
| 254 | t.ok(keyMsgs[0].text.includes('https://')) | |
| 255 | }) | |
| 256 | ||
| 257 | test('summarize - participant list sorted by message count', (t) => { | |
| 258 | const messages = [ | |
| 259 | { text: 'a', author: 'alice' }, | |
| 260 | { text: 'b', author: 'alice' }, | |
| 261 | { text: 'c', author: 'alice' }, | |
| 262 | { text: 'd', author: 'bob' }, | |
| 263 | { text: 'e', author: 'carol' } | |
| 264 | ] | |
| 265 | ||
| 266 | const participants = extractParticipants(messages) | |
| 267 | t.is(participants.length, 3) | |
| 268 | t.is(participants[0].name, 'alice') | |
| 269 | t.is(participants[0].count, 3) | |
| 270 | t.is(participants[1].name, 'bob') | |
| 271 | t.is(participants[1].count, 1) | |
| 272 | }) | |
| 273 | ||
| 274 | test('summarize - time range calculation', (t) => { | |
| 275 | const messages = [ | |
| 276 | { text: 'first', author: 'alice', timestamp: 1000 }, | |
| 277 | { text: 'second', author: 'bob', timestamp: 2000 }, | |
| 278 | { text: 'third', author: 'carol', timestamp: 5000 } | |
| 279 | ] | |
| 280 | ||
| 281 | const range = getTimeRange(messages) | |
| 282 | t.is(range.start, 1000) | |
| 283 | t.is(range.end, 5000) | |
| 284 | t.is(range.durationMs, 4000) | |
| 285 | }) | |
| 286 | ||
| 287 | test('summarize - time range with no timestamps', (t) => { | |
| 288 | const messages = [ | |
| 289 | { text: 'first', author: 'alice' }, | |
| 290 | { text: 'second', author: 'bob' } | |
| 291 | ] | |
| 292 | ||
| 293 | const range = getTimeRange(messages) | |
| 294 | t.is(range.start, 0) | |
| 295 | t.is(range.end, 0) | |
| 296 | t.is(range.durationMs, 0) | |
| 297 | }) | |
| 298 | ||
| 299 | test('summarize - summary format includes all sections', (t) => { | |
| 300 | const messages = makeMessages(20, { | |
| 301 | textFn: (i) => { | |
| 302 | if (i === 5) return 'How does hypercore replication work?' | |
| 303 | if (i === 10) return 'Check https://docs.holepunch.to for info' | |
| 304 | return `Discussing hypercore distributed database systems for message ${i}` | |
| 305 | } | |
| 306 | }) | |
| 307 | ||
| 308 | const result = summarize.process(messages) | |
| 309 | t.ok(result.summary.includes('## Conversation Summary')) | |
| 310 | t.ok(result.summary.includes('### Participants')) | |
| 311 | t.ok(result.summary.includes('### Key Messages')) | |
| 312 | t.is(result.messageCount, 20) | |
| 313 | t.ok(result.participants.length > 0) | |
| 314 | }) | |
| 315 | ||
| 316 | test('summarize - minimum message threshold', (t) => { | |
| 317 | const messages = makeMessages(5) // below default minMessages of 10 | |
| 318 | ||
| 319 | const result = summarize.process(messages) | |
| 320 | t.is(result.summary, '') | |
| 321 | t.ok(result.belowThreshold) | |
| 322 | t.is(result.messageCount, 5) | |
| 323 | // Participants still extracted even below threshold | |
| 324 | t.ok(result.participants.length > 0) | |
| 325 | }) | |
| 326 | ||
| 327 | test('summarize - empty input handling', (t) => { | |
| 328 | const result = summarize.process([]) | |
| 329 | t.is(result.summary, '') | |
| 330 | t.is(result.messageCount, 0) | |
| 331 | t.is(result.participants.length, 0) | |
| 332 | t.is(result.topics.length, 0) | |
| 333 | t.is(result.keyMessages.length, 0) | |
| 334 | }) | |
| 335 | ||
| 336 | test('summarize - null input handling', (t) => { | |
| 337 | const result = summarize.process(null) | |
| 338 | t.is(result.summary, '') | |
| 339 | t.is(result.messageCount, 0) | |
| 340 | }) | |
| 341 | ||
| 342 | test('summarize - window size limits messages', (t) => { | |
| 343 | const messages = makeMessages(100) | |
| 344 | ||
| 345 | const result = summarize.process(messages, { windowSize: 20 }) | |
| 346 | t.is(result.messageCount, 20) | |
| 347 | }) | |
| 348 | ||
| 349 | test('summarize - tokenize strips stop words', (t) => { | |
| 350 | const tokens = tokenize('the quick brown fox jumps over the lazy dog') | |
| 351 | t.absent(tokens.includes('the')) | |
| 352 | t.absent(tokens.includes('over')) | |
| 353 | t.ok(tokens.includes('quick')) | |
| 354 | t.ok(tokens.includes('brown')) | |
| 355 | t.ok(tokens.includes('fox')) | |
| 356 | }) | |
| 357 | ||
| 358 | test('summarize - tokenize strips URLs', (t) => { | |
| 359 | const tokens = tokenize('check https://example.com for details') | |
| 360 | t.absent(tokens.some(t => t.includes('https'))) | |
| 361 | t.absent(tokens.some(t => t.includes('example'))) | |
| 362 | }) | |
| 363 | ||
| 364 | test('summarize - formatDuration readable output', (t) => { | |
| 365 | t.is(formatDuration(30000), 'less than a minute') | |
| 366 | t.is(formatDuration(60000), '1 minute') | |
| 367 | t.is(formatDuration(120000), '2 minutes') | |
| 368 | t.is(formatDuration(3600000), '1 hour') | |
| 369 | t.is(formatDuration(7200000), '2 hours') | |
| 370 | t.is(formatDuration(90000000), '1 day') | |
| 371 | }) | |
| 372 | ||
| 373 | test('summarize - messages with various content types', (t) => { | |
| 374 | const messages = makeMessages(15, { | |
| 375 | textFn: (i) => { | |
| 376 | switch (i % 5) { | |
| 377 | case 0: return 'Is hypercore better than IPFS?' | |
| 378 | case 1: return 'Read https://docs.holepunch.to/hypercore' | |
| 379 | case 2: return 'The `hypercore.append()` method is key' | |
| 380 | case 3: return 'I think hypercore is amazing!' | |
| 381 | case 4: return 'ok' | |
| 382 | } | |
| 383 | } | |
| 384 | }) | |
| 385 | ||
| 386 | const keyMsgs = extractKeyMessages(messages) | |
| 387 | t.ok(keyMsgs.length > 0) | |
| 388 | ||
| 389 | // Questions should be highest priority (score 3) | |
| 390 | t.ok(keyMsgs[0].text.includes('?')) | |
| 391 | }) | |
| 392 | ||
| 393 | test('summarize - topics minCount threshold', (t) => { | |
| 394 | const messages = [ | |
| 395 | { text: 'hypercore is cool', author: 'alice' }, | |
| 396 | { text: 'hypercore rocks', author: 'bob' }, | |
| 397 | { text: 'randomword', author: 'carol' } | |
| 398 | ] | |
| 399 | ||
| 400 | // minCount=3 means hypercore (2 occurrences) won't pass | |
| 401 | const topics = extractTopics(messages, { minCount: 3 }) | |
| 402 | t.is(topics.length, 0) | |
| 403 | ||
| 404 | // minCount=2 means hypercore passes | |
| 405 | const topics2 = extractTopics(messages, { minCount: 2 }) | |
| 406 | t.ok(topics2.length > 0) | |
| 407 | t.is(topics2[0].word, 'hypercore') | |
| 408 | }) | |
| 409 | ||
| 410 | test('summarize - config override', (t) => { | |
| 411 | const messages = makeMessages(20) | |
| 412 | ||
| 413 | const result = summarize.process(messages, { | |
| 414 | includeParticipants: false, | |
| 415 | includeTopics: false | |
| 416 | }) | |
| 417 | ||
| 418 | t.is(result.participants.length, 0) | |
| 419 | t.is(result.topics.length, 0) | |
| 420 | // Key messages still extracted | |
| 421 | t.ok(result.messageCount > 0) | |
| 422 | }) |
| … | … | @@ -0,0 +1,162 @@ |
| 1 | import test from 'brittle' | |
| 2 | import { DaemonRuntime } from '../lib/runtime.js' | |
| 3 | import { tmpdir } from 'os' | |
| 4 | import { join } from 'path' | |
| 5 | import { randomBytes } from 'crypto' | |
| 6 | ||
| 7 | function tmpStorage () { | |
| 8 | return join(tmpdir(), 'hyper-daemon-test-' + randomBytes(8).toString('hex')) | |
| 9 | } | |
| 10 | ||
| 11 | test('DaemonRuntime - load and fire script', async (t) => { | |
| 12 | const logs = [] | |
| 13 | const runtime = new DaemonRuntime(tmpStorage(), { | |
| 14 | onLog: (msg) => logs.push(msg) | |
| 15 | }) | |
| 16 | await runtime.ready() | |
| 17 | ||
| 18 | let actionCalled = false | |
| 19 | ||
| 20 | await runtime.load('greet', { | |
| 21 | trigger: { type: 'message', filter: { contains: 'hello' } }, | |
| 22 | actions: [{ | |
| 23 | type: 'log', | |
| 24 | message: 'Greeting detected!' | |
| 25 | }], | |
| 26 | enabled: true | |
| 27 | }) | |
| 28 | ||
| 29 | // Override action for testing — fire triggers the registered action | |
| 30 | const results = await runtime.fire('message', { text: 'hello world' }) | |
| 31 | t.is(results.length, 1) | |
| 32 | t.ok(results[0].ok) | |
| 33 | ||
| 34 | t.ok(logs.some(l => l.includes('Loaded script: greet'))) | |
| 35 | ||
| 36 | await runtime.close() | |
| 37 | }) | |
| 38 | ||
| 39 | test('DaemonRuntime - list scripts', async (t) => { | |
| 40 | const runtime = new DaemonRuntime(tmpStorage(), { onLog: () => {} }) | |
| 41 | await runtime.ready() | |
| 42 | ||
| 43 | await runtime.load('script-a', { | |
| 44 | trigger: { type: 'message' }, | |
| 45 | actions: [{ type: 'log', message: 'a' }] | |
| 46 | }) | |
| 47 | await runtime.load('script-b', { | |
| 48 | trigger: { type: 'file-change' }, | |
| 49 | actions: [{ type: 'log', message: 'b' }] | |
| 50 | }) | |
| 51 | ||
| 52 | const list = runtime.list() | |
| 53 | t.is(list.length, 2) | |
| 54 | t.ok(list.find(s => s.id === 'script-a')) | |
| 55 | t.ok(list.find(s => s.id === 'script-b')) | |
| 56 | ||
| 57 | await runtime.close() | |
| 58 | }) | |
| 59 | ||
| 60 | test('DaemonRuntime - unload script', async (t) => { | |
| 61 | const runtime = new DaemonRuntime(tmpStorage(), { onLog: () => {} }) | |
| 62 | await runtime.ready() | |
| 63 | ||
| 64 | await runtime.load('temp', { | |
| 65 | trigger: { type: 'message' }, | |
| 66 | actions: [{ type: 'log', message: 'temp' }] | |
| 67 | }) | |
| 68 | ||
| 69 | t.is(runtime.list().length, 1) | |
| 70 | ||
| 71 | await runtime.unload('temp') | |
| 72 | t.is(runtime.list().length, 0) | |
| 73 | ||
| 74 | // Verify no triggers fire | |
| 75 | const results = await runtime.fire('message', {}) | |
| 76 | t.is(results.length, 0) | |
| 77 | ||
| 78 | await runtime.close() | |
| 79 | }) | |
| 80 | ||
| 81 | test('DaemonRuntime - enable/disable', async (t) => { | |
| 82 | const runtime = new DaemonRuntime(tmpStorage(), { onLog: () => {} }) | |
| 83 | await runtime.ready() | |
| 84 | ||
| 85 | await runtime.load('toggle', { | |
| 86 | trigger: { type: 'message' }, | |
| 87 | actions: [{ type: 'log', message: 'toggled' }] | |
| 88 | }) | |
| 89 | ||
| 90 | runtime.disable('toggle') | |
| 91 | const r1 = await runtime.fire('message', { text: 'test' }) | |
| 92 | t.is(r1.length, 0) | |
| 93 | ||
| 94 | runtime.enable('toggle') | |
| 95 | const r2 = await runtime.fire('message', { text: 'test' }) | |
| 96 | t.is(r2.length, 1) | |
| 97 | ||
| 98 | await runtime.close() | |
| 99 | }) | |
| 100 | ||
| 101 | test('DaemonRuntime - filter matching', async (t) => { | |
| 102 | const runtime = new DaemonRuntime(tmpStorage(), { onLog: () => {} }) | |
| 103 | await runtime.ready() | |
| 104 | ||
| 105 | await runtime.load('filtered', { | |
| 106 | trigger: { type: 'message', filter: { author: 'alice' } }, | |
| 107 | actions: [{ type: 'log', message: 'alice said something' }] | |
| 108 | }) | |
| 109 | ||
| 110 | const r1 = await runtime.fire('message', { author: 'alice', text: 'hi' }) | |
| 111 | t.is(r1.length, 1) | |
| 112 | ||
| 113 | const r2 = await runtime.fire('message', { author: 'bob', text: 'hi' }) | |
| 114 | t.is(r2.length, 0) | |
| 115 | ||
| 116 | await runtime.close() | |
| 117 | }) | |
| 118 | ||
| 119 | test('DaemonRuntime - event logging', async (t) => { | |
| 120 | const runtime = new DaemonRuntime(tmpStorage(), { onLog: () => {} }) | |
| 121 | await runtime.ready() | |
| 122 | ||
| 123 | await runtime.load('logged', { | |
| 124 | trigger: { type: 'message' }, | |
| 125 | actions: [{ type: 'log', message: 'event' }] | |
| 126 | }) | |
| 127 | ||
| 128 | await runtime.fire('message', { text: 'first' }) | |
| 129 | await runtime.fire('message', { text: 'second' }) | |
| 130 | ||
| 131 | const log = await runtime.getLog('logged') | |
| 132 | t.is(log.length, 2) | |
| 133 | t.ok(log[0].timestamp > 0) | |
| 134 | t.is(log[0].trigger, 'message') | |
| 135 | ||
| 136 | await runtime.close() | |
| 137 | }) | |
| 138 | ||
| 139 | test('DaemonRuntime - persistence (loadAll)', async (t) => { | |
| 140 | const storage = tmpStorage() | |
| 141 | ||
| 142 | // Save scripts | |
| 143 | const rt1 = new DaemonRuntime(storage, { onLog: () => {} }) | |
| 144 | await rt1.ready() | |
| 145 | await rt1.load('persistent', { | |
| 146 | trigger: { type: 'message' }, | |
| 147 | actions: [{ type: 'log', message: 'persistent' }] | |
| 148 | }) | |
| 149 | await rt1.close() | |
| 150 | ||
| 151 | // Reload | |
| 152 | const rt2 = new DaemonRuntime(storage, { onLog: () => {} }) | |
| 153 | await rt2.ready() | |
| 154 | const loaded = await rt2.loadAll() | |
| 155 | t.is(loaded, 1) | |
| 156 | ||
| 157 | const list = rt2.list() | |
| 158 | t.is(list.length, 1) | |
| 159 | t.is(list[0].id, 'persistent') | |
| 160 | ||
| 161 | await rt2.close() | |
| 162 | }) |
| … | … | @@ -0,0 +1,228 @@ |
| 1 | import test from 'brittle' | |
| 2 | import { TriggerEngine } from '../lib/triggers.js' | |
| 3 | ||
| 4 | test('TriggerEngine - register and fire', async (t) => { | |
| 5 | const engine = new TriggerEngine() | |
| 6 | let called = false | |
| 7 | ||
| 8 | engine.register('test1', { | |
| 9 | type: 'message', | |
| 10 | action: async (data) => { | |
| 11 | called = true | |
| 12 | return data.text | |
| 13 | } | |
| 14 | }) | |
| 15 | ||
| 16 | const results = await engine.fire('message', { text: 'hello' }) | |
| 17 | t.ok(called) | |
| 18 | t.is(results.length, 1) | |
| 19 | t.is(results[0].id, 'test1') | |
| 20 | t.ok(results[0].ok) | |
| 21 | t.is(results[0].result, 'hello') | |
| 22 | ||
| 23 | engine.destroy() | |
| 24 | }) | |
| 25 | ||
| 26 | test('TriggerEngine - no match for different event type', async (t) => { | |
| 27 | const engine = new TriggerEngine() | |
| 28 | ||
| 29 | engine.register('msg-handler', { | |
| 30 | type: 'message', | |
| 31 | action: async () => 'handled' | |
| 32 | }) | |
| 33 | ||
| 34 | const results = await engine.fire('file-change', {}) | |
| 35 | t.is(results.length, 0) | |
| 36 | ||
| 37 | engine.destroy() | |
| 38 | }) | |
| 39 | ||
| 40 | test('TriggerEngine - filter by channel', async (t) => { | |
| 41 | const engine = new TriggerEngine() | |
| 42 | ||
| 43 | engine.register('chan-filter', { | |
| 44 | type: 'message', | |
| 45 | filter: { channel: '#important' }, | |
| 46 | action: async () => 'matched' | |
| 47 | }) | |
| 48 | ||
| 49 | const r1 = await engine.fire('message', { channel: '#important', text: 'hi' }) | |
| 50 | t.is(r1.length, 1) | |
| 51 | ||
| 52 | const r2 = await engine.fire('message', { channel: '#general', text: 'hi' }) | |
| 53 | t.is(r2.length, 0) | |
| 54 | ||
| 55 | engine.destroy() | |
| 56 | }) | |
| 57 | ||
| 58 | test('TriggerEngine - filter by contains', async (t) => { | |
| 59 | const engine = new TriggerEngine() | |
| 60 | ||
| 61 | engine.register('keyword', { | |
| 62 | type: 'message', | |
| 63 | filter: { contains: '#public' }, | |
| 64 | action: async () => 'matched' | |
| 65 | }) | |
| 66 | ||
| 67 | const r1 = await engine.fire('message', { text: 'Hey everyone #public' }) | |
| 68 | t.is(r1.length, 1) | |
| 69 | ||
| 70 | const r2 = await engine.fire('message', { text: 'private message' }) | |
| 71 | t.is(r2.length, 0) | |
| 72 | ||
| 73 | engine.destroy() | |
| 74 | }) | |
| 75 | ||
| 76 | test('TriggerEngine - filter by pattern', async (t) => { | |
| 77 | const engine = new TriggerEngine() | |
| 78 | ||
| 79 | engine.register('url-detect', { | |
| 80 | type: 'message', | |
| 81 | filter: { pattern: 'https?://\\S+' }, | |
| 82 | action: async () => 'url found' | |
| 83 | }) | |
| 84 | ||
| 85 | const r1 = await engine.fire('message', { text: 'Check https://example.com' }) | |
| 86 | t.is(r1.length, 1) | |
| 87 | ||
| 88 | const r2 = await engine.fire('message', { text: 'no urls here' }) | |
| 89 | t.is(r2.length, 0) | |
| 90 | ||
| 91 | engine.destroy() | |
| 92 | }) | |
| 93 | ||
| 94 | test('TriggerEngine - filter by author', async (t) => { | |
| 95 | const engine = new TriggerEngine() | |
| 96 | ||
| 97 | engine.register('alice-only', { | |
| 98 | type: 'message', | |
| 99 | filter: { author: 'alice' }, | |
| 100 | action: async () => 'from alice' | |
| 101 | }) | |
| 102 | ||
| 103 | const r1 = await engine.fire('message', { author: 'alice', text: 'hi' }) | |
| 104 | t.is(r1.length, 1) | |
| 105 | ||
| 106 | const r2 = await engine.fire('message', { author: 'bob', text: 'hi' }) | |
| 107 | t.is(r2.length, 0) | |
| 108 | ||
| 109 | engine.destroy() | |
| 110 | }) | |
| 111 | ||
| 112 | test('TriggerEngine - filter by tags', async (t) => { | |
| 113 | const engine = new TriggerEngine() | |
| 114 | ||
| 115 | engine.register('tagged', { | |
| 116 | type: 'message', | |
| 117 | filter: { tags: ['urgent', 'critical'] }, | |
| 118 | action: async () => 'tagged' | |
| 119 | }) | |
| 120 | ||
| 121 | const r1 = await engine.fire('message', { text: 'This is URGENT please help' }) | |
| 122 | t.is(r1.length, 1) | |
| 123 | ||
| 124 | const r2 = await engine.fire('message', { text: 'normal message' }) | |
| 125 | t.is(r2.length, 0) | |
| 126 | ||
| 127 | engine.destroy() | |
| 128 | }) | |
| 129 | ||
| 130 | test('TriggerEngine - enable/disable', async (t) => { | |
| 131 | const engine = new TriggerEngine() | |
| 132 | ||
| 133 | engine.register('toggle', { | |
| 134 | type: 'message', | |
| 135 | action: async () => 'fired' | |
| 136 | }) | |
| 137 | ||
| 138 | engine.disable('toggle') | |
| 139 | const r1 = await engine.fire('message', { text: 'hi' }) | |
| 140 | t.is(r1.length, 0) | |
| 141 | ||
| 142 | engine.enable('toggle') | |
| 143 | const r2 = await engine.fire('message', { text: 'hi' }) | |
| 144 | t.is(r2.length, 1) | |
| 145 | ||
| 146 | engine.destroy() | |
| 147 | }) | |
| 148 | ||
| 149 | test('TriggerEngine - unregister', async (t) => { | |
| 150 | const engine = new TriggerEngine() | |
| 151 | ||
| 152 | engine.register('temp', { | |
| 153 | type: 'message', | |
| 154 | action: async () => 'fired' | |
| 155 | }) | |
| 156 | ||
| 157 | t.is(engine.count, 1) | |
| 158 | engine.unregister('temp') | |
| 159 | t.is(engine.count, 0) | |
| 160 | ||
| 161 | const results = await engine.fire('message', {}) | |
| 162 | t.is(results.length, 0) | |
| 163 | ||
| 164 | engine.destroy() | |
| 165 | }) | |
| 166 | ||
| 167 | test('TriggerEngine - list triggers', (t) => { | |
| 168 | const engine = new TriggerEngine() | |
| 169 | ||
| 170 | engine.register('a', { type: 'message', action: async () => {} }) | |
| 171 | engine.register('b', { type: 'file-change', filter: { channel: 'x' }, action: async () => {} }) | |
| 172 | ||
| 173 | const list = engine.list() | |
| 174 | t.is(list.length, 2) | |
| 175 | t.ok(list.find(t => t.id === 'a' && t.type === 'message')) | |
| 176 | t.ok(list.find(t => t.id === 'b' && t.hasFilter === true)) | |
| 177 | ||
| 178 | engine.destroy() | |
| 179 | }) | |
| 180 | ||
| 181 | test('TriggerEngine - multiple triggers fire in order', async (t) => { | |
| 182 | const engine = new TriggerEngine() | |
| 183 | const order = [] | |
| 184 | ||
| 185 | engine.register('first', { | |
| 186 | type: 'message', | |
| 187 | action: async () => { order.push('first') } | |
| 188 | }) | |
| 189 | engine.register('second', { | |
| 190 | type: 'message', | |
| 191 | action: async () => { order.push('second') } | |
| 192 | }) | |
| 193 | ||
| 194 | await engine.fire('message', {}) | |
| 195 | t.alike(order, ['first', 'second']) | |
| 196 | ||
| 197 | engine.destroy() | |
| 198 | }) | |
| 199 | ||
| 200 | test('TriggerEngine - action error captured', async (t) => { | |
| 201 | const engine = new TriggerEngine() | |
| 202 | ||
| 203 | engine.register('broken', { | |
| 204 | type: 'message', | |
| 205 | action: async () => { throw new Error('boom') } | |
| 206 | }) | |
| 207 | ||
| 208 | const results = await engine.fire('message', {}) | |
| 209 | t.is(results.length, 1) | |
| 210 | t.absent(results[0].ok) | |
| 211 | t.is(results[0].error, 'boom') | |
| 212 | ||
| 213 | engine.destroy() | |
| 214 | }) | |
| 215 | ||
| 216 | test('TriggerEngine - schedule trigger', async (t) => { | |
| 217 | const engine = new TriggerEngine() | |
| 218 | let count = 0 | |
| 219 | ||
| 220 | engine.startSchedule('ticker', 30, async () => { | |
| 221 | count++ | |
| 222 | }) | |
| 223 | ||
| 224 | await new Promise(r => setTimeout(r, 80)) | |
| 225 | t.ok(count >= 2, `scheduled ran ${count} times`) | |
| 226 | ||
| 227 | engine.destroy() | |
| 228 | }) |
| … | … | @@ -0,0 +1,4 @@ |
| 1 | import { Feed } from './lib/feed.js' | |
| 2 | import { FeedStore, SOURCES, TYPES } from './lib/feed-store.js' | |
| 3 | ||
| 4 | export { Feed, FeedStore, SOURCES, TYPES } |
| … | … | @@ -0,0 +1,243 @@ |
| 1 | import { randomBytes } from 'crypto' | |
| 2 | ||
| 3 | const SOURCES = ['irc', 'keet', 'mastodon', 'discord', 'email'] | |
| 4 | const TYPES = ['post', 'reply', 'boost', 'reaction', 'system'] | |
| 5 | ||
| 6 | class FeedStore { | |
| 7 | constructor (bee) { | |
| 8 | this.bee = bee | |
| 9 | } | |
| 10 | ||
| 11 | _genId () { | |
| 12 | return randomBytes(8).toString('hex') | |
| 13 | } | |
| 14 | ||
| 15 | _itemKey (id) { | |
| 16 | return `item/${id}` | |
| 17 | } | |
| 18 | ||
| 19 | _timelineKey (timestamp, id) { | |
| 20 | return `timeline/${this._padTimestamp(timestamp)}/${id}` | |
| 21 | } | |
| 22 | ||
| 23 | _sourceKey (source, timestamp, id) { | |
| 24 | return `source/${source}/${this._padTimestamp(timestamp)}/${id}` | |
| 25 | } | |
| 26 | ||
| 27 | _authorKey (authorId, timestamp, id) { | |
| 28 | return `author/${authorId}/${this._padTimestamp(timestamp)}/${id}` | |
| 29 | } | |
| 30 | ||
| 31 | _padTimestamp (ts) { | |
| 32 | return String(ts).padStart(16, '0') | |
| 33 | } | |
| 34 | ||
| 35 | async ready () { | |
| 36 | await this.bee.ready() | |
| 37 | } | |
| 38 | ||
| 39 | async add (opts = {}) { | |
| 40 | const id = opts.id || this._genId() | |
| 41 | const now = Date.now() | |
| 42 | ||
| 43 | const item = { | |
| 44 | id, | |
| 45 | source: opts.source || 'keet', | |
| 46 | author: opts.author || 'unknown', | |
| 47 | authorId: opts.authorId || opts.author || 'unknown', | |
| 48 | text: opts.text || '', | |
| 49 | timestamp: opts.timestamp || now, | |
| 50 | type: opts.type || 'post', | |
| 51 | tags: opts.tags || [], | |
| 52 | url: opts.url || null, | |
| 53 | metadata: opts.metadata || {} | |
| 54 | } | |
| 55 | ||
| 56 | const batch = this.bee.batch() | |
| 57 | ||
| 58 | // Primary record | |
| 59 | await batch.put(this._itemKey(id), item) | |
| 60 | ||
| 61 | // Timeline index (stores just the id for lookup) | |
| 62 | await batch.put(this._timelineKey(item.timestamp, id), { id }) | |
| 63 | ||
| 64 | // Source index | |
| 65 | await batch.put(this._sourceKey(item.source, item.timestamp, id), { id }) | |
| 66 | ||
| 67 | // Author index | |
| 68 | await batch.put(this._authorKey(item.authorId, item.timestamp, id), { id }) | |
| 69 | ||
| 70 | await batch.flush() | |
| 71 | ||
| 72 | return item | |
| 73 | } | |
| 74 | ||
| 75 | async get (id) { | |
| 76 | const node = await this.bee.get(this._itemKey(id)) | |
| 77 | return node ? node.value : null | |
| 78 | } | |
| 79 | ||
| 80 | async remove (id) { | |
| 81 | const item = await this.get(id) | |
| 82 | if (!item) return false | |
| 83 | ||
| 84 | const batch = this.bee.batch() | |
| 85 | ||
| 86 | // Remove primary record | |
| 87 | await batch.del(this._itemKey(id)) | |
| 88 | ||
| 89 | // Remove timeline index | |
| 90 | await batch.del(this._timelineKey(item.timestamp, id)) | |
| 91 | ||
| 92 | // Remove source index | |
| 93 | await batch.del(this._sourceKey(item.source, item.timestamp, id)) | |
| 94 | ||
| 95 | // Remove author index | |
| 96 | await batch.del(this._authorKey(item.authorId, item.timestamp, id)) | |
| 97 | ||
| 98 | await batch.flush() | |
| 99 | ||
| 100 | return true | |
| 101 | } | |
| 102 | ||
| 103 | async timeline (opts = {}) { | |
| 104 | const { limit, before, after, source, type } = opts | |
| 105 | const items = [] | |
| 106 | ||
| 107 | const rangeOpts = {} | |
| 108 | ||
| 109 | if (after) { | |
| 110 | rangeOpts.gt = `timeline/${this._padTimestamp(after)}/~` | |
| 111 | } else { | |
| 112 | rangeOpts.gte = 'timeline/' | |
| 113 | } | |
| 114 | ||
| 115 | if (before) { | |
| 116 | rangeOpts.lt = `timeline/${this._padTimestamp(before)}/` | |
| 117 | } else { | |
| 118 | rangeOpts.lte = 'timeline/~' | |
| 119 | } | |
| 120 | ||
| 121 | for await (const node of this.bee.createReadStream(rangeOpts)) { | |
| 122 | if (!node.value) continue | |
| 123 | ||
| 124 | const item = await this.get(node.value.id) | |
| 125 | if (!item) continue | |
| 126 | ||
| 127 | // Filter by source | |
| 128 | if (source && item.source !== source) continue | |
| 129 | ||
| 130 | // Filter by type | |
| 131 | if (type && item.type !== type) continue | |
| 132 | ||
| 133 | items.push(item) | |
| 134 | ||
| 135 | if (limit && items.length >= limit) break | |
| 136 | } | |
| 137 | ||
| 138 | return items | |
| 139 | } | |
| 140 | ||
| 141 | async bySource (source, opts = {}) { | |
| 142 | const { limit, before } = opts | |
| 143 | const items = [] | |
| 144 | ||
| 145 | const rangeOpts = { | |
| 146 | gte: `source/${source}/` | |
| 147 | } | |
| 148 | ||
| 149 | if (before) { | |
| 150 | rangeOpts.lt = `source/${source}/${this._padTimestamp(before)}/` | |
| 151 | } else { | |
| 152 | rangeOpts.lte = `source/${source}/~` | |
| 153 | } | |
| 154 | ||
| 155 | for await (const node of this.bee.createReadStream(rangeOpts)) { | |
| 156 | if (!node.value) continue | |
| 157 | ||
| 158 | const item = await this.get(node.value.id) | |
| 159 | if (!item) continue | |
| 160 | ||
| 161 | items.push(item) | |
| 162 | ||
| 163 | if (limit && items.length >= limit) break | |
| 164 | } | |
| 165 | ||
| 166 | return items | |
| 167 | } | |
| 168 | ||
| 169 | async byAuthor (authorId, opts = {}) { | |
| 170 | const { limit } = opts | |
| 171 | const items = [] | |
| 172 | ||
| 173 | for await (const node of this.bee.createReadStream({ | |
| 174 | gte: `author/${authorId}/`, | |
| 175 | lte: `author/${authorId}/~` | |
| 176 | })) { | |
| 177 | if (!node.value) continue | |
| 178 | ||
| 179 | const item = await this.get(node.value.id) | |
| 180 | if (!item) continue | |
| 181 | ||
| 182 | items.push(item) | |
| 183 | ||
| 184 | if (limit && items.length >= limit) break | |
| 185 | } | |
| 186 | ||
| 187 | return items | |
| 188 | } | |
| 189 | ||
| 190 | async search (query) { | |
| 191 | const q = query.toLowerCase() | |
| 192 | const results = [] | |
| 193 | ||
| 194 | for await (const node of this.bee.createReadStream({ | |
| 195 | gte: 'item/', | |
| 196 | lte: 'item/~' | |
| 197 | })) { | |
| 198 | const item = node.value | |
| 199 | if (!item) continue | |
| 200 | ||
| 201 | if (item.text && item.text.toLowerCase().includes(q)) { | |
| 202 | results.push(item) | |
| 203 | continue | |
| 204 | } | |
| 205 | ||
| 206 | if (item.tags && item.tags.some(t => t.toLowerCase().includes(q))) { | |
| 207 | results.push(item) | |
| 208 | continue | |
| 209 | } | |
| 210 | } | |
| 211 | ||
| 212 | return results | |
| 213 | } | |
| 214 | ||
| 215 | async stats () { | |
| 216 | const bySource = {} | |
| 217 | const byType = {} | |
| 218 | ||
| 219 | for await (const node of this.bee.createReadStream({ | |
| 220 | gte: 'item/', | |
| 221 | lte: 'item/~' | |
| 222 | })) { | |
| 223 | const item = node.value | |
| 224 | if (!item) continue | |
| 225 | ||
| 226 | bySource[item.source] = (bySource[item.source] || 0) + 1 | |
| 227 | byType[item.type] = (byType[item.type] || 0) + 1 | |
| 228 | } | |
| 229 | ||
| 230 | let total = 0 | |
| 231 | for (const count of Object.values(bySource)) { | |
| 232 | total += count | |
| 233 | } | |
| 234 | ||
| 235 | return { | |
| 236 | total, | |
| 237 | bySource, | |
| 238 | byType | |
| 239 | } | |
| 240 | } | |
| 241 | } | |
| 242 | ||
| 243 | export { FeedStore, SOURCES, TYPES } |
| … | … | @@ -0,0 +1,59 @@ |
| 1 | import Corestore from 'corestore' | |
| 2 | import Hyperbee from 'hyperbee' | |
| 3 | import { FeedStore } from './feed-store.js' | |
| 4 | ||
| 5 | class Feed { | |
| 6 | constructor (storage) { | |
| 7 | this.store = new Corestore(storage) | |
| 8 | this.bee = new Hyperbee(this.store.get({ name: 'feed' }), { | |
| 9 | keyEncoding: 'utf-8', | |
| 10 | valueEncoding: 'json' | |
| 11 | }) | |
| 12 | this.items = new FeedStore(this.bee) | |
| 13 | } | |
| 14 | ||
| 15 | async ready () { | |
| 16 | await this.store.ready() | |
| 17 | await this.items.ready() | |
| 18 | } | |
| 19 | ||
| 20 | // Item CRUD | |
| 21 | async add (opts) { | |
| 22 | return this.items.add(opts) | |
| 23 | } | |
| 24 | ||
| 25 | async get (id) { | |
| 26 | return this.items.get(id) | |
| 27 | } | |
| 28 | ||
| 29 | async remove (id) { | |
| 30 | return this.items.remove(id) | |
| 31 | } | |
| 32 | ||
| 33 | // Queries | |
| 34 | async timeline (opts) { | |
| 35 | return this.items.timeline(opts) | |
| 36 | } | |
| 37 | ||
| 38 | async bySource (source, opts) { | |
| 39 | return this.items.bySource(source, opts) | |
| 40 | } | |
| 41 | ||
| 42 | async byAuthor (authorId, opts) { | |
| 43 | return this.items.byAuthor(authorId, opts) | |
| 44 | } | |
| 45 | ||
| 46 | async search (query) { | |
| 47 | return this.items.search(query) | |
| 48 | } | |
| 49 | ||
| 50 | async stats () { | |
| 51 | return this.items.stats() | |
| 52 | } | |
| 53 | ||
| 54 | async close () { | |
| 55 | await this.store.close() | |
| 56 | } | |
| 57 | } | |
| 58 | ||
| 59 | export { Feed } |
| … | … | @@ -0,0 +1,18 @@ |
| 1 | { | |
| 2 | "name": "hyper-feed", | |
| 3 | "version": "1.0.0", | |
| 4 | "description": "P2P social timeline aggregator backed by Hyperbee", | |
| 5 | "type": "module", | |
| 6 | "main": "lib/feed.js", | |
| 7 | "scripts": { | |
| 8 | "test": "brittle test/*.test.js" | |
| 9 | }, | |
| 10 | "dependencies": { | |
| 11 | "hyperbee": "^2.0.0", | |
| 12 | "corestore": "^6.0.0" | |
| 13 | }, | |
| 14 | "devDependencies": { | |
| 15 | "brittle": "^3.0.0" | |
| 16 | }, | |
| 17 | "license": "MIT" | |
| 18 | } |
| … | … | @@ -0,0 +1,596 @@ |
| 1 | import test from 'brittle' | |
| 2 | import { Feed } from '../lib/feed.js' | |
| 3 | import { tmpdir } from 'os' | |
| 4 | import { join } from 'path' | |
| 5 | import { randomBytes } from 'crypto' | |
| 6 | ||
| 7 | function tmpStorage () { | |
| 8 | return join(tmpdir(), 'hyper-feed-test-' + randomBytes(8).toString('hex')) | |
| 9 | } | |
| 10 | ||
| 11 | test('Feed - ready initializes without error', async (t) => { | |
| 12 | const feed = new Feed(tmpStorage()) | |
| 13 | await feed.ready() | |
| 14 | ||
| 15 | t.pass('feed ready') | |
| 16 | ||
| 17 | await feed.close() | |
| 18 | }) | |
| 19 | ||
| 20 | test('Feed - add and get item', async (t) => { | |
| 21 | const feed = new Feed(tmpStorage()) | |
| 22 | await feed.ready() | |
| 23 | ||
| 24 | const item = await feed.add({ | |
| 25 | source: 'irc', | |
| 26 | author: 'alice', | |
| 27 | authorId: 'alice@irc', | |
| 28 | text: 'Hello from IRC!', | |
| 29 | type: 'post', | |
| 30 | tags: ['general'] | |
| 31 | }) | |
| 32 | ||
| 33 | t.ok(item.id) | |
| 34 | t.is(item.source, 'irc') | |
| 35 | t.is(item.author, 'alice') | |
| 36 | t.is(item.authorId, 'alice@irc') | |
| 37 | t.is(item.text, 'Hello from IRC!') | |
| 38 | t.is(item.type, 'post') | |
| 39 | t.alike(item.tags, ['general']) | |
| 40 | t.ok(item.timestamp > 0) | |
| 41 | ||
| 42 | const fetched = await feed.get(item.id) | |
| 43 | t.is(fetched.text, 'Hello from IRC!') | |
| 44 | t.is(fetched.id, item.id) | |
| 45 | ||
| 46 | await feed.close() | |
| 47 | }) | |
| 48 | ||
| 49 | test('Feed - add with defaults', async (t) => { | |
| 50 | const feed = new Feed(tmpStorage()) | |
| 51 | await feed.ready() | |
| 52 | ||
| 53 | const item = await feed.add({ text: 'Minimal' }) | |
| 54 | ||
| 55 | t.is(item.source, 'keet') | |
| 56 | t.is(item.author, 'unknown') | |
| 57 | t.is(item.type, 'post') | |
| 58 | t.alike(item.tags, []) | |
| 59 | t.is(item.url, null) | |
| 60 | ||
| 61 | await feed.close() | |
| 62 | }) | |
| 63 | ||
| 64 | test('Feed - add with custom id', async (t) => { | |
| 65 | const feed = new Feed(tmpStorage()) | |
| 66 | await feed.ready() | |
| 67 | ||
| 68 | const item = await feed.add({ id: 'custom123', text: 'Custom ID' }) | |
| 69 | t.is(item.id, 'custom123') | |
| 70 | ||
| 71 | const fetched = await feed.get('custom123') | |
| 72 | t.is(fetched.text, 'Custom ID') | |
| 73 | ||
| 74 | await feed.close() | |
| 75 | }) | |
| 76 | ||
| 77 | test('Feed - get nonexistent returns null', async (t) => { | |
| 78 | const feed = new Feed(tmpStorage()) | |
| 79 | await feed.ready() | |
| 80 | ||
| 81 | const result = await feed.get('nonexistent') | |
| 82 | t.is(result, null) | |
| 83 | ||
| 84 | await feed.close() | |
| 85 | }) | |
| 86 | ||
| 87 | test('Feed - remove item cleans all indexes', async (t) => { | |
| 88 | const feed = new Feed(tmpStorage()) | |
| 89 | await feed.ready() | |
| 90 | ||
| 91 | const item = await feed.add({ | |
| 92 | source: 'mastodon', | |
| 93 | author: 'bob', | |
| 94 | authorId: 'bob@mastodon', | |
| 95 | text: 'Remove me', | |
| 96 | tags: ['test'] | |
| 97 | }) | |
| 98 | ||
| 99 | // Verify it exists in all views | |
| 100 | t.ok(await feed.get(item.id)) | |
| 101 | ||
| 102 | const tl = await feed.timeline() | |
| 103 | t.is(tl.length, 1) | |
| 104 | ||
| 105 | const src = await feed.bySource('mastodon') | |
| 106 | t.is(src.length, 1) | |
| 107 | ||
| 108 | const auth = await feed.byAuthor('bob@mastodon') | |
| 109 | t.is(auth.length, 1) | |
| 110 | ||
| 111 | // Remove it | |
| 112 | const ok = await feed.remove(item.id) | |
| 113 | t.ok(ok) | |
| 114 | ||
| 115 | // Verify cleaned from all indexes | |
| 116 | t.is(await feed.get(item.id), null) | |
| 117 | ||
| 118 | const tl2 = await feed.timeline() | |
| 119 | t.is(tl2.length, 0) | |
| 120 | ||
| 121 | const src2 = await feed.bySource('mastodon') | |
| 122 | t.is(src2.length, 0) | |
| 123 | ||
| 124 | const auth2 = await feed.byAuthor('bob@mastodon') | |
| 125 | t.is(auth2.length, 0) | |
| 126 | ||
| 127 | await feed.close() | |
| 128 | }) | |
| 129 | ||
| 130 | test('Feed - remove nonexistent returns false', async (t) => { | |
| 131 | const feed = new Feed(tmpStorage()) | |
| 132 | await feed.ready() | |
| 133 | ||
| 134 | const ok = await feed.remove('nonexistent') | |
| 135 | t.is(ok, false) | |
| 136 | ||
| 137 | await feed.close() | |
| 138 | }) | |
| 139 | ||
| 140 | test('Feed - timeline returns items in order', async (t) => { | |
| 141 | const feed = new Feed(tmpStorage()) | |
| 142 | await feed.ready() | |
| 143 | ||
| 144 | const base = Date.now() | |
| 145 | ||
| 146 | await feed.add({ text: 'First', timestamp: base }) | |
| 147 | await feed.add({ text: 'Second', timestamp: base + 1000 }) | |
| 148 | await feed.add({ text: 'Third', timestamp: base + 2000 }) | |
| 149 | ||
| 150 | const tl = await feed.timeline() | |
| 151 | t.is(tl.length, 3) | |
| 152 | t.is(tl[0].text, 'First') | |
| 153 | t.is(tl[1].text, 'Second') | |
| 154 | t.is(tl[2].text, 'Third') | |
| 155 | ||
| 156 | await feed.close() | |
| 157 | }) | |
| 158 | ||
| 159 | test('Feed - timeline with limit', async (t) => { | |
| 160 | const feed = new Feed(tmpStorage()) | |
| 161 | await feed.ready() | |
| 162 | ||
| 163 | const base = Date.now() | |
| 164 | ||
| 165 | await feed.add({ text: 'A', timestamp: base }) | |
| 166 | await feed.add({ text: 'B', timestamp: base + 1000 }) | |
| 167 | await feed.add({ text: 'C', timestamp: base + 2000 }) | |
| 168 | ||
| 169 | const tl = await feed.timeline({ limit: 2 }) | |
| 170 | t.is(tl.length, 2) | |
| 171 | t.is(tl[0].text, 'A') | |
| 172 | t.is(tl[1].text, 'B') | |
| 173 | ||
| 174 | await feed.close() | |
| 175 | }) | |
| 176 | ||
| 177 | test('Feed - timeline with before', async (t) => { | |
| 178 | const feed = new Feed(tmpStorage()) | |
| 179 | await feed.ready() | |
| 180 | ||
| 181 | const base = Date.now() | |
| 182 | ||
| 183 | await feed.add({ text: 'A', timestamp: base }) | |
| 184 | await feed.add({ text: 'B', timestamp: base + 1000 }) | |
| 185 | await feed.add({ text: 'C', timestamp: base + 2000 }) | |
| 186 | ||
| 187 | const tl = await feed.timeline({ before: base + 1500 }) | |
| 188 | t.is(tl.length, 2) | |
| 189 | t.is(tl[0].text, 'A') | |
| 190 | t.is(tl[1].text, 'B') | |
| 191 | ||
| 192 | await feed.close() | |
| 193 | }) | |
| 194 | ||
| 195 | test('Feed - timeline with after', async (t) => { | |
| 196 | const feed = new Feed(tmpStorage()) | |
| 197 | await feed.ready() | |
| 198 | ||
| 199 | const base = Date.now() | |
| 200 | ||
| 201 | await feed.add({ text: 'A', timestamp: base }) | |
| 202 | await feed.add({ text: 'B', timestamp: base + 1000 }) | |
| 203 | await feed.add({ text: 'C', timestamp: base + 2000 }) | |
| 204 | ||
| 205 | const tl = await feed.timeline({ after: base + 500 }) | |
| 206 | t.is(tl.length, 2) | |
| 207 | t.is(tl[0].text, 'B') | |
| 208 | t.is(tl[1].text, 'C') | |
| 209 | ||
| 210 | await feed.close() | |
| 211 | }) | |
| 212 | ||
| 213 | test('Feed - timeline with before and after', async (t) => { | |
| 214 | const feed = new Feed(tmpStorage()) | |
| 215 | await feed.ready() | |
| 216 | ||
| 217 | const base = Date.now() | |
| 218 | ||
| 219 | await feed.add({ text: 'A', timestamp: base }) | |
| 220 | await feed.add({ text: 'B', timestamp: base + 1000 }) | |
| 221 | await feed.add({ text: 'C', timestamp: base + 2000 }) | |
| 222 | await feed.add({ text: 'D', timestamp: base + 3000 }) | |
| 223 | ||
| 224 | const tl = await feed.timeline({ after: base + 500, before: base + 2500 }) | |
| 225 | t.is(tl.length, 2) | |
| 226 | t.is(tl[0].text, 'B') | |
| 227 | t.is(tl[1].text, 'C') | |
| 228 | ||
| 229 | await feed.close() | |
| 230 | }) | |
| 231 | ||
| 232 | test('Feed - timeline filtered by source', async (t) => { | |
| 233 | const feed = new Feed(tmpStorage()) | |
| 234 | await feed.ready() | |
| 235 | ||
| 236 | const base = Date.now() | |
| 237 | ||
| 238 | await feed.add({ text: 'IRC msg', source: 'irc', timestamp: base }) | |
| 239 | await feed.add({ text: 'Keet msg', source: 'keet', timestamp: base + 1000 }) | |
| 240 | await feed.add({ text: 'IRC msg 2', source: 'irc', timestamp: base + 2000 }) | |
| 241 | ||
| 242 | const tl = await feed.timeline({ source: 'irc' }) | |
| 243 | t.is(tl.length, 2) | |
| 244 | t.is(tl[0].text, 'IRC msg') | |
| 245 | t.is(tl[1].text, 'IRC msg 2') | |
| 246 | ||
| 247 | await feed.close() | |
| 248 | }) | |
| 249 | ||
| 250 | test('Feed - timeline filtered by type', async (t) => { | |
| 251 | const feed = new Feed(tmpStorage()) | |
| 252 | await feed.ready() | |
| 253 | ||
| 254 | const base = Date.now() | |
| 255 | ||
| 256 | await feed.add({ text: 'Post', type: 'post', timestamp: base }) | |
| 257 | await feed.add({ text: 'Reply', type: 'reply', timestamp: base + 1000 }) | |
| 258 | await feed.add({ text: 'Boost', type: 'boost', timestamp: base + 2000 }) | |
| 259 | ||
| 260 | const tl = await feed.timeline({ type: 'reply' }) | |
| 261 | t.is(tl.length, 1) | |
| 262 | t.is(tl[0].text, 'Reply') | |
| 263 | ||
| 264 | await feed.close() | |
| 265 | }) | |
| 266 | ||
| 267 | test('Feed - bySource returns items from one source', async (t) => { | |
| 268 | const feed = new Feed(tmpStorage()) | |
| 269 | await feed.ready() | |
| 270 | ||
| 271 | const base = Date.now() | |
| 272 | ||
| 273 | await feed.add({ text: 'IRC 1', source: 'irc', timestamp: base }) | |
| 274 | await feed.add({ text: 'Keet 1', source: 'keet', timestamp: base + 1000 }) | |
| 275 | await feed.add({ text: 'IRC 2', source: 'irc', timestamp: base + 2000 }) | |
| 276 | await feed.add({ text: 'Discord 1', source: 'discord', timestamp: base + 3000 }) | |
| 277 | ||
| 278 | const irc = await feed.bySource('irc') | |
| 279 | t.is(irc.length, 2) | |
| 280 | t.is(irc[0].text, 'IRC 1') | |
| 281 | t.is(irc[1].text, 'IRC 2') | |
| 282 | ||
| 283 | const discord = await feed.bySource('discord') | |
| 284 | t.is(discord.length, 1) | |
| 285 | t.is(discord[0].text, 'Discord 1') | |
| 286 | ||
| 287 | await feed.close() | |
| 288 | }) | |
| 289 | ||
| 290 | test('Feed - bySource with limit', async (t) => { | |
| 291 | const feed = new Feed(tmpStorage()) | |
| 292 | await feed.ready() | |
| 293 | ||
| 294 | const base = Date.now() | |
| 295 | ||
| 296 | await feed.add({ text: 'A', source: 'irc', timestamp: base }) | |
| 297 | await feed.add({ text: 'B', source: 'irc', timestamp: base + 1000 }) | |
| 298 | await feed.add({ text: 'C', source: 'irc', timestamp: base + 2000 }) | |
| 299 | ||
| 300 | const items = await feed.bySource('irc', { limit: 2 }) | |
| 301 | t.is(items.length, 2) | |
| 302 | t.is(items[0].text, 'A') | |
| 303 | t.is(items[1].text, 'B') | |
| 304 | ||
| 305 | await feed.close() | |
| 306 | }) | |
| 307 | ||
| 308 | test('Feed - bySource with before', async (t) => { | |
| 309 | const feed = new Feed(tmpStorage()) | |
| 310 | await feed.ready() | |
| 311 | ||
| 312 | const base = Date.now() | |
| 313 | ||
| 314 | await feed.add({ text: 'A', source: 'irc', timestamp: base }) | |
| 315 | await feed.add({ text: 'B', source: 'irc', timestamp: base + 1000 }) | |
| 316 | await feed.add({ text: 'C', source: 'irc', timestamp: base + 2000 }) | |
| 317 | ||
| 318 | const items = await feed.bySource('irc', { before: base + 1500 }) | |
| 319 | t.is(items.length, 2) | |
| 320 | t.is(items[0].text, 'A') | |
| 321 | t.is(items[1].text, 'B') | |
| 322 | ||
| 323 | await feed.close() | |
| 324 | }) | |
| 325 | ||
| 326 | test('Feed - byAuthor returns items from one author', async (t) => { | |
| 327 | const feed = new Feed(tmpStorage()) | |
| 328 | await feed.ready() | |
| 329 | ||
| 330 | const base = Date.now() | |
| 331 | ||
| 332 | await feed.add({ text: 'Alice 1', author: 'Alice', authorId: 'alice@irc', timestamp: base }) | |
| 333 | await feed.add({ text: 'Bob 1', author: 'Bob', authorId: 'bob@keet', timestamp: base + 1000 }) | |
| 334 | await feed.add({ text: 'Alice 2', author: 'Alice', authorId: 'alice@irc', timestamp: base + 2000 }) | |
| 335 | ||
| 336 | const alice = await feed.byAuthor('alice@irc') | |
| 337 | t.is(alice.length, 2) | |
| 338 | t.is(alice[0].text, 'Alice 1') | |
| 339 | t.is(alice[1].text, 'Alice 2') | |
| 340 | ||
| 341 | const bob = await feed.byAuthor('bob@keet') | |
| 342 | t.is(bob.length, 1) | |
| 343 | t.is(bob[0].text, 'Bob 1') | |
| 344 | ||
| 345 | await feed.close() | |
| 346 | }) | |
| 347 | ||
| 348 | test('Feed - byAuthor with limit', async (t) => { | |
| 349 | const feed = new Feed(tmpStorage()) | |
| 350 | await feed.ready() | |
| 351 | ||
| 352 | const base = Date.now() | |
| 353 | ||
| 354 | await feed.add({ text: 'A', authorId: 'alice', timestamp: base }) | |
| 355 | await feed.add({ text: 'B', authorId: 'alice', timestamp: base + 1000 }) | |
| 356 | await feed.add({ text: 'C', authorId: 'alice', timestamp: base + 2000 }) | |
| 357 | ||
| 358 | const items = await feed.byAuthor('alice', { limit: 1 }) | |
| 359 | t.is(items.length, 1) | |
| 360 | t.is(items[0].text, 'A') | |
| 361 | ||
| 362 | await feed.close() | |
| 363 | }) | |
| 364 | ||
| 365 | test('Feed - search by text', async (t) => { | |
| 366 | const feed = new Feed(tmpStorage()) | |
| 367 | await feed.ready() | |
| 368 | ||
| 369 | await feed.add({ text: 'Hello world from IRC' }) | |
| 370 | await feed.add({ text: 'Goodbye moon from Keet' }) | |
| 371 | await feed.add({ text: 'Hello again from Mastodon' }) | |
| 372 | ||
| 373 | const results = await feed.search('hello') | |
| 374 | t.is(results.length, 2) | |
| 375 | ||
| 376 | await feed.close() | |
| 377 | }) | |
| 378 | ||
| 379 | test('Feed - search by tags', async (t) => { | |
| 380 | const feed = new Feed(tmpStorage()) | |
| 381 | await feed.ready() | |
| 382 | ||
| 383 | await feed.add({ text: 'Post about cats', tags: ['animals', 'cats'] }) | |
| 384 | await feed.add({ text: 'Post about dogs', tags: ['animals', 'dogs'] }) | |
| 385 | await feed.add({ text: 'Post about code', tags: ['programming'] }) | |
| 386 | ||
| 387 | const results = await feed.search('cats') | |
| 388 | t.is(results.length, 1) // "Post about cats" matches text, tag also matches but same item | |
| 389 | ||
| 390 | const results2 = await feed.search('programming') | |
| 391 | t.is(results2.length, 1) | |
| 392 | ||
| 393 | await feed.close() | |
| 394 | }) | |
| 395 | ||
| 396 | test('Feed - search case insensitive', async (t) => { | |
| 397 | const feed = new Feed(tmpStorage()) | |
| 398 | await feed.ready() | |
| 399 | ||
| 400 | await feed.add({ text: 'HELLO WORLD' }) | |
| 401 | await feed.add({ text: 'hello world' }) | |
| 402 | await feed.add({ text: 'Hello World' }) | |
| 403 | ||
| 404 | const results = await feed.search('hello') | |
| 405 | t.is(results.length, 3) | |
| 406 | ||
| 407 | await feed.close() | |
| 408 | }) | |
| 409 | ||
| 410 | test('Feed - search no matches', async (t) => { | |
| 411 | const feed = new Feed(tmpStorage()) | |
| 412 | await feed.ready() | |
| 413 | ||
| 414 | await feed.add({ text: 'Something else' }) | |
| 415 | ||
| 416 | const results = await feed.search('nonexistent') | |
| 417 | t.is(results.length, 0) | |
| 418 | ||
| 419 | await feed.close() | |
| 420 | }) | |
| 421 | ||
| 422 | test('Feed - stats counts by source and type', async (t) => { | |
| 423 | const feed = new Feed(tmpStorage()) | |
| 424 | await feed.ready() | |
| 425 | ||
| 426 | await feed.add({ text: 'A', source: 'irc', type: 'post' }) | |
| 427 | await feed.add({ text: 'B', source: 'irc', type: 'reply' }) | |
| 428 | await feed.add({ text: 'C', source: 'keet', type: 'post' }) | |
| 429 | await feed.add({ text: 'D', source: 'mastodon', type: 'boost' }) | |
| 430 | await feed.add({ text: 'E', source: 'irc', type: 'post' }) | |
| 431 | ||
| 432 | const stats = await feed.stats() | |
| 433 | t.is(stats.total, 5) | |
| 434 | t.is(stats.bySource.irc, 3) | |
| 435 | t.is(stats.bySource.keet, 1) | |
| 436 | t.is(stats.bySource.mastodon, 1) | |
| 437 | t.is(stats.byType.post, 3) | |
| 438 | t.is(stats.byType.reply, 1) | |
| 439 | t.is(stats.byType.boost, 1) | |
| 440 | ||
| 441 | await feed.close() | |
| 442 | }) | |
| 443 | ||
| 444 | test('Feed - stats empty', async (t) => { | |
| 445 | const feed = new Feed(tmpStorage()) | |
| 446 | await feed.ready() | |
| 447 | ||
| 448 | const stats = await feed.stats() | |
| 449 | t.is(stats.total, 0) | |
| 450 | t.alike(stats.bySource, {}) | |
| 451 | t.alike(stats.byType, {}) | |
| 452 | ||
| 453 | await feed.close() | |
| 454 | }) | |
| 455 | ||
| 456 | test('Feed - multiple sources interleaved in timeline', async (t) => { | |
| 457 | const feed = new Feed(tmpStorage()) | |
| 458 | await feed.ready() | |
| 459 | ||
| 460 | const base = Date.now() | |
| 461 | ||
| 462 | await feed.add({ text: 'IRC first', source: 'irc', timestamp: base }) | |
| 463 | await feed.add({ text: 'Keet first', source: 'keet', timestamp: base + 100 }) | |
| 464 | await feed.add({ text: 'Discord first', source: 'discord', timestamp: base + 200 }) | |
| 465 | await feed.add({ text: 'IRC second', source: 'irc', timestamp: base + 300 }) | |
| 466 | await feed.add({ text: 'Mastodon first', source: 'mastodon', timestamp: base + 400 }) | |
| 467 | await feed.add({ text: 'Keet second', source: 'keet', timestamp: base + 500 }) | |
| 468 | await feed.add({ text: 'Email first', source: 'email', timestamp: base + 600 }) | |
| 469 | ||
| 470 | // Full timeline is interleaved across all sources | |
| 471 | const tl = await feed.timeline() | |
| 472 | t.is(tl.length, 7) | |
| 473 | t.is(tl[0].text, 'IRC first') | |
| 474 | t.is(tl[0].source, 'irc') | |
| 475 | t.is(tl[1].source, 'keet') | |
| 476 | t.is(tl[2].source, 'discord') | |
| 477 | t.is(tl[3].source, 'irc') | |
| 478 | t.is(tl[4].source, 'mastodon') | |
| 479 | t.is(tl[5].source, 'keet') | |
| 480 | t.is(tl[6].source, 'email') | |
| 481 | ||
| 482 | // Filter just irc from the interleaved timeline | |
| 483 | const irc = await feed.timeline({ source: 'irc' }) | |
| 484 | t.is(irc.length, 2) | |
| 485 | t.is(irc[0].text, 'IRC first') | |
| 486 | t.is(irc[1].text, 'IRC second') | |
| 487 | ||
| 488 | await feed.close() | |
| 489 | }) | |
| 490 | ||
| 491 | test('Feed - item with url and metadata', async (t) => { | |
| 492 | const feed = new Feed(tmpStorage()) | |
| 493 | await feed.ready() | |
| 494 | ||
| 495 | const item = await feed.add({ | |
| 496 | source: 'mastodon', | |
| 497 | author: 'user@instance.social', | |
| 498 | authorId: 'user@instance.social', | |
| 499 | text: 'Check out this link!', | |
| 500 | url: 'https://instance.social/@user/123', | |
| 501 | metadata: { | |
| 502 | visibility: 'public', | |
| 503 | reblogsCount: 42, | |
| 504 | favouritesCount: 7 | |
| 505 | } | |
| 506 | }) | |
| 507 | ||
| 508 | t.is(item.url, 'https://instance.social/@user/123') | |
| 509 | t.is(item.metadata.visibility, 'public') | |
| 510 | t.is(item.metadata.reblogsCount, 42) | |
| 511 | ||
| 512 | const fetched = await feed.get(item.id) | |
| 513 | t.is(fetched.url, 'https://instance.social/@user/123') | |
| 514 | t.is(fetched.metadata.favouritesCount, 7) | |
| 515 | ||
| 516 | await feed.close() | |
| 517 | }) | |
| 518 | ||
| 519 | test('Feed - persistence across close/reopen', async (t) => { | |
| 520 | const storage = tmpStorage() | |
| 521 | ||
| 522 | // Create and populate | |
| 523 | const f1 = new Feed(storage) | |
| 524 | await f1.ready() | |
| 525 | ||
| 526 | await f1.add({ text: 'Persistent item', source: 'irc', authorId: 'alice' }) | |
| 527 | await f1.close() | |
| 528 | ||
| 529 | // Reopen and verify | |
| 530 | const f2 = new Feed(storage) | |
| 531 | await f2.ready() | |
| 532 | ||
| 533 | const tl = await f2.timeline() | |
| 534 | t.is(tl.length, 1) | |
| 535 | t.is(tl[0].text, 'Persistent item') | |
| 536 | ||
| 537 | const irc = await f2.bySource('irc') | |
| 538 | t.is(irc.length, 1) | |
| 539 | ||
| 540 | const alice = await f2.byAuthor('alice') | |
| 541 | t.is(alice.length, 1) | |
| 542 | ||
| 543 | await f2.close() | |
| 544 | }) | |
| 545 | ||
| 546 | test('Feed - remove then re-add does not leave ghosts', async (t) => { | |
| 547 | const feed = new Feed(tmpStorage()) | |
| 548 | await feed.ready() | |
| 549 | ||
| 550 | const base = Date.now() | |
| 551 | ||
| 552 | const item = await feed.add({ text: 'Ghost?', source: 'keet', authorId: 'ghost', timestamp: base }) | |
| 553 | await feed.remove(item.id) | |
| 554 | ||
| 555 | // Add a new item — timeline should only have the new one | |
| 556 | await feed.add({ text: 'Not a ghost', source: 'keet', authorId: 'ghost', timestamp: base + 1000 }) | |
| 557 | ||
| 558 | const tl = await feed.timeline() | |
| 559 | t.is(tl.length, 1) | |
| 560 | t.is(tl[0].text, 'Not a ghost') | |
| 561 | ||
| 562 | const src = await feed.bySource('keet') | |
| 563 | t.is(src.length, 1) | |
| 564 | ||
| 565 | const auth = await feed.byAuthor('ghost') | |
| 566 | t.is(auth.length, 1) | |
| 567 | ||
| 568 | await feed.close() | |
| 569 | }) | |
| 570 | ||
| 571 | test('Feed - many items pagination', async (t) => { | |
| 572 | const feed = new Feed(tmpStorage()) | |
| 573 | await feed.ready() | |
| 574 | ||
| 575 | const base = Date.now() | |
| 576 | ||
| 577 | // Add 20 items | |
| 578 | for (let i = 0; i < 20; i++) { | |
| 579 | await feed.add({ text: `Item ${i}`, timestamp: base + i * 100 }) | |
| 580 | } | |
| 581 | ||
| 582 | // Page 1: first 5 | |
| 583 | const page1 = await feed.timeline({ limit: 5 }) | |
| 584 | t.is(page1.length, 5) | |
| 585 | t.is(page1[0].text, 'Item 0') | |
| 586 | t.is(page1[4].text, 'Item 4') | |
| 587 | ||
| 588 | // Page 2: next 5 after page 1 | |
| 589 | const lastTs = page1[page1.length - 1].timestamp | |
| 590 | const page2 = await feed.timeline({ limit: 5, after: lastTs }) | |
| 591 | t.is(page2.length, 5) | |
| 592 | t.is(page2[0].text, 'Item 5') | |
| 593 | t.is(page2[4].text, 'Item 9') | |
| 594 | ||
| 595 | await feed.close() | |
| 596 | }) |
| … | … | @@ -0,0 +1,8 @@ |
| 1 | import { Hub } from './lib/hub.js' | |
| 2 | import { ServiceRegistry } from './lib/service-registry.js' | |
| 3 | import { MessageBus } from './lib/message-bus.js' | |
| 4 | import { RoomMap } from './lib/room-map.js' | |
| 5 | import { IdentityGraph } from './lib/identity-graph.js' | |
| 6 | import { Config } from './lib/config.js' | |
| 7 | ||
| 8 | export { Hub, ServiceRegistry, MessageBus, RoomMap, IdentityGraph, Config } |
| … | … | @@ -0,0 +1,89 @@ |
| 1 | // Config — persistent configuration store backed by Hyperbee | |
| 2 | // Stores bridge configs, room settings, preferences | |
| 3 | ||
| 4 | class Config { | |
| 5 | constructor (bee) { | |
| 6 | this.bee = bee | |
| 7 | } | |
| 8 | ||
| 9 | _key (section, name) { | |
| 10 | return `config/${section}/${name}` | |
| 11 | } | |
| 12 | ||
| 13 | async set (section, name, value) { | |
| 14 | await this.bee.put(this._key(section, name), { | |
| 15 | section, | |
| 16 | name, | |
| 17 | value, | |
| 18 | updated: Date.now() | |
| 19 | }) | |
| 20 | } | |
| 21 | ||
| 22 | async get (section, name) { | |
| 23 | const node = await this.bee.get(this._key(section, name)) | |
| 24 | return node ? node.value.value : undefined | |
| 25 | } | |
| 26 | ||
| 27 | async del (section, name) { | |
| 28 | await this.bee.del(this._key(section, name)) | |
| 29 | } | |
| 30 | ||
| 31 | // Get all values in a section | |
| 32 | async getSection (section) { | |
| 33 | const entries = {} | |
| 34 | ||
| 35 | for await (const node of this.bee.createReadStream({ | |
| 36 | gte: `config/${section}/`, | |
| 37 | lte: `config/${section}/~` | |
| 38 | })) { | |
| 39 | if (node.value) { | |
| 40 | entries[node.value.name] = node.value.value | |
| 41 | } | |
| 42 | } | |
| 43 | ||
| 44 | return entries | |
| 45 | } | |
| 46 | ||
| 47 | // List all sections | |
| 48 | async listSections () { | |
| 49 | const sections = new Set() | |
| 50 | ||
| 51 | for await (const node of this.bee.createReadStream({ | |
| 52 | gte: 'config/', | |
| 53 | lte: 'config/~' | |
| 54 | })) { | |
| 55 | if (node.value) { | |
| 56 | sections.add(node.value.section) | |
| 57 | } | |
| 58 | } | |
| 59 | ||
| 60 | return [...sections] | |
| 61 | } | |
| 62 | ||
| 63 | // Bulk set (for importing config) | |
| 64 | async setAll (section, entries) { | |
| 65 | for (const [name, value] of Object.entries(entries)) { | |
| 66 | await this.set(section, name, value) | |
| 67 | } | |
| 68 | } | |
| 69 | ||
| 70 | // Export all config as JSON | |
| 71 | async exportAll () { | |
| 72 | const data = {} | |
| 73 | ||
| 74 | for await (const node of this.bee.createReadStream({ | |
| 75 | gte: 'config/', | |
| 76 | lte: 'config/~' | |
| 77 | })) { | |
| 78 | if (node.value) { | |
| 79 | const { section, name, value } = node.value | |
| 80 | if (!data[section]) data[section] = {} | |
| 81 | data[section][name] = value | |
| 82 | } | |
| 83 | } | |
| 84 | ||
| 85 | return data | |
| 86 | } | |
| 87 | } | |
| 88 | ||
| 89 | export { Config } |
| … | … | @@ -0,0 +1,160 @@ |
| 1 | import Corestore from 'corestore' | |
| 2 | import Hyperbee from 'hyperbee' | |
| 3 | import { ServiceRegistry } from './service-registry.js' | |
| 4 | import { MessageBus } from './message-bus.js' | |
| 5 | import { RoomMap } from './room-map.js' | |
| 6 | import { IdentityGraph } from './identity-graph.js' | |
| 7 | import { Config } from './config.js' | |
| 8 | ||
| 9 | class Hub { | |
| 10 | constructor (storage, opts = {}) { | |
| 11 | this.store = new Corestore(storage) | |
| 12 | this.bee = new Hyperbee(this.store.get({ name: 'hub' }), { | |
| 13 | keyEncoding: 'utf-8', | |
| 14 | valueEncoding: 'json' | |
| 15 | }) | |
| 16 | ||
| 17 | this.services = new ServiceRegistry() | |
| 18 | this.bus = new MessageBus({ ttl: opts.dedupTtl }) | |
| 19 | this.rooms = new RoomMap(this.bee) | |
| 20 | this.identities = new IdentityGraph(this.bee) | |
| 21 | this.config = new Config(this.bee) | |
| 22 | this.onLog = opts.onLog || (() => {}) | |
| 23 | ||
| 24 | this._started = false | |
| 25 | } | |
| 26 | ||
| 27 | async ready () { | |
| 28 | await this.store.ready() | |
| 29 | await this.bee.ready() | |
| 30 | this.onLog('Hub ready') | |
| 31 | } | |
| 32 | ||
| 33 | // Register and optionally start a service | |
| 34 | async addService (id, service, opts = {}) { | |
| 35 | const entry = this.services.register(id, service) | |
| 36 | this.onLog(`Registered service: ${id} (${service.type || 'unknown'})`) | |
| 37 | ||
| 38 | // Wire the service into the message bus | |
| 39 | if (typeof service.onBusMessage === 'function') { | |
| 40 | service._busSubscription = this.bus.subscribe(id, (msg) => { | |
| 41 | service.onBusMessage(msg) | |
| 42 | }) | |
| 43 | } | |
| 44 | ||
| 45 | // If the service can emit messages, connect it to the bus | |
| 46 | if (typeof service.setBus === 'function') { | |
| 47 | service.setBus(this.bus, id) | |
| 48 | } | |
| 49 | ||
| 50 | // Link to rooms if specified | |
| 51 | if (opts.roomId) { | |
| 52 | await this.rooms.link(opts.roomId, id, { | |
| 53 | platform: service.platform || '', | |
| 54 | channel: opts.channel || '', | |
| 55 | direction: opts.direction || 'bidirectional', | |
| 56 | roomName: opts.roomName || '' | |
| 57 | }) | |
| 58 | } | |
| 59 | ||
| 60 | // Auto-start if requested | |
| 61 | if (opts.autoStart) { | |
| 62 | await this.services.start(id) | |
| 63 | this._started = true | |
| 64 | } | |
| 65 | ||
| 66 | return entry | |
| 67 | } | |
| 68 | ||
| 69 | // Remove a service | |
| 70 | async removeService (id) { | |
| 71 | const entry = this.services.get(id) | |
| 72 | if (!entry) return false | |
| 73 | ||
| 74 | // Unsubscribe from bus | |
| 75 | if (entry.service._busSubscription) { | |
| 76 | this.bus.unsubscribe(entry.service._busSubscription) | |
| 77 | } | |
| 78 | ||
| 79 | // Unlink from all rooms | |
| 80 | const rooms = await this.rooms.getRooms(id) | |
| 81 | for (const room of rooms) { | |
| 82 | await this.rooms.unlink(room.roomId, id) | |
| 83 | } | |
| 84 | ||
| 85 | await this.services.unregister(id) | |
| 86 | this.onLog(`Removed service: ${id}`) | |
| 87 | return true | |
| 88 | } | |
| 89 | ||
| 90 | // Start all services | |
| 91 | async startAll () { | |
| 92 | const results = await this.services.startAll() | |
| 93 | this._started = true | |
| 94 | ||
| 95 | const ok = results.filter(r => r.ok).length | |
| 96 | const failed = results.filter(r => !r.ok).length | |
| 97 | this.onLog(`Started ${ok} services (${failed} failed)`) | |
| 98 | ||
| 99 | return results | |
| 100 | } | |
| 101 | ||
| 102 | // Stop all services | |
| 103 | async stopAll () { | |
| 104 | const results = await this.services.stopAll() | |
| 105 | this._started = false | |
| 106 | this.onLog('All services stopped') | |
| 107 | return results | |
| 108 | } | |
| 109 | ||
| 110 | // Get full system status | |
| 111 | status () { | |
| 112 | return { | |
| 113 | started: this._started, | |
| 114 | services: this.services.status(), | |
| 115 | bus: this.bus.stats | |
| 116 | } | |
| 117 | } | |
| 118 | ||
| 119 | // Route a message through the hub (manually inject) | |
| 120 | route (msg) { | |
| 121 | return this.bus.publish(msg) | |
| 122 | } | |
| 123 | ||
| 124 | // Get the graph view — rooms with their linked services | |
| 125 | async graph () { | |
| 126 | const mappings = await this.rooms.listAll() | |
| 127 | const serviceList = this.services.list() | |
| 128 | const roomGroups = {} | |
| 129 | ||
| 130 | for (const mapping of mappings) { | |
| 131 | if (!roomGroups[mapping.roomId]) { | |
| 132 | roomGroups[mapping.roomId] = { | |
| 133 | roomId: mapping.roomId, | |
| 134 | services: [] | |
| 135 | } | |
| 136 | } | |
| 137 | ||
| 138 | const service = serviceList.find(s => s.id === mapping.serviceId) | |
| 139 | roomGroups[mapping.roomId].services.push({ | |
| 140 | id: mapping.serviceId, | |
| 141 | platform: mapping.platform, | |
| 142 | channel: mapping.channel, | |
| 143 | direction: mapping.direction, | |
| 144 | enabled: mapping.enabled, | |
| 145 | status: service ? service.status : 'unknown' | |
| 146 | }) | |
| 147 | } | |
| 148 | ||
| 149 | return Object.values(roomGroups) | |
| 150 | } | |
| 151 | ||
| 152 | async close () { | |
| 153 | await this.stopAll() | |
| 154 | this.bus.removeAllListeners() | |
| 155 | await this.store.close() | |
| 156 | this.onLog('Hub closed') | |
| 157 | } | |
| 158 | } | |
| 159 | ||
| 160 | export { Hub } |
| … | … | @@ -0,0 +1,177 @@ |
| 1 | // IdentityGraph — links identities across platforms | |
| 2 | // alice@mastodon.social = alice on IRC = alice-key in Keet | |
| 3 | // Forms a graph where each node is a platform identity | |
| 4 | // and edges link the same person across platforms | |
| 5 | ||
| 6 | class IdentityGraph { | |
| 7 | constructor (bee) { | |
| 8 | this.bee = bee | |
| 9 | } | |
| 10 | ||
| 11 | _identityKey (platform, handle) { | |
| 12 | return `identity/${platform}/${handle}` | |
| 13 | } | |
| 14 | ||
| 15 | _aliasKey (alias) { | |
| 16 | return `alias/${alias}` | |
| 17 | } | |
| 18 | ||
| 19 | // Create or update an identity | |
| 20 | async addIdentity (platform, handle, opts = {}) { | |
| 21 | const key = this._identityKey(platform, handle) | |
| 22 | const existing = await this.bee.get(key) | |
| 23 | ||
| 24 | const identity = { | |
| 25 | platform, | |
| 26 | handle, | |
| 27 | alias: opts.alias || existing?.value?.alias || '', | |
| 28 | displayName: opts.displayName || existing?.value?.displayName || handle, | |
| 29 | metadata: { ...(existing?.value?.metadata || {}), ...(opts.metadata || {}) }, | |
| 30 | created: existing?.value?.created || Date.now(), | |
| 31 | updated: Date.now() | |
| 32 | } | |
| 33 | ||
| 34 | await this.bee.put(key, identity) | |
| 35 | ||
| 36 | // If alias is set, maintain reverse index | |
| 37 | if (identity.alias) { | |
| 38 | const aliasEntry = await this.bee.get(this._aliasKey(identity.alias)) || { value: { identities: [] } } | |
| 39 | const identities = aliasEntry.value.identities || [] | |
| 40 | const ref = `${platform}:${handle}` | |
| 41 | if (!identities.includes(ref)) { | |
| 42 | identities.push(ref) | |
| 43 | } | |
| 44 | await this.bee.put(this._aliasKey(identity.alias), { | |
| 45 | alias: identity.alias, | |
| 46 | identities, | |
| 47 | updated: Date.now() | |
| 48 | }) | |
| 49 | } | |
| 50 | ||
| 51 | return identity | |
| 52 | } | |
| 53 | ||
| 54 | // Get an identity by platform + handle | |
| 55 | async getIdentity (platform, handle) { | |
| 56 | const node = await this.bee.get(this._identityKey(platform, handle)) | |
| 57 | return node ? node.value : null | |
| 58 | } | |
| 59 | ||
| 60 | // Link two identities as the same person | |
| 61 | async link (alias, platform, handle) { | |
| 62 | // Update the identity with the alias | |
| 63 | await this.addIdentity(platform, handle, { alias }) | |
| 64 | } | |
| 65 | ||
| 66 | // Get all identities for an alias (the same person) | |
| 67 | async getLinked (alias) { | |
| 68 | const node = await this.bee.get(this._aliasKey(alias)) | |
| 69 | if (!node) return [] | |
| 70 | ||
| 71 | const results = [] | |
| 72 | for (const ref of node.value.identities || []) { | |
| 73 | const [platform, handle] = ref.split(':', 2) | |
| 74 | const identity = await this.getIdentity(platform, handle) | |
| 75 | if (identity) results.push(identity) | |
| 76 | } | |
| 77 | ||
| 78 | return results | |
| 79 | } | |
| 80 | ||
| 81 | // Find who someone is on another platform | |
| 82 | async resolve (fromPlatform, fromHandle, toPlatform) { | |
| 83 | const identity = await this.getIdentity(fromPlatform, fromHandle) | |
| 84 | if (!identity || !identity.alias) return null | |
| 85 | ||
| 86 | const linked = await this.getLinked(identity.alias) | |
| 87 | return linked.find(i => i.platform === toPlatform) || null | |
| 88 | } | |
| 89 | ||
| 90 | // List all identities for a platform | |
| 91 | async listPlatform (platform) { | |
| 92 | const identities = [] | |
| 93 | ||
| 94 | for await (const node of this.bee.createReadStream({ | |
| 95 | gte: `identity/${platform}/`, | |
| 96 | lte: `identity/${platform}/~` | |
| 97 | })) { | |
| 98 | if (node.value) identities.push(node.value) | |
| 99 | } | |
| 100 | ||
| 101 | return identities | |
| 102 | } | |
| 103 | ||
| 104 | // List all aliases (unique people) | |
| 105 | async listAliases () { | |
| 106 | const aliases = [] | |
| 107 | ||
| 108 | for await (const node of this.bee.createReadStream({ | |
| 109 | gte: 'alias/', | |
| 110 | lte: 'alias/~' | |
| 111 | })) { | |
| 112 | if (node.value) aliases.push(node.value) | |
| 113 | } | |
| 114 | ||
| 115 | return aliases | |
| 116 | } | |
| 117 | ||
| 118 | // Remove an identity | |
| 119 | async removeIdentity (platform, handle) { | |
| 120 | const identity = await this.getIdentity(platform, handle) | |
| 121 | if (!identity) return false | |
| 122 | ||
| 123 | await this.bee.del(this._identityKey(platform, handle)) | |
| 124 | ||
| 125 | // Clean up alias index | |
| 126 | if (identity.alias) { | |
| 127 | const aliasNode = await this.bee.get(this._aliasKey(identity.alias)) | |
| 128 | if (aliasNode) { | |
| 129 | const ref = `${platform}:${handle}` | |
| 130 | const remaining = (aliasNode.value.identities || []).filter(r => r !== ref) | |
| 131 | if (remaining.length === 0) { | |
| 132 | await this.bee.del(this._aliasKey(identity.alias)) | |
| 133 | } else { | |
| 134 | await this.bee.put(this._aliasKey(identity.alias), { | |
| 135 | ...aliasNode.value, | |
| 136 | identities: remaining, | |
| 137 | updated: Date.now() | |
| 138 | }) | |
| 139 | } | |
| 140 | } | |
| 141 | } | |
| 142 | ||
| 143 | return true | |
| 144 | } | |
| 145 | ||
| 146 | // Stats | |
| 147 | async stats () { | |
| 148 | let identityCount = 0 | |
| 149 | let aliasCount = 0 | |
| 150 | const platforms = new Set() | |
| 151 | ||
| 152 | for await (const node of this.bee.createReadStream({ | |
| 153 | gte: 'identity/', | |
| 154 | lte: 'identity/~' | |
| 155 | })) { | |
| 156 | if (node.value) { | |
| 157 | identityCount++ | |
| 158 | platforms.add(node.value.platform) | |
| 159 | } | |
| 160 | } | |
| 161 | ||
| 162 | for await (const node of this.bee.createReadStream({ | |
| 163 | gte: 'alias/', | |
| 164 | lte: 'alias/~' | |
| 165 | })) { | |
| 166 | if (node.value) aliasCount++ | |
| 167 | } | |
| 168 | ||
| 169 | return { | |
| 170 | identities: identityCount, | |
| 171 | aliases: aliasCount, | |
| 172 | platforms: [...platforms] | |
| 173 | } | |
| 174 | } | |
| 175 | } | |
| 176 | ||
| 177 | export { IdentityGraph } |
| … | … | @@ -0,0 +1,97 @@ |
| 1 | import { EventEmitter } from 'events' | |
| 2 | import { createHash } from 'crypto' | |
| 3 | ||
| 4 | class MessageBus extends EventEmitter { | |
| 5 | constructor (opts = {}) { | |
| 6 | super() | |
| 7 | this._seen = new Map() | |
| 8 | this._ttl = opts.ttl || 60000 | |
| 9 | this._maxSeen = opts.maxSeen || 50000 | |
| 10 | this._stats = { routed: 0, deduped: 0, errors: 0 } | |
| 11 | } | |
| 12 | ||
| 13 | _hash (msg) { | |
| 14 | return createHash('sha256') | |
| 15 | .update(msg.source || '') | |
| 16 | .update(msg.author || '') | |
| 17 | .update(msg.text || '') | |
| 18 | .digest('hex') | |
| 19 | .slice(0, 16) | |
| 20 | } | |
| 21 | ||
| 22 | _isDuplicate (hash) { | |
| 23 | const now = Date.now() | |
| 24 | ||
| 25 | if (this._seen.size > this._maxSeen) { | |
| 26 | for (const [k, ts] of this._seen) { | |
| 27 | if (now - ts > this._ttl) this._seen.delete(k) | |
| 28 | } | |
| 29 | } | |
| 30 | ||
| 31 | if (this._seen.has(hash)) { | |
| 32 | const ts = this._seen.get(hash) | |
| 33 | if (now - ts < this._ttl) return true | |
| 34 | } | |
| 35 | ||
| 36 | this._seen.set(hash, now) | |
| 37 | return false | |
| 38 | } | |
| 39 | ||
| 40 | // Publish a message to all subscribers except the source | |
| 41 | publish (msg) { | |
| 42 | if (!msg || !msg.source) return false | |
| 43 | ||
| 44 | const hash = this._hash(msg) | |
| 45 | if (this._isDuplicate(hash)) { | |
| 46 | this._stats.deduped++ | |
| 47 | return false | |
| 48 | } | |
| 49 | ||
| 50 | this._stats.routed++ | |
| 51 | ||
| 52 | // Emit to all listeners, each bridge/service filters by interest | |
| 53 | this.emit('message', msg) | |
| 54 | ||
| 55 | // Also emit typed events for targeted listening | |
| 56 | if (msg.type) { | |
| 57 | this.emit(`message:${msg.type}`, msg) | |
| 58 | } | |
| 59 | ||
| 60 | // Platform-specific channel | |
| 61 | if (msg.platform) { | |
| 62 | this.emit(`platform:${msg.platform}`, msg) | |
| 63 | } | |
| 64 | ||
| 65 | return true | |
| 66 | } | |
| 67 | ||
| 68 | // Subscribe a handler that skips messages from its own source | |
| 69 | subscribe (sourceId, handler) { | |
| 70 | const wrapper = (msg) => { | |
| 71 | if (msg.source === sourceId) return // don't echo back to origin | |
| 72 | try { | |
| 73 | handler(msg) | |
| 74 | } catch (err) { | |
| 75 | this._stats.errors++ | |
| 76 | this.emit('error', { source: sourceId, error: err.message }) | |
| 77 | } | |
| 78 | } | |
| 79 | this.on('message', wrapper) | |
| 80 | return wrapper // return ref for unsubscribe | |
| 81 | } | |
| 82 | ||
| 83 | unsubscribe (wrapper) { | |
| 84 | this.removeListener('message', wrapper) | |
| 85 | } | |
| 86 | ||
| 87 | get stats () { | |
| 88 | return { ...this._stats, seen: this._seen.size } | |
| 89 | } | |
| 90 | ||
| 91 | reset () { | |
| 92 | this._seen.clear() | |
| 93 | this._stats = { routed: 0, deduped: 0, errors: 0 } | |
| 94 | } | |
| 95 | } | |
| 96 | ||
| 97 | export { MessageBus } |
| … | … | @@ -0,0 +1,139 @@ |
| 1 | // RoomMap — maps Keet rooms to external platform channels/bridges | |
| 2 | // A room can have multiple bridges (IRC + Discord + Mastodon all on one room) | |
| 3 | // A bridge can serve multiple rooms (one IRC bot covering several rooms) | |
| 4 | ||
| 5 | class RoomMap { | |
| 6 | constructor (bee) { | |
| 7 | this.bee = bee | |
| 8 | } | |
| 9 | ||
| 10 | _key (roomId, serviceId) { | |
| 11 | return `room-map/${roomId}/${serviceId}` | |
| 12 | } | |
| 13 | ||
| 14 | _roomKey (roomId) { | |
| 15 | return `room/${roomId}` | |
| 16 | } | |
| 17 | ||
| 18 | // Link a service (bridge/panel/daemon) to a room | |
| 19 | async link (roomId, serviceId, config = {}) { | |
| 20 | const entry = { | |
| 21 | roomId, | |
| 22 | serviceId, | |
| 23 | platform: config.platform || '', | |
| 24 | channel: config.channel || '', | |
| 25 | direction: config.direction || 'bidirectional', // in, out, bidirectional | |
| 26 | enabled: config.enabled !== false, | |
| 27 | linked: Date.now() | |
| 28 | } | |
| 29 | ||
| 30 | await this.bee.put(this._key(roomId, serviceId), entry) | |
| 31 | ||
| 32 | // Also store room metadata if provided | |
| 33 | if (config.roomName) { | |
| 34 | const existing = await this.getRoom(roomId) | |
| 35 | if (!existing) { | |
| 36 | await this.bee.put(this._roomKey(roomId), { | |
| 37 | id: roomId, | |
| 38 | name: config.roomName, | |
| 39 | created: Date.now() | |
| 40 | }) | |
| 41 | } | |
| 42 | } | |
| 43 | ||
| 44 | return entry | |
| 45 | } | |
| 46 | ||
| 47 | // Unlink a service from a room | |
| 48 | async unlink (roomId, serviceId) { | |
| 49 | const key = this._key(roomId, serviceId) | |
| 50 | const node = await this.bee.get(key) | |
| 51 | if (!node) return false | |
| 52 | ||
| 53 | await this.bee.del(key) | |
| 54 | return true | |
| 55 | } | |
| 56 | ||
| 57 | // Get all services linked to a room | |
| 58 | async getServices (roomId) { | |
| 59 | const services = [] | |
| 60 | ||
| 61 | for await (const node of this.bee.createReadStream({ | |
| 62 | gte: `room-map/${roomId}/`, | |
| 63 | lte: `room-map/${roomId}/~` | |
| 64 | })) { | |
| 65 | if (node.value) services.push(node.value) | |
| 66 | } | |
| 67 | ||
| 68 | return services | |
| 69 | } | |
| 70 | ||
| 71 | // Get all rooms a service is linked to | |
| 72 | async getRooms (serviceId) { | |
| 73 | const rooms = [] | |
| 74 | ||
| 75 | for await (const node of this.bee.createReadStream({ | |
| 76 | gte: 'room-map/', | |
| 77 | lte: 'room-map/~' | |
| 78 | })) { | |
| 79 | if (node.value && node.value.serviceId === serviceId) { | |
| 80 | rooms.push(node.value) | |
| 81 | } | |
| 82 | } | |
| 83 | ||
| 84 | return rooms | |
| 85 | } | |
| 86 | ||
| 87 | // Get room metadata | |
| 88 | async getRoom (roomId) { | |
| 89 | const node = await this.bee.get(this._roomKey(roomId)) | |
| 90 | return node ? node.value : null | |
| 91 | } | |
| 92 | ||
| 93 | // List all room mappings | |
| 94 | async listAll () { | |
| 95 | const mappings = [] | |
| 96 | ||
| 97 | for await (const node of this.bee.createReadStream({ | |
| 98 | gte: 'room-map/', | |
| 99 | lte: 'room-map/~' | |
| 100 | })) { | |
| 101 | if (node.value) mappings.push(node.value) | |
| 102 | } | |
| 103 | ||
| 104 | return mappings | |
| 105 | } | |
| 106 | ||
| 107 | // List all known rooms | |
| 108 | async listRooms () { | |
| 109 | const rooms = [] | |
| 110 | ||
| 111 | for await (const node of this.bee.createReadStream({ | |
| 112 | gte: 'room/', | |
| 113 | lte: 'room/~' | |
| 114 | })) { | |
| 115 | if (node.value) rooms.push(node.value) | |
| 116 | } | |
| 117 | ||
| 118 | return rooms | |
| 119 | } | |
| 120 | ||
| 121 | // Check if a specific service is linked to a room | |
| 122 | async isLinked (roomId, serviceId) { | |
| 123 | const node = await this.bee.get(this._key(roomId, serviceId)) | |
| 124 | return !!node | |
| 125 | } | |
| 126 | ||
| 127 | // Enable/disable a link | |
| 128 | async setEnabled (roomId, serviceId, enabled) { | |
| 129 | const key = this._key(roomId, serviceId) | |
| 130 | const node = await this.bee.get(key) | |
| 131 | if (!node) throw new Error(`Link not found: ${roomId} → ${serviceId}`) | |
| 132 | ||
| 133 | const entry = { ...node.value, enabled } | |
| 134 | await this.bee.put(key, entry) | |
| 135 | return entry | |
| 136 | } | |
| 137 | } | |
| 138 | ||
| 139 | export { RoomMap } |
| … | … | @@ -0,0 +1,156 @@ |
| 1 | class ServiceRegistry { | |
| 2 | constructor () { | |
| 3 | this._services = new Map() | |
| 4 | } | |
| 5 | ||
| 6 | // Register a service (bridge, panel, or daemon) | |
| 7 | register (id, service) { | |
| 8 | if (this._services.has(id)) { | |
| 9 | throw new Error(`Service already registered: ${id}`) | |
| 10 | } | |
| 11 | ||
| 12 | const entry = { | |
| 13 | id, | |
| 14 | type: service.type || 'unknown', // bridge, panel, daemon | |
| 15 | name: service.name || id, | |
| 16 | service, | |
| 17 | status: 'registered', | |
| 18 | startedAt: null, | |
| 19 | error: null | |
| 20 | } | |
| 21 | ||
| 22 | this._services.set(id, entry) | |
| 23 | return entry | |
| 24 | } | |
| 25 | ||
| 26 | // Start a service | |
| 27 | async start (id) { | |
| 28 | const entry = this._services.get(id) | |
| 29 | if (!entry) throw new Error(`Service not found: ${id}`) | |
| 30 | if (entry.status === 'running') return entry | |
| 31 | ||
| 32 | try { | |
| 33 | entry.status = 'starting' | |
| 34 | if (typeof entry.service.start === 'function') { | |
| 35 | await entry.service.start() | |
| 36 | } | |
| 37 | entry.status = 'running' | |
| 38 | entry.startedAt = Date.now() | |
| 39 | entry.error = null | |
| 40 | } catch (err) { | |
| 41 | entry.status = 'error' | |
| 42 | entry.error = err.message | |
| 43 | throw err | |
| 44 | } | |
| 45 | ||
| 46 | return entry | |
| 47 | } | |
| 48 | ||
| 49 | // Stop a service | |
| 50 | async stop (id) { | |
| 51 | const entry = this._services.get(id) | |
| 52 | if (!entry) throw new Error(`Service not found: ${id}`) | |
| 53 | if (entry.status === 'stopped') return entry | |
| 54 | ||
| 55 | try { | |
| 56 | entry.status = 'stopping' | |
| 57 | if (typeof entry.service.stop === 'function') { | |
| 58 | await entry.service.stop() | |
| 59 | } else if (typeof entry.service.close === 'function') { | |
| 60 | await entry.service.close() | |
| 61 | } | |
| 62 | entry.status = 'stopped' | |
| 63 | } catch (err) { | |
| 64 | entry.status = 'error' | |
| 65 | entry.error = err.message | |
| 66 | } | |
| 67 | ||
| 68 | return entry | |
| 69 | } | |
| 70 | ||
| 71 | // Unregister a service (stops it first if running) | |
| 72 | async unregister (id) { | |
| 73 | const entry = this._services.get(id) | |
| 74 | if (!entry) return false | |
| 75 | ||
| 76 | if (entry.status === 'running') { | |
| 77 | await this.stop(id) | |
| 78 | } | |
| 79 | ||
| 80 | this._services.delete(id) | |
| 81 | return true | |
| 82 | } | |
| 83 | ||
| 84 | // Get a service entry | |
| 85 | get (id) { | |
| 86 | return this._services.get(id) || null | |
| 87 | } | |
| 88 | ||
| 89 | // List all services | |
| 90 | list (filter) { | |
| 91 | const entries = [...this._services.values()] | |
| 92 | ||
| 93 | if (filter?.type) { | |
| 94 | return entries.filter(e => e.type === filter.type) | |
| 95 | } | |
| 96 | if (filter?.status) { | |
| 97 | return entries.filter(e => e.status === filter.status) | |
| 98 | } | |
| 99 | ||
| 100 | return entries | |
| 101 | } | |
| 102 | ||
| 103 | // Get service status summary | |
| 104 | status () { | |
| 105 | const entries = [...this._services.values()] | |
| 106 | const byType = {} | |
| 107 | const byStatus = {} | |
| 108 | ||
| 109 | for (const entry of entries) { | |
| 110 | byType[entry.type] = (byType[entry.type] || 0) + 1 | |
| 111 | byStatus[entry.status] = (byStatus[entry.status] || 0) + 1 | |
| 112 | } | |
| 113 | ||
| 114 | return { | |
| 115 | total: entries.length, | |
| 116 | byType, | |
| 117 | byStatus | |
| 118 | } | |
| 119 | } | |
| 120 | ||
| 121 | // Start all registered services | |
| 122 | async startAll () { | |
| 123 | const results = [] | |
| 124 | for (const [id] of this._services) { | |
| 125 | try { | |
| 126 | await this.start(id) | |
| 127 | results.push({ id, ok: true }) | |
| 128 | } catch (err) { | |
| 129 | results.push({ id, ok: false, error: err.message }) | |
| 130 | } | |
| 131 | } | |
| 132 | return results | |
| 133 | } | |
| 134 | ||
| 135 | // Stop all running services | |
| 136 | async stopAll () { | |
| 137 | const results = [] | |
| 138 | for (const [id, entry] of this._services) { | |
| 139 | if (entry.status === 'running') { | |
| 140 | try { | |
| 141 | await this.stop(id) | |
| 142 | results.push({ id, ok: true }) | |
| 143 | } catch (err) { | |
| 144 | results.push({ id, ok: false, error: err.message }) | |
| 145 | } | |
| 146 | } | |
| 147 | } | |
| 148 | return results | |
| 149 | } | |
| 150 | ||
| 151 | get count () { | |
| 152 | return this._services.size | |
| 153 | } | |
| 154 | } | |
| 155 | ||
| 156 | export { ServiceRegistry } |
| … | … | @@ -0,0 +1,1458 @@ |
| 1 | { | |
| 2 | "name": "hyper-social", | |
| 3 | "version": "1.0.0", | |
| 4 | "lockfileVersion": 3, | |
| 5 | "requires": true, | |
| 6 | "packages": { | |
| 7 | "": { | |
| 8 | "name": "hyper-social", | |
| 9 | "version": "1.0.0", | |
| 10 | "license": "MIT", | |
| 11 | "dependencies": { | |
| 12 | "b4a": "^1.0.0", | |
| 13 | "bridge-envelope": "file:../bridge-envelope", | |
| 14 | "corestore": "^6.0.0", | |
| 15 | "hyperbee": "^2.0.0" | |
| 16 | }, | |
| 17 | "devDependencies": { | |
| 18 | "brittle": "^3.0.0" | |
| 19 | } | |
| 20 | }, | |
| 21 | "../bridge-envelope": { | |
| 22 | "version": "1.0.0", | |
| 23 | "license": "MIT", | |
| 24 | "dependencies": { | |
| 25 | "b4a": "^1.0.0", | |
| 26 | "compact-encoding": "^2.0.0" | |
| 27 | }, | |
| 28 | "devDependencies": { | |
| 29 | "brittle": "^3.0.0" | |
| 30 | } | |
| 31 | }, | |
| 32 | "node_modules/@hyperswarm/secret-stream": { | |
| 33 | "version": "6.9.1", | |
| 34 | "resolved": "https://registry.npmjs.org/@hyperswarm/secret-stream/-/secret-stream-6.9.1.tgz", | |
| 35 | "integrity": "sha512-xb0S5y3YJwBakD77JOGBHlBxdp63mHClZoXBYoLv+9wH8e054ESKlmQptWqjJK5dv5VMUIVYOJB4MaOpB0JdGw==", | |
| 36 | "license": "Apache-2.0", | |
| 37 | "dependencies": { | |
| 38 | "b4a": "^1.1.0", | |
| 39 | "hypercore-crypto": "^3.3.1", | |
| 40 | "noise-curve-ed": "^2.0.1", | |
| 41 | "noise-handshake": "^4.0.0", | |
| 42 | "sodium-secretstream": "^1.1.0", | |
| 43 | "sodium-universal": "^5.0.0", | |
| 44 | "streamx": "^2.14.0", | |
| 45 | "timeout-refresh": "^2.0.0", | |
| 46 | "unslab": "^1.3.0" | |
| 47 | } | |
| 48 | }, | |
| 49 | "node_modules/@hyperswarm/secret-stream/node_modules/sodium-native": { | |
| 50 | "version": "5.0.10", | |
| 51 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 52 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 53 | "license": "MIT", | |
| 54 | "dependencies": { | |
| 55 | "require-addon": "^1.1.0", | |
| 56 | "which-runtime": "^1.2.1" | |
| 57 | }, | |
| 58 | "engines": { | |
| 59 | "bare": ">=1.16.0" | |
| 60 | } | |
| 61 | }, | |
| 62 | "node_modules/@hyperswarm/secret-stream/node_modules/sodium-universal": { | |
| 63 | "version": "5.0.1", | |
| 64 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 65 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 66 | "license": "MIT", | |
| 67 | "dependencies": { | |
| 68 | "sodium-native": "^5.0.1" | |
| 69 | }, | |
| 70 | "peerDependencies": { | |
| 71 | "sodium-javascript": "~0.8.0" | |
| 72 | }, | |
| 73 | "peerDependenciesMeta": { | |
| 74 | "sodium-javascript": { | |
| 75 | "optional": true | |
| 76 | } | |
| 77 | } | |
| 78 | }, | |
| 79 | "node_modules/@jridgewell/resolve-uri": { | |
| 80 | "version": "3.1.2", | |
| 81 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", | |
| 82 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", | |
| 83 | "dev": true, | |
| 84 | "license": "MIT", | |
| 85 | "engines": { | |
| 86 | "node": ">=6.0.0" | |
| 87 | } | |
| 88 | }, | |
| 89 | "node_modules/@jridgewell/sourcemap-codec": { | |
| 90 | "version": "1.5.5", | |
| 91 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", | |
| 92 | "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", | |
| 93 | "dev": true, | |
| 94 | "license": "MIT" | |
| 95 | }, | |
| 96 | "node_modules/@jridgewell/trace-mapping": { | |
| 97 | "version": "0.3.31", | |
| 98 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", | |
| 99 | "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", | |
| 100 | "dev": true, | |
| 101 | "license": "MIT", | |
| 102 | "dependencies": { | |
| 103 | "@jridgewell/resolve-uri": "^3.1.0", | |
| 104 | "@jridgewell/sourcemap-codec": "^1.4.14" | |
| 105 | } | |
| 106 | }, | |
| 107 | "node_modules/@types/istanbul-lib-coverage": { | |
| 108 | "version": "2.0.6", | |
| 109 | "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", | |
| 110 | "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", | |
| 111 | "dev": true, | |
| 112 | "license": "MIT" | |
| 113 | }, | |
| 114 | "node_modules/b4a": { | |
| 115 | "version": "1.7.4", | |
| 116 | "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.4.tgz", | |
| 117 | "integrity": "sha512-u20zJLDaSWpxaZ+zaAkEIB2dZZ1o+DF4T/MRbmsvGp9nletHOyiai19OzX1fF8xUBYsO1bPXxODvcd0978pnug==", | |
| 118 | "license": "Apache-2.0", | |
| 119 | "peerDependencies": { | |
| 120 | "react-native-b4a": "*" | |
| 121 | }, | |
| 122 | "peerDependenciesMeta": { | |
| 123 | "react-native-b4a": { | |
| 124 | "optional": true | |
| 125 | } | |
| 126 | } | |
| 127 | }, | |
| 128 | "node_modules/bare-addon-resolve": { | |
| 129 | "version": "1.10.0", | |
| 130 | "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.0.tgz", | |
| 131 | "integrity": "sha512-sSd0jieRJlDaODOzj0oe0RjFVC1QI0ZIjGIdPkbrTXsdVVtENg14c+lHHAhHwmWCZ2nQlMhy8jA3Y5LYPc/isA==", | |
| 132 | "license": "Apache-2.0", | |
| 133 | "dependencies": { | |
| 134 | "bare-module-resolve": "^1.10.0", | |
| 135 | "bare-semver": "^1.0.0" | |
| 136 | }, | |
| 137 | "peerDependencies": { | |
| 138 | "bare-url": "*" | |
| 139 | }, | |
| 140 | "peerDependenciesMeta": { | |
| 141 | "bare-url": { | |
| 142 | "optional": true | |
| 143 | } | |
| 144 | } | |
| 145 | }, | |
| 146 | "node_modules/bare-ansi-escapes": { | |
| 147 | "version": "2.2.3", | |
| 148 | "resolved": "https://registry.npmjs.org/bare-ansi-escapes/-/bare-ansi-escapes-2.2.3.tgz", | |
| 149 | "integrity": "sha512-02ES4/E2RbrtZSnHJ9LntBhYkLA6lPpSEeP8iqS3MccBIVhVBlEmruF1I7HZqx5Q8aiTeYfQVeqmrU9YO2yYoQ==", | |
| 150 | "dev": true, | |
| 151 | "license": "Apache-2.0", | |
| 152 | "dependencies": { | |
| 153 | "bare-stream": "^2.6.5" | |
| 154 | }, | |
| 155 | "peerDependencies": { | |
| 156 | "bare-buffer": "*" | |
| 157 | }, | |
| 158 | "peerDependenciesMeta": { | |
| 159 | "bare-buffer": { | |
| 160 | "optional": true | |
| 161 | } | |
| 162 | } | |
| 163 | }, | |
| 164 | "node_modules/bare-assert": { | |
| 165 | "version": "1.2.0", | |
| 166 | "resolved": "https://registry.npmjs.org/bare-assert/-/bare-assert-1.2.0.tgz", | |
| 167 | "integrity": "sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==", | |
| 168 | "dev": true, | |
| 169 | "license": "Apache-2.0", | |
| 170 | "dependencies": { | |
| 171 | "bare-inspect": "^3.1.2" | |
| 172 | } | |
| 173 | }, | |
| 174 | "node_modules/bare-bundle": { | |
| 175 | "version": "1.10.0", | |
| 176 | "resolved": "https://registry.npmjs.org/bare-bundle/-/bare-bundle-1.10.0.tgz", | |
| 177 | "integrity": "sha512-4LVlnJAHr00Hh6Vu6ZUJS38rcEtJT3b3vChXSsBsJ2mk1TN0lQ+gzd+Dw5L0aV7uqDZv84smuwW+O02X7PfDlw==", | |
| 178 | "dev": true, | |
| 179 | "license": "Apache-2.0", | |
| 180 | "peerDependencies": { | |
| 181 | "bare-buffer": "*", | |
| 182 | "bare-url": "*" | |
| 183 | }, | |
| 184 | "peerDependenciesMeta": { | |
| 185 | "bare-buffer": { | |
| 186 | "optional": true | |
| 187 | }, | |
| 188 | "bare-url": { | |
| 189 | "optional": true | |
| 190 | } | |
| 191 | } | |
| 192 | }, | |
| 193 | "node_modules/bare-cov": { | |
| 194 | "version": "1.2.1", | |
| 195 | "resolved": "https://registry.npmjs.org/bare-cov/-/bare-cov-1.2.1.tgz", | |
| 196 | "integrity": "sha512-SHNqb/WG9udUEj8m5qGSehoUQ9o0Xzv8nrf4HIdmG+KNU/XdhPLhTP5VPrHnSAMAXhAzEB4Q+2IYZ0x9qPzN1g==", | |
| 197 | "dev": true, | |
| 198 | "license": "Apache-2.0", | |
| 199 | "dependencies": { | |
| 200 | "bare-fs": "^4.1.2", | |
| 201 | "bare-inspector": "^4.0.1", | |
| 202 | "bare-path": "^3.0.0", | |
| 203 | "bare-process": "^4.2.1", | |
| 204 | "bare-url": "^2.1.5", | |
| 205 | "bare-v8-to-istanbul": "^1.0.2", | |
| 206 | "picomatch": "^4.0.2", | |
| 207 | "which-runtime": "^1.2.1" | |
| 208 | } | |
| 209 | }, | |
| 210 | "node_modules/bare-crypto": { | |
| 211 | "version": "1.13.0", | |
| 212 | "resolved": "https://registry.npmjs.org/bare-crypto/-/bare-crypto-1.13.0.tgz", | |
| 213 | "integrity": "sha512-RQl13yD+YTACWUZHMMck0C6LNjXgGgRyhVC557ag0xIrkxXf4IQwpnc8WOB58f4k5kSzKhIjy05oW3HUBrFpSQ==", | |
| 214 | "dev": true, | |
| 215 | "license": "Apache-2.0", | |
| 216 | "dependencies": { | |
| 217 | "bare-stream": "^2.6.3" | |
| 218 | }, | |
| 219 | "peerDependencies": { | |
| 220 | "bare-buffer": "*" | |
| 221 | }, | |
| 222 | "peerDependenciesMeta": { | |
| 223 | "bare-buffer": { | |
| 224 | "optional": true | |
| 225 | } | |
| 226 | } | |
| 227 | }, | |
| 228 | "node_modules/bare-debug-log": { | |
| 229 | "version": "2.0.0", | |
| 230 | "resolved": "https://registry.npmjs.org/bare-debug-log/-/bare-debug-log-2.0.0.tgz", | |
| 231 | "integrity": "sha512-Vi42PkMQsNV9PUpx2Gl1hikshx5O9FzMJ6o9Nnopseg7qLBBK7Nl31d0RHcfwLEAfmcPApytpc0ZFfq68u22FQ==", | |
| 232 | "dev": true, | |
| 233 | "license": "Apache-2.0", | |
| 234 | "dependencies": { | |
| 235 | "bare-os": "^3.0.1" | |
| 236 | } | |
| 237 | }, | |
| 238 | "node_modules/bare-dns": { | |
| 239 | "version": "2.1.4", | |
| 240 | "resolved": "https://registry.npmjs.org/bare-dns/-/bare-dns-2.1.4.tgz", | |
| 241 | "integrity": "sha512-abwjHmpWqSRNB7V5615QxPH92L71AVzFm/kKTs8VYiNTAi2xVdonpv0BjJ0hwXLwomoW+xsSOPjW6PZPO14asg==", | |
| 242 | "dev": true, | |
| 243 | "license": "Apache-2.0", | |
| 244 | "engines": { | |
| 245 | "bare": ">=1.7.0" | |
| 246 | } | |
| 247 | }, | |
| 248 | "node_modules/bare-encoding": { | |
| 249 | "version": "1.0.3", | |
| 250 | "resolved": "https://registry.npmjs.org/bare-encoding/-/bare-encoding-1.0.3.tgz", | |
| 251 | "integrity": "sha512-Kqf+t/azs13lUeyK4Tb7ha4wdLRXKWCXQ8w1rVmt7KtoPCPdHD/Xwt7LBIsCSwwGglrcmblo5VOLa5avkJqULA==", | |
| 252 | "dev": true, | |
| 253 | "license": "Apache-2.0", | |
| 254 | "peerDependencies": { | |
| 255 | "bare-buffer": "*" | |
| 256 | }, | |
| 257 | "peerDependenciesMeta": { | |
| 258 | "bare-buffer": { | |
| 259 | "optional": true | |
| 260 | } | |
| 261 | } | |
| 262 | }, | |
| 263 | "node_modules/bare-env": { | |
| 264 | "version": "3.0.0", | |
| 265 | "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.0.tgz", | |
| 266 | "integrity": "sha512-0u964P5ZLAxTi+lW4Kjp7YRJQ5gZr9ycYOtjLxsSrupgMz3sn5Z9n4SH/JIifHwvadsf1brA2JAjP+9IOWwTiw==", | |
| 267 | "dev": true, | |
| 268 | "license": "Apache-2.0", | |
| 269 | "dependencies": { | |
| 270 | "bare-os": "^3.0.1" | |
| 271 | } | |
| 272 | }, | |
| 273 | "node_modules/bare-events": { | |
| 274 | "version": "2.8.2", | |
| 275 | "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", | |
| 276 | "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", | |
| 277 | "license": "Apache-2.0", | |
| 278 | "peerDependencies": { | |
| 279 | "bare-abort-controller": "*" | |
| 280 | }, | |
| 281 | "peerDependenciesMeta": { | |
| 282 | "bare-abort-controller": { | |
| 283 | "optional": true | |
| 284 | } | |
| 285 | } | |
| 286 | }, | |
| 287 | "node_modules/bare-format": { | |
| 288 | "version": "1.0.1", | |
| 289 | "resolved": "https://registry.npmjs.org/bare-format/-/bare-format-1.0.1.tgz", | |
| 290 | "integrity": "sha512-1oS+LZrWK6tnYnvNSHDGljc2MPunRxwhpFriuCgzNF+oklrnwmBKD91tS0yt+jpl2j3UgcSDzBIMiVTvLs9A8w==", | |
| 291 | "dev": true, | |
| 292 | "license": "Apache-2.0", | |
| 293 | "dependencies": { | |
| 294 | "bare-inspect": "^3.0.0" | |
| 295 | } | |
| 296 | }, | |
| 297 | "node_modules/bare-fs": { | |
| 298 | "version": "4.5.4", | |
| 299 | "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.4.tgz", | |
| 300 | "integrity": "sha512-POK4oplfA7P7gqvetNmCs4CNtm9fNsx+IAh7jH7GgU0OJdge2rso0R20TNWVq6VoWcCvsTdlNDaleLHGaKx8CA==", | |
| 301 | "license": "Apache-2.0", | |
| 302 | "dependencies": { | |
| 303 | "bare-events": "^2.5.4", | |
| 304 | "bare-path": "^3.0.0", | |
| 305 | "bare-stream": "^2.6.4", | |
| 306 | "bare-url": "^2.2.2", | |
| 307 | "fast-fifo": "^1.3.2" | |
| 308 | }, | |
| 309 | "engines": { | |
| 310 | "bare": ">=1.16.0" | |
| 311 | }, | |
| 312 | "peerDependencies": { | |
| 313 | "bare-buffer": "*" | |
| 314 | }, | |
| 315 | "peerDependenciesMeta": { | |
| 316 | "bare-buffer": { | |
| 317 | "optional": true | |
| 318 | } | |
| 319 | } | |
| 320 | }, | |
| 321 | "node_modules/bare-hrtime": { | |
| 322 | "version": "2.1.1", | |
| 323 | "resolved": "https://registry.npmjs.org/bare-hrtime/-/bare-hrtime-2.1.1.tgz", | |
| 324 | "integrity": "sha512-VMb3tHo05gsnbu3OXTmkDiwTjMlOsbQmKoysKqKEyR09m77TuDrYFbj3Q5GGk10dAKsUHrnXmwCaeJqzVpB5ZA==", | |
| 325 | "dev": true, | |
| 326 | "license": "Apache-2.0" | |
| 327 | }, | |
| 328 | "node_modules/bare-http-parser": { | |
| 329 | "version": "1.0.1", | |
| 330 | "resolved": "https://registry.npmjs.org/bare-http-parser/-/bare-http-parser-1.0.1.tgz", | |
| 331 | "integrity": "sha512-A3LTDTcELcmNJ3g5liIaS038v/BQxOhA9cjhBESn7eoV7QCuMoIRBKLDadDe08flxyLbxI2f+1l2MZ/5+HnKPA==", | |
| 332 | "dev": true, | |
| 333 | "license": "Apache-2.0" | |
| 334 | }, | |
| 335 | "node_modules/bare-http1": { | |
| 336 | "version": "4.2.3", | |
| 337 | "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.2.3.tgz", | |
| 338 | "integrity": "sha512-zyvHF3xzvBJNpuw0QydE9etTn2AyC/TRVDWTo8Ed3LQEEGaFOkv6KHhaJPlf4eWEeTekk41Fowie+fTsXoNxkA==", | |
| 339 | "dev": true, | |
| 340 | "license": "Apache-2.0", | |
| 341 | "dependencies": { | |
| 342 | "bare-events": "^2.6.0", | |
| 343 | "bare-http-parser": "^1.0.0", | |
| 344 | "bare-stream": "^2.3.0", | |
| 345 | "bare-tcp": "^2.2.0" | |
| 346 | }, | |
| 347 | "peerDependencies": { | |
| 348 | "bare-buffer": "*", | |
| 349 | "bare-url": "*" | |
| 350 | }, | |
| 351 | "peerDependenciesMeta": { | |
| 352 | "bare-buffer": { | |
| 353 | "optional": true | |
| 354 | }, | |
| 355 | "bare-url": { | |
| 356 | "optional": true | |
| 357 | } | |
| 358 | } | |
| 359 | }, | |
| 360 | "node_modules/bare-https": { | |
| 361 | "version": "2.1.2", | |
| 362 | "resolved": "https://registry.npmjs.org/bare-https/-/bare-https-2.1.2.tgz", | |
| 363 | "integrity": "sha512-Q+TTydUDsuKQJvh8dX2dvOXCR9fM3xR5TBmKaFrs5p7Lj7XbKX7v4vIUJ36H0SXg2xCOQxXKIbjwrLg5tfJNYg==", | |
| 364 | "dev": true, | |
| 365 | "license": "Apache-2.0", | |
| 366 | "dependencies": { | |
| 367 | "bare-http1": "^4.0.0", | |
| 368 | "bare-tcp": "^2.2.0", | |
| 369 | "bare-tls": "^2.0.0" | |
| 370 | } | |
| 371 | }, | |
| 372 | "node_modules/bare-inspect": { | |
| 373 | "version": "3.1.4", | |
| 374 | "resolved": "https://registry.npmjs.org/bare-inspect/-/bare-inspect-3.1.4.tgz", | |
| 375 | "integrity": "sha512-jfW5KRA84o3REpI6Vr4nbvMn+hqVAw8GU1mMdRwUsY5yJovQamxYeKGVKGqdzs+8ZbG4jRzGUXP/3Ji/DnqfPg==", | |
| 376 | "dev": true, | |
| 377 | "license": "Apache-2.0", | |
| 378 | "dependencies": { | |
| 379 | "bare-ansi-escapes": "^2.1.0", | |
| 380 | "bare-type": "^1.0.0" | |
| 381 | }, | |
| 382 | "engines": { | |
| 383 | "bare": ">=1.18.0" | |
| 384 | } | |
| 385 | }, | |
| 386 | "node_modules/bare-inspector": { | |
| 387 | "version": "4.0.3", | |
| 388 | "resolved": "https://registry.npmjs.org/bare-inspector/-/bare-inspector-4.0.3.tgz", | |
| 389 | "integrity": "sha512-yYk6+7tTVKJKHOja5+QBn989ETv8oVsmwoH0nCM+QYC6w5MC03GC4aQNK+lipAZSz1EaAj42WZ8Wewjpy0L9TA==", | |
| 390 | "dev": true, | |
| 391 | "license": "Apache-2.0", | |
| 392 | "dependencies": { | |
| 393 | "bare-events": "^2.1.0", | |
| 394 | "bare-http1": "^4.0.0", | |
| 395 | "bare-stream": "^2.0.0", | |
| 396 | "bare-url": "^2.0.0", | |
| 397 | "bare-ws": "^2.0.0" | |
| 398 | }, | |
| 399 | "engines": { | |
| 400 | "bare": ">=1.2.0" | |
| 401 | }, | |
| 402 | "peerDependencies": { | |
| 403 | "bare-tcp": "*" | |
| 404 | }, | |
| 405 | "peerDependenciesMeta": { | |
| 406 | "bare-tcp": { | |
| 407 | "optional": true | |
| 408 | } | |
| 409 | } | |
| 410 | }, | |
| 411 | "node_modules/bare-module": { | |
| 412 | "version": "6.1.3", | |
| 413 | "resolved": "https://registry.npmjs.org/bare-module/-/bare-module-6.1.3.tgz", | |
| 414 | "integrity": "sha512-5XWsVHsvtWMH4tK4DQWgpNTV0t/sg3ZrAaQLIxrwjrS5+u8Q9vEgc/zQ4QaDPWDse/y/5h+d+YG1Q0JfSMt0zA==", | |
| 415 | "dev": true, | |
| 416 | "license": "Apache-2.0", | |
| 417 | "dependencies": { | |
| 418 | "bare-bundle": "^1.3.0", | |
| 419 | "bare-module-lexer": "^1.0.0", | |
| 420 | "bare-module-resolve": "^1.8.0", | |
| 421 | "bare-path": "^3.0.0", | |
| 422 | "bare-url": "^2.0.1" | |
| 423 | }, | |
| 424 | "engines": { | |
| 425 | "bare": ">=1.23.0" | |
| 426 | }, | |
| 427 | "peerDependencies": { | |
| 428 | "bare-buffer": "*" | |
| 429 | }, | |
| 430 | "peerDependenciesMeta": { | |
| 431 | "bare-buffer": { | |
| 432 | "optional": true | |
| 433 | } | |
| 434 | } | |
| 435 | }, | |
| 436 | "node_modules/bare-module-lexer": { | |
| 437 | "version": "1.4.7", | |
| 438 | "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.4.7.tgz", | |
| 439 | "integrity": "sha512-0klU4eMsjh/wcxi8FdHmNom2j2F4kmkXOhyJFL9qTaSFp2lE3m6BtbKgMHY8R5miqC9r8/IfA8wzXnC5Os14WA==", | |
| 440 | "dev": true, | |
| 441 | "license": "Apache-2.0", | |
| 442 | "dependencies": { | |
| 443 | "require-addon": "^1.0.2" | |
| 444 | }, | |
| 445 | "peerDependencies": { | |
| 446 | "bare-buffer": "*" | |
| 447 | }, | |
| 448 | "peerDependenciesMeta": { | |
| 449 | "bare-buffer": { | |
| 450 | "optional": true | |
| 451 | } | |
| 452 | } | |
| 453 | }, | |
| 454 | "node_modules/bare-module-resolve": { | |
| 455 | "version": "1.12.1", | |
| 456 | "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.1.tgz", | |
| 457 | "integrity": "sha512-hbmAPyFpEq8FoZMd5sFO3u6MC5feluWoGE8YKlA8fCrl6mNtx68Wjg4DTiDJcqRJaovTvOYKfYngoBUnbaT7eg==", | |
| 458 | "license": "Apache-2.0", | |
| 459 | "dependencies": { | |
| 460 | "bare-semver": "^1.0.0" | |
| 461 | }, | |
| 462 | "peerDependencies": { | |
| 463 | "bare-url": "*" | |
| 464 | }, | |
| 465 | "peerDependenciesMeta": { | |
| 466 | "bare-url": { | |
| 467 | "optional": true | |
| 468 | } | |
| 469 | } | |
| 470 | }, | |
| 471 | "node_modules/bare-net": { | |
| 472 | "version": "2.2.0", | |
| 473 | "resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.2.0.tgz", | |
| 474 | "integrity": "sha512-UF7cAbHsGE+H6uEqWF5IULBow1x58chZz4g3ALgHtv7wZsFcCbRDt0JKWEumf5Oma3QWS1Q6aLi0Rpll8RElMg==", | |
| 475 | "dev": true, | |
| 476 | "license": "Apache-2.0", | |
| 477 | "dependencies": { | |
| 478 | "bare-events": "^2.2.2", | |
| 479 | "bare-pipe": "^4.0.0", | |
| 480 | "bare-stream": "^2.0.0", | |
| 481 | "bare-tcp": "^2.0.0" | |
| 482 | } | |
| 483 | }, | |
| 484 | "node_modules/bare-os": { | |
| 485 | "version": "3.6.2", | |
| 486 | "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", | |
| 487 | "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", | |
| 488 | "license": "Apache-2.0", | |
| 489 | "engines": { | |
| 490 | "bare": ">=1.14.0" | |
| 491 | } | |
| 492 | }, | |
| 493 | "node_modules/bare-path": { | |
| 494 | "version": "3.0.0", | |
| 495 | "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", | |
| 496 | "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", | |
| 497 | "license": "Apache-2.0", | |
| 498 | "dependencies": { | |
| 499 | "bare-os": "^3.0.1" | |
| 500 | } | |
| 501 | }, | |
| 502 | "node_modules/bare-pipe": { | |
| 503 | "version": "4.1.3", | |
| 504 | "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.1.3.tgz", | |
| 505 | "integrity": "sha512-DqQsx93rAzre6yJ9T6l/Vgh+X+bntkVMB1X5ggtXjXtqtMmF2Y2RVlCzxxy/09R6yeR9FSWBEUIaMYJL1/5VDA==", | |
| 506 | "dev": true, | |
| 507 | "license": "Apache-2.0", | |
| 508 | "dependencies": { | |
| 509 | "bare-events": "^2.0.0", | |
| 510 | "bare-stream": "^2.0.0" | |
| 511 | }, | |
| 512 | "engines": { | |
| 513 | "bare": ">=1.16.0" | |
| 514 | } | |
| 515 | }, | |
| 516 | "node_modules/bare-process": { | |
| 517 | "version": "4.2.2", | |
| 518 | "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.2.2.tgz", | |
| 519 | "integrity": "sha512-ikzEw+HGLB+2lS/WLVEsqi8BXBwzleG3n7DYI0v6/YNklvNabOIGlLd1dof+7Jw5ob4jsBRPtyflweVahY7rFA==", | |
| 520 | "dev": true, | |
| 521 | "license": "Apache-2.0", | |
| 522 | "dependencies": { | |
| 523 | "bare-env": "^3.0.0", | |
| 524 | "bare-events": "^2.3.1", | |
| 525 | "bare-hrtime": "^2.0.0", | |
| 526 | "bare-os": "^3.5.0", | |
| 527 | "bare-pipe": "^4.0.0", | |
| 528 | "bare-signals": "^4.0.0", | |
| 529 | "bare-tty": "^5.0.0" | |
| 530 | } | |
| 531 | }, | |
| 532 | "node_modules/bare-semver": { | |
| 533 | "version": "1.0.2", | |
| 534 | "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.0.2.tgz", | |
| 535 | "integrity": "sha512-ESVaN2nzWhcI5tf3Zzcq9aqCZ676VWzqw07eEZ0qxAcEOAFYBa0pWq8sK34OQeHLY3JsfKXZS9mDyzyxGjeLzA==", | |
| 536 | "license": "Apache-2.0" | |
| 537 | }, | |
| 538 | "node_modules/bare-signals": { | |
| 539 | "version": "4.2.0", | |
| 540 | "resolved": "https://registry.npmjs.org/bare-signals/-/bare-signals-4.2.0.tgz", | |
| 541 | "integrity": "sha512-fNHMOdQIlYuTvMB3Oh9Apk99hLKn351+Ir8vz+khiPTcOqIyGG4uWWjdLTzxWdYGsA0eT+We3y0K74hjj2nq7A==", | |
| 542 | "dev": true, | |
| 543 | "license": "Apache-2.0", | |
| 544 | "dependencies": { | |
| 545 | "bare-events": "^2.5.3", | |
| 546 | "bare-os": "^3.3.1" | |
| 547 | }, | |
| 548 | "engines": { | |
| 549 | "bare": ">=1.7.0" | |
| 550 | } | |
| 551 | }, | |
| 552 | "node_modules/bare-stream": { | |
| 553 | "version": "2.7.0", | |
| 554 | "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", | |
| 555 | "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", | |
| 556 | "license": "Apache-2.0", | |
| 557 | "dependencies": { | |
| 558 | "streamx": "^2.21.0" | |
| 559 | }, | |
| 560 | "peerDependencies": { | |
| 561 | "bare-buffer": "*", | |
| 562 | "bare-events": "*" | |
| 563 | }, | |
| 564 | "peerDependenciesMeta": { | |
| 565 | "bare-buffer": { | |
| 566 | "optional": true | |
| 567 | }, | |
| 568 | "bare-events": { | |
| 569 | "optional": true | |
| 570 | } | |
| 571 | } | |
| 572 | }, | |
| 573 | "node_modules/bare-subprocess": { | |
| 574 | "version": "5.2.2", | |
| 575 | "resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.2.tgz", | |
| 576 | "integrity": "sha512-L6oXgQ1aWs25RtG5Ky0bDD06p3RAcVVrDDMWb1DfXpHtyEWxamcyIWUbSykxMTWrpLlmSj6ytbb6yoKehGFfmw==", | |
| 577 | "dev": true, | |
| 578 | "license": "Apache-2.0", | |
| 579 | "dependencies": { | |
| 580 | "bare-env": "^3.0.0", | |
| 581 | "bare-events": "^2.5.4", | |
| 582 | "bare-os": "^3.0.1", | |
| 583 | "bare-pipe": "^4.0.0", | |
| 584 | "bare-url": "^2.2.2" | |
| 585 | }, | |
| 586 | "engines": { | |
| 587 | "bare": ">=1.7.0" | |
| 588 | }, | |
| 589 | "peerDependencies": { | |
| 590 | "bare-buffer": "*" | |
| 591 | }, | |
| 592 | "peerDependenciesMeta": { | |
| 593 | "bare-buffer": { | |
| 594 | "optional": true | |
| 595 | } | |
| 596 | } | |
| 597 | }, | |
| 598 | "node_modules/bare-tcp": { | |
| 599 | "version": "2.2.4", | |
| 600 | "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.2.4.tgz", | |
| 601 | "integrity": "sha512-vGjfMkyhS1lxp+Y4+gXRDR1ijZMK2cKx0RBwtYKUuZbD90ARCoM6bAiTa1HBztumCIC+NXb8eXdMFXyuYwlPyw==", | |
| 602 | "dev": true, | |
| 603 | "license": "Apache-2.0", | |
| 604 | "dependencies": { | |
| 605 | "bare-dns": "^2.0.4", | |
| 606 | "bare-events": "^2.5.4", | |
| 607 | "bare-stream": "^2.6.4" | |
| 608 | }, | |
| 609 | "engines": { | |
| 610 | "bare": ">=1.16.0" | |
| 611 | } | |
| 612 | }, | |
| 613 | "node_modules/bare-tls": { | |
| 614 | "version": "2.1.7", | |
| 615 | "resolved": "https://registry.npmjs.org/bare-tls/-/bare-tls-2.1.7.tgz", | |
| 616 | "integrity": "sha512-h6wcNXQdBeTX7fed9tjPp0/9cA/QfcBTv3ItgjnbUk4rWAU8bEFalZCZnUDdCK/t9zrNfJ+yvcPx4D/1Y6biyA==", | |
| 617 | "dev": true, | |
| 618 | "license": "Apache-2.0", | |
| 619 | "dependencies": { | |
| 620 | "bare-net": "^2.0.1", | |
| 621 | "bare-stream": "^2.6.4" | |
| 622 | }, | |
| 623 | "engines": { | |
| 624 | "bare": ">=1.7.0" | |
| 625 | } | |
| 626 | }, | |
| 627 | "node_modules/bare-tty": { | |
| 628 | "version": "5.0.3", | |
| 629 | "resolved": "https://registry.npmjs.org/bare-tty/-/bare-tty-5.0.3.tgz", | |
| 630 | "integrity": "sha512-jW24RBWRZOMHuSEWC9mh8wUKO5WUNl0UWUTNPn3yZ8qkOqwa8vaV2dR3a+BvblONPk7V35wuvK9P8508+judAg==", | |
| 631 | "dev": true, | |
| 632 | "license": "Apache-2.0", | |
| 633 | "dependencies": { | |
| 634 | "bare-events": "^2.2.0", | |
| 635 | "bare-signals": "^4.0.0", | |
| 636 | "bare-stream": "^2.0.0" | |
| 637 | }, | |
| 638 | "engines": { | |
| 639 | "bare": ">=1.16.0" | |
| 640 | } | |
| 641 | }, | |
| 642 | "node_modules/bare-type": { | |
| 643 | "version": "1.1.0", | |
| 644 | "resolved": "https://registry.npmjs.org/bare-type/-/bare-type-1.1.0.tgz", | |
| 645 | "integrity": "sha512-LdtnnEEYldOc87Dr4GpsKnStStZk3zfgoEMXy8yvEZkXrcCv9RtYDrUYWFsBQHtaB0s1EUWmcvS6XmEZYIj3Bw==", | |
| 646 | "dev": true, | |
| 647 | "license": "Apache-2.0", | |
| 648 | "engines": { | |
| 649 | "bare": ">=1.2.0" | |
| 650 | } | |
| 651 | }, | |
| 652 | "node_modules/bare-url": { | |
| 653 | "version": "2.3.2", | |
| 654 | "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", | |
| 655 | "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", | |
| 656 | "license": "Apache-2.0", | |
| 657 | "peer": true, | |
| 658 | "dependencies": { | |
| 659 | "bare-path": "^3.0.0" | |
| 660 | } | |
| 661 | }, | |
| 662 | "node_modules/bare-utils": { | |
| 663 | "version": "1.5.1", | |
| 664 | "resolved": "https://registry.npmjs.org/bare-utils/-/bare-utils-1.5.1.tgz", | |
| 665 | "integrity": "sha512-mxCkFvmDU3mlD/mb+pT64kKXOsx2KMsWLQbngN1LB+NOXfhfnRnyvpy3VZc6m7gzQxe57Bsi+aTCBqA4/S3elQ==", | |
| 666 | "dev": true, | |
| 667 | "license": "Apache-2.0", | |
| 668 | "dependencies": { | |
| 669 | "bare-debug-log": "^2.0.0", | |
| 670 | "bare-encoding": "^1.0.0", | |
| 671 | "bare-format": "^1.0.0", | |
| 672 | "bare-inspect": "^3.0.0", | |
| 673 | "bare-type": "^1.0.6" | |
| 674 | } | |
| 675 | }, | |
| 676 | "node_modules/bare-v8-to-istanbul": { | |
| 677 | "version": "1.0.2", | |
| 678 | "resolved": "https://registry.npmjs.org/bare-v8-to-istanbul/-/bare-v8-to-istanbul-1.0.2.tgz", | |
| 679 | "integrity": "sha512-CM0wPgyQtXTQ7h4q8NjbTZ/FH5he0Jkiwgpq0lFZZA5tkVZzmQ/9ul4R7E+cNlGZJIZlQZ9v4CwbgVaFG3zOAA==", | |
| 680 | "dev": true, | |
| 681 | "license": "Apache-2.0", | |
| 682 | "dependencies": { | |
| 683 | "bare-assert": "^1.0.2", | |
| 684 | "bare-fs": "^4.1.2", | |
| 685 | "bare-module": "^6.1.2", | |
| 686 | "bare-path": "^3.0.0", | |
| 687 | "bare-process": "^4.2.0", | |
| 688 | "bare-url": "^2.1.5", | |
| 689 | "bare-utils": "^1.2.0", | |
| 690 | "v8-to-istanbul": "^9.3.0", | |
| 691 | "which-runtime": "^1.2.1" | |
| 692 | } | |
| 693 | }, | |
| 694 | "node_modules/bare-ws": { | |
| 695 | "version": "2.0.4", | |
| 696 | "resolved": "https://registry.npmjs.org/bare-ws/-/bare-ws-2.0.4.tgz", | |
| 697 | "integrity": "sha512-SQMXzBYna9dRj57Dz1/ag+VWHCRXbfCjMHgyfM2F2lhkVLzMjnVSZP72aVeFWPFqe494Rd70Kzhe2JElGwFlJQ==", | |
| 698 | "dev": true, | |
| 699 | "license": "Apache-2.0", | |
| 700 | "dependencies": { | |
| 701 | "bare-crypto": "^1.2.0", | |
| 702 | "bare-events": "^2.3.1", | |
| 703 | "bare-http1": "^4.0.0", | |
| 704 | "bare-https": "^2.0.0", | |
| 705 | "bare-stream": "^2.1.2" | |
| 706 | }, | |
| 707 | "peerDependencies": { | |
| 708 | "bare-buffer": "*", | |
| 709 | "bare-url": "*" | |
| 710 | }, | |
| 711 | "peerDependenciesMeta": { | |
| 712 | "bare-buffer": { | |
| 713 | "optional": true | |
| 714 | }, | |
| 715 | "bare-url": { | |
| 716 | "optional": true | |
| 717 | } | |
| 718 | } | |
| 719 | }, | |
| 720 | "node_modules/big-sparse-array": { | |
| 721 | "version": "1.0.3", | |
| 722 | "resolved": "https://registry.npmjs.org/big-sparse-array/-/big-sparse-array-1.0.3.tgz", | |
| 723 | "integrity": "sha512-6RjV/3mSZORlMdpUaQ6rUSpG637cZm0//E54YYGtQg1c1O+AbZP8UTdJ/TchsDZcTVLmyWZcseBfp2HBeXUXOQ==", | |
| 724 | "license": "MIT" | |
| 725 | }, | |
| 726 | "node_modules/bridge-envelope": { | |
| 727 | "resolved": "../bridge-envelope", | |
| 728 | "link": true | |
| 729 | }, | |
| 730 | "node_modules/brittle": { | |
| 731 | "version": "3.19.1", | |
| 732 | "resolved": "https://registry.npmjs.org/brittle/-/brittle-3.19.1.tgz", | |
| 733 | "integrity": "sha512-4Ted1Mt9o9B6oIA6ImJJCtB/Fv++ZO3IDNQgRcHOXWSYGRUidgxchaGrUBaRn+4mlneXrgInPkCPzi0jO8qKbA==", | |
| 734 | "dev": true, | |
| 735 | "license": "Apache-2.0", | |
| 736 | "dependencies": { | |
| 737 | "b4a": "^1.6.0", | |
| 738 | "bare-assert": "^1.0.2", | |
| 739 | "bare-cov": "^1.1.0", | |
| 740 | "bare-fs": "^4.1.6", | |
| 741 | "bare-os": "^3.6.1", | |
| 742 | "bare-path": "^3.0.0", | |
| 743 | "bare-process": "^4.2.1", | |
| 744 | "bare-subprocess": "^5.0.0", | |
| 745 | "bare-url": "^2.1.6", | |
| 746 | "error-stack-parser": "^2.1.4", | |
| 747 | "globbie": "^1.0.2", | |
| 748 | "paparam": "^1.6.2", | |
| 749 | "same-object": "^1.0.2", | |
| 750 | "test-tmp": "^1.4.0", | |
| 751 | "tmatch": "^5.0.0" | |
| 752 | }, | |
| 753 | "bin": { | |
| 754 | "brittle": "bin/node.js", | |
| 755 | "brittle-bare": "bin/bare.js", | |
| 756 | "brittle-node": "bin/node.js", | |
| 757 | "brittle-pear": "bin/pear.js" | |
| 758 | } | |
| 759 | }, | |
| 760 | "node_modules/codecs": { | |
| 761 | "version": "3.1.0", | |
| 762 | "resolved": "https://registry.npmjs.org/codecs/-/codecs-3.1.0.tgz", | |
| 763 | "integrity": "sha512-Dqx8NwvBvnMeuPQdVKy/XEF71igjR5apxBvCGeV0pP1tXadOiaLvDTXt7xh+/5wI1ASB195mXQGJbw3Ml4YDWQ==", | |
| 764 | "license": "MIT", | |
| 765 | "dependencies": { | |
| 766 | "b4a": "^1.6.3" | |
| 767 | } | |
| 768 | }, | |
| 769 | "node_modules/compact-encoding": { | |
| 770 | "version": "2.18.0", | |
| 771 | "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-2.18.0.tgz", | |
| 772 | "integrity": "sha512-goACAOlhMI2xo5jGOMUDfOLnGdRE1jGfyZ+zie8N5114nHrbPIqf6GLUtzbLof6DSyrERlYRm3EcBplte5LcQw==", | |
| 773 | "license": "Apache-2.0", | |
| 774 | "dependencies": { | |
| 775 | "b4a": "^1.3.0" | |
| 776 | } | |
| 777 | }, | |
| 778 | "node_modules/convert-source-map": { | |
| 779 | "version": "2.0.0", | |
| 780 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", | |
| 781 | "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", | |
| 782 | "dev": true, | |
| 783 | "license": "MIT" | |
| 784 | }, | |
| 785 | "node_modules/corestore": { | |
| 786 | "version": "6.18.4", | |
| 787 | "resolved": "https://registry.npmjs.org/corestore/-/corestore-6.18.4.tgz", | |
| 788 | "integrity": "sha512-KL1cOUyj3DFRNzeVRekGPRiGN4sIfGo7BsncSJQQBT3Rh2y1nQ32MOR9X5DlOMlWIT4txwqBzhzam6+JXtwFVw==", | |
| 789 | "license": "MIT", | |
| 790 | "dependencies": { | |
| 791 | "b4a": "^1.6.4", | |
| 792 | "hypercore": "^10.37.10", | |
| 793 | "hypercore-crypto": "^3.4.0", | |
| 794 | "hypercore-id-encoding": "^1.2.0", | |
| 795 | "read-write-mutexify": "^2.1.0", | |
| 796 | "ready-resource": "^1.0.0", | |
| 797 | "safety-catch": "^1.0.1", | |
| 798 | "sodium-universal": "^4.0.0", | |
| 799 | "xache": "^1.1.0" | |
| 800 | } | |
| 801 | }, | |
| 802 | "node_modules/crc-native": { | |
| 803 | "version": "1.1.8", | |
| 804 | "resolved": "https://registry.npmjs.org/crc-native/-/crc-native-1.1.8.tgz", | |
| 805 | "integrity": "sha512-R/RYbD8lhG5M1k0iLX49tJdwHM0ipevJPpZw2Wk7b4EShyvx2P0uaq79xyRpcknjiVGiUfjFA8Gyy0mOlhABvw==", | |
| 806 | "license": "Apache-2.0", | |
| 807 | "optional": true, | |
| 808 | "dependencies": { | |
| 809 | "require-addon": "^1.1.0" | |
| 810 | } | |
| 811 | }, | |
| 812 | "node_modules/crc-universal": { | |
| 813 | "version": "1.0.4", | |
| 814 | "resolved": "https://registry.npmjs.org/crc-universal/-/crc-universal-1.0.4.tgz", | |
| 815 | "integrity": "sha512-CE9xWEI6Gd5V0Bdmj5NoWH3d7+EIe4zUpy1sv6uvKYznzsDP1vhiWsTBmL6q9IrH2P6RRshp+8AkhR4CMtY5Hg==", | |
| 816 | "license": "ISC", | |
| 817 | "optionalDependencies": { | |
| 818 | "crc-native": "^1.0.3" | |
| 819 | } | |
| 820 | }, | |
| 821 | "node_modules/debounceify": { | |
| 822 | "version": "1.1.0", | |
| 823 | "resolved": "https://registry.npmjs.org/debounceify/-/debounceify-1.1.0.tgz", | |
| 824 | "integrity": "sha512-eKuHDVfJVg+u/0nPy8P+fhnLgbyuTgVxuCRrS/R7EpDSMMkBDgSes41MJtSAY1F1hcqfHz3Zy/qpqHHIp/EhdA==", | |
| 825 | "license": "MIT" | |
| 826 | }, | |
| 827 | "node_modules/error-stack-parser": { | |
| 828 | "version": "2.1.4", | |
| 829 | "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", | |
| 830 | "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", | |
| 831 | "dev": true, | |
| 832 | "license": "MIT", | |
| 833 | "dependencies": { | |
| 834 | "stackframe": "^1.3.4" | |
| 835 | } | |
| 836 | }, | |
| 837 | "node_modules/events-universal": { | |
| 838 | "version": "1.0.1", | |
| 839 | "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", | |
| 840 | "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", | |
| 841 | "license": "Apache-2.0", | |
| 842 | "dependencies": { | |
| 843 | "bare-events": "^2.7.0" | |
| 844 | } | |
| 845 | }, | |
| 846 | "node_modules/fast-fifo": { | |
| 847 | "version": "1.3.2", | |
| 848 | "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", | |
| 849 | "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", | |
| 850 | "license": "MIT" | |
| 851 | }, | |
| 852 | "node_modules/flat-tree": { | |
| 853 | "version": "1.13.0", | |
| 854 | "resolved": "https://registry.npmjs.org/flat-tree/-/flat-tree-1.13.0.tgz", | |
| 855 | "integrity": "sha512-fT3HIuCPwHhFgJ20QYzDHgUG0zMmFg5cHvFiFo5h+QMSJ28TihsEVY0f8HGliuO+pOzmvjMx1odToeaEWkTnyQ==", | |
| 856 | "license": "MIT" | |
| 857 | }, | |
| 858 | "node_modules/fs-native-extensions": { | |
| 859 | "version": "1.4.5", | |
| 860 | "resolved": "https://registry.npmjs.org/fs-native-extensions/-/fs-native-extensions-1.4.5.tgz", | |
| 861 | "integrity": "sha512-ekV0T//iDm4AvhOcuPaHpxub4DI7HvY5ucLJVDvi7T2J+NZkQ9S6MuvgP0yeQvoqNUaAGyLjVYb1905BF9bpmg==", | |
| 862 | "license": "Apache-2.0", | |
| 863 | "optional": true, | |
| 864 | "dependencies": { | |
| 865 | "require-addon": "^1.1.0", | |
| 866 | "which-runtime": "^1.2.0" | |
| 867 | } | |
| 868 | }, | |
| 869 | "node_modules/globbie": { | |
| 870 | "version": "1.0.3", | |
| 871 | "resolved": "https://registry.npmjs.org/globbie/-/globbie-1.0.3.tgz", | |
| 872 | "integrity": "sha512-hcryJmKcftf82xfBWTWxuUITWIIoYO3ec104V17SMHGFl6VLbm1d1Ju9LX9L+jyJTwICpemX/dmPI/HGYoKr1A==", | |
| 873 | "dev": true, | |
| 874 | "license": "Apache-2.0", | |
| 875 | "dependencies": { | |
| 876 | "bare-fs": "^4.1.2", | |
| 877 | "bare-path": "^3.0.0", | |
| 878 | "bare-process": "^4.2.1", | |
| 879 | "picomatch": "^4.0.2" | |
| 880 | } | |
| 881 | }, | |
| 882 | "node_modules/hyperbee": { | |
| 883 | "version": "2.27.3", | |
| 884 | "resolved": "https://registry.npmjs.org/hyperbee/-/hyperbee-2.27.3.tgz", | |
| 885 | "integrity": "sha512-PXURH2U4juUZyJRKHTrY5z1zX851pmI1Q0jfv5F/hCIErDt/ND8jOZuxc3hfOLM9f0W3qJEDTMlV5AJBkVPy8w==", | |
| 886 | "license": "MIT", | |
| 887 | "dependencies": { | |
| 888 | "b4a": "^1.6.0", | |
| 889 | "codecs": "^3.0.0", | |
| 890 | "debounceify": "^1.0.0", | |
| 891 | "hypercore-errors": "^1.0.0", | |
| 892 | "mutexify": "^1.4.0", | |
| 893 | "protocol-buffers-encodings": "^1.2.0", | |
| 894 | "rache": "^1.0.0", | |
| 895 | "ready-resource": "^1.0.0", | |
| 896 | "resolve-reject-promise": "^1.1.0", | |
| 897 | "safety-catch": "^1.0.2", | |
| 898 | "streamx": "^2.12.4", | |
| 899 | "unslab": "^1.2.0" | |
| 900 | } | |
| 901 | }, | |
| 902 | "node_modules/hypercore": { | |
| 903 | "version": "10.38.2", | |
| 904 | "resolved": "https://registry.npmjs.org/hypercore/-/hypercore-10.38.2.tgz", | |
| 905 | "integrity": "sha512-2vx42g2RKdP/o2XzlxKfvgEaZ3+h/B855gCR8iscUDnxUoOZej7vXZ2buex0sweDUEW5aEfMxFR6o5hncio3Nw==", | |
| 906 | "license": "MIT", | |
| 907 | "dependencies": { | |
| 908 | "@hyperswarm/secret-stream": "^6.0.0", | |
| 909 | "b4a": "^1.1.0", | |
| 910 | "bare-events": "^2.2.0", | |
| 911 | "big-sparse-array": "^1.0.3", | |
| 912 | "compact-encoding": "^2.11.0", | |
| 913 | "crc-universal": "^1.0.2", | |
| 914 | "fast-fifo": "^1.3.0", | |
| 915 | "flat-tree": "^1.9.0", | |
| 916 | "hypercore-crypto": "^3.2.1", | |
| 917 | "hypercore-errors": "^1.2.0", | |
| 918 | "hypercore-id-encoding": "^1.2.0", | |
| 919 | "is-options": "^1.0.1", | |
| 920 | "protomux": "^3.5.0", | |
| 921 | "quickbit-universal": "^2.2.0", | |
| 922 | "random-access-file": "^4.0.0", | |
| 923 | "random-array-iterator": "^1.0.0", | |
| 924 | "safety-catch": "^1.0.1", | |
| 925 | "sodium-universal": "^4.0.0", | |
| 926 | "streamx": "^2.12.4", | |
| 927 | "unslab": "^1.3.0", | |
| 928 | "xache": "^1.1.0", | |
| 929 | "z32": "^1.0.0" | |
| 930 | } | |
| 931 | }, | |
| 932 | "node_modules/hypercore-crypto": { | |
| 933 | "version": "3.6.1", | |
| 934 | "resolved": "https://registry.npmjs.org/hypercore-crypto/-/hypercore-crypto-3.6.1.tgz", | |
| 935 | "integrity": "sha512-ltIz2uDwy9pO/ZGTvqcjzyBkvt6O4cVm4r/nNxh0GFs/RbQtqP/i4wCvLEdmU7ptgtnw7fI67WYD1aHPuv4OVA==", | |
| 936 | "license": "MIT", | |
| 937 | "dependencies": { | |
| 938 | "b4a": "^1.6.6", | |
| 939 | "compact-encoding": "^2.15.0", | |
| 940 | "sodium-universal": "^5.0.0" | |
| 941 | } | |
| 942 | }, | |
| 943 | "node_modules/hypercore-crypto/node_modules/sodium-native": { | |
| 944 | "version": "5.0.10", | |
| 945 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 946 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 947 | "license": "MIT", | |
| 948 | "dependencies": { | |
| 949 | "require-addon": "^1.1.0", | |
| 950 | "which-runtime": "^1.2.1" | |
| 951 | }, | |
| 952 | "engines": { | |
| 953 | "bare": ">=1.16.0" | |
| 954 | } | |
| 955 | }, | |
| 956 | "node_modules/hypercore-crypto/node_modules/sodium-universal": { | |
| 957 | "version": "5.0.1", | |
| 958 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 959 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 960 | "license": "MIT", | |
| 961 | "dependencies": { | |
| 962 | "sodium-native": "^5.0.1" | |
| 963 | }, | |
| 964 | "peerDependencies": { | |
| 965 | "sodium-javascript": "~0.8.0" | |
| 966 | }, | |
| 967 | "peerDependenciesMeta": { | |
| 968 | "sodium-javascript": { | |
| 969 | "optional": true | |
| 970 | } | |
| 971 | } | |
| 972 | }, | |
| 973 | "node_modules/hypercore-errors": { | |
| 974 | "version": "1.5.0", | |
| 975 | "resolved": "https://registry.npmjs.org/hypercore-errors/-/hypercore-errors-1.5.0.tgz", | |
| 976 | "integrity": "sha512-5KQ/SuDxsvet+7qWA35Ay6zdD9WyAHQoyWHGcPUTbmJBd300gvNIJoi3oma7kp4TTCSzii6qYumNZe/s0j/saQ==", | |
| 977 | "license": "Apache-2.0", | |
| 978 | "dependencies": { | |
| 979 | "hypercore-id-encoding": "^1.3.0" | |
| 980 | } | |
| 981 | }, | |
| 982 | "node_modules/hypercore-id-encoding": { | |
| 983 | "version": "1.3.0", | |
| 984 | "resolved": "https://registry.npmjs.org/hypercore-id-encoding/-/hypercore-id-encoding-1.3.0.tgz", | |
| 985 | "integrity": "sha512-W6sHdGo5h7LXEsoWfKf/KfuROZmZRQDlGqJF2EPHW+noCK66Vvr0+zE6cL0vqQi18s0kQPeN7Sq3QyR0Ytc2VQ==", | |
| 986 | "license": "Apache-2.0", | |
| 987 | "dependencies": { | |
| 988 | "b4a": "^1.5.3", | |
| 989 | "z32": "^1.0.0" | |
| 990 | } | |
| 991 | }, | |
| 992 | "node_modules/is-options": { | |
| 993 | "version": "1.0.2", | |
| 994 | "resolved": "https://registry.npmjs.org/is-options/-/is-options-1.0.2.tgz", | |
| 995 | "integrity": "sha512-u+Ai74c8Q74aS8BuHwPdI1jptGOT1FQXgCq8/zv0xRuE+wRgSMEJLj8lVO8Zp9BeGb29BXY6AsNPinfqjkr7Fg==", | |
| 996 | "license": "MIT", | |
| 997 | "dependencies": { | |
| 998 | "b4a": "^1.1.1" | |
| 999 | } | |
| 1000 | }, | |
| 1001 | "node_modules/mutexify": { | |
| 1002 | "version": "1.4.0", | |
| 1003 | "resolved": "https://registry.npmjs.org/mutexify/-/mutexify-1.4.0.tgz", | |
| 1004 | "integrity": "sha512-pbYSsOrSB/AKN5h/WzzLRMFgZhClWccf2XIB4RSMC8JbquiB0e0/SH5AIfdQMdyHmYtv4seU7yV/TvAwPLJ1Yg==", | |
| 1005 | "license": "MIT", | |
| 1006 | "dependencies": { | |
| 1007 | "queue-tick": "^1.0.0" | |
| 1008 | } | |
| 1009 | }, | |
| 1010 | "node_modules/nanoassert": { | |
| 1011 | "version": "2.0.0", | |
| 1012 | "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz", | |
| 1013 | "integrity": "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==", | |
| 1014 | "license": "ISC" | |
| 1015 | }, | |
| 1016 | "node_modules/noise-curve-ed": { | |
| 1017 | "version": "2.1.0", | |
| 1018 | "resolved": "https://registry.npmjs.org/noise-curve-ed/-/noise-curve-ed-2.1.0.tgz", | |
| 1019 | "integrity": "sha512-zAzJx+VwZM3w6EA1hTmDhJfvAnCeBQn/1FAeZ0LtGxCcCtlAK/uJXQVF/eDVUOaAZ286lHlx77WJ+qj9SmsRRg==", | |
| 1020 | "license": "ISC", | |
| 1021 | "dependencies": { | |
| 1022 | "b4a": "^1.1.0", | |
| 1023 | "nanoassert": "^2.0.0", | |
| 1024 | "sodium-universal": "^5.0.0" | |
| 1025 | } | |
| 1026 | }, | |
| 1027 | "node_modules/noise-curve-ed/node_modules/sodium-native": { | |
| 1028 | "version": "5.0.10", | |
| 1029 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 1030 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 1031 | "license": "MIT", | |
| 1032 | "dependencies": { | |
| 1033 | "require-addon": "^1.1.0", | |
| 1034 | "which-runtime": "^1.2.1" | |
| 1035 | }, | |
| 1036 | "engines": { | |
| 1037 | "bare": ">=1.16.0" | |
| 1038 | } | |
| 1039 | }, | |
| 1040 | "node_modules/noise-curve-ed/node_modules/sodium-universal": { | |
| 1041 | "version": "5.0.1", | |
| 1042 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 1043 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 1044 | "license": "MIT", | |
| 1045 | "dependencies": { | |
| 1046 | "sodium-native": "^5.0.1" | |
| 1047 | }, | |
| 1048 | "peerDependencies": { | |
| 1049 | "sodium-javascript": "~0.8.0" | |
| 1050 | }, | |
| 1051 | "peerDependenciesMeta": { | |
| 1052 | "sodium-javascript": { | |
| 1053 | "optional": true | |
| 1054 | } | |
| 1055 | } | |
| 1056 | }, | |
| 1057 | "node_modules/noise-handshake": { | |
| 1058 | "version": "4.2.0", | |
| 1059 | "resolved": "https://registry.npmjs.org/noise-handshake/-/noise-handshake-4.2.0.tgz", | |
| 1060 | "integrity": "sha512-9O/VTNX/E2/AToyMTTDU0J/4WhaXMTdqc2DHs9vf+snoZ0cenSBq0dNYTVV1snYYEkmo6QeRrYMxtqtoYnY+LA==", | |
| 1061 | "license": "Apache-2.0", | |
| 1062 | "dependencies": { | |
| 1063 | "b4a": "^1.1.0", | |
| 1064 | "nanoassert": "^2.0.0", | |
| 1065 | "sodium-universal": "^5.0.0" | |
| 1066 | } | |
| 1067 | }, | |
| 1068 | "node_modules/noise-handshake/node_modules/sodium-native": { | |
| 1069 | "version": "5.0.10", | |
| 1070 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 1071 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 1072 | "license": "MIT", | |
| 1073 | "dependencies": { | |
| 1074 | "require-addon": "^1.1.0", | |
| 1075 | "which-runtime": "^1.2.1" | |
| 1076 | }, | |
| 1077 | "engines": { | |
| 1078 | "bare": ">=1.16.0" | |
| 1079 | } | |
| 1080 | }, | |
| 1081 | "node_modules/noise-handshake/node_modules/sodium-universal": { | |
| 1082 | "version": "5.0.1", | |
| 1083 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 1084 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 1085 | "license": "MIT", | |
| 1086 | "dependencies": { | |
| 1087 | "sodium-native": "^5.0.1" | |
| 1088 | }, | |
| 1089 | "peerDependencies": { | |
| 1090 | "sodium-javascript": "~0.8.0" | |
| 1091 | }, | |
| 1092 | "peerDependenciesMeta": { | |
| 1093 | "sodium-javascript": { | |
| 1094 | "optional": true | |
| 1095 | } | |
| 1096 | } | |
| 1097 | }, | |
| 1098 | "node_modules/paparam": { | |
| 1099 | "version": "1.10.0", | |
| 1100 | "resolved": "https://registry.npmjs.org/paparam/-/paparam-1.10.0.tgz", | |
| 1101 | "integrity": "sha512-p36QQrwU3X6fj5d2JN20B4lcZI/O3fxMVUHc1xD7MNe5bcOf3jIgE0CIK5Zv/s5YAUGlobTJ/kKXrh1FJVrs3w==", | |
| 1102 | "dev": true, | |
| 1103 | "license": "Apache-2.0" | |
| 1104 | }, | |
| 1105 | "node_modules/picomatch": { | |
| 1106 | "version": "4.0.3", | |
| 1107 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", | |
| 1108 | "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", | |
| 1109 | "dev": true, | |
| 1110 | "license": "MIT", | |
| 1111 | "engines": { | |
| 1112 | "node": ">=12" | |
| 1113 | }, | |
| 1114 | "funding": { | |
| 1115 | "url": "https://github.com/sponsors/jonschlinkert" | |
| 1116 | } | |
| 1117 | }, | |
| 1118 | "node_modules/protocol-buffers-encodings": { | |
| 1119 | "version": "1.2.0", | |
| 1120 | "resolved": "https://registry.npmjs.org/protocol-buffers-encodings/-/protocol-buffers-encodings-1.2.0.tgz", | |
| 1121 | "integrity": "sha512-daeNPuKh1NlLD1uDfbLpD+xyUTc07nEtfHwmBZmt/vH0B7VOM+JOCOpDcx9ZRpqHjAiIkGqyTDi+wfGSl17R9w==", | |
| 1122 | "license": "MIT", | |
| 1123 | "dependencies": { | |
| 1124 | "b4a": "^1.6.0", | |
| 1125 | "signed-varint": "^2.0.1", | |
| 1126 | "varint": "5.0.0" | |
| 1127 | } | |
| 1128 | }, | |
| 1129 | "node_modules/protomux": { | |
| 1130 | "version": "3.10.1", | |
| 1131 | "resolved": "https://registry.npmjs.org/protomux/-/protomux-3.10.1.tgz", | |
| 1132 | "integrity": "sha512-jgBqx8ZyaBWea/DFG4eOu1scOaeBwcnagiRC1XFVrjeGt7oAb0Pk5udPpBUpJ4DJBRjra50jD6YcZiQQTRqaaA==", | |
| 1133 | "license": "MIT", | |
| 1134 | "dependencies": { | |
| 1135 | "b4a": "^1.3.1", | |
| 1136 | "compact-encoding": "^2.5.1", | |
| 1137 | "queue-tick": "^1.0.0", | |
| 1138 | "safety-catch": "^1.0.1", | |
| 1139 | "unslab": "^1.3.0" | |
| 1140 | } | |
| 1141 | }, | |
| 1142 | "node_modules/queue-tick": { | |
| 1143 | "version": "1.0.1", | |
| 1144 | "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", | |
| 1145 | "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", | |
| 1146 | "license": "MIT" | |
| 1147 | }, | |
| 1148 | "node_modules/quickbit-native": { | |
| 1149 | "version": "2.4.8", | |
| 1150 | "resolved": "https://registry.npmjs.org/quickbit-native/-/quickbit-native-2.4.8.tgz", | |
| 1151 | "integrity": "sha512-FcCcqI+nIAWGknqhtrYT5TSD7t/N+Xd8ctM+2PrIIBuwOi5hx0SxAvuPtzLIEMfT/2h9+fhBakUe2uALOHX6yw==", | |
| 1152 | "license": "Apache-2.0", | |
| 1153 | "optional": true, | |
| 1154 | "dependencies": { | |
| 1155 | "require-addon": "^1.1.0" | |
| 1156 | } | |
| 1157 | }, | |
| 1158 | "node_modules/quickbit-universal": { | |
| 1159 | "version": "2.2.0", | |
| 1160 | "resolved": "https://registry.npmjs.org/quickbit-universal/-/quickbit-universal-2.2.0.tgz", | |
| 1161 | "integrity": "sha512-w02i1R8n7+6pEKTud8DfF8zbFY9o7RtPlUc3jWbtCkDKvhbx/AvV7oNnz4/TcmsPGpSJS+fq5Ud6RH6+YPvSGg==", | |
| 1162 | "license": "ISC", | |
| 1163 | "dependencies": { | |
| 1164 | "b4a": "^1.6.0", | |
| 1165 | "simdle-universal": "^1.1.0" | |
| 1166 | }, | |
| 1167 | "optionalDependencies": { | |
| 1168 | "quickbit-native": "^2.2.0" | |
| 1169 | } | |
| 1170 | }, | |
| 1171 | "node_modules/rache": { | |
| 1172 | "version": "1.0.0", | |
| 1173 | "resolved": "https://registry.npmjs.org/rache/-/rache-1.0.0.tgz", | |
| 1174 | "integrity": "sha512-e0k0g0w/8jOCB+7YqCIlOa+OJ38k0wrYS4x18pMSmqOvLKoyhmMhmQyCcvfY6VaP8D75cqkEnlakXs+RYYLqNg==", | |
| 1175 | "license": "Apache-2.0" | |
| 1176 | }, | |
| 1177 | "node_modules/random-access-file": { | |
| 1178 | "version": "4.1.2", | |
| 1179 | "resolved": "https://registry.npmjs.org/random-access-file/-/random-access-file-4.1.2.tgz", | |
| 1180 | "integrity": "sha512-GQM6R78DceZDcQod8KxlDFwXIiUvlvuy1EOzxTDsjuDjW5NlnlZi0MOk6iI4itAj/2vcvdqcEExYbVpC/dJcEw==", | |
| 1181 | "license": "MIT", | |
| 1182 | "dependencies": { | |
| 1183 | "bare-fs": "^4.0.1", | |
| 1184 | "bare-path": "^3.0.0", | |
| 1185 | "random-access-storage": "^3.0.0" | |
| 1186 | }, | |
| 1187 | "optionalDependencies": { | |
| 1188 | "fs-native-extensions": "^1.3.1" | |
| 1189 | } | |
| 1190 | }, | |
| 1191 | "node_modules/random-access-storage": { | |
| 1192 | "version": "3.0.2", | |
| 1193 | "resolved": "https://registry.npmjs.org/random-access-storage/-/random-access-storage-3.0.2.tgz", | |
| 1194 | "integrity": "sha512-Es9maUyWdJXWKckKy9s1+vT+DEgAt+PBb9lxPaake/0EDUsHehloKGv9v1zimS2V3gpFAcQXubvc1Rgci2sDPQ==", | |
| 1195 | "license": "MIT", | |
| 1196 | "dependencies": { | |
| 1197 | "bare-events": "^2.2.0", | |
| 1198 | "queue-tick": "^1.0.0" | |
| 1199 | } | |
| 1200 | }, | |
| 1201 | "node_modules/random-array-iterator": { | |
| 1202 | "version": "1.0.0", | |
| 1203 | "resolved": "https://registry.npmjs.org/random-array-iterator/-/random-array-iterator-1.0.0.tgz", | |
| 1204 | "integrity": "sha512-u7xCM93XqKEvPTP6xZp2ehttcAemKnh73oKNf1FvzuVCfpt6dILDt1Kxl1LeBjm2iNIeR49VGFhy4Iz3yOun+Q==", | |
| 1205 | "license": "MIT" | |
| 1206 | }, | |
| 1207 | "node_modules/read-write-mutexify": { | |
| 1208 | "version": "2.1.0", | |
| 1209 | "resolved": "https://registry.npmjs.org/read-write-mutexify/-/read-write-mutexify-2.1.0.tgz", | |
| 1210 | "integrity": "sha512-fDw/p5/acI1ytVY1UbxEDma/ej1yJH/n9NcjS9YNzcE6sPBPWdlru3ydRa/UBowUg4zqOvNMD5SOGYJrlQ6MzQ==", | |
| 1211 | "license": "MIT" | |
| 1212 | }, | |
| 1213 | "node_modules/ready-resource": { | |
| 1214 | "version": "1.2.0", | |
| 1215 | "resolved": "https://registry.npmjs.org/ready-resource/-/ready-resource-1.2.0.tgz", | |
| 1216 | "integrity": "sha512-nfcco/8iAFV0M+2PYnmIc+/xY0iRb35d42HFHQ7AfjulbGEAFa+XWpByfwSyeVeiBoMLLFVMv1HixxNCqzSQ1g==", | |
| 1217 | "license": "MIT", | |
| 1218 | "dependencies": { | |
| 1219 | "bare-events": "^2.2.0" | |
| 1220 | } | |
| 1221 | }, | |
| 1222 | "node_modules/require-addon": { | |
| 1223 | "version": "1.2.0", | |
| 1224 | "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz", | |
| 1225 | "integrity": "sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA==", | |
| 1226 | "license": "Apache-2.0", | |
| 1227 | "dependencies": { | |
| 1228 | "bare-addon-resolve": "^1.3.0" | |
| 1229 | }, | |
| 1230 | "engines": { | |
| 1231 | "bare": ">=1.10.0" | |
| 1232 | } | |
| 1233 | }, | |
| 1234 | "node_modules/resolve-reject-promise": { | |
| 1235 | "version": "1.1.0", | |
| 1236 | "resolved": "https://registry.npmjs.org/resolve-reject-promise/-/resolve-reject-promise-1.1.0.tgz", | |
| 1237 | "integrity": "sha512-LWsTOA91AqzBTjSGgX79Tc130pwcBK6xjpJEO+qRT5IKZ6bGnHKcc8QL3upUBcWuU8OTIDzKK2VNSwmmlqvAVg==", | |
| 1238 | "license": "MIT" | |
| 1239 | }, | |
| 1240 | "node_modules/safety-catch": { | |
| 1241 | "version": "1.0.2", | |
| 1242 | "resolved": "https://registry.npmjs.org/safety-catch/-/safety-catch-1.0.2.tgz", | |
| 1243 | "integrity": "sha512-C1UYVZ4dtbBxEtvOcpjBaaD27nP8MlvyAQEp2fOTOEe6pfUpk1cDUxij6BR1jZup6rSyUTaBBplK7LanskrULA==", | |
| 1244 | "license": "MIT" | |
| 1245 | }, | |
| 1246 | "node_modules/same-object": { | |
| 1247 | "version": "1.0.2", | |
| 1248 | "resolved": "https://registry.npmjs.org/same-object/-/same-object-1.0.2.tgz", | |
| 1249 | "integrity": "sha512-csHWhvUsLbIOHDM/nP+KHWM+BLPsIzWkFa8HbzaI0G7BqKXgx+7FJpKTGgLXyz5amfdY2OVBcmXTqYOMEk04og==", | |
| 1250 | "dev": true, | |
| 1251 | "license": "MIT" | |
| 1252 | }, | |
| 1253 | "node_modules/signed-varint": { | |
| 1254 | "version": "2.0.1", | |
| 1255 | "resolved": "https://registry.npmjs.org/signed-varint/-/signed-varint-2.0.1.tgz", | |
| 1256 | "integrity": "sha512-abgDPg1106vuZZOvw7cFwdCABddfJRz5akcCcchzTbhyhYnsG31y4AlZEgp315T7W3nQq5P4xeOm186ZiPVFzw==", | |
| 1257 | "license": "MIT", | |
| 1258 | "dependencies": { | |
| 1259 | "varint": "~5.0.0" | |
| 1260 | } | |
| 1261 | }, | |
| 1262 | "node_modules/simdle-native": { | |
| 1263 | "version": "1.3.9", | |
| 1264 | "resolved": "https://registry.npmjs.org/simdle-native/-/simdle-native-1.3.9.tgz", | |
| 1265 | "integrity": "sha512-Isc8sP4OiiIU0mpslD4GHEnR0VQWvR/54WN7YtwEDkNdTJVWtpmvsSvsgRlw5BNGxdYXlVRegdnrSu10H/PhvA==", | |
| 1266 | "license": "Apache-2.0", | |
| 1267 | "optional": true, | |
| 1268 | "dependencies": { | |
| 1269 | "b4a": "^1.6.0", | |
| 1270 | "require-addon": "^1.1.0" | |
| 1271 | } | |
| 1272 | }, | |
| 1273 | "node_modules/simdle-universal": { | |
| 1274 | "version": "1.1.2", | |
| 1275 | "resolved": "https://registry.npmjs.org/simdle-universal/-/simdle-universal-1.1.2.tgz", | |
| 1276 | "integrity": "sha512-3n3w1bs+uwgHKQjt6arez83EywNlhZzYvNOhvAASTl/8KqNIcqr6aHyGt3JRlfuUC7iB0tomJRPlJ2cRGIpBzA==", | |
| 1277 | "license": "ISC", | |
| 1278 | "dependencies": { | |
| 1279 | "b4a": "^1.6.0" | |
| 1280 | }, | |
| 1281 | "optionalDependencies": { | |
| 1282 | "simdle-native": "^1.1.1" | |
| 1283 | } | |
| 1284 | }, | |
| 1285 | "node_modules/sodium-native": { | |
| 1286 | "version": "4.3.3", | |
| 1287 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.3.3.tgz", | |
| 1288 | "integrity": "sha512-OnxSlN3uyY8D0EsLHpmm2HOFmKddQVvEMmsakCrXUzSd8kjjbzL413t4ZNF3n0UxSwNgwTyUvkmZHTfuCeiYSw==", | |
| 1289 | "license": "MIT", | |
| 1290 | "dependencies": { | |
| 1291 | "require-addon": "^1.1.0" | |
| 1292 | } | |
| 1293 | }, | |
| 1294 | "node_modules/sodium-secretstream": { | |
| 1295 | "version": "1.2.0", | |
| 1296 | "resolved": "https://registry.npmjs.org/sodium-secretstream/-/sodium-secretstream-1.2.0.tgz", | |
| 1297 | "integrity": "sha512-q/DbraNFXm1KfCiiZvapmz5UC3OlpirYFIvBK2MhGaOFSb3gRyk8OXTi17UI9SGfshQNCpsVvlopogbzZNyW6Q==", | |
| 1298 | "license": "MIT", | |
| 1299 | "dependencies": { | |
| 1300 | "b4a": "^1.1.1", | |
| 1301 | "sodium-universal": "^5.0.0" | |
| 1302 | } | |
| 1303 | }, | |
| 1304 | "node_modules/sodium-secretstream/node_modules/sodium-native": { | |
| 1305 | "version": "5.0.10", | |
| 1306 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 1307 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 1308 | "license": "MIT", | |
| 1309 | "dependencies": { | |
| 1310 | "require-addon": "^1.1.0", | |
| 1311 | "which-runtime": "^1.2.1" | |
| 1312 | }, | |
| 1313 | "engines": { | |
| 1314 | "bare": ">=1.16.0" | |
| 1315 | } | |
| 1316 | }, | |
| 1317 | "node_modules/sodium-secretstream/node_modules/sodium-universal": { | |
| 1318 | "version": "5.0.1", | |
| 1319 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 1320 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 1321 | "license": "MIT", | |
| 1322 | "dependencies": { | |
| 1323 | "sodium-native": "^5.0.1" | |
| 1324 | }, | |
| 1325 | "peerDependencies": { | |
| 1326 | "sodium-javascript": "~0.8.0" | |
| 1327 | }, | |
| 1328 | "peerDependenciesMeta": { | |
| 1329 | "sodium-javascript": { | |
| 1330 | "optional": true | |
| 1331 | } | |
| 1332 | } | |
| 1333 | }, | |
| 1334 | "node_modules/sodium-universal": { | |
| 1335 | "version": "4.0.1", | |
| 1336 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-4.0.1.tgz", | |
| 1337 | "integrity": "sha512-sNp13PrxYLaUFHTGoDKkSDFvoEu51bfzE12RwGlqU1fcrkpAOK0NvizaJzOWV0Omtk9me2+Pnbjcf/l0efxuGQ==", | |
| 1338 | "license": "MIT", | |
| 1339 | "dependencies": { | |
| 1340 | "sodium-native": "^4.0.0" | |
| 1341 | }, | |
| 1342 | "peerDependencies": { | |
| 1343 | "sodium-javascript": "~0.8.0" | |
| 1344 | }, | |
| 1345 | "peerDependenciesMeta": { | |
| 1346 | "sodium-javascript": { | |
| 1347 | "optional": true | |
| 1348 | } | |
| 1349 | } | |
| 1350 | }, | |
| 1351 | "node_modules/stackframe": { | |
| 1352 | "version": "1.3.4", | |
| 1353 | "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", | |
| 1354 | "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", | |
| 1355 | "dev": true, | |
| 1356 | "license": "MIT" | |
| 1357 | }, | |
| 1358 | "node_modules/streamx": { | |
| 1359 | "version": "2.23.0", | |
| 1360 | "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", | |
| 1361 | "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", | |
| 1362 | "license": "MIT", | |
| 1363 | "dependencies": { | |
| 1364 | "events-universal": "^1.0.0", | |
| 1365 | "fast-fifo": "^1.3.2", | |
| 1366 | "text-decoder": "^1.1.0" | |
| 1367 | } | |
| 1368 | }, | |
| 1369 | "node_modules/test-tmp": { | |
| 1370 | "version": "1.4.0", | |
| 1371 | "resolved": "https://registry.npmjs.org/test-tmp/-/test-tmp-1.4.0.tgz", | |
| 1372 | "integrity": "sha512-GVggxGg+jXqP2Wbju50JVLo+9E+nIOPPyWqgr63EbOnNItIKu1cEbJpTWAJeflnyGqXOtcMI7ijHRp88GUkfDA==", | |
| 1373 | "dev": true, | |
| 1374 | "license": "MIT", | |
| 1375 | "dependencies": { | |
| 1376 | "bare-fs": "^4.0.1", | |
| 1377 | "bare-os": "^3.3.0", | |
| 1378 | "bare-path": "^3.0.0" | |
| 1379 | } | |
| 1380 | }, | |
| 1381 | "node_modules/text-decoder": { | |
| 1382 | "version": "1.2.6", | |
| 1383 | "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.6.tgz", | |
| 1384 | "integrity": "sha512-27FeW5GQFDfw0FpwMQhMagB7BztOOlmjcSRi97t2oplhKVTZtp0DZbSegSaXS5IIC6mxMvBG4AR1Sgc6BX3CQg==", | |
| 1385 | "license": "Apache-2.0", | |
| 1386 | "dependencies": { | |
| 1387 | "b4a": "^1.6.4" | |
| 1388 | } | |
| 1389 | }, | |
| 1390 | "node_modules/timeout-refresh": { | |
| 1391 | "version": "2.0.1", | |
| 1392 | "resolved": "https://registry.npmjs.org/timeout-refresh/-/timeout-refresh-2.0.1.tgz", | |
| 1393 | "integrity": "sha512-SVqEcMZBsZF9mA78rjzCrYrUs37LMJk3ShZ851ygZYW1cMeIjs9mL57KO6Iv5mmjSQnOe/29/VAfGXo+oRCiVw==", | |
| 1394 | "license": "MIT" | |
| 1395 | }, | |
| 1396 | "node_modules/tmatch": { | |
| 1397 | "version": "5.0.0", | |
| 1398 | "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-5.0.0.tgz", | |
| 1399 | "integrity": "sha512-Ib9OtBkpHn07tXP04SlN1SYRxFgTk6wSM2EBmjjxug4u5RXPRVLkdFJSS1PmrQidaSB8Lru9nRtViQBsbxzE5Q==", | |
| 1400 | "dev": true, | |
| 1401 | "license": "ISC", | |
| 1402 | "engines": { | |
| 1403 | "node": ">=8" | |
| 1404 | } | |
| 1405 | }, | |
| 1406 | "node_modules/unslab": { | |
| 1407 | "version": "1.3.0", | |
| 1408 | "resolved": "https://registry.npmjs.org/unslab/-/unslab-1.3.0.tgz", | |
| 1409 | "integrity": "sha512-YATkfKAFj47kTzmiQrWXMyRvaVrHsW6MEALa4bm+FhiA2YG4oira+Z3DXN6LrYOYn2Y8eO94Lwl9DOHjs1FpoQ==", | |
| 1410 | "license": "Apache-2.0", | |
| 1411 | "dependencies": { | |
| 1412 | "b4a": "^1.6.6" | |
| 1413 | } | |
| 1414 | }, | |
| 1415 | "node_modules/v8-to-istanbul": { | |
| 1416 | "version": "9.3.0", | |
| 1417 | "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", | |
| 1418 | "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", | |
| 1419 | "dev": true, | |
| 1420 | "license": "ISC", | |
| 1421 | "dependencies": { | |
| 1422 | "@jridgewell/trace-mapping": "^0.3.12", | |
| 1423 | "@types/istanbul-lib-coverage": "^2.0.1", | |
| 1424 | "convert-source-map": "^2.0.0" | |
| 1425 | }, | |
| 1426 | "engines": { | |
| 1427 | "node": ">=10.12.0" | |
| 1428 | } | |
| 1429 | }, | |
| 1430 | "node_modules/varint": { | |
| 1431 | "version": "5.0.0", | |
| 1432 | "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.0.tgz", | |
| 1433 | "integrity": "sha512-gC13b/bWrqQoKY2EmROCZ+AR0jitc6DnDGaQ6Ls9QpKmuSgJB1eQ7H3KETtQm7qSdMWMKCmsshyCmUwMLh3OAA==", | |
| 1434 | "license": "MIT" | |
| 1435 | }, | |
| 1436 | "node_modules/which-runtime": { | |
| 1437 | "version": "1.3.2", | |
| 1438 | "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.3.2.tgz", | |
| 1439 | "integrity": "sha512-5kwCfWml7+b2NO7KrLMhYihjRx0teKkd3yGp1Xk5Vaf2JGdSh+rgVhEALAD9c/59dP+YwJHXoEO7e8QPy7gOkw==", | |
| 1440 | "license": "Apache-2.0" | |
| 1441 | }, | |
| 1442 | "node_modules/xache": { | |
| 1443 | "version": "1.2.1", | |
| 1444 | "resolved": "https://registry.npmjs.org/xache/-/xache-1.2.1.tgz", | |
| 1445 | "integrity": "sha512-igRS6jPreJ54ABdzhh4mCDXcz+XMaWO2q1ABRV2yWYuk29jlp8VT7UBdCqNkX7rpYBbXsebVVKkwIuYZjyZNqA==", | |
| 1446 | "license": "MIT" | |
| 1447 | }, | |
| 1448 | "node_modules/z32": { | |
| 1449 | "version": "1.1.0", | |
| 1450 | "resolved": "https://registry.npmjs.org/z32/-/z32-1.1.0.tgz", | |
| 1451 | "integrity": "sha512-1WUHy+VS6d0HPNspDxvLssBbeQjXMjSnpv0vH82vRAUfg847NmX3OXozp/hRP5jPhxBbrVzrgvAt+UsGNzRFQQ==", | |
| 1452 | "license": "MIT", | |
| 1453 | "dependencies": { | |
| 1454 | "b4a": "^1.5.3" | |
| 1455 | } | |
| 1456 | } | |
| 1457 | } | |
| 1458 | } |
| … | … | @@ -0,0 +1,24 @@ |
| 1 | { | |
| 2 | "name": "hyper-social", | |
| 3 | "version": "1.0.0", | |
| 4 | "description": "P2P social hub — composes bridges, panels, and daemons into a unified system", | |
| 5 | "type": "module", | |
| 6 | "main": "index.js", | |
| 7 | "pear": { | |
| 8 | "name": "hyper-social", | |
| 9 | "type": "terminal" | |
| 10 | }, | |
| 11 | "scripts": { | |
| 12 | "test": "brittle test/*.test.js" | |
| 13 | }, | |
| 14 | "dependencies": { | |
| 15 | "bridge-envelope": "file:../bridge-envelope", | |
| 16 | "hyperbee": "^2.0.0", | |
| 17 | "corestore": "^6.0.0", | |
| 18 | "b4a": "^1.0.0" | |
| 19 | }, | |
| 20 | "devDependencies": { | |
| 21 | "brittle": "^3.0.0" | |
| 22 | }, | |
| 23 | "license": "MIT" | |
| 24 | } |
| … | … | @@ -0,0 +1,141 @@ |
| 1 | import test from 'brittle' | |
| 2 | import Corestore from 'corestore' | |
| 3 | import Hyperbee from 'hyperbee' | |
| 4 | import { Config } from '../lib/config.js' | |
| 5 | import { tmpdir } from 'os' | |
| 6 | import { join } from 'path' | |
| 7 | import { randomBytes } from 'crypto' | |
| 8 | ||
| 9 | function tmpStorage () { | |
| 10 | return join(tmpdir(), 'config-test-' + randomBytes(8).toString('hex')) | |
| 11 | } | |
| 12 | ||
| 13 | async function createConfig () { | |
| 14 | const store = new Corestore(tmpStorage()) | |
| 15 | const bee = new Hyperbee(store.get({ name: 'test' }), { | |
| 16 | keyEncoding: 'utf-8', | |
| 17 | valueEncoding: 'json' | |
| 18 | }) | |
| 19 | await bee.ready() | |
| 20 | return { config: new Config(bee), store } | |
| 21 | } | |
| 22 | ||
| 23 | test('Config - set and get', async (t) => { | |
| 24 | const { config, store } = await createConfig() | |
| 25 | ||
| 26 | await config.set('bridge-irc', 'server', 'irc.libera.chat') | |
| 27 | const val = await config.get('bridge-irc', 'server') | |
| 28 | t.is(val, 'irc.libera.chat') | |
| 29 | ||
| 30 | await store.close() | |
| 31 | }) | |
| 32 | ||
| 33 | test('Config - get missing returns undefined', async (t) => { | |
| 34 | const { config, store } = await createConfig() | |
| 35 | ||
| 36 | const val = await config.get('nonexistent', 'key') | |
| 37 | t.is(val, undefined) | |
| 38 | ||
| 39 | await store.close() | |
| 40 | }) | |
| 41 | ||
| 42 | test('Config - overwrite value', async (t) => { | |
| 43 | const { config, store } = await createConfig() | |
| 44 | ||
| 45 | await config.set('irc', 'channel', '#old') | |
| 46 | await config.set('irc', 'channel', '#new') | |
| 47 | ||
| 48 | t.is(await config.get('irc', 'channel'), '#new') | |
| 49 | ||
| 50 | await store.close() | |
| 51 | }) | |
| 52 | ||
| 53 | test('Config - del', async (t) => { | |
| 54 | const { config, store } = await createConfig() | |
| 55 | ||
| 56 | await config.set('irc', 'key', 'value') | |
| 57 | await config.del('irc', 'key') | |
| 58 | ||
| 59 | t.is(await config.get('irc', 'key'), undefined) | |
| 60 | ||
| 61 | await store.close() | |
| 62 | }) | |
| 63 | ||
| 64 | test('Config - getSection', async (t) => { | |
| 65 | const { config, store } = await createConfig() | |
| 66 | ||
| 67 | await config.set('irc', 'server', 'libera') | |
| 68 | await config.set('irc', 'channel', '#test') | |
| 69 | await config.set('irc', 'nick', 'bridge-bot') | |
| 70 | await config.set('discord', 'token', 'abc123') | |
| 71 | ||
| 72 | const irc = await config.getSection('irc') | |
| 73 | t.is(irc.server, 'libera') | |
| 74 | t.is(irc.channel, '#test') | |
| 75 | t.is(irc.nick, 'bridge-bot') | |
| 76 | t.is(Object.keys(irc).length, 3) | |
| 77 | ||
| 78 | await store.close() | |
| 79 | }) | |
| 80 | ||
| 81 | test('Config - listSections', async (t) => { | |
| 82 | const { config, store } = await createConfig() | |
| 83 | ||
| 84 | await config.set('irc', 'key', 'val') | |
| 85 | await config.set('discord', 'key', 'val') | |
| 86 | await config.set('mastodon', 'key', 'val') | |
| 87 | ||
| 88 | const sections = await config.listSections() | |
| 89 | t.is(sections.length, 3) | |
| 90 | t.ok(sections.includes('irc')) | |
| 91 | t.ok(sections.includes('discord')) | |
| 92 | t.ok(sections.includes('mastodon')) | |
| 93 | ||
| 94 | await store.close() | |
| 95 | }) | |
| 96 | ||
| 97 | test('Config - setAll bulk', async (t) => { | |
| 98 | const { config, store } = await createConfig() | |
| 99 | ||
| 100 | await config.setAll('irc', { | |
| 101 | server: 'libera', | |
| 102 | channel: '#keet', | |
| 103 | nick: 'bot' | |
| 104 | }) | |
| 105 | ||
| 106 | t.is(await config.get('irc', 'server'), 'libera') | |
| 107 | t.is(await config.get('irc', 'channel'), '#keet') | |
| 108 | t.is(await config.get('irc', 'nick'), 'bot') | |
| 109 | ||
| 110 | await store.close() | |
| 111 | }) | |
| 112 | ||
| 113 | test('Config - exportAll', async (t) => { | |
| 114 | const { config, store } = await createConfig() | |
| 115 | ||
| 116 | await config.set('irc', 'server', 'libera') | |
| 117 | await config.set('irc', 'channel', '#test') | |
| 118 | await config.set('discord', 'token', 'abc') | |
| 119 | ||
| 120 | const exported = await config.exportAll() | |
| 121 | t.is(exported.irc.server, 'libera') | |
| 122 | t.is(exported.irc.channel, '#test') | |
| 123 | t.is(exported.discord.token, 'abc') | |
| 124 | ||
| 125 | await store.close() | |
| 126 | }) | |
| 127 | ||
| 128 | test('Config - complex values (objects)', async (t) => { | |
| 129 | const { config, store } = await createConfig() | |
| 130 | ||
| 131 | await config.set('irc', 'rooms', [ | |
| 132 | { channel: '#a', roomId: 'r1' }, | |
| 133 | { channel: '#b', roomId: 'r2' } | |
| 134 | ]) | |
| 135 | ||
| 136 | const rooms = await config.get('irc', 'rooms') | |
| 137 | t.is(rooms.length, 2) | |
| 138 | t.is(rooms[0].channel, '#a') | |
| 139 | ||
| 140 | await store.close() | |
| 141 | }) |
| … | … | @@ -0,0 +1,299 @@ |
| 1 | import test from 'brittle' | |
| 2 | import { Hub } from '../lib/hub.js' | |
| 3 | import { tmpdir } from 'os' | |
| 4 | import { join } from 'path' | |
| 5 | import { randomBytes } from 'crypto' | |
| 6 | ||
| 7 | function tmpStorage () { | |
| 8 | return join(tmpdir(), 'hub-test-' + randomBytes(8).toString('hex')) | |
| 9 | } | |
| 10 | ||
| 11 | function mockBridge (id, opts = {}) { | |
| 12 | const state = { started: false, messages: [] } | |
| 13 | return { | |
| 14 | type: 'bridge', | |
| 15 | name: opts.name || id, | |
| 16 | platform: opts.platform || id, | |
| 17 | state, | |
| 18 | start: async () => { state.started = true }, | |
| 19 | stop: async () => { state.started = false }, | |
| 20 | onBusMessage: (msg) => { state.messages.push(msg) }, | |
| 21 | setBus: (bus, sourceId) => { | |
| 22 | state.bus = bus | |
| 23 | state.sourceId = sourceId | |
| 24 | } | |
| 25 | } | |
| 26 | } | |
| 27 | ||
| 28 | test('Hub - ready', async (t) => { | |
| 29 | const hub = new Hub(tmpStorage()) | |
| 30 | await hub.ready() | |
| 31 | ||
| 32 | const s = hub.status() | |
| 33 | t.absent(s.started) | |
| 34 | t.is(s.services.total, 0) | |
| 35 | ||
| 36 | await hub.close() | |
| 37 | }) | |
| 38 | ||
| 39 | test('Hub - addService registers and wires', async (t) => { | |
| 40 | const hub = new Hub(tmpStorage()) | |
| 41 | await hub.ready() | |
| 42 | ||
| 43 | const irc = mockBridge('irc', { platform: 'irc' }) | |
| 44 | await hub.addService('irc', irc) | |
| 45 | ||
| 46 | t.is(hub.services.count, 1) | |
| 47 | t.ok(irc.state.bus) // bus was set | |
| 48 | t.is(irc.state.sourceId, 'irc') | |
| 49 | ||
| 50 | await hub.close() | |
| 51 | }) | |
| 52 | ||
| 53 | test('Hub - addService with room link', async (t) => { | |
| 54 | const hub = new Hub(tmpStorage()) | |
| 55 | await hub.ready() | |
| 56 | ||
| 57 | const irc = mockBridge('irc') | |
| 58 | await hub.addService('irc', irc, { | |
| 59 | roomId: 'room-abc', | |
| 60 | channel: '#test', | |
| 61 | roomName: 'The Garage' | |
| 62 | }) | |
| 63 | ||
| 64 | const services = await hub.rooms.getServices('room-abc') | |
| 65 | t.is(services.length, 1) | |
| 66 | t.is(services[0].serviceId, 'irc') | |
| 67 | ||
| 68 | const room = await hub.rooms.getRoom('room-abc') | |
| 69 | t.is(room.name, 'The Garage') | |
| 70 | ||
| 71 | await hub.close() | |
| 72 | }) | |
| 73 | ||
| 74 | test('Hub - addService with autoStart', async (t) => { | |
| 75 | const hub = new Hub(tmpStorage()) | |
| 76 | await hub.ready() | |
| 77 | ||
| 78 | const irc = mockBridge('irc') | |
| 79 | await hub.addService('irc', irc, { autoStart: true }) | |
| 80 | ||
| 81 | t.ok(irc.state.started) | |
| 82 | t.is(hub.services.get('irc').status, 'running') | |
| 83 | ||
| 84 | await hub.close() | |
| 85 | }) | |
| 86 | ||
| 87 | test('Hub - removeService cleans up', async (t) => { | |
| 88 | const hub = new Hub(tmpStorage()) | |
| 89 | await hub.ready() | |
| 90 | ||
| 91 | const irc = mockBridge('irc') | |
| 92 | await hub.addService('irc', irc, { | |
| 93 | roomId: 'room-1', | |
| 94 | autoStart: true | |
| 95 | }) | |
| 96 | ||
| 97 | await hub.removeService('irc') | |
| 98 | ||
| 99 | t.is(hub.services.count, 0) | |
| 100 | const services = await hub.rooms.getServices('room-1') | |
| 101 | t.is(services.length, 0) | |
| 102 | ||
| 103 | await hub.close() | |
| 104 | }) | |
| 105 | ||
| 106 | test('Hub - message bus routing between services', async (t) => { | |
| 107 | const hub = new Hub(tmpStorage()) | |
| 108 | await hub.ready() | |
| 109 | ||
| 110 | const irc = mockBridge('irc') | |
| 111 | const discord = mockBridge('discord') | |
| 112 | const mastodon = mockBridge('mastodon') | |
| 113 | ||
| 114 | await hub.addService('irc', irc) | |
| 115 | await hub.addService('discord', discord) | |
| 116 | await hub.addService('mastodon', mastodon) | |
| 117 | ||
| 118 | // IRC sends a message — should reach discord and mastodon, not irc | |
| 119 | hub.route({ source: 'irc', author: 'alice', text: 'hello', type: 'chat' }) | |
| 120 | ||
| 121 | t.is(irc.state.messages.length, 0) // source filtered out | |
| 122 | t.is(discord.state.messages.length, 1) | |
| 123 | t.is(mastodon.state.messages.length, 1) | |
| 124 | t.is(discord.state.messages[0].author, 'alice') | |
| 125 | ||
| 126 | await hub.close() | |
| 127 | }) | |
| 128 | ||
| 129 | test('Hub - bus dedup across services', async (t) => { | |
| 130 | const hub = new Hub(tmpStorage()) | |
| 131 | await hub.ready() | |
| 132 | ||
| 133 | const irc = mockBridge('irc') | |
| 134 | const discord = mockBridge('discord') | |
| 135 | ||
| 136 | await hub.addService('irc', irc) | |
| 137 | await hub.addService('discord', discord) | |
| 138 | ||
| 139 | hub.route({ source: 'irc', author: 'alice', text: 'hello' }) | |
| 140 | hub.route({ source: 'irc', author: 'alice', text: 'hello' }) // deduped | |
| 141 | ||
| 142 | t.is(discord.state.messages.length, 1) | |
| 143 | t.is(hub.bus.stats.deduped, 1) | |
| 144 | ||
| 145 | await hub.close() | |
| 146 | }) | |
| 147 | ||
| 148 | test('Hub - startAll and stopAll', async (t) => { | |
| 149 | const hub = new Hub(tmpStorage()) | |
| 150 | await hub.ready() | |
| 151 | ||
| 152 | const irc = mockBridge('irc') | |
| 153 | const discord = mockBridge('discord') | |
| 154 | ||
| 155 | await hub.addService('irc', irc) | |
| 156 | await hub.addService('discord', discord) | |
| 157 | ||
| 158 | const startResults = await hub.startAll() | |
| 159 | t.is(startResults.length, 2) | |
| 160 | t.ok(startResults.every(r => r.ok)) | |
| 161 | t.ok(irc.state.started) | |
| 162 | t.ok(discord.state.started) | |
| 163 | ||
| 164 | const stopResults = await hub.stopAll() | |
| 165 | t.is(stopResults.length, 2) | |
| 166 | t.absent(irc.state.started) | |
| 167 | t.absent(discord.state.started) | |
| 168 | ||
| 169 | await hub.close() | |
| 170 | }) | |
| 171 | ||
| 172 | test('Hub - graph view', async (t) => { | |
| 173 | const hub = new Hub(tmpStorage()) | |
| 174 | await hub.ready() | |
| 175 | ||
| 176 | const irc = mockBridge('irc', { platform: 'irc' }) | |
| 177 | const discord = mockBridge('discord', { platform: 'discord' }) | |
| 178 | ||
| 179 | await hub.addService('irc', irc, { | |
| 180 | roomId: 'room-1', | |
| 181 | autoStart: true | |
| 182 | }) | |
| 183 | await hub.addService('discord', discord, { | |
| 184 | roomId: 'room-1', | |
| 185 | autoStart: true | |
| 186 | }) | |
| 187 | ||
| 188 | const graph = await hub.graph() | |
| 189 | t.is(graph.length, 1) // one room | |
| 190 | t.is(graph[0].roomId, 'room-1') | |
| 191 | t.is(graph[0].services.length, 2) | |
| 192 | t.ok(graph[0].services.find(s => s.id === 'irc' && s.status === 'running')) | |
| 193 | t.ok(graph[0].services.find(s => s.id === 'discord' && s.status === 'running')) | |
| 194 | ||
| 195 | await hub.close() | |
| 196 | }) | |
| 197 | ||
| 198 | test('Hub - graph with multiple rooms', async (t) => { | |
| 199 | const hub = new Hub(tmpStorage()) | |
| 200 | await hub.ready() | |
| 201 | ||
| 202 | await hub.addService('irc-1', mockBridge('irc-1'), { roomId: 'room-a' }) | |
| 203 | await hub.addService('irc-2', mockBridge('irc-2'), { roomId: 'room-b' }) | |
| 204 | await hub.addService('discord', mockBridge('discord'), { roomId: 'room-a' }) | |
| 205 | ||
| 206 | const graph = await hub.graph() | |
| 207 | t.is(graph.length, 2) // two rooms | |
| 208 | ||
| 209 | const roomA = graph.find(g => g.roomId === 'room-a') | |
| 210 | t.is(roomA.services.length, 2) | |
| 211 | ||
| 212 | const roomB = graph.find(g => g.roomId === 'room-b') | |
| 213 | t.is(roomB.services.length, 1) | |
| 214 | ||
| 215 | await hub.close() | |
| 216 | }) | |
| 217 | ||
| 218 | test('Hub - status', async (t) => { | |
| 219 | const hub = new Hub(tmpStorage()) | |
| 220 | await hub.ready() | |
| 221 | ||
| 222 | await hub.addService('irc', mockBridge('irc'), { autoStart: true }) | |
| 223 | await hub.addService('discord', mockBridge('discord')) | |
| 224 | ||
| 225 | hub.route({ source: 'irc', author: 'a', text: 'msg' }) | |
| 226 | ||
| 227 | const s = hub.status() | |
| 228 | t.ok(s.started) // startAll was called implicitly via autoStart | |
| 229 | t.is(s.services.total, 2) | |
| 230 | t.is(s.bus.routed, 1) | |
| 231 | ||
| 232 | await hub.close() | |
| 233 | }) | |
| 234 | ||
| 235 | test('Hub - config persists', async (t) => { | |
| 236 | const storage = tmpStorage() | |
| 237 | ||
| 238 | const h1 = new Hub(storage) | |
| 239 | await h1.ready() | |
| 240 | await h1.config.set('irc', 'server', 'libera') | |
| 241 | await h1.close() | |
| 242 | ||
| 243 | const h2 = new Hub(storage) | |
| 244 | await h2.ready() | |
| 245 | const val = await h2.config.get('irc', 'server') | |
| 246 | t.is(val, 'libera') | |
| 247 | await h2.close() | |
| 248 | }) | |
| 249 | ||
| 250 | test('Hub - identities persist', async (t) => { | |
| 251 | const storage = tmpStorage() | |
| 252 | ||
| 253 | const h1 = new Hub(storage) | |
| 254 | await h1.ready() | |
| 255 | await h1.identities.addIdentity('irc', 'alice', { alias: 'alice-real' }) | |
| 256 | await h1.close() | |
| 257 | ||
| 258 | const h2 = new Hub(storage) | |
| 259 | await h2.ready() | |
| 260 | const id = await h2.identities.getIdentity('irc', 'alice') | |
| 261 | t.is(id.alias, 'alice-real') | |
| 262 | await h2.close() | |
| 263 | }) | |
| 264 | ||
| 265 | test('Hub - room map persists', async (t) => { | |
| 266 | const storage = tmpStorage() | |
| 267 | ||
| 268 | const h1 = new Hub(storage) | |
| 269 | await h1.ready() | |
| 270 | await h1.rooms.link('room-1', 'irc', { platform: 'irc' }) | |
| 271 | await h1.close() | |
| 272 | ||
| 273 | const h2 = new Hub(storage) | |
| 274 | await h2.ready() | |
| 275 | const linked = await h2.rooms.isLinked('room-1', 'irc') | |
| 276 | t.ok(linked) | |
| 277 | await h2.close() | |
| 278 | }) | |
| 279 | ||
| 280 | test('Hub - close stops all services', async (t) => { | |
| 281 | const hub = new Hub(tmpStorage()) | |
| 282 | await hub.ready() | |
| 283 | ||
| 284 | const irc = mockBridge('irc') | |
| 285 | await hub.addService('irc', irc, { autoStart: true }) | |
| 286 | t.ok(irc.state.started) | |
| 287 | ||
| 288 | await hub.close() | |
| 289 | t.absent(irc.state.started) | |
| 290 | }) | |
| 291 | ||
| 292 | test('Hub - removeService nonexistent returns false', async (t) => { | |
| 293 | const hub = new Hub(tmpStorage()) | |
| 294 | await hub.ready() | |
| 295 | ||
| 296 | t.absent(await hub.removeService('ghost')) | |
| 297 | ||
| 298 | await hub.close() | |
| 299 | }) |
| … | … | @@ -0,0 +1,198 @@ |
| 1 | import test from 'brittle' | |
| 2 | import Corestore from 'corestore' | |
| 3 | import Hyperbee from 'hyperbee' | |
| 4 | import { IdentityGraph } from '../lib/identity-graph.js' | |
| 5 | import { tmpdir } from 'os' | |
| 6 | import { join } from 'path' | |
| 7 | import { randomBytes } from 'crypto' | |
| 8 | ||
| 9 | function tmpStorage () { | |
| 10 | return join(tmpdir(), 'identity-test-' + randomBytes(8).toString('hex')) | |
| 11 | } | |
| 12 | ||
| 13 | async function createGraph () { | |
| 14 | const store = new Corestore(tmpStorage()) | |
| 15 | const bee = new Hyperbee(store.get({ name: 'test' }), { | |
| 16 | keyEncoding: 'utf-8', | |
| 17 | valueEncoding: 'json' | |
| 18 | }) | |
| 19 | await bee.ready() | |
| 20 | return { graph: new IdentityGraph(bee), store } | |
| 21 | } | |
| 22 | ||
| 23 | test('IdentityGraph - add and get identity', async (t) => { | |
| 24 | const { graph, store } = await createGraph() | |
| 25 | ||
| 26 | const id = await graph.addIdentity('irc', 'alice', { | |
| 27 | displayName: 'Alice', | |
| 28 | metadata: { server: 'libera.chat' } | |
| 29 | }) | |
| 30 | ||
| 31 | t.is(id.platform, 'irc') | |
| 32 | t.is(id.handle, 'alice') | |
| 33 | t.is(id.displayName, 'Alice') | |
| 34 | t.is(id.metadata.server, 'libera.chat') | |
| 35 | ||
| 36 | const fetched = await graph.getIdentity('irc', 'alice') | |
| 37 | t.is(fetched.displayName, 'Alice') | |
| 38 | ||
| 39 | await store.close() | |
| 40 | }) | |
| 41 | ||
| 42 | test('IdentityGraph - update identity preserves fields', async (t) => { | |
| 43 | const { graph, store } = await createGraph() | |
| 44 | ||
| 45 | await graph.addIdentity('irc', 'alice', { displayName: 'Alice' }) | |
| 46 | await graph.addIdentity('irc', 'alice', { metadata: { verified: true } }) | |
| 47 | ||
| 48 | const id = await graph.getIdentity('irc', 'alice') | |
| 49 | t.is(id.displayName, 'Alice') // preserved | |
| 50 | t.ok(id.metadata.verified) // merged | |
| 51 | ||
| 52 | await store.close() | |
| 53 | }) | |
| 54 | ||
| 55 | test('IdentityGraph - link identities with alias', async (t) => { | |
| 56 | const { graph, store } = await createGraph() | |
| 57 | ||
| 58 | await graph.addIdentity('irc', 'alice', { alias: 'alice-real' }) | |
| 59 | await graph.addIdentity('mastodon', 'alice@mastodon.social', { alias: 'alice-real' }) | |
| 60 | await graph.addIdentity('keet', 'alice-key-123', { alias: 'alice-real' }) | |
| 61 | ||
| 62 | const linked = await graph.getLinked('alice-real') | |
| 63 | t.is(linked.length, 3) | |
| 64 | t.ok(linked.find(i => i.platform === 'irc')) | |
| 65 | t.ok(linked.find(i => i.platform === 'mastodon')) | |
| 66 | t.ok(linked.find(i => i.platform === 'keet')) | |
| 67 | ||
| 68 | await store.close() | |
| 69 | }) | |
| 70 | ||
| 71 | test('IdentityGraph - link via link method', async (t) => { | |
| 72 | const { graph, store } = await createGraph() | |
| 73 | ||
| 74 | await graph.addIdentity('irc', 'bob') | |
| 75 | await graph.link('bob-real', 'irc', 'bob') | |
| 76 | await graph.link('bob-real', 'discord', 'bob#1234') | |
| 77 | ||
| 78 | const linked = await graph.getLinked('bob-real') | |
| 79 | t.is(linked.length, 2) | |
| 80 | ||
| 81 | await store.close() | |
| 82 | }) | |
| 83 | ||
| 84 | test('IdentityGraph - resolve cross-platform', async (t) => { | |
| 85 | const { graph, store } = await createGraph() | |
| 86 | ||
| 87 | await graph.addIdentity('irc', 'carol', { alias: 'carol-person' }) | |
| 88 | await graph.addIdentity('mastodon', 'carol@social.coop', { alias: 'carol-person' }) | |
| 89 | ||
| 90 | const resolved = await graph.resolve('irc', 'carol', 'mastodon') | |
| 91 | t.ok(resolved) | |
| 92 | t.is(resolved.handle, 'carol@social.coop') | |
| 93 | ||
| 94 | // Resolve to unknown platform returns null | |
| 95 | const none = await graph.resolve('irc', 'carol', 'discord') | |
| 96 | t.is(none, null) | |
| 97 | ||
| 98 | await store.close() | |
| 99 | }) | |
| 100 | ||
| 101 | test('IdentityGraph - resolve without alias returns null', async (t) => { | |
| 102 | const { graph, store } = await createGraph() | |
| 103 | ||
| 104 | await graph.addIdentity('irc', 'lonely') | |
| 105 | ||
| 106 | const result = await graph.resolve('irc', 'lonely', 'mastodon') | |
| 107 | t.is(result, null) | |
| 108 | ||
| 109 | await store.close() | |
| 110 | }) | |
| 111 | ||
| 112 | test('IdentityGraph - listPlatform', async (t) => { | |
| 113 | const { graph, store } = await createGraph() | |
| 114 | ||
| 115 | await graph.addIdentity('irc', 'alice') | |
| 116 | await graph.addIdentity('irc', 'bob') | |
| 117 | await graph.addIdentity('mastodon', 'carol@m.social') | |
| 118 | ||
| 119 | const ircUsers = await graph.listPlatform('irc') | |
| 120 | t.is(ircUsers.length, 2) | |
| 121 | ||
| 122 | const mastoUsers = await graph.listPlatform('mastodon') | |
| 123 | t.is(mastoUsers.length, 1) | |
| 124 | ||
| 125 | await store.close() | |
| 126 | }) | |
| 127 | ||
| 128 | test('IdentityGraph - listAliases', async (t) => { | |
| 129 | const { graph, store } = await createGraph() | |
| 130 | ||
| 131 | await graph.addIdentity('irc', 'alice', { alias: 'alice-real' }) | |
| 132 | await graph.addIdentity('irc', 'bob', { alias: 'bob-real' }) | |
| 133 | ||
| 134 | const aliases = await graph.listAliases() | |
| 135 | t.is(aliases.length, 2) | |
| 136 | t.ok(aliases.find(a => a.alias === 'alice-real')) | |
| 137 | t.ok(aliases.find(a => a.alias === 'bob-real')) | |
| 138 | ||
| 139 | await store.close() | |
| 140 | }) | |
| 141 | ||
| 142 | test('IdentityGraph - removeIdentity', async (t) => { | |
| 143 | const { graph, store } = await createGraph() | |
| 144 | ||
| 145 | await graph.addIdentity('irc', 'temp', { alias: 'temp-alias' }) | |
| 146 | await graph.addIdentity('discord', 'temp#0', { alias: 'temp-alias' }) | |
| 147 | ||
| 148 | const ok = await graph.removeIdentity('irc', 'temp') | |
| 149 | t.ok(ok) | |
| 150 | ||
| 151 | const gone = await graph.getIdentity('irc', 'temp') | |
| 152 | t.is(gone, null) | |
| 153 | ||
| 154 | // Alias should still have the discord entry | |
| 155 | const linked = await graph.getLinked('temp-alias') | |
| 156 | t.is(linked.length, 1) | |
| 157 | t.is(linked[0].platform, 'discord') | |
| 158 | ||
| 159 | await store.close() | |
| 160 | }) | |
| 161 | ||
| 162 | test('IdentityGraph - remove last identity cleans up alias', async (t) => { | |
| 163 | const { graph, store } = await createGraph() | |
| 164 | ||
| 165 | await graph.addIdentity('irc', 'solo', { alias: 'solo-alias' }) | |
| 166 | ||
| 167 | await graph.removeIdentity('irc', 'solo') | |
| 168 | ||
| 169 | const linked = await graph.getLinked('solo-alias') | |
| 170 | t.is(linked.length, 0) | |
| 171 | ||
| 172 | await store.close() | |
| 173 | }) | |
| 174 | ||
| 175 | test('IdentityGraph - remove nonexistent returns false', async (t) => { | |
| 176 | const { graph, store } = await createGraph() | |
| 177 | ||
| 178 | t.absent(await graph.removeIdentity('irc', 'ghost')) | |
| 179 | ||
| 180 | await store.close() | |
| 181 | }) | |
| 182 | ||
| 183 | test('IdentityGraph - stats', async (t) => { | |
| 184 | const { graph, store } = await createGraph() | |
| 185 | ||
| 186 | await graph.addIdentity('irc', 'alice', { alias: 'a' }) | |
| 187 | await graph.addIdentity('mastodon', 'alice@m', { alias: 'a' }) | |
| 188 | await graph.addIdentity('discord', 'bob#0', { alias: 'b' }) | |
| 189 | ||
| 190 | const s = await graph.stats() | |
| 191 | t.is(s.identities, 3) | |
| 192 | t.is(s.aliases, 2) | |
| 193 | t.ok(s.platforms.includes('irc')) | |
| 194 | t.ok(s.platforms.includes('mastodon')) | |
| 195 | t.ok(s.platforms.includes('discord')) | |
| 196 | ||
| 197 | await store.close() | |
| 198 | }) |
| … | … | @@ -0,0 +1,141 @@ |
| 1 | import test from 'brittle' | |
| 2 | import { MessageBus } from '../lib/message-bus.js' | |
| 3 | ||
| 4 | test('MessageBus - publish emits message', (t) => { | |
| 5 | const bus = new MessageBus() | |
| 6 | let received = null | |
| 7 | ||
| 8 | bus.on('message', (msg) => { received = msg }) | |
| 9 | ||
| 10 | bus.publish({ source: 'irc', author: 'alice', text: 'hello' }) | |
| 11 | t.ok(received) | |
| 12 | t.is(received.author, 'alice') | |
| 13 | t.is(received.source, 'irc') | |
| 14 | }) | |
| 15 | ||
| 16 | test('MessageBus - publish requires source', (t) => { | |
| 17 | const bus = new MessageBus() | |
| 18 | const ok = bus.publish({ author: 'alice', text: 'hello' }) | |
| 19 | t.is(ok, false) | |
| 20 | }) | |
| 21 | ||
| 22 | test('MessageBus - typed events', (t) => { | |
| 23 | const bus = new MessageBus() | |
| 24 | let typed = null | |
| 25 | ||
| 26 | bus.on('message:chat', (msg) => { typed = msg }) | |
| 27 | ||
| 28 | bus.publish({ source: 'irc', type: 'chat', text: 'hi' }) | |
| 29 | t.ok(typed) | |
| 30 | t.is(typed.type, 'chat') | |
| 31 | }) | |
| 32 | ||
| 33 | test('MessageBus - platform events', (t) => { | |
| 34 | const bus = new MessageBus() | |
| 35 | let platform = null | |
| 36 | ||
| 37 | bus.on('platform:mastodon', (msg) => { platform = msg }) | |
| 38 | ||
| 39 | bus.publish({ source: 'masto-bridge', platform: 'mastodon', text: 'toot' }) | |
| 40 | t.ok(platform) | |
| 41 | t.is(platform.platform, 'mastodon') | |
| 42 | }) | |
| 43 | ||
| 44 | test('MessageBus - dedup identical messages', (t) => { | |
| 45 | const bus = new MessageBus() | |
| 46 | let count = 0 | |
| 47 | ||
| 48 | bus.on('message', () => { count++ }) | |
| 49 | ||
| 50 | bus.publish({ source: 'irc', author: 'alice', text: 'hello' }) | |
| 51 | bus.publish({ source: 'irc', author: 'alice', text: 'hello' }) | |
| 52 | bus.publish({ source: 'irc', author: 'alice', text: 'hello' }) | |
| 53 | ||
| 54 | t.is(count, 1) | |
| 55 | t.is(bus.stats.deduped, 2) | |
| 56 | }) | |
| 57 | ||
| 58 | test('MessageBus - different messages not deduped', (t) => { | |
| 59 | const bus = new MessageBus() | |
| 60 | let count = 0 | |
| 61 | ||
| 62 | bus.on('message', () => { count++ }) | |
| 63 | ||
| 64 | bus.publish({ source: 'irc', author: 'alice', text: 'msg 1' }) | |
| 65 | bus.publish({ source: 'irc', author: 'alice', text: 'msg 2' }) | |
| 66 | bus.publish({ source: 'irc', author: 'bob', text: 'msg 1' }) | |
| 67 | ||
| 68 | t.is(count, 3) | |
| 69 | }) | |
| 70 | ||
| 71 | test('MessageBus - subscribe skips own source', (t) => { | |
| 72 | const bus = new MessageBus() | |
| 73 | let received = [] | |
| 74 | ||
| 75 | bus.subscribe('irc', (msg) => { received.push(msg) }) | |
| 76 | ||
| 77 | bus.publish({ source: 'irc', text: 'from irc' }) // should be skipped | |
| 78 | bus.publish({ source: 'discord', text: 'from discord' }) // should arrive | |
| 79 | bus.publish({ source: 'mastodon', text: 'from mastodon' }) // should arrive | |
| 80 | ||
| 81 | t.is(received.length, 2) | |
| 82 | t.is(received[0].source, 'discord') | |
| 83 | t.is(received[1].source, 'mastodon') | |
| 84 | }) | |
| 85 | ||
| 86 | test('MessageBus - unsubscribe', (t) => { | |
| 87 | const bus = new MessageBus() | |
| 88 | let count = 0 | |
| 89 | ||
| 90 | const ref = bus.subscribe('irc', () => { count++ }) | |
| 91 | ||
| 92 | bus.publish({ source: 'discord', text: 'before' }) | |
| 93 | t.is(count, 1) | |
| 94 | ||
| 95 | bus.unsubscribe(ref) | |
| 96 | ||
| 97 | bus.publish({ source: 'discord', text: 'after' }) | |
| 98 | t.is(count, 1) | |
| 99 | }) | |
| 100 | ||
| 101 | test('MessageBus - subscriber error captured', (t) => { | |
| 102 | const bus = new MessageBus() | |
| 103 | let errorEvent = null | |
| 104 | ||
| 105 | bus.on('error', (e) => { errorEvent = e }) | |
| 106 | bus.subscribe('discord', () => { throw new Error('boom') }) | |
| 107 | ||
| 108 | bus.publish({ source: 'irc', text: 'test' }) | |
| 109 | t.ok(errorEvent) | |
| 110 | t.is(errorEvent.source, 'discord') | |
| 111 | t.is(errorEvent.error, 'boom') | |
| 112 | t.is(bus.stats.errors, 1) | |
| 113 | }) | |
| 114 | ||
| 115 | test('MessageBus - stats', (t) => { | |
| 116 | const bus = new MessageBus() | |
| 117 | ||
| 118 | bus.publish({ source: 'a', text: '1' }) | |
| 119 | bus.publish({ source: 'a', text: '2' }) | |
| 120 | bus.publish({ source: 'a', text: '1' }) // deduped | |
| 121 | ||
| 122 | const s = bus.stats | |
| 123 | t.is(s.routed, 2) | |
| 124 | t.is(s.deduped, 1) | |
| 125 | t.is(s.seen, 2) | |
| 126 | }) | |
| 127 | ||
| 128 | test('MessageBus - reset', (t) => { | |
| 129 | const bus = new MessageBus() | |
| 130 | ||
| 131 | bus.publish({ source: 'a', text: '1' }) | |
| 132 | bus.publish({ source: 'a', text: '1' }) // deduped first time | |
| 133 | ||
| 134 | bus.reset() | |
| 135 | ||
| 136 | // After reset, same message should be routable again | |
| 137 | const ok = bus.publish({ source: 'a', text: '1' }) | |
| 138 | t.ok(ok) | |
| 139 | t.is(bus.stats.routed, 1) | |
| 140 | t.is(bus.stats.deduped, 0) | |
| 141 | }) |
| … | … | @@ -0,0 +1,176 @@ |
| 1 | import test from 'brittle' | |
| 2 | import Corestore from 'corestore' | |
| 3 | import Hyperbee from 'hyperbee' | |
| 4 | import { RoomMap } from '../lib/room-map.js' | |
| 5 | import { tmpdir } from 'os' | |
| 6 | import { join } from 'path' | |
| 7 | import { randomBytes } from 'crypto' | |
| 8 | ||
| 9 | function tmpStorage () { | |
| 10 | return join(tmpdir(), 'room-map-test-' + randomBytes(8).toString('hex')) | |
| 11 | } | |
| 12 | ||
| 13 | async function createRoomMap () { | |
| 14 | const store = new Corestore(tmpStorage()) | |
| 15 | const bee = new Hyperbee(store.get({ name: 'test' }), { | |
| 16 | keyEncoding: 'utf-8', | |
| 17 | valueEncoding: 'json' | |
| 18 | }) | |
| 19 | await bee.ready() | |
| 20 | return { rooms: new RoomMap(bee), store } | |
| 21 | } | |
| 22 | ||
| 23 | test('RoomMap - link service to room', async (t) => { | |
| 24 | const { rooms, store } = await createRoomMap() | |
| 25 | ||
| 26 | const entry = await rooms.link('room-abc', 'irc-bridge', { | |
| 27 | platform: 'irc', | |
| 28 | channel: '#test', | |
| 29 | roomName: 'The Garage' | |
| 30 | }) | |
| 31 | ||
| 32 | t.is(entry.roomId, 'room-abc') | |
| 33 | t.is(entry.serviceId, 'irc-bridge') | |
| 34 | t.is(entry.platform, 'irc') | |
| 35 | t.is(entry.direction, 'bidirectional') | |
| 36 | t.ok(entry.enabled) | |
| 37 | ||
| 38 | await store.close() | |
| 39 | }) | |
| 40 | ||
| 41 | test('RoomMap - get services for room', async (t) => { | |
| 42 | const { rooms, store } = await createRoomMap() | |
| 43 | ||
| 44 | await rooms.link('room-1', 'irc', { platform: 'irc' }) | |
| 45 | await rooms.link('room-1', 'discord', { platform: 'discord' }) | |
| 46 | await rooms.link('room-2', 'mastodon', { platform: 'mastodon' }) | |
| 47 | ||
| 48 | const services = await rooms.getServices('room-1') | |
| 49 | t.is(services.length, 2) | |
| 50 | t.ok(services.find(s => s.serviceId === 'irc')) | |
| 51 | t.ok(services.find(s => s.serviceId === 'discord')) | |
| 52 | ||
| 53 | await store.close() | |
| 54 | }) | |
| 55 | ||
| 56 | test('RoomMap - get rooms for service', async (t) => { | |
| 57 | const { rooms, store } = await createRoomMap() | |
| 58 | ||
| 59 | await rooms.link('room-1', 'irc', { platform: 'irc' }) | |
| 60 | await rooms.link('room-2', 'irc', { platform: 'irc' }) | |
| 61 | await rooms.link('room-3', 'discord', { platform: 'discord' }) | |
| 62 | ||
| 63 | const ircRooms = await rooms.getRooms('irc') | |
| 64 | t.is(ircRooms.length, 2) | |
| 65 | ||
| 66 | await store.close() | |
| 67 | }) | |
| 68 | ||
| 69 | test('RoomMap - unlink', async (t) => { | |
| 70 | const { rooms, store } = await createRoomMap() | |
| 71 | ||
| 72 | await rooms.link('room-1', 'irc', {}) | |
| 73 | t.ok(await rooms.isLinked('room-1', 'irc')) | |
| 74 | ||
| 75 | const ok = await rooms.unlink('room-1', 'irc') | |
| 76 | t.ok(ok) | |
| 77 | t.absent(await rooms.isLinked('room-1', 'irc')) | |
| 78 | ||
| 79 | // Unlink nonexistent returns false | |
| 80 | t.absent(await rooms.unlink('room-1', 'nope')) | |
| 81 | ||
| 82 | await store.close() | |
| 83 | }) | |
| 84 | ||
| 85 | test('RoomMap - isLinked', async (t) => { | |
| 86 | const { rooms, store } = await createRoomMap() | |
| 87 | ||
| 88 | await rooms.link('room-1', 'irc', {}) | |
| 89 | ||
| 90 | t.ok(await rooms.isLinked('room-1', 'irc')) | |
| 91 | t.absent(await rooms.isLinked('room-1', 'discord')) | |
| 92 | t.absent(await rooms.isLinked('room-2', 'irc')) | |
| 93 | ||
| 94 | await store.close() | |
| 95 | }) | |
| 96 | ||
| 97 | test('RoomMap - listAll', async (t) => { | |
| 98 | const { rooms, store } = await createRoomMap() | |
| 99 | ||
| 100 | await rooms.link('r1', 'a', {}) | |
| 101 | await rooms.link('r1', 'b', {}) | |
| 102 | await rooms.link('r2', 'a', {}) | |
| 103 | ||
| 104 | const all = await rooms.listAll() | |
| 105 | t.is(all.length, 3) | |
| 106 | ||
| 107 | await store.close() | |
| 108 | }) | |
| 109 | ||
| 110 | test('RoomMap - room metadata stored', async (t) => { | |
| 111 | const { rooms, store } = await createRoomMap() | |
| 112 | ||
| 113 | await rooms.link('room-abc', 'irc', { roomName: 'The Garage' }) | |
| 114 | ||
| 115 | const room = await rooms.getRoom('room-abc') | |
| 116 | t.ok(room) | |
| 117 | t.is(room.name, 'The Garage') | |
| 118 | t.is(room.id, 'room-abc') | |
| 119 | ||
| 120 | await store.close() | |
| 121 | }) | |
| 122 | ||
| 123 | test('RoomMap - listRooms', async (t) => { | |
| 124 | const { rooms, store } = await createRoomMap() | |
| 125 | ||
| 126 | await rooms.link('r1', 'a', { roomName: 'Room One' }) | |
| 127 | await rooms.link('r2', 'b', { roomName: 'Room Two' }) | |
| 128 | ||
| 129 | const list = await rooms.listRooms() | |
| 130 | t.is(list.length, 2) | |
| 131 | ||
| 132 | await store.close() | |
| 133 | }) | |
| 134 | ||
| 135 | test('RoomMap - setEnabled', async (t) => { | |
| 136 | const { rooms, store } = await createRoomMap() | |
| 137 | ||
| 138 | await rooms.link('r1', 'irc', {}) | |
| 139 | ||
| 140 | const disabled = await rooms.setEnabled('r1', 'irc', false) | |
| 141 | t.absent(disabled.enabled) | |
| 142 | ||
| 143 | const enabled = await rooms.setEnabled('r1', 'irc', true) | |
| 144 | t.ok(enabled.enabled) | |
| 145 | ||
| 146 | await store.close() | |
| 147 | }) | |
| 148 | ||
| 149 | test('RoomMap - setEnabled on nonexistent throws', async (t) => { | |
| 150 | const { rooms, store } = await createRoomMap() | |
| 151 | ||
| 152 | try { | |
| 153 | await rooms.setEnabled('nope', 'nah', false) | |
| 154 | t.fail('Should have thrown') | |
| 155 | } catch (err) { | |
| 156 | t.ok(err.message.includes('not found')) | |
| 157 | } | |
| 158 | ||
| 159 | await store.close() | |
| 160 | }) | |
| 161 | ||
| 162 | test('RoomMap - direction options', async (t) => { | |
| 163 | const { rooms, store } = await createRoomMap() | |
| 164 | ||
| 165 | await rooms.link('r1', 'feed-in', { direction: 'in' }) | |
| 166 | await rooms.link('r1', 'feed-out', { direction: 'out' }) | |
| 167 | ||
| 168 | const services = await rooms.getServices('r1') | |
| 169 | const inSvc = services.find(s => s.serviceId === 'feed-in') | |
| 170 | const outSvc = services.find(s => s.serviceId === 'feed-out') | |
| 171 | ||
| 172 | t.is(inSvc.direction, 'in') | |
| 173 | t.is(outSvc.direction, 'out') | |
| 174 | ||
| 175 | await store.close() | |
| 176 | }) |
| … | … | @@ -0,0 +1,227 @@ |
| 1 | import test from 'brittle' | |
| 2 | import { ServiceRegistry } from '../lib/service-registry.js' | |
| 3 | ||
| 4 | function mockService (opts = {}) { | |
| 5 | return { | |
| 6 | type: opts.type || 'bridge', | |
| 7 | name: opts.name || 'test-service', | |
| 8 | started: false, | |
| 9 | start: async () => { mockService.started = true }, | |
| 10 | stop: async () => { mockService.started = false }, | |
| 11 | ...opts | |
| 12 | } | |
| 13 | } | |
| 14 | ||
| 15 | test('ServiceRegistry - register', (t) => { | |
| 16 | const reg = new ServiceRegistry() | |
| 17 | const entry = reg.register('irc', mockService({ type: 'bridge', name: 'IRC Bridge' })) | |
| 18 | ||
| 19 | t.is(entry.id, 'irc') | |
| 20 | t.is(entry.type, 'bridge') | |
| 21 | t.is(entry.status, 'registered') | |
| 22 | t.is(reg.count, 1) | |
| 23 | }) | |
| 24 | ||
| 25 | test('ServiceRegistry - duplicate register throws', (t) => { | |
| 26 | const reg = new ServiceRegistry() | |
| 27 | reg.register('irc', mockService()) | |
| 28 | ||
| 29 | try { | |
| 30 | reg.register('irc', mockService()) | |
| 31 | t.fail('Should have thrown') | |
| 32 | } catch (err) { | |
| 33 | t.ok(err.message.includes('already registered')) | |
| 34 | } | |
| 35 | }) | |
| 36 | ||
| 37 | test('ServiceRegistry - start and stop', async (t) => { | |
| 38 | const reg = new ServiceRegistry() | |
| 39 | let started = false | |
| 40 | ||
| 41 | reg.register('svc', { | |
| 42 | type: 'bridge', | |
| 43 | start: async () => { started = true }, | |
| 44 | stop: async () => { started = false } | |
| 45 | }) | |
| 46 | ||
| 47 | await reg.start('svc') | |
| 48 | t.ok(started) | |
| 49 | t.is(reg.get('svc').status, 'running') | |
| 50 | t.ok(reg.get('svc').startedAt > 0) | |
| 51 | ||
| 52 | await reg.stop('svc') | |
| 53 | t.absent(started) | |
| 54 | t.is(reg.get('svc').status, 'stopped') | |
| 55 | }) | |
| 56 | ||
| 57 | test('ServiceRegistry - start nonexistent throws', async (t) => { | |
| 58 | const reg = new ServiceRegistry() | |
| 59 | ||
| 60 | try { | |
| 61 | await reg.start('nope') | |
| 62 | t.fail('Should have thrown') | |
| 63 | } catch (err) { | |
| 64 | t.ok(err.message.includes('not found')) | |
| 65 | } | |
| 66 | }) | |
| 67 | ||
| 68 | test('ServiceRegistry - start error captured', async (t) => { | |
| 69 | const reg = new ServiceRegistry() | |
| 70 | reg.register('broken', { | |
| 71 | type: 'bridge', | |
| 72 | start: async () => { throw new Error('auth failed') } | |
| 73 | }) | |
| 74 | ||
| 75 | try { | |
| 76 | await reg.start('broken') | |
| 77 | t.fail('Should have thrown') | |
| 78 | } catch (err) { | |
| 79 | t.is(err.message, 'auth failed') | |
| 80 | } | |
| 81 | ||
| 82 | t.is(reg.get('broken').status, 'error') | |
| 83 | t.is(reg.get('broken').error, 'auth failed') | |
| 84 | }) | |
| 85 | ||
| 86 | test('ServiceRegistry - unregister', async (t) => { | |
| 87 | const reg = new ServiceRegistry() | |
| 88 | reg.register('temp', mockService()) | |
| 89 | ||
| 90 | t.is(reg.count, 1) | |
| 91 | const ok = await reg.unregister('temp') | |
| 92 | t.ok(ok) | |
| 93 | t.is(reg.count, 0) | |
| 94 | ||
| 95 | const nope = await reg.unregister('nonexistent') | |
| 96 | t.absent(nope) | |
| 97 | }) | |
| 98 | ||
| 99 | test('ServiceRegistry - unregister stops running service', async (t) => { | |
| 100 | const reg = new ServiceRegistry() | |
| 101 | let stopped = false | |
| 102 | ||
| 103 | reg.register('svc', { | |
| 104 | type: 'bridge', | |
| 105 | start: async () => {}, | |
| 106 | stop: async () => { stopped = true } | |
| 107 | }) | |
| 108 | ||
| 109 | await reg.start('svc') | |
| 110 | await reg.unregister('svc') | |
| 111 | t.ok(stopped) | |
| 112 | }) | |
| 113 | ||
| 114 | test('ServiceRegistry - list all', (t) => { | |
| 115 | const reg = new ServiceRegistry() | |
| 116 | reg.register('a', { type: 'bridge', name: 'A' }) | |
| 117 | reg.register('b', { type: 'panel', name: 'B' }) | |
| 118 | reg.register('c', { type: 'daemon', name: 'C' }) | |
| 119 | ||
| 120 | t.is(reg.list().length, 3) | |
| 121 | }) | |
| 122 | ||
| 123 | test('ServiceRegistry - list by type', (t) => { | |
| 124 | const reg = new ServiceRegistry() | |
| 125 | reg.register('a', { type: 'bridge' }) | |
| 126 | reg.register('b', { type: 'panel' }) | |
| 127 | reg.register('c', { type: 'bridge' }) | |
| 128 | ||
| 129 | const bridges = reg.list({ type: 'bridge' }) | |
| 130 | t.is(bridges.length, 2) | |
| 131 | ||
| 132 | const panels = reg.list({ type: 'panel' }) | |
| 133 | t.is(panels.length, 1) | |
| 134 | }) | |
| 135 | ||
| 136 | test('ServiceRegistry - list by status', async (t) => { | |
| 137 | const reg = new ServiceRegistry() | |
| 138 | reg.register('a', { type: 'bridge', start: async () => {}, stop: async () => {} }) | |
| 139 | reg.register('b', { type: 'bridge' }) | |
| 140 | ||
| 141 | await reg.start('a') | |
| 142 | ||
| 143 | const running = reg.list({ status: 'running' }) | |
| 144 | t.is(running.length, 1) | |
| 145 | t.is(running[0].id, 'a') | |
| 146 | }) | |
| 147 | ||
| 148 | test('ServiceRegistry - status summary', async (t) => { | |
| 149 | const reg = new ServiceRegistry() | |
| 150 | reg.register('a', { type: 'bridge', start: async () => {}, stop: async () => {} }) | |
| 151 | reg.register('b', { type: 'panel' }) | |
| 152 | reg.register('c', { type: 'bridge' }) | |
| 153 | ||
| 154 | await reg.start('a') | |
| 155 | ||
| 156 | const s = reg.status() | |
| 157 | t.is(s.total, 3) | |
| 158 | t.is(s.byType.bridge, 2) | |
| 159 | t.is(s.byType.panel, 1) | |
| 160 | t.is(s.byStatus.running, 1) | |
| 161 | t.is(s.byStatus.registered, 2) | |
| 162 | }) | |
| 163 | ||
| 164 | test('ServiceRegistry - startAll and stopAll', async (t) => { | |
| 165 | const reg = new ServiceRegistry() | |
| 166 | let started = 0 | |
| 167 | ||
| 168 | for (const id of ['a', 'b', 'c']) { | |
| 169 | reg.register(id, { | |
| 170 | type: 'bridge', | |
| 171 | start: async () => { started++ }, | |
| 172 | stop: async () => { started-- } | |
| 173 | }) | |
| 174 | } | |
| 175 | ||
| 176 | const startResults = await reg.startAll() | |
| 177 | t.is(startResults.length, 3) | |
| 178 | t.ok(startResults.every(r => r.ok)) | |
| 179 | t.is(started, 3) | |
| 180 | ||
| 181 | const stopResults = await reg.stopAll() | |
| 182 | t.is(stopResults.length, 3) | |
| 183 | t.is(started, 0) | |
| 184 | }) | |
| 185 | ||
| 186 | test('ServiceRegistry - startAll with partial failure', async (t) => { | |
| 187 | const reg = new ServiceRegistry() | |
| 188 | ||
| 189 | reg.register('good', { | |
| 190 | type: 'bridge', | |
| 191 | start: async () => {}, | |
| 192 | stop: async () => {} | |
| 193 | }) | |
| 194 | reg.register('bad', { | |
| 195 | type: 'bridge', | |
| 196 | start: async () => { throw new Error('nope') } | |
| 197 | }) | |
| 198 | ||
| 199 | const results = await reg.startAll() | |
| 200 | t.is(results.length, 2) | |
| 201 | ||
| 202 | const good = results.find(r => r.id === 'good') | |
| 203 | t.ok(good.ok) | |
| 204 | ||
| 205 | const bad = results.find(r => r.id === 'bad') | |
| 206 | t.absent(bad.ok) | |
| 207 | t.is(bad.error, 'nope') | |
| 208 | }) | |
| 209 | ||
| 210 | test('ServiceRegistry - get returns null for unknown', (t) => { | |
| 211 | const reg = new ServiceRegistry() | |
| 212 | t.is(reg.get('nonexistent'), null) | |
| 213 | }) | |
| 214 | ||
| 215 | test('ServiceRegistry - close fallback', async (t) => { | |
| 216 | const reg = new ServiceRegistry() | |
| 217 | let closed = false | |
| 218 | ||
| 219 | reg.register('closeable', { | |
| 220 | type: 'panel', | |
| 221 | close: async () => { closed = true } | |
| 222 | }) | |
| 223 | ||
| 224 | await reg.start('closeable') // no start method, still goes to running | |
| 225 | await reg.stop('closeable') | |
| 226 | t.ok(closed) | |
| 227 | }) |
| … | … | @@ -0,0 +1,134 @@ |
| 1 | import { Wiki } from './lib/wiki.js' | |
| 2 | ||
| 3 | export { Wiki } | |
| 4 | ||
| 5 | // CLI entry point | |
| 6 | const isMain = typeof Pear !== 'undefined' || process.argv[1]?.includes('hyper-wiki') | |
| 7 | ||
| 8 | if (isMain && process.argv.length > 2) { | |
| 9 | const args = parseArgs(typeof Pear !== 'undefined' ? Pear.config.args : process.argv.slice(2)) | |
| 10 | ||
| 11 | if (args.help) { | |
| 12 | console.log(` | |
| 13 | hyper-wiki — P2P wiki with Hyperdrive storage | |
| 14 | ||
| 15 | Usage: | |
| 16 | hyper-wiki create <slug> --title "Page Title" --content "Markdown content" | |
| 17 | hyper-wiki read <slug> | |
| 18 | hyper-wiki update <slug> --content "Updated content" | |
| 19 | hyper-wiki delete <slug> | |
| 20 | hyper-wiki list | |
| 21 | hyper-wiki search <query> | |
| 22 | ||
| 23 | Options: | |
| 24 | --storage <path> Storage directory (default: ./wiki-data) | |
| 25 | --title <title> Page title | |
| 26 | --content <content> Page content (markdown) | |
| 27 | --author <name> Author name | |
| 28 | --replicate Enable P2P replication via Hyperswarm | |
| 29 | --help Show this help | |
| 30 | `) | |
| 31 | process.exit(0) | |
| 32 | } | |
| 33 | ||
| 34 | run(args).catch((err) => { | |
| 35 | console.error('Error:', err.message) | |
| 36 | process.exit(1) | |
| 37 | }) | |
| 38 | } | |
| 39 | ||
| 40 | async function run (args) { | |
| 41 | const storage = args.storage || './wiki-data' | |
| 42 | const wiki = new Wiki(storage, { replicate: args.replicate }) | |
| 43 | await wiki.ready() | |
| 44 | ||
| 45 | const cmd = args._[0] | |
| 46 | const slug = args._[1] | |
| 47 | ||
| 48 | switch (cmd) { | |
| 49 | case 'create': | |
| 50 | case 'update': { | |
| 51 | if (!slug) throw new Error('Usage: hyper-wiki create <slug>') | |
| 52 | const content = args.content || '' | |
| 53 | const meta = await wiki.create(slug, content, { | |
| 54 | title: args.title || slug, | |
| 55 | author: args.author || '' | |
| 56 | }) | |
| 57 | console.log(`${cmd === 'create' ? 'Created' : 'Updated'}: ${slug} (v${meta.version})`) | |
| 58 | break | |
| 59 | } | |
| 60 | case 'read': { | |
| 61 | if (!slug) throw new Error('Usage: hyper-wiki read <slug>') | |
| 62 | const page = await wiki.read(slug) | |
| 63 | if (!page) { | |
| 64 | console.log(`Page not found: ${slug}`) | |
| 65 | } else { | |
| 66 | console.log(`# ${page.title} (v${page.version})`) | |
| 67 | console.log(`Author: ${page.author || 'unknown'} | Updated: ${new Date(page.updated).toISOString()}`) | |
| 68 | console.log('---') | |
| 69 | console.log(page.content) | |
| 70 | } | |
| 71 | break | |
| 72 | } | |
| 73 | case 'delete': { | |
| 74 | if (!slug) throw new Error('Usage: hyper-wiki delete <slug>') | |
| 75 | await wiki.remove(slug) | |
| 76 | console.log(`Deleted: ${slug}`) | |
| 77 | break | |
| 78 | } | |
| 79 | case 'list': { | |
| 80 | const pages = await wiki.list() | |
| 81 | if (pages.length === 0) { | |
| 82 | console.log('No pages yet.') | |
| 83 | } else { | |
| 84 | for (const p of pages) { | |
| 85 | console.log(` ${p.slug.padEnd(30)} v${p.version} ${new Date(p.updated).toISOString().slice(0, 10)} ${p.title}`) | |
| 86 | } | |
| 87 | } | |
| 88 | break | |
| 89 | } | |
| 90 | case 'search': { | |
| 91 | const query = args._.slice(1).join(' ') | |
| 92 | if (!query) throw new Error('Usage: hyper-wiki search <query>') | |
| 93 | const results = await wiki.search(query) | |
| 94 | if (results.length === 0) { | |
| 95 | console.log(`No results for: ${query}`) | |
| 96 | } else { | |
| 97 | for (const r of results) { | |
| 98 | console.log(` ${r.slug.padEnd(30)} score:${r.score} ${r.title}`) | |
| 99 | } | |
| 100 | } | |
| 101 | break | |
| 102 | } | |
| 103 | default: | |
| 104 | console.error(`Unknown command: ${cmd}`) | |
| 105 | console.error('Run with --help for usage') | |
| 106 | process.exit(1) | |
| 107 | } | |
| 108 | ||
| 109 | await wiki.close() | |
| 110 | } | |
| 111 | ||
| 112 | function parseArgs (argv) { | |
| 113 | const result = { _: [] } | |
| 114 | for (let i = 0; i < argv.length; i++) { | |
| 115 | const arg = argv[i] | |
| 116 | if (arg === '--help') { | |
| 117 | result.help = true | |
| 118 | } else if (arg === '--replicate') { | |
| 119 | result.replicate = true | |
| 120 | } else if (arg.startsWith('--')) { | |
| 121 | const key = arg.slice(2) | |
| 122 | const next = argv[i + 1] | |
| 123 | if (next && !next.startsWith('--')) { | |
| 124 | result[key] = next | |
| 125 | i++ | |
| 126 | } else { | |
| 127 | result[key] = true | |
| 128 | } | |
| 129 | } else { | |
| 130 | result._.push(arg) | |
| 131 | } | |
| 132 | } | |
| 133 | return result | |
| 134 | } |
| … | … | @@ -0,0 +1,105 @@ |
| 1 | import Hyperbee from 'hyperbee' | |
| 2 | ||
| 3 | class SearchIndex { | |
| 4 | constructor (store) { | |
| 5 | this._store = store | |
| 6 | this._db = null | |
| 7 | this._ready = false | |
| 8 | } | |
| 9 | ||
| 10 | async ready () { | |
| 11 | if (this._ready) return | |
| 12 | ||
| 13 | const core = this._store.get({ name: 'wiki-search-index' }) | |
| 14 | this._db = new Hyperbee(core, { | |
| 15 | keyEncoding: 'utf-8', | |
| 16 | valueEncoding: 'utf-8' | |
| 17 | }) | |
| 18 | await this._db.ready() | |
| 19 | this._ready = true | |
| 20 | } | |
| 21 | ||
| 22 | async index (slug, content, meta = {}) { | |
| 23 | await this.ready() | |
| 24 | ||
| 25 | const title = meta.title || slug | |
| 26 | const text = `${title}\n${content}`.toLowerCase() | |
| 27 | ||
| 28 | // Extract unique words (simple tokenizer) | |
| 29 | const words = new Set(text.match(/\w{2,}/g) || []) | |
| 30 | ||
| 31 | // Store word → slug mappings | |
| 32 | for (const word of words) { | |
| 33 | const key = `word/${word}/${slug}` | |
| 34 | await this._db.put(key, title) | |
| 35 | } | |
| 36 | ||
| 37 | // Store slug → word count for ranking | |
| 38 | await this._db.put(`doc/${slug}`, JSON.stringify({ | |
| 39 | title, | |
| 40 | wordCount: words.size, | |
| 41 | updated: meta.updated || Date.now() | |
| 42 | })) | |
| 43 | } | |
| 44 | ||
| 45 | async remove (slug) { | |
| 46 | await this.ready() | |
| 47 | ||
| 48 | // Remove all word entries for this slug | |
| 49 | const batch = this._db.batch() | |
| 50 | for await (const entry of this._db.createReadStream({ | |
| 51 | gte: 'word/', | |
| 52 | lt: 'word/\xff' | |
| 53 | })) { | |
| 54 | if (entry.key.endsWith(`/${slug}`)) { | |
| 55 | await batch.del(entry.key) | |
| 56 | } | |
| 57 | } | |
| 58 | await batch.del(`doc/${slug}`) | |
| 59 | await batch.flush() | |
| 60 | } | |
| 61 | ||
| 62 | async search (query, limit = 20) { | |
| 63 | await this.ready() | |
| 64 | ||
| 65 | const terms = query.toLowerCase().match(/\w{2,}/g) || [] | |
| 66 | if (terms.length === 0) return [] | |
| 67 | ||
| 68 | // Find pages matching any term | |
| 69 | const scores = new Map() | |
| 70 | ||
| 71 | for (const term of terms) { | |
| 72 | for await (const entry of this._db.createReadStream({ | |
| 73 | gte: `word/${term}/`, | |
| 74 | lt: `word/${term}/\xff` | |
| 75 | })) { | |
| 76 | const slug = entry.key.split('/').pop() | |
| 77 | scores.set(slug, (scores.get(slug) || 0) + 1) | |
| 78 | } | |
| 79 | } | |
| 80 | ||
| 81 | // Sort by score (more matching terms = higher) | |
| 82 | const results = [...scores.entries()] | |
| 83 | .sort((a, b) => b[1] - a[1]) | |
| 84 | .slice(0, limit) | |
| 85 | ||
| 86 | // Fetch metadata | |
| 87 | const pages = [] | |
| 88 | for (const [slug, score] of results) { | |
| 89 | const docEntry = await this._db.get(`doc/${slug}`) | |
| 90 | let meta = { title: slug } | |
| 91 | if (docEntry) { | |
| 92 | try { meta = JSON.parse(docEntry.value) } catch {} | |
| 93 | } | |
| 94 | pages.push({ slug, score, title: meta.title }) | |
| 95 | } | |
| 96 | ||
| 97 | return pages | |
| 98 | } | |
| 99 | ||
| 100 | async close () { | |
| 101 | // Hyperbee closes with corestore | |
| 102 | } | |
| 103 | } | |
| 104 | ||
| 105 | export { SearchIndex } |
| … | … | @@ -0,0 +1,128 @@ |
| 1 | import Hyperdrive from 'hyperdrive' | |
| 2 | import b4a from 'b4a' | |
| 3 | ||
| 4 | class WikiStore { | |
| 5 | constructor (store, opts = {}) { | |
| 6 | this._store = store // Corestore instance (shared) | |
| 7 | this._key = opts.key || null | |
| 8 | this._drive = null | |
| 9 | this._ready = false | |
| 10 | } | |
| 11 | ||
| 12 | async ready () { | |
| 13 | if (this._ready) return | |
| 14 | ||
| 15 | this._drive = this._key | |
| 16 | ? new Hyperdrive(this._store, this._key) | |
| 17 | : new Hyperdrive(this._store) | |
| 18 | ||
| 19 | await this._drive.ready() | |
| 20 | this._ready = true | |
| 21 | } | |
| 22 | ||
| 23 | get key () { | |
| 24 | return this._drive?.key || null | |
| 25 | } | |
| 26 | ||
| 27 | get discoveryKey () { | |
| 28 | return this._drive?.discoveryKey || null | |
| 29 | } | |
| 30 | ||
| 31 | _pagePath (slug) { | |
| 32 | return `/wiki/${slug}.md` | |
| 33 | } | |
| 34 | ||
| 35 | _metaPath (slug) { | |
| 36 | return `/wiki/${slug}.meta.json` | |
| 37 | } | |
| 38 | ||
| 39 | async put (slug, content, meta = {}) { | |
| 40 | await this.ready() | |
| 41 | ||
| 42 | const now = Date.now() | |
| 43 | const existing = await this.getMeta(slug) | |
| 44 | ||
| 45 | const metadata = { | |
| 46 | title: meta.title || existing?.title || slug, | |
| 47 | author: meta.author || existing?.author || '', | |
| 48 | created: existing?.created || now, | |
| 49 | updated: now, | |
| 50 | tags: meta.tags || existing?.tags || [], | |
| 51 | version: (existing?.version || 0) + 1 | |
| 52 | } | |
| 53 | ||
| 54 | await this._drive.put(this._pagePath(slug), b4a.from(content)) | |
| 55 | await this._drive.put(this._metaPath(slug), b4a.from(JSON.stringify(metadata))) | |
| 56 | ||
| 57 | return metadata | |
| 58 | } | |
| 59 | ||
| 60 | async get (slug) { | |
| 61 | await this.ready() | |
| 62 | ||
| 63 | const buf = await this._drive.get(this._pagePath(slug)) | |
| 64 | if (!buf) return null | |
| 65 | ||
| 66 | const meta = await this.getMeta(slug) | |
| 67 | return { | |
| 68 | slug, | |
| 69 | content: b4a.toString(buf), | |
| 70 | ...meta | |
| 71 | } | |
| 72 | } | |
| 73 | ||
| 74 | async getMeta (slug) { | |
| 75 | await this.ready() | |
| 76 | ||
| 77 | const buf = await this._drive.get(this._metaPath(slug)) | |
| 78 | if (!buf) return null | |
| 79 | ||
| 80 | try { | |
| 81 | return JSON.parse(b4a.toString(buf)) | |
| 82 | } catch { | |
| 83 | return null | |
| 84 | } | |
| 85 | } | |
| 86 | ||
| 87 | async del (slug) { | |
| 88 | await this.ready() | |
| 89 | ||
| 90 | await this._drive.del(this._pagePath(slug)) | |
| 91 | await this._drive.del(this._metaPath(slug)) | |
| 92 | } | |
| 93 | ||
| 94 | async exists (slug) { | |
| 95 | await this.ready() | |
| 96 | const entry = await this._drive.entry(this._pagePath(slug)) | |
| 97 | return entry !== null | |
| 98 | } | |
| 99 | ||
| 100 | async list () { | |
| 101 | await this.ready() | |
| 102 | ||
| 103 | const pages = [] | |
| 104 | for await (const entry of this._drive.list('/wiki/')) { | |
| 105 | const name = entry.key | |
| 106 | if (name.endsWith('.md')) { | |
| 107 | const slug = name.replace('/wiki/', '').replace('.md', '') | |
| 108 | const meta = await this.getMeta(slug) | |
| 109 | pages.push({ | |
| 110 | slug, | |
| 111 | title: meta?.title || slug, | |
| 112 | updated: meta?.updated || 0, | |
| 113 | version: meta?.version || 1 | |
| 114 | }) | |
| 115 | } | |
| 116 | } | |
| 117 | ||
| 118 | pages.sort((a, b) => b.updated - a.updated) | |
| 119 | return pages | |
| 120 | } | |
| 121 | ||
| 122 | async close () { | |
| 123 | if (this._drive) await this._drive.close() | |
| 124 | this._ready = false | |
| 125 | } | |
| 126 | } | |
| 127 | ||
| 128 | export { WikiStore } |
| … | … | @@ -0,0 +1,95 @@ |
| 1 | import Corestore from 'corestore' | |
| 2 | import Hyperswarm from 'hyperswarm' | |
| 3 | import { WikiStore } from './wiki-store.js' | |
| 4 | import { SearchIndex } from './search.js' | |
| 5 | ||
| 6 | class Wiki { | |
| 7 | constructor (storage, opts = {}) { | |
| 8 | this._storage = storage | |
| 9 | this._key = opts.key || null | |
| 10 | this._store = null | |
| 11 | this._wiki = null | |
| 12 | this._search = null | |
| 13 | this._swarm = null | |
| 14 | this._ready = false | |
| 15 | this._replicate = opts.replicate ?? false | |
| 16 | } | |
| 17 | ||
| 18 | async ready () { | |
| 19 | if (this._ready) return | |
| 20 | ||
| 21 | // Single shared Corestore for all components | |
| 22 | this._store = new Corestore(this._storage) | |
| 23 | ||
| 24 | this._wiki = new WikiStore(this._store, { key: this._key }) | |
| 25 | await this._wiki.ready() | |
| 26 | ||
| 27 | this._search = new SearchIndex(this._store) | |
| 28 | await this._search.ready() | |
| 29 | ||
| 30 | if (this._replicate) { | |
| 31 | this._swarm = new Hyperswarm() | |
| 32 | this._swarm.on('connection', (conn) => { | |
| 33 | this._store.replicate(conn) | |
| 34 | }) | |
| 35 | const topic = this._wiki.discoveryKey | |
| 36 | if (topic) { | |
| 37 | this._swarm.join(topic, { server: true, client: true }) | |
| 38 | } | |
| 39 | } | |
| 40 | ||
| 41 | this._ready = true | |
| 42 | } | |
| 43 | ||
| 44 | get key () { | |
| 45 | return this._wiki?.key || null | |
| 46 | } | |
| 47 | ||
| 48 | async create (slug, content, meta = {}) { | |
| 49 | await this.ready() | |
| 50 | const result = await this._wiki.put(slug, content, meta) | |
| 51 | await this._search.index(slug, content, { ...meta, ...result }) | |
| 52 | return result | |
| 53 | } | |
| 54 | ||
| 55 | async read (slug) { | |
| 56 | await this.ready() | |
| 57 | return this._wiki.get(slug) | |
| 58 | } | |
| 59 | ||
| 60 | async update (slug, content, meta = {}) { | |
| 61 | return this.create(slug, content, meta) | |
| 62 | } | |
| 63 | ||
| 64 | async remove (slug) { | |
| 65 | await this.ready() | |
| 66 | await this._wiki.del(slug) | |
| 67 | await this._search.remove(slug) | |
| 68 | } | |
| 69 | ||
| 70 | async exists (slug) { | |
| 71 | await this.ready() | |
| 72 | return this._wiki.exists(slug) | |
| 73 | } | |
| 74 | ||
| 75 | async list () { | |
| 76 | await this.ready() | |
| 77 | return this._wiki.list() | |
| 78 | } | |
| 79 | ||
| 80 | async search (query, limit = 20) { | |
| 81 | await this.ready() | |
| 82 | return this._search.search(query, limit) | |
| 83 | } | |
| 84 | ||
| 85 | async close () { | |
| 86 | if (this._swarm) { | |
| 87 | await this._swarm.destroy() | |
| 88 | } | |
| 89 | await this._wiki.close() | |
| 90 | if (this._store) await this._store.close() | |
| 91 | this._ready = false | |
| 92 | } | |
| 93 | } | |
| 94 | ||
| 95 | export { Wiki } |
| … | … | @@ -0,0 +1,1813 @@ |
| 1 | { | |
| 2 | "name": "hyper-wiki", | |
| 3 | "version": "1.0.0", | |
| 4 | "lockfileVersion": 3, | |
| 5 | "requires": true, | |
| 6 | "packages": { | |
| 7 | "": { | |
| 8 | "name": "hyper-wiki", | |
| 9 | "version": "1.0.0", | |
| 10 | "license": "MIT", | |
| 11 | "dependencies": { | |
| 12 | "b4a": "^1.0.0", | |
| 13 | "corestore": "^6.0.0", | |
| 14 | "hyperbee": "^2.0.0", | |
| 15 | "hypercore": "^10.0.0", | |
| 16 | "hyperdrive": "^11.0.0", | |
| 17 | "hyperswarm": "^4.0.0" | |
| 18 | }, | |
| 19 | "devDependencies": { | |
| 20 | "brittle": "^3.0.0" | |
| 21 | } | |
| 22 | }, | |
| 23 | "node_modules/@hyperswarm/secret-stream": { | |
| 24 | "version": "6.9.1", | |
| 25 | "resolved": "https://registry.npmjs.org/@hyperswarm/secret-stream/-/secret-stream-6.9.1.tgz", | |
| 26 | "integrity": "sha512-xb0S5y3YJwBakD77JOGBHlBxdp63mHClZoXBYoLv+9wH8e054ESKlmQptWqjJK5dv5VMUIVYOJB4MaOpB0JdGw==", | |
| 27 | "license": "Apache-2.0", | |
| 28 | "dependencies": { | |
| 29 | "b4a": "^1.1.0", | |
| 30 | "hypercore-crypto": "^3.3.1", | |
| 31 | "noise-curve-ed": "^2.0.1", | |
| 32 | "noise-handshake": "^4.0.0", | |
| 33 | "sodium-secretstream": "^1.1.0", | |
| 34 | "sodium-universal": "^5.0.0", | |
| 35 | "streamx": "^2.14.0", | |
| 36 | "timeout-refresh": "^2.0.0", | |
| 37 | "unslab": "^1.3.0" | |
| 38 | } | |
| 39 | }, | |
| 40 | "node_modules/@hyperswarm/secret-stream/node_modules/sodium-native": { | |
| 41 | "version": "5.0.10", | |
| 42 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 43 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 44 | "license": "MIT", | |
| 45 | "dependencies": { | |
| 46 | "require-addon": "^1.1.0", | |
| 47 | "which-runtime": "^1.2.1" | |
| 48 | }, | |
| 49 | "engines": { | |
| 50 | "bare": ">=1.16.0" | |
| 51 | } | |
| 52 | }, | |
| 53 | "node_modules/@hyperswarm/secret-stream/node_modules/sodium-universal": { | |
| 54 | "version": "5.0.1", | |
| 55 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 56 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 57 | "license": "MIT", | |
| 58 | "dependencies": { | |
| 59 | "sodium-native": "^5.0.1" | |
| 60 | }, | |
| 61 | "peerDependencies": { | |
| 62 | "sodium-javascript": "~0.8.0" | |
| 63 | }, | |
| 64 | "peerDependenciesMeta": { | |
| 65 | "sodium-javascript": { | |
| 66 | "optional": true | |
| 67 | } | |
| 68 | } | |
| 69 | }, | |
| 70 | "node_modules/@jridgewell/resolve-uri": { | |
| 71 | "version": "3.1.2", | |
| 72 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", | |
| 73 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", | |
| 74 | "dev": true, | |
| 75 | "license": "MIT", | |
| 76 | "engines": { | |
| 77 | "node": ">=6.0.0" | |
| 78 | } | |
| 79 | }, | |
| 80 | "node_modules/@jridgewell/sourcemap-codec": { | |
| 81 | "version": "1.5.5", | |
| 82 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", | |
| 83 | "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", | |
| 84 | "dev": true, | |
| 85 | "license": "MIT" | |
| 86 | }, | |
| 87 | "node_modules/@jridgewell/trace-mapping": { | |
| 88 | "version": "0.3.31", | |
| 89 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", | |
| 90 | "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", | |
| 91 | "dev": true, | |
| 92 | "license": "MIT", | |
| 93 | "dependencies": { | |
| 94 | "@jridgewell/resolve-uri": "^3.1.0", | |
| 95 | "@jridgewell/sourcemap-codec": "^1.4.14" | |
| 96 | } | |
| 97 | }, | |
| 98 | "node_modules/@types/istanbul-lib-coverage": { | |
| 99 | "version": "2.0.6", | |
| 100 | "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", | |
| 101 | "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", | |
| 102 | "dev": true, | |
| 103 | "license": "MIT" | |
| 104 | }, | |
| 105 | "node_modules/adaptive-timeout": { | |
| 106 | "version": "1.0.1", | |
| 107 | "resolved": "https://registry.npmjs.org/adaptive-timeout/-/adaptive-timeout-1.0.1.tgz", | |
| 108 | "integrity": "sha512-+seGiBtHqbnyZ0Quq/ZGxxwP66153WBABawa07/QeyuPFzbduPBjiGQAyCiriiLDzt3dkbMBskSlfqtRwblK8Q==", | |
| 109 | "license": "Apache-2.0", | |
| 110 | "dependencies": { | |
| 111 | "xache": "^1.2.1" | |
| 112 | } | |
| 113 | }, | |
| 114 | "node_modules/b4a": { | |
| 115 | "version": "1.7.4", | |
| 116 | "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.4.tgz", | |
| 117 | "integrity": "sha512-u20zJLDaSWpxaZ+zaAkEIB2dZZ1o+DF4T/MRbmsvGp9nletHOyiai19OzX1fF8xUBYsO1bPXxODvcd0978pnug==", | |
| 118 | "license": "Apache-2.0", | |
| 119 | "peerDependencies": { | |
| 120 | "react-native-b4a": "*" | |
| 121 | }, | |
| 122 | "peerDependenciesMeta": { | |
| 123 | "react-native-b4a": { | |
| 124 | "optional": true | |
| 125 | } | |
| 126 | } | |
| 127 | }, | |
| 128 | "node_modules/bare-addon-resolve": { | |
| 129 | "version": "1.10.0", | |
| 130 | "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.0.tgz", | |
| 131 | "integrity": "sha512-sSd0jieRJlDaODOzj0oe0RjFVC1QI0ZIjGIdPkbrTXsdVVtENg14c+lHHAhHwmWCZ2nQlMhy8jA3Y5LYPc/isA==", | |
| 132 | "license": "Apache-2.0", | |
| 133 | "dependencies": { | |
| 134 | "bare-module-resolve": "^1.10.0", | |
| 135 | "bare-semver": "^1.0.0" | |
| 136 | }, | |
| 137 | "peerDependencies": { | |
| 138 | "bare-url": "*" | |
| 139 | }, | |
| 140 | "peerDependenciesMeta": { | |
| 141 | "bare-url": { | |
| 142 | "optional": true | |
| 143 | } | |
| 144 | } | |
| 145 | }, | |
| 146 | "node_modules/bare-ansi-escapes": { | |
| 147 | "version": "2.2.3", | |
| 148 | "resolved": "https://registry.npmjs.org/bare-ansi-escapes/-/bare-ansi-escapes-2.2.3.tgz", | |
| 149 | "integrity": "sha512-02ES4/E2RbrtZSnHJ9LntBhYkLA6lPpSEeP8iqS3MccBIVhVBlEmruF1I7HZqx5Q8aiTeYfQVeqmrU9YO2yYoQ==", | |
| 150 | "dev": true, | |
| 151 | "license": "Apache-2.0", | |
| 152 | "dependencies": { | |
| 153 | "bare-stream": "^2.6.5" | |
| 154 | }, | |
| 155 | "peerDependencies": { | |
| 156 | "bare-buffer": "*" | |
| 157 | }, | |
| 158 | "peerDependenciesMeta": { | |
| 159 | "bare-buffer": { | |
| 160 | "optional": true | |
| 161 | } | |
| 162 | } | |
| 163 | }, | |
| 164 | "node_modules/bare-assert": { | |
| 165 | "version": "1.2.0", | |
| 166 | "resolved": "https://registry.npmjs.org/bare-assert/-/bare-assert-1.2.0.tgz", | |
| 167 | "integrity": "sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==", | |
| 168 | "dev": true, | |
| 169 | "license": "Apache-2.0", | |
| 170 | "dependencies": { | |
| 171 | "bare-inspect": "^3.1.2" | |
| 172 | } | |
| 173 | }, | |
| 174 | "node_modules/bare-bundle": { | |
| 175 | "version": "1.10.0", | |
| 176 | "resolved": "https://registry.npmjs.org/bare-bundle/-/bare-bundle-1.10.0.tgz", | |
| 177 | "integrity": "sha512-4LVlnJAHr00Hh6Vu6ZUJS38rcEtJT3b3vChXSsBsJ2mk1TN0lQ+gzd+Dw5L0aV7uqDZv84smuwW+O02X7PfDlw==", | |
| 178 | "dev": true, | |
| 179 | "license": "Apache-2.0", | |
| 180 | "peerDependencies": { | |
| 181 | "bare-buffer": "*", | |
| 182 | "bare-url": "*" | |
| 183 | }, | |
| 184 | "peerDependenciesMeta": { | |
| 185 | "bare-buffer": { | |
| 186 | "optional": true | |
| 187 | }, | |
| 188 | "bare-url": { | |
| 189 | "optional": true | |
| 190 | } | |
| 191 | } | |
| 192 | }, | |
| 193 | "node_modules/bare-cov": { | |
| 194 | "version": "1.2.1", | |
| 195 | "resolved": "https://registry.npmjs.org/bare-cov/-/bare-cov-1.2.1.tgz", | |
| 196 | "integrity": "sha512-SHNqb/WG9udUEj8m5qGSehoUQ9o0Xzv8nrf4HIdmG+KNU/XdhPLhTP5VPrHnSAMAXhAzEB4Q+2IYZ0x9qPzN1g==", | |
| 197 | "dev": true, | |
| 198 | "license": "Apache-2.0", | |
| 199 | "dependencies": { | |
| 200 | "bare-fs": "^4.1.2", | |
| 201 | "bare-inspector": "^4.0.1", | |
| 202 | "bare-path": "^3.0.0", | |
| 203 | "bare-process": "^4.2.1", | |
| 204 | "bare-url": "^2.1.5", | |
| 205 | "bare-v8-to-istanbul": "^1.0.2", | |
| 206 | "picomatch": "^4.0.2", | |
| 207 | "which-runtime": "^1.2.1" | |
| 208 | } | |
| 209 | }, | |
| 210 | "node_modules/bare-crypto": { | |
| 211 | "version": "1.13.0", | |
| 212 | "resolved": "https://registry.npmjs.org/bare-crypto/-/bare-crypto-1.13.0.tgz", | |
| 213 | "integrity": "sha512-RQl13yD+YTACWUZHMMck0C6LNjXgGgRyhVC557ag0xIrkxXf4IQwpnc8WOB58f4k5kSzKhIjy05oW3HUBrFpSQ==", | |
| 214 | "dev": true, | |
| 215 | "license": "Apache-2.0", | |
| 216 | "dependencies": { | |
| 217 | "bare-stream": "^2.6.3" | |
| 218 | }, | |
| 219 | "peerDependencies": { | |
| 220 | "bare-buffer": "*" | |
| 221 | }, | |
| 222 | "peerDependenciesMeta": { | |
| 223 | "bare-buffer": { | |
| 224 | "optional": true | |
| 225 | } | |
| 226 | } | |
| 227 | }, | |
| 228 | "node_modules/bare-debug-log": { | |
| 229 | "version": "2.0.0", | |
| 230 | "resolved": "https://registry.npmjs.org/bare-debug-log/-/bare-debug-log-2.0.0.tgz", | |
| 231 | "integrity": "sha512-Vi42PkMQsNV9PUpx2Gl1hikshx5O9FzMJ6o9Nnopseg7qLBBK7Nl31d0RHcfwLEAfmcPApytpc0ZFfq68u22FQ==", | |
| 232 | "dev": true, | |
| 233 | "license": "Apache-2.0", | |
| 234 | "dependencies": { | |
| 235 | "bare-os": "^3.0.1" | |
| 236 | } | |
| 237 | }, | |
| 238 | "node_modules/bare-dns": { | |
| 239 | "version": "2.1.4", | |
| 240 | "resolved": "https://registry.npmjs.org/bare-dns/-/bare-dns-2.1.4.tgz", | |
| 241 | "integrity": "sha512-abwjHmpWqSRNB7V5615QxPH92L71AVzFm/kKTs8VYiNTAi2xVdonpv0BjJ0hwXLwomoW+xsSOPjW6PZPO14asg==", | |
| 242 | "dev": true, | |
| 243 | "license": "Apache-2.0", | |
| 244 | "engines": { | |
| 245 | "bare": ">=1.7.0" | |
| 246 | } | |
| 247 | }, | |
| 248 | "node_modules/bare-encoding": { | |
| 249 | "version": "1.0.3", | |
| 250 | "resolved": "https://registry.npmjs.org/bare-encoding/-/bare-encoding-1.0.3.tgz", | |
| 251 | "integrity": "sha512-Kqf+t/azs13lUeyK4Tb7ha4wdLRXKWCXQ8w1rVmt7KtoPCPdHD/Xwt7LBIsCSwwGglrcmblo5VOLa5avkJqULA==", | |
| 252 | "dev": true, | |
| 253 | "license": "Apache-2.0", | |
| 254 | "peerDependencies": { | |
| 255 | "bare-buffer": "*" | |
| 256 | }, | |
| 257 | "peerDependenciesMeta": { | |
| 258 | "bare-buffer": { | |
| 259 | "optional": true | |
| 260 | } | |
| 261 | } | |
| 262 | }, | |
| 263 | "node_modules/bare-env": { | |
| 264 | "version": "3.0.0", | |
| 265 | "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.0.tgz", | |
| 266 | "integrity": "sha512-0u964P5ZLAxTi+lW4Kjp7YRJQ5gZr9ycYOtjLxsSrupgMz3sn5Z9n4SH/JIifHwvadsf1brA2JAjP+9IOWwTiw==", | |
| 267 | "dev": true, | |
| 268 | "license": "Apache-2.0", | |
| 269 | "dependencies": { | |
| 270 | "bare-os": "^3.0.1" | |
| 271 | } | |
| 272 | }, | |
| 273 | "node_modules/bare-events": { | |
| 274 | "version": "2.8.2", | |
| 275 | "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", | |
| 276 | "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", | |
| 277 | "license": "Apache-2.0", | |
| 278 | "peerDependencies": { | |
| 279 | "bare-abort-controller": "*" | |
| 280 | }, | |
| 281 | "peerDependenciesMeta": { | |
| 282 | "bare-abort-controller": { | |
| 283 | "optional": true | |
| 284 | } | |
| 285 | } | |
| 286 | }, | |
| 287 | "node_modules/bare-format": { | |
| 288 | "version": "1.0.1", | |
| 289 | "resolved": "https://registry.npmjs.org/bare-format/-/bare-format-1.0.1.tgz", | |
| 290 | "integrity": "sha512-1oS+LZrWK6tnYnvNSHDGljc2MPunRxwhpFriuCgzNF+oklrnwmBKD91tS0yt+jpl2j3UgcSDzBIMiVTvLs9A8w==", | |
| 291 | "dev": true, | |
| 292 | "license": "Apache-2.0", | |
| 293 | "dependencies": { | |
| 294 | "bare-inspect": "^3.0.0" | |
| 295 | } | |
| 296 | }, | |
| 297 | "node_modules/bare-fs": { | |
| 298 | "version": "4.5.4", | |
| 299 | "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.4.tgz", | |
| 300 | "integrity": "sha512-POK4oplfA7P7gqvetNmCs4CNtm9fNsx+IAh7jH7GgU0OJdge2rso0R20TNWVq6VoWcCvsTdlNDaleLHGaKx8CA==", | |
| 301 | "license": "Apache-2.0", | |
| 302 | "dependencies": { | |
| 303 | "bare-events": "^2.5.4", | |
| 304 | "bare-path": "^3.0.0", | |
| 305 | "bare-stream": "^2.6.4", | |
| 306 | "bare-url": "^2.2.2", | |
| 307 | "fast-fifo": "^1.3.2" | |
| 308 | }, | |
| 309 | "engines": { | |
| 310 | "bare": ">=1.16.0" | |
| 311 | }, | |
| 312 | "peerDependencies": { | |
| 313 | "bare-buffer": "*" | |
| 314 | }, | |
| 315 | "peerDependenciesMeta": { | |
| 316 | "bare-buffer": { | |
| 317 | "optional": true | |
| 318 | } | |
| 319 | } | |
| 320 | }, | |
| 321 | "node_modules/bare-hrtime": { | |
| 322 | "version": "2.1.1", | |
| 323 | "resolved": "https://registry.npmjs.org/bare-hrtime/-/bare-hrtime-2.1.1.tgz", | |
| 324 | "integrity": "sha512-VMb3tHo05gsnbu3OXTmkDiwTjMlOsbQmKoysKqKEyR09m77TuDrYFbj3Q5GGk10dAKsUHrnXmwCaeJqzVpB5ZA==", | |
| 325 | "dev": true, | |
| 326 | "license": "Apache-2.0" | |
| 327 | }, | |
| 328 | "node_modules/bare-http-parser": { | |
| 329 | "version": "1.0.1", | |
| 330 | "resolved": "https://registry.npmjs.org/bare-http-parser/-/bare-http-parser-1.0.1.tgz", | |
| 331 | "integrity": "sha512-A3LTDTcELcmNJ3g5liIaS038v/BQxOhA9cjhBESn7eoV7QCuMoIRBKLDadDe08flxyLbxI2f+1l2MZ/5+HnKPA==", | |
| 332 | "dev": true, | |
| 333 | "license": "Apache-2.0" | |
| 334 | }, | |
| 335 | "node_modules/bare-http1": { | |
| 336 | "version": "4.2.3", | |
| 337 | "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.2.3.tgz", | |
| 338 | "integrity": "sha512-zyvHF3xzvBJNpuw0QydE9etTn2AyC/TRVDWTo8Ed3LQEEGaFOkv6KHhaJPlf4eWEeTekk41Fowie+fTsXoNxkA==", | |
| 339 | "dev": true, | |
| 340 | "license": "Apache-2.0", | |
| 341 | "dependencies": { | |
| 342 | "bare-events": "^2.6.0", | |
| 343 | "bare-http-parser": "^1.0.0", | |
| 344 | "bare-stream": "^2.3.0", | |
| 345 | "bare-tcp": "^2.2.0" | |
| 346 | }, | |
| 347 | "peerDependencies": { | |
| 348 | "bare-buffer": "*", | |
| 349 | "bare-url": "*" | |
| 350 | }, | |
| 351 | "peerDependenciesMeta": { | |
| 352 | "bare-buffer": { | |
| 353 | "optional": true | |
| 354 | }, | |
| 355 | "bare-url": { | |
| 356 | "optional": true | |
| 357 | } | |
| 358 | } | |
| 359 | }, | |
| 360 | "node_modules/bare-https": { | |
| 361 | "version": "2.1.2", | |
| 362 | "resolved": "https://registry.npmjs.org/bare-https/-/bare-https-2.1.2.tgz", | |
| 363 | "integrity": "sha512-Q+TTydUDsuKQJvh8dX2dvOXCR9fM3xR5TBmKaFrs5p7Lj7XbKX7v4vIUJ36H0SXg2xCOQxXKIbjwrLg5tfJNYg==", | |
| 364 | "dev": true, | |
| 365 | "license": "Apache-2.0", | |
| 366 | "dependencies": { | |
| 367 | "bare-http1": "^4.0.0", | |
| 368 | "bare-tcp": "^2.2.0", | |
| 369 | "bare-tls": "^2.0.0" | |
| 370 | } | |
| 371 | }, | |
| 372 | "node_modules/bare-inspect": { | |
| 373 | "version": "3.1.4", | |
| 374 | "resolved": "https://registry.npmjs.org/bare-inspect/-/bare-inspect-3.1.4.tgz", | |
| 375 | "integrity": "sha512-jfW5KRA84o3REpI6Vr4nbvMn+hqVAw8GU1mMdRwUsY5yJovQamxYeKGVKGqdzs+8ZbG4jRzGUXP/3Ji/DnqfPg==", | |
| 376 | "dev": true, | |
| 377 | "license": "Apache-2.0", | |
| 378 | "dependencies": { | |
| 379 | "bare-ansi-escapes": "^2.1.0", | |
| 380 | "bare-type": "^1.0.0" | |
| 381 | }, | |
| 382 | "engines": { | |
| 383 | "bare": ">=1.18.0" | |
| 384 | } | |
| 385 | }, | |
| 386 | "node_modules/bare-inspector": { | |
| 387 | "version": "4.0.3", | |
| 388 | "resolved": "https://registry.npmjs.org/bare-inspector/-/bare-inspector-4.0.3.tgz", | |
| 389 | "integrity": "sha512-yYk6+7tTVKJKHOja5+QBn989ETv8oVsmwoH0nCM+QYC6w5MC03GC4aQNK+lipAZSz1EaAj42WZ8Wewjpy0L9TA==", | |
| 390 | "dev": true, | |
| 391 | "license": "Apache-2.0", | |
| 392 | "dependencies": { | |
| 393 | "bare-events": "^2.1.0", | |
| 394 | "bare-http1": "^4.0.0", | |
| 395 | "bare-stream": "^2.0.0", | |
| 396 | "bare-url": "^2.0.0", | |
| 397 | "bare-ws": "^2.0.0" | |
| 398 | }, | |
| 399 | "engines": { | |
| 400 | "bare": ">=1.2.0" | |
| 401 | }, | |
| 402 | "peerDependencies": { | |
| 403 | "bare-tcp": "*" | |
| 404 | }, | |
| 405 | "peerDependenciesMeta": { | |
| 406 | "bare-tcp": { | |
| 407 | "optional": true | |
| 408 | } | |
| 409 | } | |
| 410 | }, | |
| 411 | "node_modules/bare-module": { | |
| 412 | "version": "6.1.3", | |
| 413 | "resolved": "https://registry.npmjs.org/bare-module/-/bare-module-6.1.3.tgz", | |
| 414 | "integrity": "sha512-5XWsVHsvtWMH4tK4DQWgpNTV0t/sg3ZrAaQLIxrwjrS5+u8Q9vEgc/zQ4QaDPWDse/y/5h+d+YG1Q0JfSMt0zA==", | |
| 415 | "dev": true, | |
| 416 | "license": "Apache-2.0", | |
| 417 | "dependencies": { | |
| 418 | "bare-bundle": "^1.3.0", | |
| 419 | "bare-module-lexer": "^1.0.0", | |
| 420 | "bare-module-resolve": "^1.8.0", | |
| 421 | "bare-path": "^3.0.0", | |
| 422 | "bare-url": "^2.0.1" | |
| 423 | }, | |
| 424 | "engines": { | |
| 425 | "bare": ">=1.23.0" | |
| 426 | }, | |
| 427 | "peerDependencies": { | |
| 428 | "bare-buffer": "*" | |
| 429 | }, | |
| 430 | "peerDependenciesMeta": { | |
| 431 | "bare-buffer": { | |
| 432 | "optional": true | |
| 433 | } | |
| 434 | } | |
| 435 | }, | |
| 436 | "node_modules/bare-module-lexer": { | |
| 437 | "version": "1.4.7", | |
| 438 | "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.4.7.tgz", | |
| 439 | "integrity": "sha512-0klU4eMsjh/wcxi8FdHmNom2j2F4kmkXOhyJFL9qTaSFp2lE3m6BtbKgMHY8R5miqC9r8/IfA8wzXnC5Os14WA==", | |
| 440 | "dev": true, | |
| 441 | "license": "Apache-2.0", | |
| 442 | "dependencies": { | |
| 443 | "require-addon": "^1.0.2" | |
| 444 | }, | |
| 445 | "peerDependencies": { | |
| 446 | "bare-buffer": "*" | |
| 447 | }, | |
| 448 | "peerDependenciesMeta": { | |
| 449 | "bare-buffer": { | |
| 450 | "optional": true | |
| 451 | } | |
| 452 | } | |
| 453 | }, | |
| 454 | "node_modules/bare-module-resolve": { | |
| 455 | "version": "1.12.1", | |
| 456 | "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.1.tgz", | |
| 457 | "integrity": "sha512-hbmAPyFpEq8FoZMd5sFO3u6MC5feluWoGE8YKlA8fCrl6mNtx68Wjg4DTiDJcqRJaovTvOYKfYngoBUnbaT7eg==", | |
| 458 | "license": "Apache-2.0", | |
| 459 | "dependencies": { | |
| 460 | "bare-semver": "^1.0.0" | |
| 461 | }, | |
| 462 | "peerDependencies": { | |
| 463 | "bare-url": "*" | |
| 464 | }, | |
| 465 | "peerDependenciesMeta": { | |
| 466 | "bare-url": { | |
| 467 | "optional": true | |
| 468 | } | |
| 469 | } | |
| 470 | }, | |
| 471 | "node_modules/bare-net": { | |
| 472 | "version": "2.2.0", | |
| 473 | "resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.2.0.tgz", | |
| 474 | "integrity": "sha512-UF7cAbHsGE+H6uEqWF5IULBow1x58chZz4g3ALgHtv7wZsFcCbRDt0JKWEumf5Oma3QWS1Q6aLi0Rpll8RElMg==", | |
| 475 | "dev": true, | |
| 476 | "license": "Apache-2.0", | |
| 477 | "dependencies": { | |
| 478 | "bare-events": "^2.2.2", | |
| 479 | "bare-pipe": "^4.0.0", | |
| 480 | "bare-stream": "^2.0.0", | |
| 481 | "bare-tcp": "^2.0.0" | |
| 482 | } | |
| 483 | }, | |
| 484 | "node_modules/bare-os": { | |
| 485 | "version": "3.6.2", | |
| 486 | "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", | |
| 487 | "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", | |
| 488 | "license": "Apache-2.0", | |
| 489 | "engines": { | |
| 490 | "bare": ">=1.14.0" | |
| 491 | } | |
| 492 | }, | |
| 493 | "node_modules/bare-path": { | |
| 494 | "version": "3.0.0", | |
| 495 | "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", | |
| 496 | "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", | |
| 497 | "license": "Apache-2.0", | |
| 498 | "dependencies": { | |
| 499 | "bare-os": "^3.0.1" | |
| 500 | } | |
| 501 | }, | |
| 502 | "node_modules/bare-pipe": { | |
| 503 | "version": "4.1.3", | |
| 504 | "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.1.3.tgz", | |
| 505 | "integrity": "sha512-DqQsx93rAzre6yJ9T6l/Vgh+X+bntkVMB1X5ggtXjXtqtMmF2Y2RVlCzxxy/09R6yeR9FSWBEUIaMYJL1/5VDA==", | |
| 506 | "dev": true, | |
| 507 | "license": "Apache-2.0", | |
| 508 | "dependencies": { | |
| 509 | "bare-events": "^2.0.0", | |
| 510 | "bare-stream": "^2.0.0" | |
| 511 | }, | |
| 512 | "engines": { | |
| 513 | "bare": ">=1.16.0" | |
| 514 | } | |
| 515 | }, | |
| 516 | "node_modules/bare-process": { | |
| 517 | "version": "4.2.2", | |
| 518 | "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.2.2.tgz", | |
| 519 | "integrity": "sha512-ikzEw+HGLB+2lS/WLVEsqi8BXBwzleG3n7DYI0v6/YNklvNabOIGlLd1dof+7Jw5ob4jsBRPtyflweVahY7rFA==", | |
| 520 | "dev": true, | |
| 521 | "license": "Apache-2.0", | |
| 522 | "dependencies": { | |
| 523 | "bare-env": "^3.0.0", | |
| 524 | "bare-events": "^2.3.1", | |
| 525 | "bare-hrtime": "^2.0.0", | |
| 526 | "bare-os": "^3.5.0", | |
| 527 | "bare-pipe": "^4.0.0", | |
| 528 | "bare-signals": "^4.0.0", | |
| 529 | "bare-tty": "^5.0.0" | |
| 530 | } | |
| 531 | }, | |
| 532 | "node_modules/bare-semver": { | |
| 533 | "version": "1.0.2", | |
| 534 | "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.0.2.tgz", | |
| 535 | "integrity": "sha512-ESVaN2nzWhcI5tf3Zzcq9aqCZ676VWzqw07eEZ0qxAcEOAFYBa0pWq8sK34OQeHLY3JsfKXZS9mDyzyxGjeLzA==", | |
| 536 | "license": "Apache-2.0" | |
| 537 | }, | |
| 538 | "node_modules/bare-signals": { | |
| 539 | "version": "4.2.0", | |
| 540 | "resolved": "https://registry.npmjs.org/bare-signals/-/bare-signals-4.2.0.tgz", | |
| 541 | "integrity": "sha512-fNHMOdQIlYuTvMB3Oh9Apk99hLKn351+Ir8vz+khiPTcOqIyGG4uWWjdLTzxWdYGsA0eT+We3y0K74hjj2nq7A==", | |
| 542 | "dev": true, | |
| 543 | "license": "Apache-2.0", | |
| 544 | "dependencies": { | |
| 545 | "bare-events": "^2.5.3", | |
| 546 | "bare-os": "^3.3.1" | |
| 547 | }, | |
| 548 | "engines": { | |
| 549 | "bare": ">=1.7.0" | |
| 550 | } | |
| 551 | }, | |
| 552 | "node_modules/bare-stream": { | |
| 553 | "version": "2.7.0", | |
| 554 | "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", | |
| 555 | "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", | |
| 556 | "license": "Apache-2.0", | |
| 557 | "dependencies": { | |
| 558 | "streamx": "^2.21.0" | |
| 559 | }, | |
| 560 | "peerDependencies": { | |
| 561 | "bare-buffer": "*", | |
| 562 | "bare-events": "*" | |
| 563 | }, | |
| 564 | "peerDependenciesMeta": { | |
| 565 | "bare-buffer": { | |
| 566 | "optional": true | |
| 567 | }, | |
| 568 | "bare-events": { | |
| 569 | "optional": true | |
| 570 | } | |
| 571 | } | |
| 572 | }, | |
| 573 | "node_modules/bare-subprocess": { | |
| 574 | "version": "5.2.2", | |
| 575 | "resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.2.tgz", | |
| 576 | "integrity": "sha512-L6oXgQ1aWs25RtG5Ky0bDD06p3RAcVVrDDMWb1DfXpHtyEWxamcyIWUbSykxMTWrpLlmSj6ytbb6yoKehGFfmw==", | |
| 577 | "dev": true, | |
| 578 | "license": "Apache-2.0", | |
| 579 | "dependencies": { | |
| 580 | "bare-env": "^3.0.0", | |
| 581 | "bare-events": "^2.5.4", | |
| 582 | "bare-os": "^3.0.1", | |
| 583 | "bare-pipe": "^4.0.0", | |
| 584 | "bare-url": "^2.2.2" | |
| 585 | }, | |
| 586 | "engines": { | |
| 587 | "bare": ">=1.7.0" | |
| 588 | }, | |
| 589 | "peerDependencies": { | |
| 590 | "bare-buffer": "*" | |
| 591 | }, | |
| 592 | "peerDependenciesMeta": { | |
| 593 | "bare-buffer": { | |
| 594 | "optional": true | |
| 595 | } | |
| 596 | } | |
| 597 | }, | |
| 598 | "node_modules/bare-tcp": { | |
| 599 | "version": "2.2.4", | |
| 600 | "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.2.4.tgz", | |
| 601 | "integrity": "sha512-vGjfMkyhS1lxp+Y4+gXRDR1ijZMK2cKx0RBwtYKUuZbD90ARCoM6bAiTa1HBztumCIC+NXb8eXdMFXyuYwlPyw==", | |
| 602 | "dev": true, | |
| 603 | "license": "Apache-2.0", | |
| 604 | "dependencies": { | |
| 605 | "bare-dns": "^2.0.4", | |
| 606 | "bare-events": "^2.5.4", | |
| 607 | "bare-stream": "^2.6.4" | |
| 608 | }, | |
| 609 | "engines": { | |
| 610 | "bare": ">=1.16.0" | |
| 611 | } | |
| 612 | }, | |
| 613 | "node_modules/bare-tls": { | |
| 614 | "version": "2.1.7", | |
| 615 | "resolved": "https://registry.npmjs.org/bare-tls/-/bare-tls-2.1.7.tgz", | |
| 616 | "integrity": "sha512-h6wcNXQdBeTX7fed9tjPp0/9cA/QfcBTv3ItgjnbUk4rWAU8bEFalZCZnUDdCK/t9zrNfJ+yvcPx4D/1Y6biyA==", | |
| 617 | "dev": true, | |
| 618 | "license": "Apache-2.0", | |
| 619 | "dependencies": { | |
| 620 | "bare-net": "^2.0.1", | |
| 621 | "bare-stream": "^2.6.4" | |
| 622 | }, | |
| 623 | "engines": { | |
| 624 | "bare": ">=1.7.0" | |
| 625 | } | |
| 626 | }, | |
| 627 | "node_modules/bare-tty": { | |
| 628 | "version": "5.0.3", | |
| 629 | "resolved": "https://registry.npmjs.org/bare-tty/-/bare-tty-5.0.3.tgz", | |
| 630 | "integrity": "sha512-jW24RBWRZOMHuSEWC9mh8wUKO5WUNl0UWUTNPn3yZ8qkOqwa8vaV2dR3a+BvblONPk7V35wuvK9P8508+judAg==", | |
| 631 | "dev": true, | |
| 632 | "license": "Apache-2.0", | |
| 633 | "dependencies": { | |
| 634 | "bare-events": "^2.2.0", | |
| 635 | "bare-signals": "^4.0.0", | |
| 636 | "bare-stream": "^2.0.0" | |
| 637 | }, | |
| 638 | "engines": { | |
| 639 | "bare": ">=1.16.0" | |
| 640 | } | |
| 641 | }, | |
| 642 | "node_modules/bare-type": { | |
| 643 | "version": "1.1.0", | |
| 644 | "resolved": "https://registry.npmjs.org/bare-type/-/bare-type-1.1.0.tgz", | |
| 645 | "integrity": "sha512-LdtnnEEYldOc87Dr4GpsKnStStZk3zfgoEMXy8yvEZkXrcCv9RtYDrUYWFsBQHtaB0s1EUWmcvS6XmEZYIj3Bw==", | |
| 646 | "dev": true, | |
| 647 | "license": "Apache-2.0", | |
| 648 | "engines": { | |
| 649 | "bare": ">=1.2.0" | |
| 650 | } | |
| 651 | }, | |
| 652 | "node_modules/bare-url": { | |
| 653 | "version": "2.3.2", | |
| 654 | "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", | |
| 655 | "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", | |
| 656 | "license": "Apache-2.0", | |
| 657 | "peer": true, | |
| 658 | "dependencies": { | |
| 659 | "bare-path": "^3.0.0" | |
| 660 | } | |
| 661 | }, | |
| 662 | "node_modules/bare-utils": { | |
| 663 | "version": "1.5.1", | |
| 664 | "resolved": "https://registry.npmjs.org/bare-utils/-/bare-utils-1.5.1.tgz", | |
| 665 | "integrity": "sha512-mxCkFvmDU3mlD/mb+pT64kKXOsx2KMsWLQbngN1LB+NOXfhfnRnyvpy3VZc6m7gzQxe57Bsi+aTCBqA4/S3elQ==", | |
| 666 | "dev": true, | |
| 667 | "license": "Apache-2.0", | |
| 668 | "dependencies": { | |
| 669 | "bare-debug-log": "^2.0.0", | |
| 670 | "bare-encoding": "^1.0.0", | |
| 671 | "bare-format": "^1.0.0", | |
| 672 | "bare-inspect": "^3.0.0", | |
| 673 | "bare-type": "^1.0.6" | |
| 674 | } | |
| 675 | }, | |
| 676 | "node_modules/bare-v8-to-istanbul": { | |
| 677 | "version": "1.0.2", | |
| 678 | "resolved": "https://registry.npmjs.org/bare-v8-to-istanbul/-/bare-v8-to-istanbul-1.0.2.tgz", | |
| 679 | "integrity": "sha512-CM0wPgyQtXTQ7h4q8NjbTZ/FH5he0Jkiwgpq0lFZZA5tkVZzmQ/9ul4R7E+cNlGZJIZlQZ9v4CwbgVaFG3zOAA==", | |
| 680 | "dev": true, | |
| 681 | "license": "Apache-2.0", | |
| 682 | "dependencies": { | |
| 683 | "bare-assert": "^1.0.2", | |
| 684 | "bare-fs": "^4.1.2", | |
| 685 | "bare-module": "^6.1.2", | |
| 686 | "bare-path": "^3.0.0", | |
| 687 | "bare-process": "^4.2.0", | |
| 688 | "bare-url": "^2.1.5", | |
| 689 | "bare-utils": "^1.2.0", | |
| 690 | "v8-to-istanbul": "^9.3.0", | |
| 691 | "which-runtime": "^1.2.1" | |
| 692 | } | |
| 693 | }, | |
| 694 | "node_modules/bare-ws": { | |
| 695 | "version": "2.0.4", | |
| 696 | "resolved": "https://registry.npmjs.org/bare-ws/-/bare-ws-2.0.4.tgz", | |
| 697 | "integrity": "sha512-SQMXzBYna9dRj57Dz1/ag+VWHCRXbfCjMHgyfM2F2lhkVLzMjnVSZP72aVeFWPFqe494Rd70Kzhe2JElGwFlJQ==", | |
| 698 | "dev": true, | |
| 699 | "license": "Apache-2.0", | |
| 700 | "dependencies": { | |
| 701 | "bare-crypto": "^1.2.0", | |
| 702 | "bare-events": "^2.3.1", | |
| 703 | "bare-http1": "^4.0.0", | |
| 704 | "bare-https": "^2.0.0", | |
| 705 | "bare-stream": "^2.1.2" | |
| 706 | }, | |
| 707 | "peerDependencies": { | |
| 708 | "bare-buffer": "*", | |
| 709 | "bare-url": "*" | |
| 710 | }, | |
| 711 | "peerDependenciesMeta": { | |
| 712 | "bare-buffer": { | |
| 713 | "optional": true | |
| 714 | }, | |
| 715 | "bare-url": { | |
| 716 | "optional": true | |
| 717 | } | |
| 718 | } | |
| 719 | }, | |
| 720 | "node_modules/big-sparse-array": { | |
| 721 | "version": "1.0.3", | |
| 722 | "resolved": "https://registry.npmjs.org/big-sparse-array/-/big-sparse-array-1.0.3.tgz", | |
| 723 | "integrity": "sha512-6RjV/3mSZORlMdpUaQ6rUSpG637cZm0//E54YYGtQg1c1O+AbZP8UTdJ/TchsDZcTVLmyWZcseBfp2HBeXUXOQ==", | |
| 724 | "license": "MIT" | |
| 725 | }, | |
| 726 | "node_modules/binary-stream-equals": { | |
| 727 | "version": "1.0.0", | |
| 728 | "resolved": "https://registry.npmjs.org/binary-stream-equals/-/binary-stream-equals-1.0.0.tgz", | |
| 729 | "integrity": "sha512-xiUT5LGfD8JiLhbXiG+ByOnbgb9f2ssRLfZDQMl3nZdf89EotQZGZuMkDN8J3n46emabE7RnJ1q0r7Hv3INExw==", | |
| 730 | "license": "MIT", | |
| 731 | "dependencies": { | |
| 732 | "b4a": "^1.3.1" | |
| 733 | } | |
| 734 | }, | |
| 735 | "node_modules/bits-to-bytes": { | |
| 736 | "version": "1.3.0", | |
| 737 | "resolved": "https://registry.npmjs.org/bits-to-bytes/-/bits-to-bytes-1.3.0.tgz", | |
| 738 | "integrity": "sha512-OJoHTpFXS9bXHBCekGTByf3MqM8CGblBDIduKQeeVVeiU9dDWywSSirXIBYGgg3d1zbVuvnMa1vD4r6PA0kOKg==", | |
| 739 | "license": "ISC", | |
| 740 | "dependencies": { | |
| 741 | "b4a": "^1.5.0" | |
| 742 | } | |
| 743 | }, | |
| 744 | "node_modules/blind-relay": { | |
| 745 | "version": "1.4.0", | |
| 746 | "resolved": "https://registry.npmjs.org/blind-relay/-/blind-relay-1.4.0.tgz", | |
| 747 | "integrity": "sha512-6xt7fDfCs6eGmNNym6I9N42jmjcMQn2qwwOVnkP9ZnrkXFk6c4/tdO1xqRmDEzKzV8gigd+DVdCUG/RUYnen7Q==", | |
| 748 | "license": "Apache-2.0", | |
| 749 | "dependencies": { | |
| 750 | "b4a": "^1.6.4", | |
| 751 | "bare-events": "^2.2.0", | |
| 752 | "bits-to-bytes": "^1.3.0", | |
| 753 | "compact-encoding": "^2.12.0", | |
| 754 | "compact-encoding-bitfield": "^1.0.0", | |
| 755 | "protomux": "^3.5.1", | |
| 756 | "sodium-universal": "^5.0.0", | |
| 757 | "streamx": "^2.15.1" | |
| 758 | } | |
| 759 | }, | |
| 760 | "node_modules/blind-relay/node_modules/sodium-native": { | |
| 761 | "version": "5.0.10", | |
| 762 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 763 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 764 | "license": "MIT", | |
| 765 | "dependencies": { | |
| 766 | "require-addon": "^1.1.0", | |
| 767 | "which-runtime": "^1.2.1" | |
| 768 | }, | |
| 769 | "engines": { | |
| 770 | "bare": ">=1.16.0" | |
| 771 | } | |
| 772 | }, | |
| 773 | "node_modules/blind-relay/node_modules/sodium-universal": { | |
| 774 | "version": "5.0.1", | |
| 775 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 776 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 777 | "license": "MIT", | |
| 778 | "dependencies": { | |
| 779 | "sodium-native": "^5.0.1" | |
| 780 | }, | |
| 781 | "peerDependencies": { | |
| 782 | "sodium-javascript": "~0.8.0" | |
| 783 | }, | |
| 784 | "peerDependenciesMeta": { | |
| 785 | "sodium-javascript": { | |
| 786 | "optional": true | |
| 787 | } | |
| 788 | } | |
| 789 | }, | |
| 790 | "node_modules/bogon": { | |
| 791 | "version": "1.2.0", | |
| 792 | "resolved": "https://registry.npmjs.org/bogon/-/bogon-1.2.0.tgz", | |
| 793 | "integrity": "sha512-FqOBr/1VMzCOsoJd+fzNUMarUYki2+TKt07A2+xaulsNx4r53iJ7MV5k0jbqg7W2U0CsLqxCZOrFibdG6h6HSg==", | |
| 794 | "license": "MIT", | |
| 795 | "dependencies": { | |
| 796 | "compact-encoding": "^2.11.0", | |
| 797 | "compact-encoding-net": "^1.2.0" | |
| 798 | } | |
| 799 | }, | |
| 800 | "node_modules/brittle": { | |
| 801 | "version": "3.19.1", | |
| 802 | "resolved": "https://registry.npmjs.org/brittle/-/brittle-3.19.1.tgz", | |
| 803 | "integrity": "sha512-4Ted1Mt9o9B6oIA6ImJJCtB/Fv++ZO3IDNQgRcHOXWSYGRUidgxchaGrUBaRn+4mlneXrgInPkCPzi0jO8qKbA==", | |
| 804 | "dev": true, | |
| 805 | "license": "Apache-2.0", | |
| 806 | "dependencies": { | |
| 807 | "b4a": "^1.6.0", | |
| 808 | "bare-assert": "^1.0.2", | |
| 809 | "bare-cov": "^1.1.0", | |
| 810 | "bare-fs": "^4.1.6", | |
| 811 | "bare-os": "^3.6.1", | |
| 812 | "bare-path": "^3.0.0", | |
| 813 | "bare-process": "^4.2.1", | |
| 814 | "bare-subprocess": "^5.0.0", | |
| 815 | "bare-url": "^2.1.6", | |
| 816 | "error-stack-parser": "^2.1.4", | |
| 817 | "globbie": "^1.0.2", | |
| 818 | "paparam": "^1.6.2", | |
| 819 | "same-object": "^1.0.2", | |
| 820 | "test-tmp": "^1.4.0", | |
| 821 | "tmatch": "^5.0.0" | |
| 822 | }, | |
| 823 | "bin": { | |
| 824 | "brittle": "bin/node.js", | |
| 825 | "brittle-bare": "bin/bare.js", | |
| 826 | "brittle-node": "bin/node.js", | |
| 827 | "brittle-pear": "bin/pear.js" | |
| 828 | } | |
| 829 | }, | |
| 830 | "node_modules/codecs": { | |
| 831 | "version": "3.1.0", | |
| 832 | "resolved": "https://registry.npmjs.org/codecs/-/codecs-3.1.0.tgz", | |
| 833 | "integrity": "sha512-Dqx8NwvBvnMeuPQdVKy/XEF71igjR5apxBvCGeV0pP1tXadOiaLvDTXt7xh+/5wI1ASB195mXQGJbw3Ml4YDWQ==", | |
| 834 | "license": "MIT", | |
| 835 | "dependencies": { | |
| 836 | "b4a": "^1.6.3" | |
| 837 | } | |
| 838 | }, | |
| 839 | "node_modules/compact-encoding": { | |
| 840 | "version": "2.18.0", | |
| 841 | "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-2.18.0.tgz", | |
| 842 | "integrity": "sha512-goACAOlhMI2xo5jGOMUDfOLnGdRE1jGfyZ+zie8N5114nHrbPIqf6GLUtzbLof6DSyrERlYRm3EcBplte5LcQw==", | |
| 843 | "license": "Apache-2.0", | |
| 844 | "dependencies": { | |
| 845 | "b4a": "^1.3.0" | |
| 846 | } | |
| 847 | }, | |
| 848 | "node_modules/compact-encoding-bitfield": { | |
| 849 | "version": "1.0.0", | |
| 850 | "resolved": "https://registry.npmjs.org/compact-encoding-bitfield/-/compact-encoding-bitfield-1.0.0.tgz", | |
| 851 | "integrity": "sha512-3nMVKUg+PF72UHfainmCL8uKvyWfxsjqOtUY+HiMPGLPCTjnwzoKfFAMo1Ad7nwTPdjBqtGK5b3BOFTFW4EBTg==", | |
| 852 | "license": "ISC", | |
| 853 | "dependencies": { | |
| 854 | "compact-encoding": "^2.4.1" | |
| 855 | } | |
| 856 | }, | |
| 857 | "node_modules/compact-encoding-net": { | |
| 858 | "version": "1.2.0", | |
| 859 | "resolved": "https://registry.npmjs.org/compact-encoding-net/-/compact-encoding-net-1.2.0.tgz", | |
| 860 | "integrity": "sha512-LVXpNpF7PGQeHRVVLGgYWzuVoYAaDZvKUsUxRioGfkotzvOh4AzoQF1HBH3zMNaSnx7gJXuUr3hkjnijaH/Eng==", | |
| 861 | "license": "ISC", | |
| 862 | "dependencies": { | |
| 863 | "compact-encoding": "^2.4.1" | |
| 864 | } | |
| 865 | }, | |
| 866 | "node_modules/convert-source-map": { | |
| 867 | "version": "2.0.0", | |
| 868 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", | |
| 869 | "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", | |
| 870 | "dev": true, | |
| 871 | "license": "MIT" | |
| 872 | }, | |
| 873 | "node_modules/corestore": { | |
| 874 | "version": "6.18.4", | |
| 875 | "resolved": "https://registry.npmjs.org/corestore/-/corestore-6.18.4.tgz", | |
| 876 | "integrity": "sha512-KL1cOUyj3DFRNzeVRekGPRiGN4sIfGo7BsncSJQQBT3Rh2y1nQ32MOR9X5DlOMlWIT4txwqBzhzam6+JXtwFVw==", | |
| 877 | "license": "MIT", | |
| 878 | "dependencies": { | |
| 879 | "b4a": "^1.6.4", | |
| 880 | "hypercore": "^10.37.10", | |
| 881 | "hypercore-crypto": "^3.4.0", | |
| 882 | "hypercore-id-encoding": "^1.2.0", | |
| 883 | "read-write-mutexify": "^2.1.0", | |
| 884 | "ready-resource": "^1.0.0", | |
| 885 | "safety-catch": "^1.0.1", | |
| 886 | "sodium-universal": "^4.0.0", | |
| 887 | "xache": "^1.1.0" | |
| 888 | } | |
| 889 | }, | |
| 890 | "node_modules/crc-native": { | |
| 891 | "version": "1.1.8", | |
| 892 | "resolved": "https://registry.npmjs.org/crc-native/-/crc-native-1.1.8.tgz", | |
| 893 | "integrity": "sha512-R/RYbD8lhG5M1k0iLX49tJdwHM0ipevJPpZw2Wk7b4EShyvx2P0uaq79xyRpcknjiVGiUfjFA8Gyy0mOlhABvw==", | |
| 894 | "license": "Apache-2.0", | |
| 895 | "optional": true, | |
| 896 | "dependencies": { | |
| 897 | "require-addon": "^1.1.0" | |
| 898 | } | |
| 899 | }, | |
| 900 | "node_modules/crc-universal": { | |
| 901 | "version": "1.0.4", | |
| 902 | "resolved": "https://registry.npmjs.org/crc-universal/-/crc-universal-1.0.4.tgz", | |
| 903 | "integrity": "sha512-CE9xWEI6Gd5V0Bdmj5NoWH3d7+EIe4zUpy1sv6uvKYznzsDP1vhiWsTBmL6q9IrH2P6RRshp+8AkhR4CMtY5Hg==", | |
| 904 | "license": "ISC", | |
| 905 | "optionalDependencies": { | |
| 906 | "crc-native": "^1.0.3" | |
| 907 | } | |
| 908 | }, | |
| 909 | "node_modules/debounceify": { | |
| 910 | "version": "1.1.0", | |
| 911 | "resolved": "https://registry.npmjs.org/debounceify/-/debounceify-1.1.0.tgz", | |
| 912 | "integrity": "sha512-eKuHDVfJVg+u/0nPy8P+fhnLgbyuTgVxuCRrS/R7EpDSMMkBDgSes41MJtSAY1F1hcqfHz3Zy/qpqHHIp/EhdA==", | |
| 913 | "license": "MIT" | |
| 914 | }, | |
| 915 | "node_modules/dht-rpc": { | |
| 916 | "version": "6.26.1", | |
| 917 | "resolved": "https://registry.npmjs.org/dht-rpc/-/dht-rpc-6.26.1.tgz", | |
| 918 | "integrity": "sha512-FzveCZDP000Ks1aRlgpwM6sLGgMFnoE3FxO0BAf9XlvLo9dogq/CHjB1+I2BtyZSa4ZthAkaUN+JX9rFpO8gig==", | |
| 919 | "license": "MIT", | |
| 920 | "dependencies": { | |
| 921 | "adaptive-timeout": "^1.0.1", | |
| 922 | "b4a": "^1.6.1", | |
| 923 | "bare-events": "^2.2.0", | |
| 924 | "compact-encoding": "^2.11.0", | |
| 925 | "compact-encoding-net": "^1.2.0", | |
| 926 | "fast-fifo": "^1.1.0", | |
| 927 | "kademlia-routing-table": "^1.0.1", | |
| 928 | "nat-sampler": "^1.0.1", | |
| 929 | "sodium-universal": "^5.0.0", | |
| 930 | "streamx": "^2.13.2", | |
| 931 | "time-ordered-set": "^2.0.0", | |
| 932 | "udx-native": "^1.5.3" | |
| 933 | } | |
| 934 | }, | |
| 935 | "node_modules/dht-rpc/node_modules/sodium-native": { | |
| 936 | "version": "5.0.10", | |
| 937 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 938 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 939 | "license": "MIT", | |
| 940 | "dependencies": { | |
| 941 | "require-addon": "^1.1.0", | |
| 942 | "which-runtime": "^1.2.1" | |
| 943 | }, | |
| 944 | "engines": { | |
| 945 | "bare": ">=1.16.0" | |
| 946 | } | |
| 947 | }, | |
| 948 | "node_modules/dht-rpc/node_modules/sodium-universal": { | |
| 949 | "version": "5.0.1", | |
| 950 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 951 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 952 | "license": "MIT", | |
| 953 | "dependencies": { | |
| 954 | "sodium-native": "^5.0.1" | |
| 955 | }, | |
| 956 | "peerDependencies": { | |
| 957 | "sodium-javascript": "~0.8.0" | |
| 958 | }, | |
| 959 | "peerDependenciesMeta": { | |
| 960 | "sodium-javascript": { | |
| 961 | "optional": true | |
| 962 | } | |
| 963 | } | |
| 964 | }, | |
| 965 | "node_modules/error-stack-parser": { | |
| 966 | "version": "2.1.4", | |
| 967 | "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", | |
| 968 | "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", | |
| 969 | "dev": true, | |
| 970 | "license": "MIT", | |
| 971 | "dependencies": { | |
| 972 | "stackframe": "^1.3.4" | |
| 973 | } | |
| 974 | }, | |
| 975 | "node_modules/events-universal": { | |
| 976 | "version": "1.0.1", | |
| 977 | "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", | |
| 978 | "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", | |
| 979 | "license": "Apache-2.0", | |
| 980 | "dependencies": { | |
| 981 | "bare-events": "^2.7.0" | |
| 982 | } | |
| 983 | }, | |
| 984 | "node_modules/fast-fifo": { | |
| 985 | "version": "1.3.2", | |
| 986 | "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", | |
| 987 | "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", | |
| 988 | "license": "MIT" | |
| 989 | }, | |
| 990 | "node_modules/flat-tree": { | |
| 991 | "version": "1.13.0", | |
| 992 | "resolved": "https://registry.npmjs.org/flat-tree/-/flat-tree-1.13.0.tgz", | |
| 993 | "integrity": "sha512-fT3HIuCPwHhFgJ20QYzDHgUG0zMmFg5cHvFiFo5h+QMSJ28TihsEVY0f8HGliuO+pOzmvjMx1odToeaEWkTnyQ==", | |
| 994 | "license": "MIT" | |
| 995 | }, | |
| 996 | "node_modules/fs-native-extensions": { | |
| 997 | "version": "1.4.5", | |
| 998 | "resolved": "https://registry.npmjs.org/fs-native-extensions/-/fs-native-extensions-1.4.5.tgz", | |
| 999 | "integrity": "sha512-ekV0T//iDm4AvhOcuPaHpxub4DI7HvY5ucLJVDvi7T2J+NZkQ9S6MuvgP0yeQvoqNUaAGyLjVYb1905BF9bpmg==", | |
| 1000 | "license": "Apache-2.0", | |
| 1001 | "optional": true, | |
| 1002 | "dependencies": { | |
| 1003 | "require-addon": "^1.1.0", | |
| 1004 | "which-runtime": "^1.2.0" | |
| 1005 | } | |
| 1006 | }, | |
| 1007 | "node_modules/globbie": { | |
| 1008 | "version": "1.0.3", | |
| 1009 | "resolved": "https://registry.npmjs.org/globbie/-/globbie-1.0.3.tgz", | |
| 1010 | "integrity": "sha512-hcryJmKcftf82xfBWTWxuUITWIIoYO3ec104V17SMHGFl6VLbm1d1Ju9LX9L+jyJTwICpemX/dmPI/HGYoKr1A==", | |
| 1011 | "dev": true, | |
| 1012 | "license": "Apache-2.0", | |
| 1013 | "dependencies": { | |
| 1014 | "bare-fs": "^4.1.2", | |
| 1015 | "bare-path": "^3.0.0", | |
| 1016 | "bare-process": "^4.2.1", | |
| 1017 | "picomatch": "^4.0.2" | |
| 1018 | } | |
| 1019 | }, | |
| 1020 | "node_modules/hyperbee": { | |
| 1021 | "version": "2.27.3", | |
| 1022 | "resolved": "https://registry.npmjs.org/hyperbee/-/hyperbee-2.27.3.tgz", | |
| 1023 | "integrity": "sha512-PXURH2U4juUZyJRKHTrY5z1zX851pmI1Q0jfv5F/hCIErDt/ND8jOZuxc3hfOLM9f0W3qJEDTMlV5AJBkVPy8w==", | |
| 1024 | "license": "MIT", | |
| 1025 | "dependencies": { | |
| 1026 | "b4a": "^1.6.0", | |
| 1027 | "codecs": "^3.0.0", | |
| 1028 | "debounceify": "^1.0.0", | |
| 1029 | "hypercore-errors": "^1.0.0", | |
| 1030 | "mutexify": "^1.4.0", | |
| 1031 | "protocol-buffers-encodings": "^1.2.0", | |
| 1032 | "rache": "^1.0.0", | |
| 1033 | "ready-resource": "^1.0.0", | |
| 1034 | "resolve-reject-promise": "^1.1.0", | |
| 1035 | "safety-catch": "^1.0.2", | |
| 1036 | "streamx": "^2.12.4", | |
| 1037 | "unslab": "^1.2.0" | |
| 1038 | } | |
| 1039 | }, | |
| 1040 | "node_modules/hyperblobs": { | |
| 1041 | "version": "2.8.0", | |
| 1042 | "resolved": "https://registry.npmjs.org/hyperblobs/-/hyperblobs-2.8.0.tgz", | |
| 1043 | "integrity": "sha512-s9txNdHAdDMZjOn7B5nPOKXcUe/IGnzgWYhczAQs3TxEAcXFoXTBAtBd0HjorLa0pfvcSgq+v61cW2Chr4LACA==", | |
| 1044 | "license": "Apache-2.0", | |
| 1045 | "dependencies": { | |
| 1046 | "b4a": "^1.6.1", | |
| 1047 | "bare-events": "^2.5.0", | |
| 1048 | "hypercore-errors": "^1.1.1", | |
| 1049 | "mutexify": "^1.4.0", | |
| 1050 | "speedometer": "^1.1.0", | |
| 1051 | "streamx": "^2.13.2" | |
| 1052 | } | |
| 1053 | }, | |
| 1054 | "node_modules/hypercore": { | |
| 1055 | "version": "10.38.2", | |
| 1056 | "resolved": "https://registry.npmjs.org/hypercore/-/hypercore-10.38.2.tgz", | |
| 1057 | "integrity": "sha512-2vx42g2RKdP/o2XzlxKfvgEaZ3+h/B855gCR8iscUDnxUoOZej7vXZ2buex0sweDUEW5aEfMxFR6o5hncio3Nw==", | |
| 1058 | "license": "MIT", | |
| 1059 | "dependencies": { | |
| 1060 | "@hyperswarm/secret-stream": "^6.0.0", | |
| 1061 | "b4a": "^1.1.0", | |
| 1062 | "bare-events": "^2.2.0", | |
| 1063 | "big-sparse-array": "^1.0.3", | |
| 1064 | "compact-encoding": "^2.11.0", | |
| 1065 | "crc-universal": "^1.0.2", | |
| 1066 | "fast-fifo": "^1.3.0", | |
| 1067 | "flat-tree": "^1.9.0", | |
| 1068 | "hypercore-crypto": "^3.2.1", | |
| 1069 | "hypercore-errors": "^1.2.0", | |
| 1070 | "hypercore-id-encoding": "^1.2.0", | |
| 1071 | "is-options": "^1.0.1", | |
| 1072 | "protomux": "^3.5.0", | |
| 1073 | "quickbit-universal": "^2.2.0", | |
| 1074 | "random-access-file": "^4.0.0", | |
| 1075 | "random-array-iterator": "^1.0.0", | |
| 1076 | "safety-catch": "^1.0.1", | |
| 1077 | "sodium-universal": "^4.0.0", | |
| 1078 | "streamx": "^2.12.4", | |
| 1079 | "unslab": "^1.3.0", | |
| 1080 | "xache": "^1.1.0", | |
| 1081 | "z32": "^1.0.0" | |
| 1082 | } | |
| 1083 | }, | |
| 1084 | "node_modules/hypercore-crypto": { | |
| 1085 | "version": "3.6.1", | |
| 1086 | "resolved": "https://registry.npmjs.org/hypercore-crypto/-/hypercore-crypto-3.6.1.tgz", | |
| 1087 | "integrity": "sha512-ltIz2uDwy9pO/ZGTvqcjzyBkvt6O4cVm4r/nNxh0GFs/RbQtqP/i4wCvLEdmU7ptgtnw7fI67WYD1aHPuv4OVA==", | |
| 1088 | "license": "MIT", | |
| 1089 | "dependencies": { | |
| 1090 | "b4a": "^1.6.6", | |
| 1091 | "compact-encoding": "^2.15.0", | |
| 1092 | "sodium-universal": "^5.0.0" | |
| 1093 | } | |
| 1094 | }, | |
| 1095 | "node_modules/hypercore-crypto/node_modules/sodium-native": { | |
| 1096 | "version": "5.0.10", | |
| 1097 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 1098 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 1099 | "license": "MIT", | |
| 1100 | "dependencies": { | |
| 1101 | "require-addon": "^1.1.0", | |
| 1102 | "which-runtime": "^1.2.1" | |
| 1103 | }, | |
| 1104 | "engines": { | |
| 1105 | "bare": ">=1.16.0" | |
| 1106 | } | |
| 1107 | }, | |
| 1108 | "node_modules/hypercore-crypto/node_modules/sodium-universal": { | |
| 1109 | "version": "5.0.1", | |
| 1110 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 1111 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 1112 | "license": "MIT", | |
| 1113 | "dependencies": { | |
| 1114 | "sodium-native": "^5.0.1" | |
| 1115 | }, | |
| 1116 | "peerDependencies": { | |
| 1117 | "sodium-javascript": "~0.8.0" | |
| 1118 | }, | |
| 1119 | "peerDependenciesMeta": { | |
| 1120 | "sodium-javascript": { | |
| 1121 | "optional": true | |
| 1122 | } | |
| 1123 | } | |
| 1124 | }, | |
| 1125 | "node_modules/hypercore-errors": { | |
| 1126 | "version": "1.5.0", | |
| 1127 | "resolved": "https://registry.npmjs.org/hypercore-errors/-/hypercore-errors-1.5.0.tgz", | |
| 1128 | "integrity": "sha512-5KQ/SuDxsvet+7qWA35Ay6zdD9WyAHQoyWHGcPUTbmJBd300gvNIJoi3oma7kp4TTCSzii6qYumNZe/s0j/saQ==", | |
| 1129 | "license": "Apache-2.0", | |
| 1130 | "dependencies": { | |
| 1131 | "hypercore-id-encoding": "^1.3.0" | |
| 1132 | } | |
| 1133 | }, | |
| 1134 | "node_modules/hypercore-id-encoding": { | |
| 1135 | "version": "1.3.0", | |
| 1136 | "resolved": "https://registry.npmjs.org/hypercore-id-encoding/-/hypercore-id-encoding-1.3.0.tgz", | |
| 1137 | "integrity": "sha512-W6sHdGo5h7LXEsoWfKf/KfuROZmZRQDlGqJF2EPHW+noCK66Vvr0+zE6cL0vqQi18s0kQPeN7Sq3QyR0Ytc2VQ==", | |
| 1138 | "license": "Apache-2.0", | |
| 1139 | "dependencies": { | |
| 1140 | "b4a": "^1.5.3", | |
| 1141 | "z32": "^1.0.0" | |
| 1142 | } | |
| 1143 | }, | |
| 1144 | "node_modules/hyperdht": { | |
| 1145 | "version": "6.29.0", | |
| 1146 | "resolved": "https://registry.npmjs.org/hyperdht/-/hyperdht-6.29.0.tgz", | |
| 1147 | "integrity": "sha512-okIpelpIhRTBDCCe2CNWfLHbVSQ5DPbl1Npb1zet8SqrkCsYF27RHJ04IaYqiigHI9y0KZgjOHdZbejalbFVoQ==", | |
| 1148 | "license": "MIT", | |
| 1149 | "dependencies": { | |
| 1150 | "@hyperswarm/secret-stream": "^6.6.2", | |
| 1151 | "b4a": "^1.3.1", | |
| 1152 | "bare-events": "^2.2.0", | |
| 1153 | "blind-relay": "^1.3.0", | |
| 1154 | "bogon": "^1.0.0", | |
| 1155 | "compact-encoding": "^2.4.1", | |
| 1156 | "compact-encoding-net": "^1.0.1", | |
| 1157 | "dht-rpc": "^6.15.1", | |
| 1158 | "hypercore-crypto": "^3.3.0", | |
| 1159 | "hypercore-id-encoding": "^1.2.0", | |
| 1160 | "noise-curve-ed": "^2.0.0", | |
| 1161 | "noise-handshake": "^4.0.0", | |
| 1162 | "record-cache": "^1.1.1", | |
| 1163 | "safety-catch": "^1.0.1", | |
| 1164 | "signal-promise": "^1.0.3", | |
| 1165 | "sodium-universal": "^5.0.1", | |
| 1166 | "streamx": "^2.16.1", | |
| 1167 | "unslab": "^1.3.0", | |
| 1168 | "xache": "^1.1.0" | |
| 1169 | }, | |
| 1170 | "bin": { | |
| 1171 | "hyperdht": "bin.js" | |
| 1172 | } | |
| 1173 | }, | |
| 1174 | "node_modules/hyperdht/node_modules/sodium-native": { | |
| 1175 | "version": "5.0.10", | |
| 1176 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 1177 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 1178 | "license": "MIT", | |
| 1179 | "dependencies": { | |
| 1180 | "require-addon": "^1.1.0", | |
| 1181 | "which-runtime": "^1.2.1" | |
| 1182 | }, | |
| 1183 | "engines": { | |
| 1184 | "bare": ">=1.16.0" | |
| 1185 | } | |
| 1186 | }, | |
| 1187 | "node_modules/hyperdht/node_modules/sodium-universal": { | |
| 1188 | "version": "5.0.1", | |
| 1189 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 1190 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 1191 | "license": "MIT", | |
| 1192 | "dependencies": { | |
| 1193 | "sodium-native": "^5.0.1" | |
| 1194 | }, | |
| 1195 | "peerDependencies": { | |
| 1196 | "sodium-javascript": "~0.8.0" | |
| 1197 | }, | |
| 1198 | "peerDependenciesMeta": { | |
| 1199 | "sodium-javascript": { | |
| 1200 | "optional": true | |
| 1201 | } | |
| 1202 | } | |
| 1203 | }, | |
| 1204 | "node_modules/hyperdrive": { | |
| 1205 | "version": "11.13.4", | |
| 1206 | "resolved": "https://registry.npmjs.org/hyperdrive/-/hyperdrive-11.13.4.tgz", | |
| 1207 | "integrity": "sha512-PN0gClSRx+QBbsLGnGqo2ei9TavJN8d6D3jv2yNMlu7zgXSnqR6QcGo8mgyKnrzgR336oNfvQ2C2Y6cY5klScg==", | |
| 1208 | "license": "Apache-2.0", | |
| 1209 | "dependencies": { | |
| 1210 | "hyperbee": "^2.11.1", | |
| 1211 | "hyperblobs": "^2.3.0", | |
| 1212 | "hypercore": "^10.33.0", | |
| 1213 | "hypercore-errors": "^1.0.0", | |
| 1214 | "is-options": "^1.0.2", | |
| 1215 | "mirror-drive": "^1.2.0", | |
| 1216 | "ready-resource": "^1.0.0", | |
| 1217 | "safety-catch": "^1.0.2", | |
| 1218 | "speedometer": "^1.1.0", | |
| 1219 | "streamx": "^2.12.4", | |
| 1220 | "sub-encoder": "^2.1.1", | |
| 1221 | "unix-path-resolve": "^1.0.2" | |
| 1222 | } | |
| 1223 | }, | |
| 1224 | "node_modules/hyperswarm": { | |
| 1225 | "version": "4.16.0", | |
| 1226 | "resolved": "https://registry.npmjs.org/hyperswarm/-/hyperswarm-4.16.0.tgz", | |
| 1227 | "integrity": "sha512-f86RsI1qP/nbBTv4wYxOhu1JnWvGZgiC1w69aRFn0xGSavLMWjvbbVspVApOnRjiPKpYVVGACXdrvEOrL3rHdw==", | |
| 1228 | "license": "MIT", | |
| 1229 | "dependencies": { | |
| 1230 | "b4a": "^1.3.1", | |
| 1231 | "bare-events": "^2.2.0", | |
| 1232 | "hyperdht": "^6.21.0", | |
| 1233 | "safety-catch": "^1.0.2", | |
| 1234 | "shuffled-priority-queue": "^2.1.0", | |
| 1235 | "streamx": "^2.22.1", | |
| 1236 | "unslab": "^1.3.0" | |
| 1237 | } | |
| 1238 | }, | |
| 1239 | "node_modules/is-options": { | |
| 1240 | "version": "1.0.2", | |
| 1241 | "resolved": "https://registry.npmjs.org/is-options/-/is-options-1.0.2.tgz", | |
| 1242 | "integrity": "sha512-u+Ai74c8Q74aS8BuHwPdI1jptGOT1FQXgCq8/zv0xRuE+wRgSMEJLj8lVO8Zp9BeGb29BXY6AsNPinfqjkr7Fg==", | |
| 1243 | "license": "MIT", | |
| 1244 | "dependencies": { | |
| 1245 | "b4a": "^1.1.1" | |
| 1246 | } | |
| 1247 | }, | |
| 1248 | "node_modules/kademlia-routing-table": { | |
| 1249 | "version": "1.0.6", | |
| 1250 | "resolved": "https://registry.npmjs.org/kademlia-routing-table/-/kademlia-routing-table-1.0.6.tgz", | |
| 1251 | "integrity": "sha512-Ve6jwIlUCYvUzBnXnzVRHDZCFgXURW9gmF3r7n05kZs/2rNbLHXwGdcq0qIaSwdmJCvtosgR4JensnVU65hzNQ==", | |
| 1252 | "license": "MIT", | |
| 1253 | "dependencies": { | |
| 1254 | "bare-events": "^2.2.0" | |
| 1255 | } | |
| 1256 | }, | |
| 1257 | "node_modules/mirror-drive": { | |
| 1258 | "version": "1.12.0", | |
| 1259 | "resolved": "https://registry.npmjs.org/mirror-drive/-/mirror-drive-1.12.0.tgz", | |
| 1260 | "integrity": "sha512-ASoYsRul+fzGT8rzgEEiinKCLZYd/03nclngLim6Gwm97n8jPMHbhIXgBlnkcqeASKsCnTIm3w9hxR6OBbRW4Q==", | |
| 1261 | "license": "Apache-2.0", | |
| 1262 | "dependencies": { | |
| 1263 | "bare-events": "^2.8.2", | |
| 1264 | "binary-stream-equals": "^1.0.0", | |
| 1265 | "same-data": "^1.0.0", | |
| 1266 | "speedometer": "^1.1.0", | |
| 1267 | "streamx": "^2.22.1", | |
| 1268 | "unix-path-resolve": "^1.0.2" | |
| 1269 | } | |
| 1270 | }, | |
| 1271 | "node_modules/mutexify": { | |
| 1272 | "version": "1.4.0", | |
| 1273 | "resolved": "https://registry.npmjs.org/mutexify/-/mutexify-1.4.0.tgz", | |
| 1274 | "integrity": "sha512-pbYSsOrSB/AKN5h/WzzLRMFgZhClWccf2XIB4RSMC8JbquiB0e0/SH5AIfdQMdyHmYtv4seU7yV/TvAwPLJ1Yg==", | |
| 1275 | "license": "MIT", | |
| 1276 | "dependencies": { | |
| 1277 | "queue-tick": "^1.0.0" | |
| 1278 | } | |
| 1279 | }, | |
| 1280 | "node_modules/nanoassert": { | |
| 1281 | "version": "2.0.0", | |
| 1282 | "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz", | |
| 1283 | "integrity": "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==", | |
| 1284 | "license": "ISC" | |
| 1285 | }, | |
| 1286 | "node_modules/nat-sampler": { | |
| 1287 | "version": "1.0.1", | |
| 1288 | "resolved": "https://registry.npmjs.org/nat-sampler/-/nat-sampler-1.0.1.tgz", | |
| 1289 | "integrity": "sha512-yQvyNN7xbqR8crTKk3U8gRgpcV1Az+vfCEijiHu9oHHsnIl8n3x+yXNHl42M6L3czGynAVoOT9TqBfS87gDdcw==", | |
| 1290 | "license": "MIT" | |
| 1291 | }, | |
| 1292 | "node_modules/noise-curve-ed": { | |
| 1293 | "version": "2.1.0", | |
| 1294 | "resolved": "https://registry.npmjs.org/noise-curve-ed/-/noise-curve-ed-2.1.0.tgz", | |
| 1295 | "integrity": "sha512-zAzJx+VwZM3w6EA1hTmDhJfvAnCeBQn/1FAeZ0LtGxCcCtlAK/uJXQVF/eDVUOaAZ286lHlx77WJ+qj9SmsRRg==", | |
| 1296 | "license": "ISC", | |
| 1297 | "dependencies": { | |
| 1298 | "b4a": "^1.1.0", | |
| 1299 | "nanoassert": "^2.0.0", | |
| 1300 | "sodium-universal": "^5.0.0" | |
| 1301 | } | |
| 1302 | }, | |
| 1303 | "node_modules/noise-curve-ed/node_modules/sodium-native": { | |
| 1304 | "version": "5.0.10", | |
| 1305 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 1306 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 1307 | "license": "MIT", | |
| 1308 | "dependencies": { | |
| 1309 | "require-addon": "^1.1.0", | |
| 1310 | "which-runtime": "^1.2.1" | |
| 1311 | }, | |
| 1312 | "engines": { | |
| 1313 | "bare": ">=1.16.0" | |
| 1314 | } | |
| 1315 | }, | |
| 1316 | "node_modules/noise-curve-ed/node_modules/sodium-universal": { | |
| 1317 | "version": "5.0.1", | |
| 1318 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 1319 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 1320 | "license": "MIT", | |
| 1321 | "dependencies": { | |
| 1322 | "sodium-native": "^5.0.1" | |
| 1323 | }, | |
| 1324 | "peerDependencies": { | |
| 1325 | "sodium-javascript": "~0.8.0" | |
| 1326 | }, | |
| 1327 | "peerDependenciesMeta": { | |
| 1328 | "sodium-javascript": { | |
| 1329 | "optional": true | |
| 1330 | } | |
| 1331 | } | |
| 1332 | }, | |
| 1333 | "node_modules/noise-handshake": { | |
| 1334 | "version": "4.2.0", | |
| 1335 | "resolved": "https://registry.npmjs.org/noise-handshake/-/noise-handshake-4.2.0.tgz", | |
| 1336 | "integrity": "sha512-9O/VTNX/E2/AToyMTTDU0J/4WhaXMTdqc2DHs9vf+snoZ0cenSBq0dNYTVV1snYYEkmo6QeRrYMxtqtoYnY+LA==", | |
| 1337 | "license": "Apache-2.0", | |
| 1338 | "dependencies": { | |
| 1339 | "b4a": "^1.1.0", | |
| 1340 | "nanoassert": "^2.0.0", | |
| 1341 | "sodium-universal": "^5.0.0" | |
| 1342 | } | |
| 1343 | }, | |
| 1344 | "node_modules/noise-handshake/node_modules/sodium-native": { | |
| 1345 | "version": "5.0.10", | |
| 1346 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 1347 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 1348 | "license": "MIT", | |
| 1349 | "dependencies": { | |
| 1350 | "require-addon": "^1.1.0", | |
| 1351 | "which-runtime": "^1.2.1" | |
| 1352 | }, | |
| 1353 | "engines": { | |
| 1354 | "bare": ">=1.16.0" | |
| 1355 | } | |
| 1356 | }, | |
| 1357 | "node_modules/noise-handshake/node_modules/sodium-universal": { | |
| 1358 | "version": "5.0.1", | |
| 1359 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 1360 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 1361 | "license": "MIT", | |
| 1362 | "dependencies": { | |
| 1363 | "sodium-native": "^5.0.1" | |
| 1364 | }, | |
| 1365 | "peerDependencies": { | |
| 1366 | "sodium-javascript": "~0.8.0" | |
| 1367 | }, | |
| 1368 | "peerDependenciesMeta": { | |
| 1369 | "sodium-javascript": { | |
| 1370 | "optional": true | |
| 1371 | } | |
| 1372 | } | |
| 1373 | }, | |
| 1374 | "node_modules/paparam": { | |
| 1375 | "version": "1.10.0", | |
| 1376 | "resolved": "https://registry.npmjs.org/paparam/-/paparam-1.10.0.tgz", | |
| 1377 | "integrity": "sha512-p36QQrwU3X6fj5d2JN20B4lcZI/O3fxMVUHc1xD7MNe5bcOf3jIgE0CIK5Zv/s5YAUGlobTJ/kKXrh1FJVrs3w==", | |
| 1378 | "dev": true, | |
| 1379 | "license": "Apache-2.0" | |
| 1380 | }, | |
| 1381 | "node_modules/picomatch": { | |
| 1382 | "version": "4.0.3", | |
| 1383 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", | |
| 1384 | "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", | |
| 1385 | "dev": true, | |
| 1386 | "license": "MIT", | |
| 1387 | "engines": { | |
| 1388 | "node": ">=12" | |
| 1389 | }, | |
| 1390 | "funding": { | |
| 1391 | "url": "https://github.com/sponsors/jonschlinkert" | |
| 1392 | } | |
| 1393 | }, | |
| 1394 | "node_modules/protocol-buffers-encodings": { | |
| 1395 | "version": "1.2.0", | |
| 1396 | "resolved": "https://registry.npmjs.org/protocol-buffers-encodings/-/protocol-buffers-encodings-1.2.0.tgz", | |
| 1397 | "integrity": "sha512-daeNPuKh1NlLD1uDfbLpD+xyUTc07nEtfHwmBZmt/vH0B7VOM+JOCOpDcx9ZRpqHjAiIkGqyTDi+wfGSl17R9w==", | |
| 1398 | "license": "MIT", | |
| 1399 | "dependencies": { | |
| 1400 | "b4a": "^1.6.0", | |
| 1401 | "signed-varint": "^2.0.1", | |
| 1402 | "varint": "5.0.0" | |
| 1403 | } | |
| 1404 | }, | |
| 1405 | "node_modules/protomux": { | |
| 1406 | "version": "3.10.1", | |
| 1407 | "resolved": "https://registry.npmjs.org/protomux/-/protomux-3.10.1.tgz", | |
| 1408 | "integrity": "sha512-jgBqx8ZyaBWea/DFG4eOu1scOaeBwcnagiRC1XFVrjeGt7oAb0Pk5udPpBUpJ4DJBRjra50jD6YcZiQQTRqaaA==", | |
| 1409 | "license": "MIT", | |
| 1410 | "dependencies": { | |
| 1411 | "b4a": "^1.3.1", | |
| 1412 | "compact-encoding": "^2.5.1", | |
| 1413 | "queue-tick": "^1.0.0", | |
| 1414 | "safety-catch": "^1.0.1", | |
| 1415 | "unslab": "^1.3.0" | |
| 1416 | } | |
| 1417 | }, | |
| 1418 | "node_modules/queue-tick": { | |
| 1419 | "version": "1.0.1", | |
| 1420 | "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", | |
| 1421 | "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", | |
| 1422 | "license": "MIT" | |
| 1423 | }, | |
| 1424 | "node_modules/quickbit-native": { | |
| 1425 | "version": "2.4.8", | |
| 1426 | "resolved": "https://registry.npmjs.org/quickbit-native/-/quickbit-native-2.4.8.tgz", | |
| 1427 | "integrity": "sha512-FcCcqI+nIAWGknqhtrYT5TSD7t/N+Xd8ctM+2PrIIBuwOi5hx0SxAvuPtzLIEMfT/2h9+fhBakUe2uALOHX6yw==", | |
| 1428 | "license": "Apache-2.0", | |
| 1429 | "optional": true, | |
| 1430 | "dependencies": { | |
| 1431 | "require-addon": "^1.1.0" | |
| 1432 | } | |
| 1433 | }, | |
| 1434 | "node_modules/quickbit-universal": { | |
| 1435 | "version": "2.2.0", | |
| 1436 | "resolved": "https://registry.npmjs.org/quickbit-universal/-/quickbit-universal-2.2.0.tgz", | |
| 1437 | "integrity": "sha512-w02i1R8n7+6pEKTud8DfF8zbFY9o7RtPlUc3jWbtCkDKvhbx/AvV7oNnz4/TcmsPGpSJS+fq5Ud6RH6+YPvSGg==", | |
| 1438 | "license": "ISC", | |
| 1439 | "dependencies": { | |
| 1440 | "b4a": "^1.6.0", | |
| 1441 | "simdle-universal": "^1.1.0" | |
| 1442 | }, | |
| 1443 | "optionalDependencies": { | |
| 1444 | "quickbit-native": "^2.2.0" | |
| 1445 | } | |
| 1446 | }, | |
| 1447 | "node_modules/rache": { | |
| 1448 | "version": "1.0.0", | |
| 1449 | "resolved": "https://registry.npmjs.org/rache/-/rache-1.0.0.tgz", | |
| 1450 | "integrity": "sha512-e0k0g0w/8jOCB+7YqCIlOa+OJ38k0wrYS4x18pMSmqOvLKoyhmMhmQyCcvfY6VaP8D75cqkEnlakXs+RYYLqNg==", | |
| 1451 | "license": "Apache-2.0" | |
| 1452 | }, | |
| 1453 | "node_modules/random-access-file": { | |
| 1454 | "version": "4.1.2", | |
| 1455 | "resolved": "https://registry.npmjs.org/random-access-file/-/random-access-file-4.1.2.tgz", | |
| 1456 | "integrity": "sha512-GQM6R78DceZDcQod8KxlDFwXIiUvlvuy1EOzxTDsjuDjW5NlnlZi0MOk6iI4itAj/2vcvdqcEExYbVpC/dJcEw==", | |
| 1457 | "license": "MIT", | |
| 1458 | "dependencies": { | |
| 1459 | "bare-fs": "^4.0.1", | |
| 1460 | "bare-path": "^3.0.0", | |
| 1461 | "random-access-storage": "^3.0.0" | |
| 1462 | }, | |
| 1463 | "optionalDependencies": { | |
| 1464 | "fs-native-extensions": "^1.3.1" | |
| 1465 | } | |
| 1466 | }, | |
| 1467 | "node_modules/random-access-storage": { | |
| 1468 | "version": "3.0.2", | |
| 1469 | "resolved": "https://registry.npmjs.org/random-access-storage/-/random-access-storage-3.0.2.tgz", | |
| 1470 | "integrity": "sha512-Es9maUyWdJXWKckKy9s1+vT+DEgAt+PBb9lxPaake/0EDUsHehloKGv9v1zimS2V3gpFAcQXubvc1Rgci2sDPQ==", | |
| 1471 | "license": "MIT", | |
| 1472 | "dependencies": { | |
| 1473 | "bare-events": "^2.2.0", | |
| 1474 | "queue-tick": "^1.0.0" | |
| 1475 | } | |
| 1476 | }, | |
| 1477 | "node_modules/random-array-iterator": { | |
| 1478 | "version": "1.0.0", | |
| 1479 | "resolved": "https://registry.npmjs.org/random-array-iterator/-/random-array-iterator-1.0.0.tgz", | |
| 1480 | "integrity": "sha512-u7xCM93XqKEvPTP6xZp2ehttcAemKnh73oKNf1FvzuVCfpt6dILDt1Kxl1LeBjm2iNIeR49VGFhy4Iz3yOun+Q==", | |
| 1481 | "license": "MIT" | |
| 1482 | }, | |
| 1483 | "node_modules/read-write-mutexify": { | |
| 1484 | "version": "2.1.0", | |
| 1485 | "resolved": "https://registry.npmjs.org/read-write-mutexify/-/read-write-mutexify-2.1.0.tgz", | |
| 1486 | "integrity": "sha512-fDw/p5/acI1ytVY1UbxEDma/ej1yJH/n9NcjS9YNzcE6sPBPWdlru3ydRa/UBowUg4zqOvNMD5SOGYJrlQ6MzQ==", | |
| 1487 | "license": "MIT" | |
| 1488 | }, | |
| 1489 | "node_modules/ready-resource": { | |
| 1490 | "version": "1.2.0", | |
| 1491 | "resolved": "https://registry.npmjs.org/ready-resource/-/ready-resource-1.2.0.tgz", | |
| 1492 | "integrity": "sha512-nfcco/8iAFV0M+2PYnmIc+/xY0iRb35d42HFHQ7AfjulbGEAFa+XWpByfwSyeVeiBoMLLFVMv1HixxNCqzSQ1g==", | |
| 1493 | "license": "MIT", | |
| 1494 | "dependencies": { | |
| 1495 | "bare-events": "^2.2.0" | |
| 1496 | } | |
| 1497 | }, | |
| 1498 | "node_modules/record-cache": { | |
| 1499 | "version": "1.2.0", | |
| 1500 | "resolved": "https://registry.npmjs.org/record-cache/-/record-cache-1.2.0.tgz", | |
| 1501 | "integrity": "sha512-kyy3HWCez2WrotaL3O4fTn0rsIdfRKOdQQcEJ9KpvmKmbffKVvwsloX063EgRUlpJIXHiDQFhJcTbZequ2uTZw==", | |
| 1502 | "license": "MIT", | |
| 1503 | "dependencies": { | |
| 1504 | "b4a": "^1.3.1" | |
| 1505 | } | |
| 1506 | }, | |
| 1507 | "node_modules/require-addon": { | |
| 1508 | "version": "1.2.0", | |
| 1509 | "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz", | |
| 1510 | "integrity": "sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA==", | |
| 1511 | "license": "Apache-2.0", | |
| 1512 | "dependencies": { | |
| 1513 | "bare-addon-resolve": "^1.3.0" | |
| 1514 | }, | |
| 1515 | "engines": { | |
| 1516 | "bare": ">=1.10.0" | |
| 1517 | } | |
| 1518 | }, | |
| 1519 | "node_modules/resolve-reject-promise": { | |
| 1520 | "version": "1.1.0", | |
| 1521 | "resolved": "https://registry.npmjs.org/resolve-reject-promise/-/resolve-reject-promise-1.1.0.tgz", | |
| 1522 | "integrity": "sha512-LWsTOA91AqzBTjSGgX79Tc130pwcBK6xjpJEO+qRT5IKZ6bGnHKcc8QL3upUBcWuU8OTIDzKK2VNSwmmlqvAVg==", | |
| 1523 | "license": "MIT" | |
| 1524 | }, | |
| 1525 | "node_modules/safety-catch": { | |
| 1526 | "version": "1.0.2", | |
| 1527 | "resolved": "https://registry.npmjs.org/safety-catch/-/safety-catch-1.0.2.tgz", | |
| 1528 | "integrity": "sha512-C1UYVZ4dtbBxEtvOcpjBaaD27nP8MlvyAQEp2fOTOEe6pfUpk1cDUxij6BR1jZup6rSyUTaBBplK7LanskrULA==", | |
| 1529 | "license": "MIT" | |
| 1530 | }, | |
| 1531 | "node_modules/same-data": { | |
| 1532 | "version": "1.0.0", | |
| 1533 | "resolved": "https://registry.npmjs.org/same-data/-/same-data-1.0.0.tgz", | |
| 1534 | "integrity": "sha512-Eqn7N2yV+aKMlUHTRqUwYG1Iv0cJqjlvLKj/GoP5PozJn361QaOYX14+v87r7NqQUZC22noP/LfLrSQiPwAygw==", | |
| 1535 | "license": "MIT" | |
| 1536 | }, | |
| 1537 | "node_modules/same-object": { | |
| 1538 | "version": "1.0.2", | |
| 1539 | "resolved": "https://registry.npmjs.org/same-object/-/same-object-1.0.2.tgz", | |
| 1540 | "integrity": "sha512-csHWhvUsLbIOHDM/nP+KHWM+BLPsIzWkFa8HbzaI0G7BqKXgx+7FJpKTGgLXyz5amfdY2OVBcmXTqYOMEk04og==", | |
| 1541 | "dev": true, | |
| 1542 | "license": "MIT" | |
| 1543 | }, | |
| 1544 | "node_modules/shuffled-priority-queue": { | |
| 1545 | "version": "2.1.0", | |
| 1546 | "resolved": "https://registry.npmjs.org/shuffled-priority-queue/-/shuffled-priority-queue-2.1.0.tgz", | |
| 1547 | "integrity": "sha512-xhdh7fHyMsr0m/w2kDfRJuBFRS96b9l8ZPNWGaQ+PMvnUnZ/Eh+gJJ9NsHBd7P9k0399WYlCLzsy18EaMfyadA==", | |
| 1548 | "license": "MIT", | |
| 1549 | "dependencies": { | |
| 1550 | "unordered-set": "^2.0.1" | |
| 1551 | } | |
| 1552 | }, | |
| 1553 | "node_modules/signal-promise": { | |
| 1554 | "version": "1.0.3", | |
| 1555 | "resolved": "https://registry.npmjs.org/signal-promise/-/signal-promise-1.0.3.tgz", | |
| 1556 | "integrity": "sha512-WBgv0UnIq2C+Aeh0/n+IRpP6967eIx9WpynTUoiW3isPpfe1zu2LJzyfXdo9Tgef8yR/sGjcMvoUXD7EYdiz+g==", | |
| 1557 | "license": "MIT" | |
| 1558 | }, | |
| 1559 | "node_modules/signed-varint": { | |
| 1560 | "version": "2.0.1", | |
| 1561 | "resolved": "https://registry.npmjs.org/signed-varint/-/signed-varint-2.0.1.tgz", | |
| 1562 | "integrity": "sha512-abgDPg1106vuZZOvw7cFwdCABddfJRz5akcCcchzTbhyhYnsG31y4AlZEgp315T7W3nQq5P4xeOm186ZiPVFzw==", | |
| 1563 | "license": "MIT", | |
| 1564 | "dependencies": { | |
| 1565 | "varint": "~5.0.0" | |
| 1566 | } | |
| 1567 | }, | |
| 1568 | "node_modules/simdle-native": { | |
| 1569 | "version": "1.3.9", | |
| 1570 | "resolved": "https://registry.npmjs.org/simdle-native/-/simdle-native-1.3.9.tgz", | |
| 1571 | "integrity": "sha512-Isc8sP4OiiIU0mpslD4GHEnR0VQWvR/54WN7YtwEDkNdTJVWtpmvsSvsgRlw5BNGxdYXlVRegdnrSu10H/PhvA==", | |
| 1572 | "license": "Apache-2.0", | |
| 1573 | "optional": true, | |
| 1574 | "dependencies": { | |
| 1575 | "b4a": "^1.6.0", | |
| 1576 | "require-addon": "^1.1.0" | |
| 1577 | } | |
| 1578 | }, | |
| 1579 | "node_modules/simdle-universal": { | |
| 1580 | "version": "1.1.2", | |
| 1581 | "resolved": "https://registry.npmjs.org/simdle-universal/-/simdle-universal-1.1.2.tgz", | |
| 1582 | "integrity": "sha512-3n3w1bs+uwgHKQjt6arez83EywNlhZzYvNOhvAASTl/8KqNIcqr6aHyGt3JRlfuUC7iB0tomJRPlJ2cRGIpBzA==", | |
| 1583 | "license": "ISC", | |
| 1584 | "dependencies": { | |
| 1585 | "b4a": "^1.6.0" | |
| 1586 | }, | |
| 1587 | "optionalDependencies": { | |
| 1588 | "simdle-native": "^1.1.1" | |
| 1589 | } | |
| 1590 | }, | |
| 1591 | "node_modules/sodium-native": { | |
| 1592 | "version": "4.3.3", | |
| 1593 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.3.3.tgz", | |
| 1594 | "integrity": "sha512-OnxSlN3uyY8D0EsLHpmm2HOFmKddQVvEMmsakCrXUzSd8kjjbzL413t4ZNF3n0UxSwNgwTyUvkmZHTfuCeiYSw==", | |
| 1595 | "license": "MIT", | |
| 1596 | "dependencies": { | |
| 1597 | "require-addon": "^1.1.0" | |
| 1598 | } | |
| 1599 | }, | |
| 1600 | "node_modules/sodium-secretstream": { | |
| 1601 | "version": "1.2.0", | |
| 1602 | "resolved": "https://registry.npmjs.org/sodium-secretstream/-/sodium-secretstream-1.2.0.tgz", | |
| 1603 | "integrity": "sha512-q/DbraNFXm1KfCiiZvapmz5UC3OlpirYFIvBK2MhGaOFSb3gRyk8OXTi17UI9SGfshQNCpsVvlopogbzZNyW6Q==", | |
| 1604 | "license": "MIT", | |
| 1605 | "dependencies": { | |
| 1606 | "b4a": "^1.1.1", | |
| 1607 | "sodium-universal": "^5.0.0" | |
| 1608 | } | |
| 1609 | }, | |
| 1610 | "node_modules/sodium-secretstream/node_modules/sodium-native": { | |
| 1611 | "version": "5.0.10", | |
| 1612 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", | |
| 1613 | "integrity": "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA==", | |
| 1614 | "license": "MIT", | |
| 1615 | "dependencies": { | |
| 1616 | "require-addon": "^1.1.0", | |
| 1617 | "which-runtime": "^1.2.1" | |
| 1618 | }, | |
| 1619 | "engines": { | |
| 1620 | "bare": ">=1.16.0" | |
| 1621 | } | |
| 1622 | }, | |
| 1623 | "node_modules/sodium-secretstream/node_modules/sodium-universal": { | |
| 1624 | "version": "5.0.1", | |
| 1625 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 1626 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 1627 | "license": "MIT", | |
| 1628 | "dependencies": { | |
| 1629 | "sodium-native": "^5.0.1" | |
| 1630 | }, | |
| 1631 | "peerDependencies": { | |
| 1632 | "sodium-javascript": "~0.8.0" | |
| 1633 | }, | |
| 1634 | "peerDependenciesMeta": { | |
| 1635 | "sodium-javascript": { | |
| 1636 | "optional": true | |
| 1637 | } | |
| 1638 | } | |
| 1639 | }, | |
| 1640 | "node_modules/sodium-universal": { | |
| 1641 | "version": "4.0.1", | |
| 1642 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-4.0.1.tgz", | |
| 1643 | "integrity": "sha512-sNp13PrxYLaUFHTGoDKkSDFvoEu51bfzE12RwGlqU1fcrkpAOK0NvizaJzOWV0Omtk9me2+Pnbjcf/l0efxuGQ==", | |
| 1644 | "license": "MIT", | |
| 1645 | "dependencies": { | |
| 1646 | "sodium-native": "^4.0.0" | |
| 1647 | }, | |
| 1648 | "peerDependencies": { | |
| 1649 | "sodium-javascript": "~0.8.0" | |
| 1650 | }, | |
| 1651 | "peerDependenciesMeta": { | |
| 1652 | "sodium-javascript": { | |
| 1653 | "optional": true | |
| 1654 | } | |
| 1655 | } | |
| 1656 | }, | |
| 1657 | "node_modules/speedometer": { | |
| 1658 | "version": "1.1.0", | |
| 1659 | "resolved": "https://registry.npmjs.org/speedometer/-/speedometer-1.1.0.tgz", | |
| 1660 | "integrity": "sha512-z/wAiTESw2XVPssY2XRcme4niTc4S5FkkJ4gknudtVoc33Zil8TdTxHy5torRcgqMqksJV2Yz8HQcvtbsnw0mQ==", | |
| 1661 | "license": "MIT" | |
| 1662 | }, | |
| 1663 | "node_modules/stackframe": { | |
| 1664 | "version": "1.3.4", | |
| 1665 | "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", | |
| 1666 | "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", | |
| 1667 | "dev": true, | |
| 1668 | "license": "MIT" | |
| 1669 | }, | |
| 1670 | "node_modules/streamx": { | |
| 1671 | "version": "2.23.0", | |
| 1672 | "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", | |
| 1673 | "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", | |
| 1674 | "license": "MIT", | |
| 1675 | "dependencies": { | |
| 1676 | "events-universal": "^1.0.0", | |
| 1677 | "fast-fifo": "^1.3.2", | |
| 1678 | "text-decoder": "^1.1.0" | |
| 1679 | } | |
| 1680 | }, | |
| 1681 | "node_modules/sub-encoder": { | |
| 1682 | "version": "2.1.3", | |
| 1683 | "resolved": "https://registry.npmjs.org/sub-encoder/-/sub-encoder-2.1.3.tgz", | |
| 1684 | "integrity": "sha512-Xxx04ygZo/1J3yHvaSA6VhDmiSaBQkw/PmO3YnnYFXle+tfOGToC6FcDpIfMztWZXJzuKG14b/57HMkiL58C6A==", | |
| 1685 | "license": "Apache-2.0", | |
| 1686 | "dependencies": { | |
| 1687 | "b4a": "^1.6.0", | |
| 1688 | "codecs": "^3.1.0" | |
| 1689 | } | |
| 1690 | }, | |
| 1691 | "node_modules/test-tmp": { | |
| 1692 | "version": "1.4.0", | |
| 1693 | "resolved": "https://registry.npmjs.org/test-tmp/-/test-tmp-1.4.0.tgz", | |
| 1694 | "integrity": "sha512-GVggxGg+jXqP2Wbju50JVLo+9E+nIOPPyWqgr63EbOnNItIKu1cEbJpTWAJeflnyGqXOtcMI7ijHRp88GUkfDA==", | |
| 1695 | "dev": true, | |
| 1696 | "license": "MIT", | |
| 1697 | "dependencies": { | |
| 1698 | "bare-fs": "^4.0.1", | |
| 1699 | "bare-os": "^3.3.0", | |
| 1700 | "bare-path": "^3.0.0" | |
| 1701 | } | |
| 1702 | }, | |
| 1703 | "node_modules/text-decoder": { | |
| 1704 | "version": "1.2.6", | |
| 1705 | "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.6.tgz", | |
| 1706 | "integrity": "sha512-27FeW5GQFDfw0FpwMQhMagB7BztOOlmjcSRi97t2oplhKVTZtp0DZbSegSaXS5IIC6mxMvBG4AR1Sgc6BX3CQg==", | |
| 1707 | "license": "Apache-2.0", | |
| 1708 | "dependencies": { | |
| 1709 | "b4a": "^1.6.4" | |
| 1710 | } | |
| 1711 | }, | |
| 1712 | "node_modules/time-ordered-set": { | |
| 1713 | "version": "2.0.1", | |
| 1714 | "resolved": "https://registry.npmjs.org/time-ordered-set/-/time-ordered-set-2.0.1.tgz", | |
| 1715 | "integrity": "sha512-VJEKmgSN2UiOLB8BpN8Sh2b9LGMHTP5OPrQRpnKjvOheOyzk0mufbjzjKTIG2gO4A+Y+vDJ+0TcLbpUmMLsg8A==", | |
| 1716 | "license": "MIT" | |
| 1717 | }, | |
| 1718 | "node_modules/timeout-refresh": { | |
| 1719 | "version": "2.0.1", | |
| 1720 | "resolved": "https://registry.npmjs.org/timeout-refresh/-/timeout-refresh-2.0.1.tgz", | |
| 1721 | "integrity": "sha512-SVqEcMZBsZF9mA78rjzCrYrUs37LMJk3ShZ851ygZYW1cMeIjs9mL57KO6Iv5mmjSQnOe/29/VAfGXo+oRCiVw==", | |
| 1722 | "license": "MIT" | |
| 1723 | }, | |
| 1724 | "node_modules/tmatch": { | |
| 1725 | "version": "5.0.0", | |
| 1726 | "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-5.0.0.tgz", | |
| 1727 | "integrity": "sha512-Ib9OtBkpHn07tXP04SlN1SYRxFgTk6wSM2EBmjjxug4u5RXPRVLkdFJSS1PmrQidaSB8Lru9nRtViQBsbxzE5Q==", | |
| 1728 | "dev": true, | |
| 1729 | "license": "ISC", | |
| 1730 | "engines": { | |
| 1731 | "node": ">=8" | |
| 1732 | } | |
| 1733 | }, | |
| 1734 | "node_modules/udx-native": { | |
| 1735 | "version": "1.19.2", | |
| 1736 | "resolved": "https://registry.npmjs.org/udx-native/-/udx-native-1.19.2.tgz", | |
| 1737 | "integrity": "sha512-RNYh+UhfryCsF5hE2ZOuIqcZ+qdipXK3UsarwxWJwsUQZFE3ybwz0mPjwb5ev1PMBcjFahWiepS/q0wwL51c2g==", | |
| 1738 | "license": "Apache-2.0", | |
| 1739 | "dependencies": { | |
| 1740 | "b4a": "^1.5.0", | |
| 1741 | "bare-events": "^2.2.0", | |
| 1742 | "require-addon": "^1.1.0", | |
| 1743 | "streamx": "^2.22.0" | |
| 1744 | }, | |
| 1745 | "engines": { | |
| 1746 | "bare": ">=1.17.4" | |
| 1747 | } | |
| 1748 | }, | |
| 1749 | "node_modules/unix-path-resolve": { | |
| 1750 | "version": "1.0.2", | |
| 1751 | "resolved": "https://registry.npmjs.org/unix-path-resolve/-/unix-path-resolve-1.0.2.tgz", | |
| 1752 | "integrity": "sha512-kG4g5nobBBaMnH2XbrS4sLUXEpx4nY2J3C6KAlAUcnahG2HChxSPVKWYrqEq76iTo+cyMkLUjqxGaQR2tz097Q==", | |
| 1753 | "license": "MIT" | |
| 1754 | }, | |
| 1755 | "node_modules/unordered-set": { | |
| 1756 | "version": "2.0.1", | |
| 1757 | "resolved": "https://registry.npmjs.org/unordered-set/-/unordered-set-2.0.1.tgz", | |
| 1758 | "integrity": "sha512-eUmNTPzdx+q/WvOHW0bgGYLWvWHNT3PTKEQLg0MAQhc0AHASHVHoP/9YytYd4RBVariqno/mEUhVZN98CmD7bg==", | |
| 1759 | "license": "MIT" | |
| 1760 | }, | |
| 1761 | "node_modules/unslab": { | |
| 1762 | "version": "1.3.0", | |
| 1763 | "resolved": "https://registry.npmjs.org/unslab/-/unslab-1.3.0.tgz", | |
| 1764 | "integrity": "sha512-YATkfKAFj47kTzmiQrWXMyRvaVrHsW6MEALa4bm+FhiA2YG4oira+Z3DXN6LrYOYn2Y8eO94Lwl9DOHjs1FpoQ==", | |
| 1765 | "license": "Apache-2.0", | |
| 1766 | "dependencies": { | |
| 1767 | "b4a": "^1.6.6" | |
| 1768 | } | |
| 1769 | }, | |
| 1770 | "node_modules/v8-to-istanbul": { | |
| 1771 | "version": "9.3.0", | |
| 1772 | "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", | |
| 1773 | "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", | |
| 1774 | "dev": true, | |
| 1775 | "license": "ISC", | |
| 1776 | "dependencies": { | |
| 1777 | "@jridgewell/trace-mapping": "^0.3.12", | |
| 1778 | "@types/istanbul-lib-coverage": "^2.0.1", | |
| 1779 | "convert-source-map": "^2.0.0" | |
| 1780 | }, | |
| 1781 | "engines": { | |
| 1782 | "node": ">=10.12.0" | |
| 1783 | } | |
| 1784 | }, | |
| 1785 | "node_modules/varint": { | |
| 1786 | "version": "5.0.0", | |
| 1787 | "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.0.tgz", | |
| 1788 | "integrity": "sha512-gC13b/bWrqQoKY2EmROCZ+AR0jitc6DnDGaQ6Ls9QpKmuSgJB1eQ7H3KETtQm7qSdMWMKCmsshyCmUwMLh3OAA==", | |
| 1789 | "license": "MIT" | |
| 1790 | }, | |
| 1791 | "node_modules/which-runtime": { | |
| 1792 | "version": "1.3.2", | |
| 1793 | "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.3.2.tgz", | |
| 1794 | "integrity": "sha512-5kwCfWml7+b2NO7KrLMhYihjRx0teKkd3yGp1Xk5Vaf2JGdSh+rgVhEALAD9c/59dP+YwJHXoEO7e8QPy7gOkw==", | |
| 1795 | "license": "Apache-2.0" | |
| 1796 | }, | |
| 1797 | "node_modules/xache": { | |
| 1798 | "version": "1.2.1", | |
| 1799 | "resolved": "https://registry.npmjs.org/xache/-/xache-1.2.1.tgz", | |
| 1800 | "integrity": "sha512-igRS6jPreJ54ABdzhh4mCDXcz+XMaWO2q1ABRV2yWYuk29jlp8VT7UBdCqNkX7rpYBbXsebVVKkwIuYZjyZNqA==", | |
| 1801 | "license": "MIT" | |
| 1802 | }, | |
| 1803 | "node_modules/z32": { | |
| 1804 | "version": "1.1.0", | |
| 1805 | "resolved": "https://registry.npmjs.org/z32/-/z32-1.1.0.tgz", | |
| 1806 | "integrity": "sha512-1WUHy+VS6d0HPNspDxvLssBbeQjXMjSnpv0vH82vRAUfg847NmX3OXozp/hRP5jPhxBbrVzrgvAt+UsGNzRFQQ==", | |
| 1807 | "license": "MIT", | |
| 1808 | "dependencies": { | |
| 1809 | "b4a": "^1.5.3" | |
| 1810 | } | |
| 1811 | } | |
| 1812 | } | |
| 1813 | } |
| … | … | @@ -0,0 +1,26 @@ |
| 1 | { | |
| 2 | "name": "hyper-wiki", | |
| 3 | "version": "1.0.0", | |
| 4 | "description": "P2P wiki — Hyperdrive-backed, versioned, collaborative", | |
| 5 | "type": "module", | |
| 6 | "main": "index.js", | |
| 7 | "pear": { | |
| 8 | "name": "hyper-wiki", | |
| 9 | "type": "terminal" | |
| 10 | }, | |
| 11 | "scripts": { | |
| 12 | "test": "brittle test/*.test.js" | |
| 13 | }, | |
| 14 | "dependencies": { | |
| 15 | "hyperdrive": "^11.0.0", | |
| 16 | "hyperbee": "^2.0.0", | |
| 17 | "hypercore": "^10.0.0", | |
| 18 | "corestore": "^6.0.0", | |
| 19 | "hyperswarm": "^4.0.0", | |
| 20 | "b4a": "^1.0.0" | |
| 21 | }, | |
| 22 | "devDependencies": { | |
| 23 | "brittle": "^3.0.0" | |
| 24 | }, | |
| 25 | "license": "MIT" | |
| 26 | } |
| … | … | @@ -0,0 +1,226 @@ |
| 1 | import test from 'brittle' | |
| 2 | import { Wiki } from '../lib/wiki.js' | |
| 3 | import { tmpdir } from 'os' | |
| 4 | import { join } from 'path' | |
| 5 | import { randomBytes } from 'crypto' | |
| 6 | ||
| 7 | function tmpStorage () { | |
| 8 | return join(tmpdir(), 'hyper-wiki-test-' + randomBytes(8).toString('hex')) | |
| 9 | } | |
| 10 | ||
| 11 | test('Wiki - create and read page', async (t) => { | |
| 12 | const wiki = new Wiki(tmpStorage()) | |
| 13 | await wiki.ready() | |
| 14 | ||
| 15 | const meta = await wiki.create('hello-world', '# Hello World\n\nThis is a test page.', { | |
| 16 | title: 'Hello World', | |
| 17 | author: 'alice' | |
| 18 | }) | |
| 19 | ||
| 20 | t.is(meta.title, 'Hello World') | |
| 21 | t.is(meta.author, 'alice') | |
| 22 | t.is(meta.version, 1) | |
| 23 | t.ok(meta.created > 0) | |
| 24 | ||
| 25 | const page = await wiki.read('hello-world') | |
| 26 | t.ok(page) | |
| 27 | t.is(page.slug, 'hello-world') | |
| 28 | t.is(page.content, '# Hello World\n\nThis is a test page.') | |
| 29 | t.is(page.title, 'Hello World') | |
| 30 | t.is(page.version, 1) | |
| 31 | ||
| 32 | await wiki.close() | |
| 33 | }) | |
| 34 | ||
| 35 | test('Wiki - read nonexistent returns null', async (t) => { | |
| 36 | const wiki = new Wiki(tmpStorage()) | |
| 37 | await wiki.ready() | |
| 38 | ||
| 39 | const page = await wiki.read('nonexistent') | |
| 40 | t.is(page, null) | |
| 41 | ||
| 42 | await wiki.close() | |
| 43 | }) | |
| 44 | ||
| 45 | test('Wiki - update page increments version', async (t) => { | |
| 46 | const wiki = new Wiki(tmpStorage()) | |
| 47 | await wiki.ready() | |
| 48 | ||
| 49 | await wiki.create('test', 'Version 1', { title: 'Test Page' }) | |
| 50 | const meta = await wiki.update('test', 'Version 2') | |
| 51 | ||
| 52 | t.is(meta.version, 2) | |
| 53 | t.is(meta.title, 'Test Page') // title preserved | |
| 54 | ||
| 55 | const page = await wiki.read('test') | |
| 56 | t.is(page.content, 'Version 2') | |
| 57 | t.is(page.version, 2) | |
| 58 | ||
| 59 | await wiki.close() | |
| 60 | }) | |
| 61 | ||
| 62 | test('Wiki - delete page', async (t) => { | |
| 63 | const wiki = new Wiki(tmpStorage()) | |
| 64 | await wiki.ready() | |
| 65 | ||
| 66 | await wiki.create('to-delete', 'content') | |
| 67 | t.ok(await wiki.exists('to-delete')) | |
| 68 | ||
| 69 | await wiki.remove('to-delete') | |
| 70 | t.absent(await wiki.exists('to-delete')) | |
| 71 | t.is(await wiki.read('to-delete'), null) | |
| 72 | ||
| 73 | await wiki.close() | |
| 74 | }) | |
| 75 | ||
| 76 | test('Wiki - list pages', async (t) => { | |
| 77 | const wiki = new Wiki(tmpStorage()) | |
| 78 | await wiki.ready() | |
| 79 | ||
| 80 | await wiki.create('page-a', 'Content A', { title: 'Page A' }) | |
| 81 | await wiki.create('page-b', 'Content B', { title: 'Page B' }) | |
| 82 | await wiki.create('page-c', 'Content C', { title: 'Page C' }) | |
| 83 | ||
| 84 | const pages = await wiki.list() | |
| 85 | t.is(pages.length, 3) | |
| 86 | ||
| 87 | // Check sorted by updated desc (page-c is most recent) | |
| 88 | t.is(pages[0].slug, 'page-c') | |
| 89 | t.is(pages[0].title, 'Page C') | |
| 90 | ||
| 91 | await wiki.close() | |
| 92 | }) | |
| 93 | ||
| 94 | test('Wiki - list empty returns empty array', async (t) => { | |
| 95 | const wiki = new Wiki(tmpStorage()) | |
| 96 | await wiki.ready() | |
| 97 | ||
| 98 | const pages = await wiki.list() | |
| 99 | t.alike(pages, []) | |
| 100 | ||
| 101 | await wiki.close() | |
| 102 | }) | |
| 103 | ||
| 104 | test('Wiki - exists', async (t) => { | |
| 105 | const wiki = new Wiki(tmpStorage()) | |
| 106 | await wiki.ready() | |
| 107 | ||
| 108 | t.absent(await wiki.exists('nope')) | |
| 109 | await wiki.create('yep', 'exists') | |
| 110 | t.ok(await wiki.exists('yep')) | |
| 111 | ||
| 112 | await wiki.close() | |
| 113 | }) | |
| 114 | ||
| 115 | test('Wiki - search by content', async (t) => { | |
| 116 | const wiki = new Wiki(tmpStorage()) | |
| 117 | await wiki.ready() | |
| 118 | ||
| 119 | await wiki.create('hypercore-guide', '# Hypercore\n\nAppend-only log for P2P', { title: 'Hypercore Guide' }) | |
| 120 | await wiki.create('hyperbee-guide', '# Hyperbee\n\nDatabase on top of Hypercore', { title: 'Hyperbee Guide' }) | |
| 121 | await wiki.create('unrelated', '# Cooking\n\nHow to make pasta', { title: 'Cooking' }) | |
| 122 | ||
| 123 | const results = await wiki.search('hypercore') | |
| 124 | t.ok(results.length >= 2, 'finds pages mentioning hypercore') | |
| 125 | t.ok(results.some(r => r.slug === 'hypercore-guide')) | |
| 126 | t.ok(results.some(r => r.slug === 'hyperbee-guide')) | |
| 127 | ||
| 128 | await wiki.close() | |
| 129 | }) | |
| 130 | ||
| 131 | test('Wiki - search by title', async (t) => { | |
| 132 | const wiki = new Wiki(tmpStorage()) | |
| 133 | await wiki.ready() | |
| 134 | ||
| 135 | await wiki.create('setup', 'Install instructions', { title: 'Setup Guide' }) | |
| 136 | await wiki.create('faq', 'Common questions', { title: 'FAQ' }) | |
| 137 | ||
| 138 | const results = await wiki.search('setup') | |
| 139 | t.ok(results.length >= 1) | |
| 140 | t.ok(results.some(r => r.slug === 'setup')) | |
| 141 | ||
| 142 | await wiki.close() | |
| 143 | }) | |
| 144 | ||
| 145 | test('Wiki - search no results', async (t) => { | |
| 146 | const wiki = new Wiki(tmpStorage()) | |
| 147 | await wiki.ready() | |
| 148 | ||
| 149 | await wiki.create('page1', 'some content') | |
| 150 | const results = await wiki.search('nonexistent-term-xyz') | |
| 151 | t.is(results.length, 0) | |
| 152 | ||
| 153 | await wiki.close() | |
| 154 | }) | |
| 155 | ||
| 156 | test('Wiki - multiple updates preserve created time', async (t) => { | |
| 157 | const wiki = new Wiki(tmpStorage()) | |
| 158 | await wiki.ready() | |
| 159 | ||
| 160 | const v1 = await wiki.create('evolving', 'v1') | |
| 161 | const created = v1.created | |
| 162 | ||
| 163 | await new Promise(r => setTimeout(r, 10)) | |
| 164 | const v2 = await wiki.update('evolving', 'v2') | |
| 165 | ||
| 166 | t.is(v2.created, created, 'created time unchanged') | |
| 167 | t.ok(v2.updated >= created, 'updated time is newer') | |
| 168 | t.is(v2.version, 2) | |
| 169 | ||
| 170 | await wiki.close() | |
| 171 | }) | |
| 172 | ||
| 173 | test('Wiki - unicode content', async (t) => { | |
| 174 | const wiki = new Wiki(tmpStorage()) | |
| 175 | await wiki.ready() | |
| 176 | ||
| 177 | await wiki.create('unicode', '# 日本語ページ\n\n🎉 こんにちは世界', { | |
| 178 | title: '日本語テスト', | |
| 179 | author: 'ユーザー' | |
| 180 | }) | |
| 181 | ||
| 182 | const page = await wiki.read('unicode') | |
| 183 | t.is(page.title, '日本語テスト') | |
| 184 | t.ok(page.content.includes('🎉')) | |
| 185 | t.ok(page.content.includes('こんにちは')) | |
| 186 | ||
| 187 | await wiki.close() | |
| 188 | }) | |
| 189 | ||
| 190 | test('Wiki - large content', async (t) => { | |
| 191 | const wiki = new Wiki(tmpStorage()) | |
| 192 | await wiki.ready() | |
| 193 | ||
| 194 | const content = '# Big Page\n\n' + 'Lorem ipsum dolor sit amet. '.repeat(1000) | |
| 195 | await wiki.create('big', content, { title: 'Big Page' }) | |
| 196 | ||
| 197 | const page = await wiki.read('big') | |
| 198 | t.is(page.content, content) | |
| 199 | ||
| 200 | await wiki.close() | |
| 201 | }) | |
| 202 | ||
| 203 | test('Wiki - tags preserved across updates', async (t) => { | |
| 204 | const wiki = new Wiki(tmpStorage()) | |
| 205 | await wiki.ready() | |
| 206 | ||
| 207 | await wiki.create('tagged', 'content', { | |
| 208 | title: 'Tagged Page', | |
| 209 | tags: ['p2p', 'hypercore'] | |
| 210 | }) | |
| 211 | ||
| 212 | await wiki.update('tagged', 'updated content') | |
| 213 | const page = await wiki.read('tagged') | |
| 214 | t.alike(page.tags, ['p2p', 'hypercore'], 'tags preserved') | |
| 215 | ||
| 216 | await wiki.close() | |
| 217 | }) | |
| 218 | ||
| 219 | test('Wiki - has discoveryKey', async (t) => { | |
| 220 | const wiki = new Wiki(tmpStorage()) | |
| 221 | await wiki.ready() | |
| 222 | ||
| 223 | t.ok(wiki.key, 'has key') | |
| 224 | ||
| 225 | await wiki.close() | |
| 226 | }) |
| … | … | @@ -0,0 +1,124 @@ |
| 1 | # keet-social — Godot 4 Plugin for Keet P2P Chat | |
| 2 | ||
| 3 | A pure GDScript addon that connects Godot 4 to [keet-daemon](https://github.com/user/keet-cli) via its HTTP JSON-RPC API. Chat, manage rooms, track identities, and aggregate feeds — all P2P, no servers. | |
| 4 | ||
| 5 | ## Requirements | |
| 6 | ||
| 7 | - Godot 4.3+ | |
| 8 | - keet-daemon running on `localhost:3117` | |
| 9 | ||
| 10 | ## Installation | |
| 11 | ||
| 12 | Copy `addons/keet_social/` into your Godot project's `addons/` folder. Enable the plugin in Project > Project Settings > Plugins. | |
| 13 | ||
| 14 | ## Components | |
| 15 | ||
| 16 | | Node | Purpose | | |
| 17 | |------|---------| | |
| 18 | | **KeetClient** | Core HTTP client. All other nodes reference this. | | |
| 19 | | **KeetRoom** | List, join, leave, create rooms. | | |
| 20 | | **KeetChat** | Send/receive messages with auto-polling. | | |
| 21 | | **KeetIdentity** | Get/set identity, lookup users. | | |
| 22 | | **KeetFeed** | Multi-room message timeline aggregator. | | |
| 23 | ||
| 24 | ## Quick Start | |
| 25 | ||
| 26 | 1. Add a `KeetClient` node to your scene | |
| 27 | 2. Add a `KeetChat` node, set its `client` property to the KeetClient | |
| 28 | 3. Set `room_id` on the KeetChat node | |
| 29 | 4. Connect signals: | |
| 30 | ||
| 31 | ```gdscript | |
| 32 | func _ready() -> void: | |
| 33 | $KeetChat.message_received.connect(_on_message) | |
| 34 | $KeetChat.send("Hello from Godot!") | |
| 35 | ||
| 36 | func _on_message(msg: Dictionary) -> void: | |
| 37 | print("%s: %s" % [msg.get("name", "?"), msg.get("text", "")]) | |
| 38 | ``` | |
| 39 | ||
| 40 | ## API | |
| 41 | ||
| 42 | ### KeetClient | |
| 43 | ||
| 44 | ```gdscript | |
| 45 | # JSON-RPC call with callback | |
| 46 | client.rpc("rooms", {}, func(result, error): ...) | |
| 47 | ||
| 48 | # Async version (use with await) | |
| 49 | var response = await client.rpc_async("rooms") | |
| 50 | ||
| 51 | # Signals | |
| 52 | signal connected | |
| 53 | signal disconnected | |
| 54 | signal error_occurred(message: String) | |
| 55 | ``` | |
| 56 | ||
| 57 | ### KeetChat | |
| 58 | ||
| 59 | ```gdscript | |
| 60 | chat.watch("room_id_here") # Start watching a room | |
| 61 | chat.send("Hello!") # Send a message | |
| 62 | chat.load_history(50) # Load last 50 messages | |
| 63 | chat.search("keyword") # Search messages | |
| 64 | chat.react(message_id, "👍") # React to a message | |
| 65 | ||
| 66 | signal message_received(message: Dictionary) | |
| 67 | signal messages_loaded(messages: Array) | |
| 68 | signal message_sent | |
| 69 | ``` | |
| 70 | ||
| 71 | ### KeetRoom | |
| 72 | ||
| 73 | ```gdscript | |
| 74 | room.list_rooms() # List all rooms | |
| 75 | room.create_room("My Room") # Create a room | |
| 76 | room.join_room("invite_token") # Join by invite | |
| 77 | room.leave_room("room_id") # Leave a room | |
| 78 | room.create_invite("room_id") # Get invite token | |
| 79 | room.list_members("room_id") # List members | |
| 80 | ||
| 81 | signal rooms_loaded(rooms: Array) | |
| 82 | signal room_created(room_id: String) | |
| 83 | signal members_loaded(room_id: String, members: Array) | |
| 84 | ``` | |
| 85 | ||
| 86 | ### KeetIdentity | |
| 87 | ||
| 88 | ```gdscript | |
| 89 | identity.get_identity() # Get current identity | |
| 90 | identity.set_name("My Name") # Set display name | |
| 91 | identity.lookup_user("alice0") # Find user by username | |
| 92 | identity.check_username("bob0") # Check availability | |
| 93 | ||
| 94 | signal identity_loaded(identity: Dictionary) | |
| 95 | signal name_changed(name: String) | |
| 96 | ``` | |
| 97 | ||
| 98 | ### KeetFeed | |
| 99 | ||
| 100 | ```gdscript | |
| 101 | feed.add_room("room_id_1") # Watch a room | |
| 102 | feed.add_room("room_id_2") # Watch another | |
| 103 | feed.start() # Start polling all rooms | |
| 104 | ||
| 105 | signal new_entry(room_id: String, message: Dictionary) | |
| 106 | signal feed_updated(messages: Array) # Full timeline | |
| 107 | ``` | |
| 108 | ||
| 109 | ## Demo | |
| 110 | ||
| 111 | Run `demo/chat_demo.tscn` for a working 2D chat client. Requires keet-daemon on port 3117. | |
| 112 | ||
| 113 | ## Architecture | |
| 114 | ||
| 115 | ``` | |
| 116 | Godot App | |
| 117 | └── KeetClient (HTTP JSON-RPC to localhost:3117) | |
| 118 | ├── KeetRoom (room management) | |
| 119 | ├── KeetChat (messaging + polling) | |
| 120 | ├── KeetIdentity (profiles) | |
| 121 | └── KeetFeed (multi-room timeline) | |
| 122 | ``` | |
| 123 | ||
| 124 | All communication goes through keet-daemon's HTTP API. No WebSocket, no native dependencies, no compilation. Pure GDScript. |
| … | … | @@ -0,0 +1,182 @@ |
| 1 | class_name KeetChat | |
| 2 | extends Node | |
| 3 | ## Chat messaging for a single Keet room. | |
| 4 | ## Send/receive messages with automatic polling for new messages. | |
| 5 | ||
| 6 | signal message_received(message: Dictionary) | |
| 7 | signal messages_loaded(messages: Array) | |
| 8 | signal message_sent | |
| 9 | signal error_occurred(message: String) | |
| 10 | ||
| 11 | @export var client: KeetClient | |
| 12 | @export var room_id: String | |
| 13 | @export var auto_poll: bool = true | |
| 14 | @export_range(0.5, 10.0) var poll_interval: float = 2.0 | |
| 15 | @export_range(1, 100) var message_limit: int = 50 | |
| 16 | ||
| 17 | var _poll_timer: Timer | |
| 18 | var _high_seq: int = -1 | |
| 19 | var _known_ids: Dictionary = {} | |
| 20 | ||
| 21 | ||
| 22 | func _ready() -> void: | |
| 23 | _poll_timer = Timer.new() | |
| 24 | _poll_timer.wait_time = poll_interval | |
| 25 | _poll_timer.timeout.connect(_poll_messages) | |
| 26 | add_child(_poll_timer) | |
| 27 | if auto_poll and room_id != "": | |
| 28 | start_polling() | |
| 29 | ||
| 30 | ||
| 31 | ## Start watching a room for new messages. | |
| 32 | func watch(rid: String) -> void: | |
| 33 | room_id = rid | |
| 34 | _high_seq = -1 | |
| 35 | _known_ids.clear() | |
| 36 | start_polling() | |
| 37 | ||
| 38 | ||
| 39 | ## Stop watching for messages. | |
| 40 | func unwatch() -> void: | |
| 41 | stop_polling() | |
| 42 | room_id = "" | |
| 43 | _high_seq = -1 | |
| 44 | _known_ids.clear() | |
| 45 | ||
| 46 | ||
| 47 | ## Send a text message to the current room. | |
| 48 | func send(text: String, callback: Callable = Callable()) -> void: | |
| 49 | if room_id == "": | |
| 50 | error_occurred.emit("No room_id set") | |
| 51 | return | |
| 52 | _require_client() | |
| 53 | client.rpc("send", {"roomId": room_id, "text": text}, func(result, err): | |
| 54 | if err != null: | |
| 55 | error_occurred.emit("send: %s" % str(err)) | |
| 56 | return | |
| 57 | message_sent.emit() | |
| 58 | if callback.is_valid(): | |
| 59 | callback.call(result) | |
| 60 | ) | |
| 61 | ||
| 62 | ||
| 63 | ## Load message history for the current room. | |
| 64 | func load_history(limit: int = 0, callback: Callable = Callable()) -> void: | |
| 65 | if room_id == "": | |
| 66 | error_occurred.emit("No room_id set") | |
| 67 | return | |
| 68 | _require_client() | |
| 69 | var lim := limit if limit > 0 else message_limit | |
| 70 | client.rpc("read", {"roomId": room_id, "limit": lim}, func(result, err): | |
| 71 | if err != null: | |
| 72 | error_occurred.emit("load_history: %s" % str(err)) | |
| 73 | return | |
| 74 | var msgs: Array = result if result is Array else [] | |
| 75 | # Track seen messages | |
| 76 | for msg: Variant in msgs: | |
| 77 | if msg is Dictionary: | |
| 78 | var mid: String = _msg_id(msg) | |
| 79 | _known_ids[mid] = true | |
| 80 | var seq: int = msg.get("seq", -1) as int | |
| 81 | if seq > _high_seq: | |
| 82 | _high_seq = seq | |
| 83 | messages_loaded.emit(msgs) | |
| 84 | if callback.is_valid(): | |
| 85 | callback.call(msgs) | |
| 86 | ) | |
| 87 | ||
| 88 | ||
| 89 | ## Edit a message. | |
| 90 | func edit(message_id: Variant, text: String, callback: Callable = Callable()) -> void: | |
| 91 | _require_client() | |
| 92 | client.rpc("msg.edit", {"roomId": room_id, "messageId": message_id, "text": text}, func(result, err): | |
| 93 | if err != null: | |
| 94 | error_occurred.emit("edit: %s" % str(err)) | |
| 95 | return | |
| 96 | if callback.is_valid(): | |
| 97 | callback.call(result) | |
| 98 | ) | |
| 99 | ||
| 100 | ||
| 101 | ## Delete a message. | |
| 102 | func delete(message_id: Variant, callback: Callable = Callable()) -> void: | |
| 103 | _require_client() | |
| 104 | client.rpc("msg.delete", {"roomId": room_id, "messageId": message_id}, func(result, err): | |
| 105 | if err != null: | |
| 106 | error_occurred.emit("delete: %s" % str(err)) | |
| 107 | return | |
| 108 | if callback.is_valid(): | |
| 109 | callback.call(result) | |
| 110 | ) | |
| 111 | ||
| 112 | ||
| 113 | ## React to a message. | |
| 114 | func react(message_id: Variant, emoji: String, callback: Callable = Callable()) -> void: | |
| 115 | _require_client() | |
| 116 | client.rpc("react", {"roomId": room_id, "messageId": message_id, "emoji": emoji}, func(result, err): | |
| 117 | if err != null: | |
| 118 | error_occurred.emit("react: %s" % str(err)) | |
| 119 | return | |
| 120 | if callback.is_valid(): | |
| 121 | callback.call(result) | |
| 122 | ) | |
| 123 | ||
| 124 | ||
| 125 | ## Search messages in the current room. | |
| 126 | func search(query: String, callback: Callable = Callable()) -> void: | |
| 127 | _require_client() | |
| 128 | client.rpc("msg.search", {"roomId": room_id, "query": query}, func(result, err): | |
| 129 | if err != null: | |
| 130 | error_occurred.emit("search: %s" % str(err)) | |
| 131 | return | |
| 132 | if callback.is_valid(): | |
| 133 | callback.call(result) | |
| 134 | ) | |
| 135 | ||
| 136 | ||
| 137 | func start_polling() -> void: | |
| 138 | if not _poll_timer.is_stopped(): | |
| 139 | return | |
| 140 | _poll_timer.start() | |
| 141 | # Immediate first poll | |
| 142 | _poll_messages() | |
| 143 | ||
| 144 | ||
| 145 | func stop_polling() -> void: | |
| 146 | _poll_timer.stop() | |
| 147 | ||
| 148 | ||
| 149 | func _poll_messages() -> void: | |
| 150 | if room_id == "" or client == null or not client.is_connected: | |
| 151 | return | |
| 152 | client.rpc("read", {"roomId": room_id, "limit": message_limit}, func(result, err): | |
| 153 | if err != null: | |
| 154 | return | |
| 155 | if not result is Array: | |
| 156 | return | |
| 157 | for msg: Variant in result: | |
| 158 | if not msg is Dictionary: | |
| 159 | continue | |
| 160 | var mid: String = _msg_id(msg) | |
| 161 | if _known_ids.has(mid): | |
| 162 | continue | |
| 163 | _known_ids[mid] = true | |
| 164 | var seq: int = msg.get("seq", -1) as int | |
| 165 | if seq > _high_seq: | |
| 166 | _high_seq = seq | |
| 167 | message_received.emit(msg) | |
| 168 | ) | |
| 169 | ||
| 170 | ||
| 171 | func _msg_id(msg: Dictionary) -> String: | |
| 172 | var id: Variant = msg.get("id", null) | |
| 173 | if id is Dictionary: | |
| 174 | return "%s:%d" % [id.get("deviceId", ""), id.get("seq", 0)] | |
| 175 | if id is String: | |
| 176 | return id | |
| 177 | return "%s:%d" % [msg.get("memberId", ""), msg.get("seq", 0)] | |
| 178 | ||
| 179 | ||
| 180 | func _require_client() -> void: | |
| 181 | if client == null: | |
| 182 | push_error("KeetChat: client not set. Assign a KeetClient node.") |
| … | … | @@ -0,0 +1,150 @@ |
| 1 | class_name KeetClient | |
| 2 | extends Node | |
| 3 | ## Core HTTP client for keet-daemon (localhost:3117). | |
| 4 | ## Provides JSON-RPC 2.0 calls and connection management. | |
| 5 | ||
| 6 | signal connected | |
| 7 | signal disconnected | |
| 8 | signal error_occurred(message: String) | |
| 9 | signal status_updated(status: Dictionary) | |
| 10 | ||
| 11 | @export var host: String = "127.0.0.1" | |
| 12 | @export var port: int = 3117 | |
| 13 | @export var auto_connect: bool = true | |
| 14 | @export_range(1.0, 30.0) var health_interval: float = 10.0 | |
| 15 | ||
| 16 | var is_connected: bool = false | |
| 17 | ||
| 18 | var _base_url: String | |
| 19 | var _health_timer: Timer | |
| 20 | var _pending_requests: int = 0 | |
| 21 | ||
| 22 | ||
| 23 | func _ready() -> void: | |
| 24 | _base_url = "http://%s:%d" % [host, port] | |
| 25 | _health_timer = Timer.new() | |
| 26 | _health_timer.wait_time = health_interval | |
| 27 | _health_timer.timeout.connect(_check_health) | |
| 28 | add_child(_health_timer) | |
| 29 | if auto_connect: | |
| 30 | check_connection() | |
| 31 | _health_timer.start() | |
| 32 | ||
| 33 | ||
| 34 | ## Make a JSON-RPC call to keet-daemon. | |
| 35 | ## Returns result via callback(result, error). | |
| 36 | func rpc(method: String, params: Dictionary = {}, callback: Callable = Callable()) -> void: | |
| 37 | var req := HTTPRequest.new() | |
| 38 | req.timeout = 10.0 | |
| 39 | add_child(req) | |
| 40 | var body := JSON.stringify({"method": method, "params": params}) | |
| 41 | req.request_completed.connect(_on_rpc_response.bind(callback, req)) | |
| 42 | _pending_requests += 1 | |
| 43 | var err := req.request( | |
| 44 | _base_url + "/rpc", | |
| 45 | ["Content-Type: application/json"], | |
| 46 | HTTPClient.METHOD_POST, | |
| 47 | body | |
| 48 | ) | |
| 49 | if err != OK: | |
| 50 | _pending_requests -= 1 | |
| 51 | req.queue_free() | |
| 52 | if callback.is_valid(): | |
| 53 | callback.call(null, "Request failed: %d" % err) | |
| 54 | ||
| 55 | ||
| 56 | ## Convenience: async RPC via await. Returns {"result": ..., "error": ...}. | |
| 57 | func rpc_async(method: String, params: Dictionary = {}) -> Dictionary: | |
| 58 | var response := {"result": null, "error": null} | |
| 59 | var done := false | |
| 60 | rpc(method, params, func(result, err): | |
| 61 | response.result = result | |
| 62 | response.error = err | |
| 63 | done = true | |
| 64 | ) | |
| 65 | while not done: | |
| 66 | await get_tree().process_frame | |
| 67 | return response | |
| 68 | ||
| 69 | ||
| 70 | ## GET a convenience endpoint (e.g. "/rpc/rooms", "/rpc/identity"). | |
| 71 | func get_endpoint(path: String, callback: Callable = Callable()) -> void: | |
| 72 | var req := HTTPRequest.new() | |
| 73 | req.timeout = 10.0 | |
| 74 | add_child(req) | |
| 75 | req.request_completed.connect(_on_get_response.bind(callback, req)) | |
| 76 | var err := req.request(_base_url + path) | |
| 77 | if err != OK: | |
| 78 | req.queue_free() | |
| 79 | if callback.is_valid(): | |
| 80 | callback.call(null, "GET failed: %d" % err) | |
| 81 | ||
| 82 | ||
| 83 | ## Check if keet-daemon is reachable. | |
| 84 | func check_connection() -> void: | |
| 85 | rpc("status", {}, func(result, err): | |
| 86 | var was_connected := is_connected | |
| 87 | if err != null: | |
| 88 | is_connected = false | |
| 89 | if was_connected: | |
| 90 | disconnected.emit() | |
| 91 | else: | |
| 92 | is_connected = true | |
| 93 | if not was_connected: | |
| 94 | connected.emit() | |
| 95 | status_updated.emit(result if result is Dictionary else {}) | |
| 96 | ) | |
| 97 | ||
| 98 | ||
| 99 | func _on_rpc_response(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray, callback: Callable, req: HTTPRequest) -> void: | |
| 100 | _pending_requests -= 1 | |
| 101 | req.queue_free() | |
| 102 | ||
| 103 | if result != HTTPRequest.RESULT_SUCCESS: | |
| 104 | _handle_disconnect() | |
| 105 | if callback.is_valid(): | |
| 106 | callback.call(null, "HTTP error: %d" % result) | |
| 107 | return | |
| 108 | ||
| 109 | if response_code < 200 or response_code >= 300: | |
| 110 | if callback.is_valid(): | |
| 111 | callback.call(null, "HTTP %d" % response_code) | |
| 112 | return | |
| 113 | ||
| 114 | var text := body.get_string_from_utf8() | |
| 115 | var data = JSON.parse_string(text) | |
| 116 | if data == null: | |
| 117 | if callback.is_valid(): | |
| 118 | callback.call(null, "Invalid JSON") | |
| 119 | return | |
| 120 | ||
| 121 | if not is_connected: | |
| 122 | is_connected = true | |
| 123 | connected.emit() | |
| 124 | ||
| 125 | if callback.is_valid(): | |
| 126 | callback.call(data.get("result"), data.get("error")) | |
| 127 | ||
| 128 | ||
| 129 | func _on_get_response(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray, callback: Callable, req: HTTPRequest) -> void: | |
| 130 | req.queue_free() | |
| 131 | ||
| 132 | if result != HTTPRequest.RESULT_SUCCESS or response_code < 200 or response_code >= 300: | |
| 133 | if callback.is_valid(): | |
| 134 | callback.call(null, "GET failed") | |
| 135 | return | |
| 136 | ||
| 137 | var text := body.get_string_from_utf8() | |
| 138 | var data = JSON.parse_string(text) | |
| 139 | if callback.is_valid(): | |
| 140 | callback.call(data, null) | |
| 141 | ||
| 142 | ||
| 143 | func _check_health() -> void: | |
| 144 | check_connection() | |
| 145 | ||
| 146 | ||
| 147 | func _handle_disconnect() -> void: | |
| 148 | if is_connected: | |
| 149 | is_connected = false | |
| 150 | disconnected.emit() |
| … | … | @@ -0,0 +1,114 @@ |
| 1 | class_name KeetFeed | |
| 2 | extends Node | |
| 3 | ## Multi-room message aggregator. | |
| 4 | ## Polls multiple rooms and emits a unified timeline of messages. | |
| 5 | ||
| 6 | signal feed_updated(messages: Array) | |
| 7 | signal new_entry(room_id: String, message: Dictionary) | |
| 8 | signal error_occurred(message: String) | |
| 9 | ||
| 10 | @export var client: KeetClient | |
| 11 | @export var auto_poll: bool = false | |
| 12 | @export_range(1.0, 30.0) var poll_interval: float = 5.0 | |
| 13 | @export_range(1, 50) var per_room_limit: int = 10 | |
| 14 | ||
| 15 | var _watched_rooms: Array[String] = [] | |
| 16 | var _poll_timer: Timer | |
| 17 | var _known_ids: Dictionary = {} | |
| 18 | var _timeline: Array = [] | |
| 19 | ||
| 20 | ||
| 21 | func _ready() -> void: | |
| 22 | _poll_timer = Timer.new() | |
| 23 | _poll_timer.wait_time = poll_interval | |
| 24 | _poll_timer.timeout.connect(_poll_all) | |
| 25 | add_child(_poll_timer) | |
| 26 | if auto_poll and _watched_rooms.size() > 0: | |
| 27 | start() | |
| 28 | ||
| 29 | ||
| 30 | ## Add a room to the feed. | |
| 31 | func add_room(room_id: String) -> void: | |
| 32 | if room_id not in _watched_rooms: | |
| 33 | _watched_rooms.append(room_id) | |
| 34 | ||
| 35 | ||
| 36 | ## Remove a room from the feed. | |
| 37 | func remove_room(room_id: String) -> void: | |
| 38 | _watched_rooms.erase(room_id) | |
| 39 | ||
| 40 | ||
| 41 | ## Set all watched rooms at once. | |
| 42 | func set_rooms(room_ids: Array[String]) -> void: | |
| 43 | _watched_rooms = room_ids | |
| 44 | ||
| 45 | ||
| 46 | ## Start polling all watched rooms. | |
| 47 | func start() -> void: | |
| 48 | if _poll_timer.is_stopped(): | |
| 49 | _poll_timer.start() | |
| 50 | _poll_all() | |
| 51 | ||
| 52 | ||
| 53 | ## Stop polling. | |
| 54 | func stop() -> void: | |
| 55 | _poll_timer.stop() | |
| 56 | ||
| 57 | ||
| 58 | ## Get the current timeline (sorted by timestamp, newest first). | |
| 59 | func get_timeline() -> Array: | |
| 60 | return _timeline | |
| 61 | ||
| 62 | ||
| 63 | ## Refresh the feed immediately. | |
| 64 | func refresh() -> void: | |
| 65 | _poll_all() | |
| 66 | ||
| 67 | ||
| 68 | func _poll_all() -> void: | |
| 69 | if client == null or not client.is_connected: | |
| 70 | return | |
| 71 | for rid: String in _watched_rooms: | |
| 72 | _poll_room(rid) | |
| 73 | ||
| 74 | ||
| 75 | func _poll_room(room_id: String) -> void: | |
| 76 | client.rpc("read", {"roomId": room_id, "limit": per_room_limit}, func(result, err): | |
| 77 | if err != null: | |
| 78 | return | |
| 79 | if not result is Array: | |
| 80 | return | |
| 81 | var has_new := false | |
| 82 | for msg: Variant in result: | |
| 83 | if not msg is Dictionary: | |
| 84 | continue | |
| 85 | var mid: String = _msg_id(room_id, msg) | |
| 86 | if _known_ids.has(mid): | |
| 87 | continue | |
| 88 | _known_ids[mid] = true | |
| 89 | var entry: Dictionary = { | |
| 90 | "room_id": room_id, | |
| 91 | "message": msg, | |
| 92 | "timestamp": msg.get("timestamp", 0) | |
| 93 | } | |
| 94 | _timeline.append(entry) | |
| 95 | has_new = true | |
| 96 | new_entry.emit(room_id, msg) | |
| 97 | if has_new: | |
| 98 | _timeline.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: | |
| 99 | return a.get("timestamp", 0) > b.get("timestamp", 0) | |
| 100 | ) | |
| 101 | # Cap timeline size | |
| 102 | if _timeline.size() > 200: | |
| 103 | _timeline.resize(200) | |
| 104 | feed_updated.emit(_timeline) | |
| 105 | ) | |
| 106 | ||
| 107 | ||
| 108 | func _msg_id(room_id: String, msg: Dictionary) -> String: | |
| 109 | var id: Variant = msg.get("id", null) | |
| 110 | if id is Dictionary: | |
| 111 | return "%s/%s:%d" % [room_id, id.get("deviceId", ""), id.get("seq", 0)] | |
| 112 | if id is String: | |
| 113 | return "%s/%s" % [room_id, id] | |
| 114 | return "%s/%s:%d" % [room_id, msg.get("memberId", ""), msg.get("seq", 0)] |
| … | … | @@ -0,0 +1,95 @@ |
| 1 | class_name KeetIdentity | |
| 2 | extends Node | |
| 3 | ## Identity management for keet-daemon. | |
| 4 | ## Get/set display name, lookup users, manage profile. | |
| 5 | ||
| 6 | signal identity_loaded(identity: Dictionary) | |
| 7 | signal name_changed(name: String) | |
| 8 | signal member_found(member: Dictionary) | |
| 9 | signal error_occurred(message: String) | |
| 10 | ||
| 11 | @export var client: KeetClient | |
| 12 | ||
| 13 | ||
| 14 | ## Get the current identity. | |
| 15 | func get_identity(callback: Callable = Callable()) -> void: | |
| 16 | _require_client() | |
| 17 | client.rpc("identity", {}, func(result, err): | |
| 18 | if err != null: | |
| 19 | error_occurred.emit("get_identity: %s" % str(err)) | |
| 20 | return | |
| 21 | var ident: Dictionary = result if result is Dictionary else {} | |
| 22 | identity_loaded.emit(ident) | |
| 23 | if callback.is_valid(): | |
| 24 | callback.call(ident) | |
| 25 | ) | |
| 26 | ||
| 27 | ||
| 28 | ## Set display name. | |
| 29 | func set_name(display_name: String, callback: Callable = Callable()) -> void: | |
| 30 | _require_client() | |
| 31 | client.rpc("identity.setName", {"name": display_name}, func(result, err): | |
| 32 | if err != null: | |
| 33 | error_occurred.emit("set_name: %s" % str(err)) | |
| 34 | return | |
| 35 | name_changed.emit(display_name) | |
| 36 | if callback.is_valid(): | |
| 37 | callback.call(result) | |
| 38 | ) | |
| 39 | ||
| 40 | ||
| 41 | ## Check if a username is available. | |
| 42 | func check_username(username: String, callback: Callable = Callable()) -> void: | |
| 43 | _require_client() | |
| 44 | client.rpc("identity.checkName", {"username": username}, func(result, err): | |
| 45 | if err != null: | |
| 46 | error_occurred.emit("check_username: %s" % str(err)) | |
| 47 | return | |
| 48 | if callback.is_valid(): | |
| 49 | callback.call(result) | |
| 50 | ) | |
| 51 | ||
| 52 | ||
| 53 | ## Lookup a user by username. | |
| 54 | func lookup_user(username: String, callback: Callable = Callable()) -> void: | |
| 55 | _require_client() | |
| 56 | client.rpc("identity.lookup", {"username": username}, func(result, err): | |
| 57 | if err != null: | |
| 58 | error_occurred.emit("lookup_user: %s" % str(err)) | |
| 59 | return | |
| 60 | var member: Dictionary = result if result is Dictionary else {} | |
| 61 | member_found.emit(member) | |
| 62 | if callback.is_valid(): | |
| 63 | callback.call(member) | |
| 64 | ) | |
| 65 | ||
| 66 | ||
| 67 | ## Lookup a member by ID within a room. | |
| 68 | func lookup_member(room_id: String, member_id: String, callback: Callable = Callable()) -> void: | |
| 69 | _require_client() | |
| 70 | client.rpc("identity.lookupMember", {"roomId": room_id, "memberId": member_id}, func(result, err): | |
| 71 | if err != null: | |
| 72 | error_occurred.emit("lookup_member: %s" % str(err)) | |
| 73 | return | |
| 74 | var member: Dictionary = result if result is Dictionary else {} | |
| 75 | member_found.emit(member) | |
| 76 | if callback.is_valid(): | |
| 77 | callback.call(member) | |
| 78 | ) | |
| 79 | ||
| 80 | ||
| 81 | ## Get daemon status (uptime, rooms, identity summary). | |
| 82 | func get_status(callback: Callable = Callable()) -> void: | |
| 83 | _require_client() | |
| 84 | client.rpc("status", {}, func(result, err): | |
| 85 | if err != null: | |
| 86 | error_occurred.emit("get_status: %s" % str(err)) | |
| 87 | return | |
| 88 | if callback.is_valid(): | |
| 89 | callback.call(result) | |
| 90 | ) | |
| 91 | ||
| 92 | ||
| 93 | func _require_client() -> void: | |
| 94 | if client == null: | |
| 95 | push_error("KeetIdentity: client not set. Assign a KeetClient node.") |
| … | … | @@ -0,0 +1,130 @@ |
| 1 | class_name KeetRoom | |
| 2 | extends Node | |
| 3 | ## Room management for keet-daemon. | |
| 4 | ## List, join, leave, create, and configure rooms. | |
| 5 | ||
| 6 | signal rooms_loaded(rooms: Array) | |
| 7 | signal room_joined(room_id: String) | |
| 8 | signal room_left(room_id: String) | |
| 9 | signal room_created(room_id: String) | |
| 10 | signal members_loaded(room_id: String, members: Array) | |
| 11 | signal invite_created(room_id: String, invite: String) | |
| 12 | signal error_occurred(message: String) | |
| 13 | ||
| 14 | @export var client: KeetClient | |
| 15 | ||
| 16 | ||
| 17 | ## List all rooms. | |
| 18 | func list_rooms(callback: Callable = Callable()) -> void: | |
| 19 | _require_client() | |
| 20 | client.rpc("rooms", {}, func(result, err): | |
| 21 | if err != null: | |
| 22 | error_occurred.emit("list_rooms: %s" % str(err)) | |
| 23 | return | |
| 24 | var rooms: Array = result if result is Array else [] | |
| 25 | rooms_loaded.emit(rooms) | |
| 26 | if callback.is_valid(): | |
| 27 | callback.call(rooms) | |
| 28 | ) | |
| 29 | ||
| 30 | ||
| 31 | ## Get info for a specific room. | |
| 32 | func get_room(room_id: String, callback: Callable = Callable()) -> void: | |
| 33 | _require_client() | |
| 34 | client.rpc("room", {"roomId": room_id}, func(result, err): | |
| 35 | if err != null: | |
| 36 | error_occurred.emit("get_room: %s" % str(err)) | |
| 37 | return | |
| 38 | if callback.is_valid(): | |
| 39 | callback.call(result) | |
| 40 | ) | |
| 41 | ||
| 42 | ||
| 43 | ## Create a new room. | |
| 44 | func create_room(title: String, callback: Callable = Callable()) -> void: | |
| 45 | _require_client() | |
| 46 | client.rpc("room.create", {"title": title}, func(result, err): | |
| 47 | if err != null: | |
| 48 | error_occurred.emit("create_room: %s" % str(err)) | |
| 49 | return | |
| 50 | var rid: String = result.get("roomId", "") if result is Dictionary else "" | |
| 51 | room_created.emit(rid) | |
| 52 | if callback.is_valid(): | |
| 53 | callback.call(result) | |
| 54 | ) | |
| 55 | ||
| 56 | ||
| 57 | ## Join a room by invite link or token. | |
| 58 | func join_room(invite: String, callback: Callable = Callable()) -> void: | |
| 59 | _require_client() | |
| 60 | client.rpc("room.join", {"invite": invite}, func(result, err): | |
| 61 | if err != null: | |
| 62 | error_occurred.emit("join_room: %s" % str(err)) | |
| 63 | return | |
| 64 | var rid: String = result.get("roomId", "") if result is Dictionary else "" | |
| 65 | room_joined.emit(rid) | |
| 66 | if callback.is_valid(): | |
| 67 | callback.call(result) | |
| 68 | ) | |
| 69 | ||
| 70 | ||
| 71 | ## Leave a room. | |
| 72 | func leave_room(room_id: String, callback: Callable = Callable()) -> void: | |
| 73 | _require_client() | |
| 74 | client.rpc("room.leave", {"roomId": room_id}, func(result, err): | |
| 75 | if err != null: | |
| 76 | error_occurred.emit("leave_room: %s" % str(err)) | |
| 77 | return | |
| 78 | room_left.emit(room_id) | |
| 79 | if callback.is_valid(): | |
| 80 | callback.call(result) | |
| 81 | ) | |
| 82 | ||
| 83 | ||
| 84 | ## Get an invite token for a room. | |
| 85 | func create_invite(room_id: String, callback: Callable = Callable()) -> void: | |
| 86 | _require_client() | |
| 87 | client.rpc("room.invite", {"roomId": room_id}, func(result, err): | |
| 88 | if err != null: | |
| 89 | error_occurred.emit("create_invite: %s" % str(err)) | |
| 90 | return | |
| 91 | var token: String = "" | |
| 92 | if result is Dictionary: | |
| 93 | token = result.get("token", result.get("invite", "")) | |
| 94 | elif result is String: | |
| 95 | token = result | |
| 96 | invite_created.emit(room_id, token) | |
| 97 | if callback.is_valid(): | |
| 98 | callback.call(token) | |
| 99 | ) | |
| 100 | ||
| 101 | ||
| 102 | ## List members of a room. | |
| 103 | func list_members(room_id: String, callback: Callable = Callable()) -> void: | |
| 104 | _require_client() | |
| 105 | client.rpc("members", {"roomId": room_id}, func(result, err): | |
| 106 | if err != null: | |
| 107 | error_occurred.emit("list_members: %s" % str(err)) | |
| 108 | return | |
| 109 | var members: Array = result if result is Array else [] | |
| 110 | members_loaded.emit(room_id, members) | |
| 111 | if callback.is_valid(): | |
| 112 | callback.call(members) | |
| 113 | ) | |
| 114 | ||
| 115 | ||
| 116 | ## Set room configuration (title, description, etc). | |
| 117 | func set_config(room_id: String, key: String, value: Variant, callback: Callable = Callable()) -> void: | |
| 118 | _require_client() | |
| 119 | client.rpc("room.config.set", {"roomId": room_id, "key": key, "value": value}, func(result, err): | |
| 120 | if err != null: | |
| 121 | error_occurred.emit("set_config: %s" % str(err)) | |
| 122 | return | |
| 123 | if callback.is_valid(): | |
| 124 | callback.call(result) | |
| 125 | ) | |
| 126 | ||
| 127 | ||
| 128 | func _require_client() -> void: | |
| 129 | if client == null: | |
| 130 | push_error("KeetRoom: client not set. Assign a KeetClient node.") |
| … | … | @@ -0,0 +1,7 @@ |
| 1 | [plugin] | |
| 2 | ||
| 3 | name="Keet Social" | |
| 4 | description="P2P social integration via keet-daemon. Chat, rooms, identity, and feeds." | |
| 5 | author="hyper-social" | |
| 6 | version="0.1.0" | |
| 7 | script="plugin.gd" |
| … | … | @@ -0,0 +1,18 @@ |
| 1 | @tool | |
| 2 | extends EditorPlugin | |
| 3 | ||
| 4 | ||
| 5 | func _enter_tree() -> void: | |
| 6 | add_custom_type("KeetClient", "Node", preload("keet_client.gd"), null) | |
| 7 | add_custom_type("KeetRoom", "Node", preload("keet_room.gd"), null) | |
| 8 | add_custom_type("KeetChat", "Node", preload("keet_chat.gd"), null) | |
| 9 | add_custom_type("KeetIdentity", "Node", preload("keet_identity.gd"), null) | |
| 10 | add_custom_type("KeetFeed", "Node", preload("keet_feed.gd"), null) | |
| 11 | ||
| 12 | ||
| 13 | func _exit_tree() -> void: | |
| 14 | remove_custom_type("KeetClient") | |
| 15 | remove_custom_type("KeetRoom") | |
| 16 | remove_custom_type("KeetChat") | |
| 17 | remove_custom_type("KeetIdentity") | |
| 18 | remove_custom_type("KeetFeed") |
| … | … | @@ -0,0 +1,115 @@ |
| 1 | extends Control | |
| 2 | ## 2D chat demo using keet-social plugin. | |
| 3 | ## Connects to keet-daemon, lists rooms, and enables chatting. | |
| 4 | ||
| 5 | @onready var client: KeetClient = $KeetClient | |
| 6 | @onready var room_manager: KeetRoom = $KeetRoom | |
| 7 | @onready var chat: KeetChat = $KeetChat | |
| 8 | @onready var identity: KeetIdentity = $KeetIdentity | |
| 9 | ||
| 10 | @onready var status_label: Label = $UI/TopBar/StatusLabel | |
| 11 | @onready var room_list: ItemList = $UI/HSplit/RoomPanel/RoomList | |
| 12 | @onready var chat_log: RichTextLabel = $UI/HSplit/ChatPanel/VBox/ChatLog | |
| 13 | @onready var input_field: LineEdit = $UI/HSplit/ChatPanel/VBox/HBox/InputField | |
| 14 | @onready var send_button: Button = $UI/HSplit/ChatPanel/VBox/HBox/SendButton | |
| 15 | @onready var room_title: Label = $UI/HSplit/ChatPanel/RoomTitle | |
| 16 | ||
| 17 | var _rooms: Array = [] | |
| 18 | var _current_room: String = "" | |
| 19 | var _identity_name: String = "unknown" | |
| 20 | ||
| 21 | ||
| 22 | func _ready() -> void: | |
| 23 | # Wire up client | |
| 24 | client.connected.connect(_on_connected) | |
| 25 | client.disconnected.connect(_on_disconnected) | |
| 26 | ||
| 27 | # Wire up room manager | |
| 28 | room_manager.client = client | |
| 29 | room_manager.rooms_loaded.connect(_on_rooms_loaded) | |
| 30 | ||
| 31 | # Wire up chat | |
| 32 | chat.client = client | |
| 33 | chat.message_received.connect(_on_message_received) | |
| 34 | chat.messages_loaded.connect(_on_history_loaded) | |
| 35 | ||
| 36 | # Wire up identity | |
| 37 | identity.client = client | |
| 38 | ||
| 39 | # Wire up UI | |
| 40 | send_button.pressed.connect(_on_send) | |
| 41 | input_field.text_submitted.connect(_on_text_submitted) | |
| 42 | room_list.item_selected.connect(_on_room_selected) | |
| 43 | ||
| 44 | status_label.text = "Connecting..." | |
| 45 | room_title.text = "Select a room" | |
| 46 | ||
| 47 | ||
| 48 | func _on_connected() -> void: | |
| 49 | status_label.text = "Connected" | |
| 50 | identity.get_identity(func(ident: Dictionary): | |
| 51 | _identity_name = ident.get("displayName", ident.get("name", "unknown")) | |
| 52 | status_label.text = "Connected as %s" % _identity_name | |
| 53 | ) | |
| 54 | room_manager.list_rooms() | |
| 55 | ||
| 56 | ||
| 57 | func _on_disconnected() -> void: | |
| 58 | status_label.text = "Disconnected - retrying..." | |
| 59 | chat.stop_polling() | |
| 60 | ||
| 61 | ||
| 62 | func _on_rooms_loaded(rooms: Array) -> void: | |
| 63 | _rooms = rooms | |
| 64 | room_list.clear() | |
| 65 | for room: Variant in rooms: | |
| 66 | if room is Dictionary: | |
| 67 | room_list.add_item(room.get("title", room.get("roomId", "?"))) | |
| 68 | ||
| 69 | ||
| 70 | func _on_room_selected(index: int) -> void: | |
| 71 | if index < 0 or index >= _rooms.size(): | |
| 72 | return | |
| 73 | var room: Dictionary = _rooms[index] | |
| 74 | _current_room = room.get("roomId", "") | |
| 75 | room_title.text = room.get("title", _current_room) | |
| 76 | chat_log.clear() | |
| 77 | chat.watch(_current_room) | |
| 78 | chat.load_history() | |
| 79 | ||
| 80 | ||
| 81 | func _on_history_loaded(messages: Array) -> void: | |
| 82 | chat_log.clear() | |
| 83 | for msg: Variant in messages: | |
| 84 | if msg is Dictionary: | |
| 85 | _append_message(msg) | |
| 86 | ||
| 87 | ||
| 88 | func _on_message_received(msg: Dictionary) -> void: | |
| 89 | _append_message(msg) | |
| 90 | ||
| 91 | ||
| 92 | func _on_send() -> void: | |
| 93 | _send_message() | |
| 94 | ||
| 95 | ||
| 96 | func _on_text_submitted(_text: String) -> void: | |
| 97 | _send_message() | |
| 98 | ||
| 99 | ||
| 100 | func _send_message() -> void: | |
| 101 | var text := input_field.text.strip_edges() | |
| 102 | if text == "" or _current_room == "": | |
| 103 | return | |
| 104 | input_field.text = "" | |
| 105 | chat.send(text) | |
| 106 | ||
| 107 | ||
| 108 | func _append_message(msg: Dictionary) -> void: | |
| 109 | var name: String = msg.get("name", msg.get("memberId", "?")) | |
| 110 | var text: String = msg.get("text", "") | |
| 111 | var is_system: bool = msg.get("system", false) | |
| 112 | if is_system: | |
| 113 | chat_log.append_text("[color=gray]--- %s ---[/color]\n" % text) | |
| 114 | else: | |
| 115 | chat_log.append_text("[b]%s:[/b] %s\n" % [name, text]) |
| … | … | @@ -0,0 +1,106 @@ |
| 1 | [gd_scene load_steps=7 format=3 uid="uid://demo_chat"] | |
| 2 | ||
| 3 | [ext_resource type="Script" path="res://demo/chat_demo.gd" id="1"] | |
| 4 | [ext_resource type="Script" path="res://addons/keet_social/keet_client.gd" id="2"] | |
| 5 | [ext_resource type="Script" path="res://addons/keet_social/keet_room.gd" id="3"] | |
| 6 | [ext_resource type="Script" path="res://addons/keet_social/keet_chat.gd" id="4"] | |
| 7 | [ext_resource type="Script" path="res://addons/keet_social/keet_identity.gd" id="5"] | |
| 8 | [ext_resource type="Script" path="res://addons/keet_social/keet_feed.gd" id="6"] | |
| 9 | ||
| 10 | [node name="ChatDemo" type="Control"] | |
| 11 | layout_mode = 3 | |
| 12 | anchors_preset = 15 | |
| 13 | anchor_right = 1.0 | |
| 14 | anchor_bottom = 1.0 | |
| 15 | grow_horizontal = 2 | |
| 16 | grow_vertical = 2 | |
| 17 | script = ExtResource("1") | |
| 18 | ||
| 19 | [node name="KeetClient" type="Node" parent="."] | |
| 20 | script = ExtResource("2") | |
| 21 | ||
| 22 | [node name="KeetRoom" type="Node" parent="."] | |
| 23 | script = ExtResource("3") | |
| 24 | ||
| 25 | [node name="KeetChat" type="Node" parent="."] | |
| 26 | script = ExtResource("4") | |
| 27 | ||
| 28 | [node name="KeetIdentity" type="Node" parent="."] | |
| 29 | script = ExtResource("5") | |
| 30 | ||
| 31 | [node name="KeetFeed" type="Node" parent="."] | |
| 32 | script = ExtResource("6") | |
| 33 | ||
| 34 | [node name="UI" type="VBoxContainer" parent="."] | |
| 35 | layout_mode = 1 | |
| 36 | anchors_preset = 15 | |
| 37 | anchor_right = 1.0 | |
| 38 | anchor_bottom = 1.0 | |
| 39 | grow_horizontal = 2 | |
| 40 | grow_vertical = 2 | |
| 41 | ||
| 42 | [node name="TopBar" type="HBoxContainer" parent="UI"] | |
| 43 | layout_mode = 2 | |
| 44 | custom_minimum_size = Vector2(0, 40) | |
| 45 | ||
| 46 | [node name="TitleLabel" type="Label" parent="UI/TopBar"] | |
| 47 | layout_mode = 2 | |
| 48 | text = "Keet Social" | |
| 49 | ||
| 50 | [node name="HSeparator" type="HSeparator" parent="UI/TopBar"] | |
| 51 | layout_mode = 2 | |
| 52 | size_flags_horizontal = 3 | |
| 53 | ||
| 54 | [node name="StatusLabel" type="Label" parent="UI/TopBar"] | |
| 55 | layout_mode = 2 | |
| 56 | text = "Disconnected" | |
| 57 | horizontal_alignment = 2 | |
| 58 | ||
| 59 | [node name="HSplit" type="HSplitContainer" parent="UI"] | |
| 60 | layout_mode = 2 | |
| 61 | size_flags_vertical = 3 | |
| 62 | split_offset = 200 | |
| 63 | ||
| 64 | [node name="RoomPanel" type="VBoxContainer" parent="UI/HSplit"] | |
| 65 | layout_mode = 2 | |
| 66 | custom_minimum_size = Vector2(180, 0) | |
| 67 | ||
| 68 | [node name="RoomLabel" type="Label" parent="UI/HSplit/RoomPanel"] | |
| 69 | layout_mode = 2 | |
| 70 | text = "Rooms" | |
| 71 | ||
| 72 | [node name="RoomList" type="ItemList" parent="UI/HSplit/RoomPanel"] | |
| 73 | layout_mode = 2 | |
| 74 | size_flags_vertical = 3 | |
| 75 | ||
| 76 | [node name="ChatPanel" type="VBoxContainer" parent="UI/HSplit"] | |
| 77 | layout_mode = 2 | |
| 78 | size_flags_horizontal = 3 | |
| 79 | ||
| 80 | [node name="RoomTitle" type="Label" parent="UI/HSplit/ChatPanel"] | |
| 81 | layout_mode = 2 | |
| 82 | text = "Select a room" | |
| 83 | ||
| 84 | [node name="VBox" type="VBoxContainer" parent="UI/HSplit/ChatPanel"] | |
| 85 | layout_mode = 2 | |
| 86 | size_flags_vertical = 3 | |
| 87 | ||
| 88 | [node name="ChatLog" type="RichTextLabel" parent="UI/HSplit/ChatPanel/VBox"] | |
| 89 | layout_mode = 2 | |
| 90 | size_flags_vertical = 3 | |
| 91 | bbcode_enabled = true | |
| 92 | scroll_following = true | |
| 93 | ||
| 94 | [node name="HBox" type="HBoxContainer" parent="UI/HSplit/ChatPanel/VBox"] | |
| 95 | layout_mode = 2 | |
| 96 | custom_minimum_size = Vector2(0, 36) | |
| 97 | ||
| 98 | [node name="InputField" type="LineEdit" parent="UI/HSplit/ChatPanel/VBox/HBox"] | |
| 99 | layout_mode = 2 | |
| 100 | size_flags_horizontal = 3 | |
| 101 | placeholder_text = "Type a message..." | |
| 102 | ||
| 103 | [node name="SendButton" type="Button" parent="UI/HSplit/ChatPanel/VBox/HBox"] | |
| 104 | layout_mode = 2 | |
| 105 | custom_minimum_size = Vector2(80, 0) | |
| 106 | text = "Send" |
| … | … | @@ -0,0 +1,13 @@ |
| 1 | ; Engine configuration file. | |
| 2 | ; It's best edited using the editor UI and not directly, | |
| 3 | ; but it can also be edited via text for source control. | |
| 4 | ||
| 5 | [application] | |
| 6 | ||
| 7 | config/name="Keet Social Demo" | |
| 8 | run/main_scene="res://demo/chat_demo.tscn" | |
| 9 | config/features=PackedStringArray("4.3") | |
| 10 | ||
| 11 | [editor_plugins] | |
| 12 | ||
| 13 | enabled=PackedStringArray("res://addons/keet_social/plugin.cfg") |