Skip to content

Commit

Permalink
Chelonia in SW (#2357)
Browse files Browse the repository at this point in the history
* Chelonia in SW

* Test fixes

* Fix group-chat-direct-message spec

* Fix issue with the no-results slot being (incorrectly) used

* Changes supporting failing chat tests

* Fix attachments in SW

* Fix Flow fypes

* Use atomic for chatroom members

* Chat bugfixes

* Use session storage for tab logs. Refactor logging to be more generic.

* Bugfixes and removal of unnecessary selectors

* SW logs

* Bugfix for loading preferences (fix event handlers)

* State for KV events

* Serious banner error

* More consistent chelonia / vuex state use

* Remove debug logging

* Logs UI

* Lint

* Autologout for non-exisiting identity contracts

* Safari workaround

* Bugfix

* Bugfixes

* Logout flow fixes

* Last logged in event

* Avoid SW logs spam

* Chatroom position events

* Feedback

* WIP

* Stability bugfixes

* Port notifications code

* Backend functions for sending

* Server push events

* Improvements

* Proposals spec selector specificity fix

* Add wait to contributions spec

* Push subscription improvements

* Server: persist subscriptions

* Push subscription reporting fixes

* Fix to add and join chatroom

* Notification permissions improvements

* NotificationSettings based on permission

* Fixes. Bugfix for messageReceivePostEffect

* Fixes

* Native notifications logic for display and opening

* PWA improvements

* Add PWA check to background sound

* Node 18 cypto fixes

* Push subscription setup fixes

* Document VAPID_EMAIL

* Add notification enabled check to main

* Add comments

* Feedback

* Fixes

* Feedback

* Feedback

* Fix pending messages issue

* Fix types. Add contractID to message

* Cleanup

* Better PWA detection

* DRY PWA check

* Simplify visibility check for PWA

* Lazy init indexedDB + fix types

* Fix query check

* Notifications error handling

* Fix #2422 (sp: -> shelter:)

* Show native notification if the app isn't focused

* Add comment to indexedDB lazy init

* Feedback & createMessage as object

* Fixes

* Maximum payload size

* Add type to debug log

* Update VAPID cache time

* Feedback

* Cleanup

* App logs styling

* Don't set notifications.notificationEnabled if an error occurred

* Simplify RPC cleanup

* Remove finally(cleanup)

* Add comments explaining service-worker/setup-push-subscription

* Feedback

* Feedback

* Comments

* Feedback
  • Loading branch information
corrideat authored Dec 11, 2024
1 parent 7b1c2b3 commit e4e1f9b
Show file tree
Hide file tree
Showing 70 changed files with 3,461 additions and 1,838 deletions.
2 changes: 1 addition & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ module.exports = (grunt) => {
;(function defineApiEnvars () {
const API_PORT = Number.parseInt(grunt.option('port') ?? process.env.API_PORT ?? '8000', 10)

if (Number.isNaN(API_PORT) || API_PORT < 8000 || API_PORT > 65535) {
if (Number.isNaN(API_PORT) || API_PORT < 1024 || API_PORT > 65535) {
throw new RangeError(`Invalid API_PORT value: ${API_PORT}.`)
}
process.env.API_PORT = String(API_PORT)
Expand Down
3 changes: 2 additions & 1 deletion backend/database.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import path from 'node:path'
import '@sbp/okturtles.data'
import { checkKey, parsePrefixableKey, prefixHandlers } from '~/shared/domains/chelonia/db.js'
import LRU from 'lru-cache'
import { initVapid } from './vapid.js'
import { initZkpp } from './zkppSalt.js'

const Boom = require('@hapi/boom')
Expand Down Expand Up @@ -211,5 +212,5 @@ export default async () => {
}
numNewKeys && console.info(`[chelonia.db] Preloaded ${numNewKeys} new entries`)
}
await initZkpp()
await Promise.all([initVapid(), initZkpp()])
}
74 changes: 68 additions & 6 deletions backend/pubsub.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type {
} from '~/shared/pubsub.js'

import type { JSONType, JSONObject } from '~/shared/types.js'
import { postEvent } from './push.js'

const { bold } = require('chalk')
const WebSocket = require('ws')
Expand Down Expand Up @@ -105,7 +106,7 @@ export function createServer (httpServer: Object, options?: Object = {}): Object
server.channels = new Set()
server.customServerEventHandlers = { ...options.serverHandlers }
server.customSocketEventHandlers = { ...options.socketHandlers }
server.messageHandlers = { ...defaultMessageHandlers, ...options.messageHandlers }
server.customMessageHandlers = { ...options.messageHandlers }
server.pingIntervalID = undefined
server.subscribersByChannelID = Object.create(null)
server.pushSubscriptions = Object.create(null)
Expand Down Expand Up @@ -163,11 +164,22 @@ const defaultServerHandlers = {
const url = request.url
const urlSearch = url.includes('?') ? url.slice(url.lastIndexOf('?')) : ''
const debugID = new URLSearchParams(urlSearch).get('debugID') || ''
const send = socket.send.bind(socket)
socket.id = generateSocketID(debugID)
socket.activeSinceLastPing = true
socket.pinged = false
socket.server = server
socket.subscriptions = new Set()
// Sometimes (like when using `createMessage`), we want to send objects that
// are serialized as strings. The `ws` library sends these as binary data,
// whereas the client expects strings. This avoids having to manually
// specify `{ binary: false }` along with calls.
socket.send = function (data) {
if (typeof data === 'object' && typeof data[Symbol.toPrimitive] === 'function') {
return send(data[Symbol.toPrimitive]())
}
return send(data)
}

log.bold(`Socket ${socket.id} connected. Total: ${this.clients.size}`)

Expand Down Expand Up @@ -231,14 +243,16 @@ const defaultSocketEventHandlers = {
}
// The socket can be marked as active since it just received a message.
socket.activeSinceLastPing = true
const handler = server.messageHandlers[msg.type]
const defaultHandler = defaultMessageHandlers[msg.type]
const customHandler = server.customMessageHandlers[msg.type]

if (handler) {
if (defaultHandler || customHandler) {
try {
handler.call(socket, msg)
defaultHandler?.call(socket, msg)
customHandler?.call(socket, msg)
} catch (error) {
// Log the error message and stack trace but do not send it to the client.
log.error(error, 'onMesage')
log.error(error, 'onMessage')
server.rejectMessageAndTerminateSocket(msg, socket)
}
} else {
Expand Down Expand Up @@ -322,9 +336,57 @@ const publicMethods = {
) {
const server = this

const msg = typeof message === 'string' ? message : JSON.stringify(message)
let shortMsg
// Utility function to remove `data` (i.e., the GIMessage data) from a
// message. We need this for push notifications, which may have a certain
// maximum size (usually around 4 KiB)
const shortenPayload = () => {
if (!shortMsg && (typeof message === 'object' && message.type === NOTIFICATION_TYPE.ENTRY && message.data)) {
delete message.data
shortMsg = JSON.stringify(message)
}
return shortMsg
}

for (const client of to || server.clients) {
// `client` could be either a WebSocket or a wrapped subscription info
// object
// Duplicate message sending (over both WS and push) is handled on the
// WS logic, for the `close` event (to remove the WS and send over push)
// and for the `STORE_SUBSCRIPTION` WS action.
if (client.endpoint) {
// `client.endpoint` means the client is a subscription info object
// The max length for push notifications in many providers is 4 KiB.
// However, encrypting adds a slight overhead of 17 bytes at the end
// and 86 bytes at the start.
if (msg.length > (4096 - 86 - 17)) {
if (!shortenPayload()) {
console.info('Skipping too large of a payload for', client.id)
continue
}
}
postEvent(client, shortMsg || msg).catch(e => {
// If we have an error posting due to too large of a payload and the
// message wasn't already shortened, try again
if (e?.message === 'Payload too large') {
if (shortMsg || !shortenPayload()) {
// The max length for push notifications in many providers is 4 KiB.
console.info('Skipping too large of a payload for', client.id)
return
}
postEvent(client, shortMsg).catch(e => {
console.error(e, 'Error posting push notification')
})
return
}
console.error(e, 'Error posting push notification')
})
continue
}
if (client.readyState === WebSocket.OPEN && client !== except) {
client.send(typeof message === 'string' ? message : JSON.stringify(message))
// In this branch, we're dealing with a WebSocket
client.send(msg)
}
}
},
Expand Down
Loading

0 comments on commit e4e1f9b

Please sign in to comment.