Skip to content

Commit

Permalink
Merge branch 'e2e-protocol' into e2e-protocol-ricardo
Browse files Browse the repository at this point in the history
  • Loading branch information
corrideat committed Jan 11, 2024
2 parents f926a20 + 3772bd1 commit 729ff89
Show file tree
Hide file tree
Showing 10 changed files with 191 additions and 166 deletions.
127 changes: 62 additions & 65 deletions backend/pubsub.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* globals logger */
'use strict'

/*
* Pub/Sub server implementation using the `ws` library.
* See https://github.com/websockets/ws#api-docs
Expand All @@ -16,8 +15,8 @@ import {
} from '~/shared/pubsub.js'

import type {
Message, SubMessage, UnsubMessage,
NotificationTypeEnum, ResponseTypeEnum
Message, PubMessage, SubMessage, UnsubMessage,
NotificationTypeEnum
} from '~/shared/pubsub.js'

import type { JSONType, JSONObject } from '~/shared/types.js'
Expand All @@ -26,8 +25,14 @@ const { bold } = require('chalk')
const WebSocket = require('ws')

const { PING, PONG, PUB, SUB, UNSUB } = NOTIFICATION_TYPE
const { ERROR, SUCCESS } = RESPONSE_TYPE
const { ERROR, OK } = RESPONSE_TYPE

const defaultOptions = {
logPingRounds: process.env.NODE_ENV !== 'production' && !process.env.CI,
logPongMessages: false,
maxPayload: 6 * 1024 * 1024,
pingInterval: 30000
}
// Used to tag console output.
const tag = '[pubsub]'

Expand Down Expand Up @@ -67,8 +72,8 @@ export function createNotification (type: NotificationTypeEnum, data: JSONType):
return JSON.stringify({ type, data })
}

export function createResponse (type: ResponseTypeEnum, data: JSONType): string {
return JSON.stringify({ type, data })
export function createOkResponse (data: JSONType): string {
return JSON.stringify({ type: OK, data })
}

/**
Expand Down Expand Up @@ -96,11 +101,12 @@ export function createServer (httpServer: Object, options?: Object = {}): Object
...{ clientTracking: true },
server: httpServer
})
server.channels = new Set()
server.customServerEventHandlers = { ...options.serverHandlers }
server.customSocketEventHandlers = { ...options.socketHandlers }
server.messageHandlers = { ...defaultMessageHandlers, ...options.messageHandlers }
server.pingIntervalID = undefined
server.subscribersByContractID = Object.create(null)
server.subscribersByChannelID = Object.create(null)
server.pushSubscriptions = Object.create(null)

// Add listeners for server events, i.e. events emitted on the server object.
Expand Down Expand Up @@ -138,13 +144,6 @@ export function createServer (httpServer: Object, options?: Object = {}): Object
return Object.assign(server, publicMethods)
}

const defaultOptions = {
logPingRounds: process.env.NODE_ENV !== 'production' && !process.env.CI,
logPongMessages: false,
maxPayload: 6 * 1024 * 1024,
pingInterval: 30000
}

// Default handlers for server events.
// The `this` binding refers to the server object.
const defaultServerHandlers = {
Expand Down Expand Up @@ -204,15 +203,11 @@ const defaultServerHandlers = {
const defaultSocketEventHandlers = {
close (code: string, reason: string) {
const socket = this
const { server, id: socketID } = this

// Notify other client sockets that this one has left any room they shared.
for (const contractID of socket.subscriptions) {
const subscribers = server.subscribersByContractID[contractID]
// Remove this socket from the subscribers of the given contract.
subscribers.delete(socket)
const notification = createNotification(UNSUB, { contractID, socketID })
server.broadcast(notification, { to: subscribers })
const { server } = this

for (const channelID of socket.subscriptions) {
// Remove this socket from the channel subscribers.
server.subscribersByChannelID[channelID].delete(socket)
}
socket.subscriptions.clear()
},
Expand Down Expand Up @@ -262,52 +257,54 @@ const defaultMessageHandlers = {
socket.activeSinceLastPing = true
},

[PUB] (msg: Message) {
// Currently unused.
[PUB] (msg: PubMessage) {
const { server } = this
const subscribers = server.subscribersByChannelID[msg.channelID]
server.broadcast(msg, { to: subscribers ?? [] })
},

[SUB] ({ contractID, dontBroadcast }: SubMessage) {
[SUB] ({ channelID }: SubMessage) {
const socket = this
const { server, id: socketID } = this

if (!socket.subscriptions.has(contractID)) {
log('Already subscribed to', contractID)
// Add the given contract ID to our subscriptions.
socket.subscriptions.add(contractID)
if (!server.subscribersByContractID[contractID]) {
server.subscribersByContractID[contractID] = new Set()
}
const subscribers = server.subscribersByContractID[contractID]
// Add this socket to the subscribers of the given contract.
subscribers.add(socket)
if (!dontBroadcast) {
// Broadcast a notification to every other open subscriber.
const notification = createNotification(SUB, { contractID, socketID })
server.broadcast(notification, { to: subscribers, except: socket })
const { server } = this

if (!server.channels.has(channelID)) {
socket.send(createErrorResponse(
{ type: SUB, channelID, reason: `Unknown channel id: ${channelID}` }
))
return
}
if (!socket.subscriptions.has(channelID)) {
// Add the given channel ID to our subscriptions.
socket.subscriptions.add(channelID)
if (!server.subscribersByChannelID[channelID]) {
server.subscribersByChannelID[channelID] = new Set()
}
// Add this socket to the channel subscribers.
server.subscribersByChannelID[channelID].add(socket)
} else {
log('Already subscribed to', channelID)
}
socket.send(createResponse(SUCCESS, { type: SUB, contractID }))
socket.send(createOkResponse({ type: SUB, channelID }))
},

[UNSUB] ({ contractID, dontBroadcast }: UnsubMessage) {
[UNSUB] ({ channelID }: UnsubMessage) {
const socket = this
const { server, id: socketID } = this

if (socket.subscriptions.has(contractID)) {
// Remove the given contract ID from our subscriptions.
socket.subscriptions.delete(contractID)
if (server.subscribersByContractID[contractID]) {
const subscribers = server.subscribersByContractID[contractID]
// Remove this socket from the subscribers of the given contract.
subscribers.delete(socket)
if (!dontBroadcast) {
const notification = createNotification(UNSUB, { contractID, socketID })
// Broadcast a notification to every other open subscriber.
server.broadcast(notification, { to: subscribers, except: socket })
}
const { server } = this

if (!server.channels.has(channelID)) {
socket.send(createErrorResponse(
{ type: UNSUB, channelID, reason: `Unknown channel id: ${channelID}` }
))
}
if (socket.subscriptions.has(channelID)) {
// Remove the given channel ID from our subscriptions.
socket.subscriptions.delete(channelID)
if (server.subscribersByChannelID[channelID]) {
// Remove this socket from the channel subscribers.
server.subscribersByChannelID[channelID].delete(socket)
}
}
socket.send(createResponse(SUCCESS, { type: UNSUB, contractID }))
socket.send(createOkResponse({ type: UNSUB, channelID }))
}
}

Expand All @@ -320,24 +317,24 @@ const publicMethods = {
* @param except - A recipient to exclude. Optional.
*/
broadcast (
message: Message,
message: Message | string,
{ to, except }: { to?: Iterable<Object>, except?: Object }
) {
const server = this

for (const client of to || server.clients) {
if (client.readyState === WebSocket.OPEN && client !== except) {
client.send(message)
client.send(typeof message === 'string' ? message : JSON.stringify(message))
}
}
},

// Enumerates the subscribers of a given contract.
* enumerateSubscribers (contractID: string): Iterable<Object> {
// Enumerates the subscribers of a given channel.
* enumerateSubscribers (channelID: string): Iterable<Object> {
const server = this

if (contractID in server.subscribersByContractID) {
yield * server.subscribersByContractID[contractID]
if (channelID in server.subscribersByChannelID) {
yield * server.subscribersByChannelID[channelID]
}
},

Expand Down
1 change: 1 addition & 0 deletions backend/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ sbp('sbp/selectors/register', {
await pubsub.broadcast(pubsubMessage, { to: subscribers })
},
'backend/server/handleEntry': async function (entry: GIMessage) {
sbp('okTurtles.data/get', PUBSUB_INSTANCE).channels.add(entry.contractID())
await sbp('chelonia/db/addEntry', entry)
await sbp('backend/server/broadcastEntry', entry)
},
Expand Down
3 changes: 1 addition & 2 deletions frontend/assets/pwa-manifest.webmanifest
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,5 @@
"type": "image/svg+xml"
}
],
"background_color": "#EDEDED",
"theme_color": "#EDEDED"
"background_color": "#EDEDED"
}
28 changes: 14 additions & 14 deletions frontend/controller/service-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,11 @@ sbp('sbp/selectors/register', {
))
} else {
return new Promise((resolve) => {
try {
// Generate a new push subscription
sbp('okTurtles.events/once', REQUEST_TYPE.PUSH_ACTION, async ({ data }) => {
const PUBLIC_VAPID_KEY = data
// Generate a new push subscription
sbp('okTurtles.events/once', REQUEST_TYPE.PUSH_ACTION, async ({ data }) => {
const PUBLIC_VAPID_KEY = data

try {
// 1. Add a new subscription to pushManager using it.
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
Expand All @@ -86,16 +86,16 @@ sbp('sbp/selectors/register', {
))

resolve()
})

pubsub.socket.send(createMessage(
REQUEST_TYPE.PUSH_ACTION,
{ action: PUSH_SERVER_ACTION_TYPE.SEND_PUBLIC_KEY }
))
} catch (err) {
console.error('[sw] service-worker/setup-push-subscription failed with the following error: ', err)
resolve()
}
} catch (err) {
console.error('[sw] service-worker/setup-push-subscription failed with the following error: ', err)
resolve()
}
})

pubsub.socket.send(createMessage(
REQUEST_TYPE.PUSH_ACTION,
{ action: PUSH_SERVER_ACTION_TYPE.SEND_PUBLIC_KEY }
))
})
}
},
Expand Down
1 change: 1 addition & 0 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<title>Group Income</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<meta name="theme-color" content="#F5F5F5">
<link rel="stylesheet" href="/assets/css/main.css">
<link rel="manifest" href="/assets/pwa-manifest.webmanifest" />
<link rel="icon" href="/assets/images/group-income-icon-transparent.png" sizes="32x32">
Expand Down
11 changes: 10 additions & 1 deletion frontend/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type { GIMessage } from '~/shared/domains/chelonia/chelonia.js'
import '~/shared/domains/chelonia/chelonia.js'
import { CONTRACT_IS_SYNCING } from '~/shared/domains/chelonia/events.js'
import * as Common from '@common/common.js'
import { LOGIN, LOGOUT, SWITCH_GROUP } from './utils/events.js'
import { LOGIN, LOGOUT, SWITCH_GROUP, THEME_CHANGE } from './utils/events.js'
import './controller/namespace.js'
import './controller/actions/index.js'
import './controller/backend.js'
Expand Down Expand Up @@ -319,6 +319,14 @@ async function startApp () {
},
'reconnection-succeeded' () {
sbp('gi.ui/clearBanner')
},
'subscription-succeeded' (event) {
const { channelID } = event.detail
if (channelID in sbp('state/vuex/state').contracts) {
sbp('chelonia/contract/sync', channelID, { force: true }).catch(err => {
console.warn(`[chelonia] Syncing contract ${channelID} failed: ${err.message}`)
})
}
}
})
})
Expand All @@ -337,6 +345,7 @@ async function startApp () {
)
}

sbp('okTurtles.events/emit', THEME_CHANGE, this.$store.state.settings.themeColor)
this.setBadgeOnTab()
},
computed: {
Expand Down
19 changes: 17 additions & 2 deletions frontend/model/settings/vuexModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import sbp from '@sbp/sbp'

import Colors from './colors.js'
import { LOGOUT, SET_APP_LOGS_FILTER } from '@utils/events.js'
import { LOGOUT, SET_APP_LOGS_FILTER, THEME_CHANGE } from '@utils/events.js'
import { cloneDeep } from '~/frontend/model/contracts/shared/giLodash.js'
import { THEME_LIGHT, THEME_DARK } from './themes.js'

Expand All @@ -13,6 +13,15 @@ const checkSystemColor = () => {
: THEME_LIGHT
}

const updateMetaThemeTag = (theme: string) => {
// update the content of <meta name='theme-color' /> according to the changed theme
const metaTag: any = document.querySelector('meta[name="theme-color"]')

if (metaTag) {
metaTag.content = theme === THEME_DARK ? '#2E3032' : '#F5F5F5'
}
}

const defaultTheme = 'system'
const defaultColor: string = checkSystemColor()

Expand Down Expand Up @@ -72,13 +81,19 @@ const mutations = {
},
setTheme (state, theme) {
state.theme = theme
state.themeColor = theme === 'system' ? checkSystemColor() : theme

const themeColor = theme === 'system' ? checkSystemColor() : theme
state.themeColor = themeColor
sbp('okTurtles.events/emit', THEME_CHANGE, themeColor)
}
}

// Default application settings must apply again when we're no longer logged in (#1344).
sbp('okTurtles.events/on', LOGOUT, () => sbp('state/vuex/commit', 'resetSettings'))

// make sure to set the status bar color according to the theme setting change.
sbp('okTurtles.events/on', THEME_CHANGE, updateMetaThemeTag)

export default ({
state: () => cloneDeep(defaultSettings),
getters,
Expand Down
2 changes: 2 additions & 0 deletions frontend/utils/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ export const INCOME_DETAILS_UPDATE = 'income-details-update'
export const PAYMENTS_RECORDED = 'payments-recorded'

export const AVATAR_EDITED = 'avatar-edited'

export const THEME_CHANGE = 'theme-change'
5 changes: 2 additions & 3 deletions shared/domains/chelonia/chelonia.js
Original file line number Diff line number Diff line change
Expand Up @@ -587,13 +587,12 @@ export default (sbp('sbp/selectors/register', {
const toUnsubscribe = difference(subscribedIDs, leaveSubscribed)
const toSubscribe = difference(currentIDs, leaveSubscribed)
// There is currently no need to tell other clients about our sub/unsubscriptions.
const dontBroadcast = true
try {
for (const contractID of toUnsubscribe) {
client.unsub(contractID, dontBroadcast)
client.unsub(contractID)
}
for (const contractID of toSubscribe) {
client.sub(contractID, dontBroadcast)
client.sub(contractID)
}
} catch (e) {
console.error(`[chelonia] pubsub/update: error ${e.name}: ${e.message}`, { toUnsubscribe, toSubscribe }, e)
Expand Down
Loading

0 comments on commit 729ff89

Please sign in to comment.