diff --git a/archaeologist/src/background.ts b/archaeologist/src/background.ts index cd82e649..78c38460 100644 --- a/archaeologist/src/background.ts +++ b/archaeologist/src/background.ts @@ -40,6 +40,7 @@ import { makeAlwaysThrowingStorageApi, makeDatacenterStorageApi, processMsgFromMsgProxyStorageApi, + UserAccount, } from 'smuggler-api' import { makeBrowserExtStorageApi } from './storage_api_browser_ext' @@ -824,17 +825,30 @@ async function initMazedPartsOfTab( } } -function makeStorageApi(appSettings: AppSettings): StorageApi { +function makeStorageApi( + appSettings: AppSettings, + account: UserAccount +): StorageApi { switch (appSettings.storageType) { case 'datacenter': return makeDatacenterStorageApi() case 'browser_ext': - return makeBrowserExtStorageApi(browser.storage.local) + return makeBrowserExtStorageApi(browser.storage.local, account) } } async function initBackground() { - storage = makeStorageApi(await getAppSettings(browser.storage.local)) + auth.observe({ + onLogin: async (account: UserAccount) => { + storage = makeStorageApi( + await getAppSettings(browser.storage.local), + account + ) + }, + onLogout: () => { + storage = makeAlwaysThrowingStorageApi() + }, + }) browser.runtime.onMessage.addListener( async ( diff --git a/archaeologist/src/background/auth.ts b/archaeologist/src/background/auth.ts index 07d35b34..5ed97946 100644 --- a/archaeologist/src/background/auth.ts +++ b/archaeologist/src/background/auth.ts @@ -7,7 +7,8 @@ import { UserAccount, } from 'smuggler-api' import { Knocker, authCookie } from 'smuggler-api' -import { log, isAbortError } from 'armoury' +import { log, isAbortError, errorise } from 'armoury' +import { v4 as uuidv4 } from 'uuid' // Periodically renew auth token using Knocker const _authKnocker = new Knocker( @@ -60,10 +61,35 @@ const _authKnocker = new Knocker( ) let _account: AccountInterface = new AnonymousAccount() -// External listener for login events -let _onLoginListener: ((account: UserAccount) => void) | null = null -// External listener for logout events -let _onLogoutListener: (() => void) | null = null +const _listeners: Map< + string /*listener ID*/, + { + // External listener for login events + onLogin: (account: UserAccount) => void + // External listener for logout events + onLogout: () => void + } +> = new Map() + +function _onLogin(account: UserAccount) { + for (const { onLogin } of _listeners.values()) { + try { + onLogin(account) + } catch (reason) { + log.error(`onLogin listener failed: ${errorise(reason).message}`) + } + } +} + +function _onLogout() { + for (const { onLogout } of _listeners.values()) { + try { + onLogout() + } catch (reason) { + log.error(`onLogin listener failed: ${errorise(reason).message}`) + } + } +} function isAuthenticated(account: AccountInterface): account is UserAccount { return account.isAuthenticated() @@ -91,9 +117,7 @@ async function _loginHandler() { if (!_authKnocker.isActive()) { await _authKnocker.start({ onKnockFailure }) } - if (_onLoginListener) { - _onLoginListener(_account) - } + _onLogin(_account) } await badge.setActive(_account.isAuthenticated()) } @@ -103,9 +127,7 @@ async function _logoutHandler() { _account = new AnonymousAccount() await _authKnocker.abort() await badge.setActive(false) - if (_onLogoutListener) { - _onLogoutListener() - } + _onLogout() } // Actions to do on authorisation token renewal failure (knock of Knocker) @@ -155,23 +177,16 @@ export function observe({ onLogin: (account: UserAccount) => void onLogout: () => void }): () => void { - if (_onLoginListener != null || _onLogoutListener != null) { - throw new Error( - 'Background authentication is already being observed, ' + - 'to observe from multiple places the functionality has to be extended' - ) - } - _onLoginListener = onLogin - _onLogoutListener = onLogout + const listenerId = uuidv4() + _listeners.set(listenerId, { onLogin, onLogout }) const unregister = () => { - _onLoginListener = null - _onLogoutListener = null + _listeners.delete(listenerId) } try { if (isAuthenticated(_account)) { - _onLoginListener(_account) + onLogin(_account) } } catch (e) { unregister() @@ -202,12 +217,9 @@ export async function register() { browser.cookies.onChanged.removeListener(onChangedCookiesListener) await _logoutHandler() try { - if (_onLogoutListener) { - _onLogoutListener() - } + _onLogout() } finally { - _onLoginListener = null - _onLogoutListener = null + _listeners.clear() } } } diff --git a/archaeologist/src/storage_api_browser_ext.ts b/archaeologist/src/storage_api_browser_ext.ts index cdf4039d..b39fb4ca 100644 --- a/archaeologist/src/storage_api_browser_ext.ts +++ b/archaeologist/src/storage_api_browser_ext.ts @@ -43,6 +43,7 @@ import type { ExternalIngestionAdvanceArgs, ExternalIngestionGetArgs, NodeGetAllNidsArgs, + UserAccount, } from 'smuggler-api' import { INodeIterator, @@ -278,12 +279,12 @@ function generateEid(): Eid { async function createNode( store: YekLavStore, - args: NodeCreateArgs + args: NodeCreateArgs, + account: UserAccount ): Promise { // TODO[snikitin@outlook.com] Below keys must become functional somehow. // const _created_via: NodeCreatedVia | undefined = args.created_via - // TODO[snikitin@outlook.com] This graph structure has to work somehow const from_nid: Nid[] = args.from_nid ?? [] const to_nid: Nid[] = args.to_nid ?? [] @@ -298,6 +299,9 @@ async function createNode( index_text: args.index_text, created_at: createdAt, updated_at: createdAt, + meta: { + uid: account.getUid(), + }, } let records: YekLav[] = [ @@ -326,9 +330,7 @@ async function createNode( crtd: createdAt, upd: createdAt, is_sticky: false, - // TODO[snikitin@outlook.com] Evaluate if ownership support is needed - // and implement if yes - owned_by: 'todo', + owned_by: account.getUid(), } // Step 1: create records that allow to discover connected nodes from the // newly created node @@ -487,12 +489,9 @@ class Iterator implements INodeIterator { async function createEdge( store: YekLavStore, - args: EdgeCreateArgs + args: EdgeCreateArgs, + account: UserAccount ): Promise { - // TODO[snikitin@outlook.com] Evaluate if ownership support is needed - // and implement if yes - const owned_by = 'todo' - const createdAt: number = unixtime.now() const edge: TEdgeJson = { eid: generateEid(), @@ -501,7 +500,7 @@ async function createEdge( crtd: createdAt, upd: createdAt, is_sticky: false, - owned_by, + owned_by: account.getUid(), } const items: YekLav[] = [] @@ -645,7 +644,8 @@ export async function getAllNids( } export function makeBrowserExtStorageApi( - browserStore: browser.Storage.StorageArea + browserStore: browser.Storage.StorageArea, + account: UserAccount ): StorageApi { const store = new YekLavStore(browserStore) @@ -664,7 +664,7 @@ export function makeBrowserExtStorageApi( getByOrigin: (args: NodeGetByOriginArgs) => getNodesByOrigin(store, args), getAllNids: (args: NodeGetAllNidsArgs) => getAllNids(store, args), update: (args: NodeUpdateArgs) => updateNode(store, args), - create: (args: NodeCreateArgs) => createNode(store, args), + create: (args: NodeCreateArgs) => createNode(store, args, account), iterate: () => new Iterator(store), delete: throwUnimplementedError('node.delete'), bulkDelete: throwUnimplementedError('node.bulkdDelete'), @@ -692,7 +692,7 @@ export function makeBrowserExtStorageApi( }, }, edge: { - create: (args: EdgeCreateArgs) => createEdge(store, args), + create: (args: EdgeCreateArgs) => createEdge(store, args, account), get: (args: EdgeGetArgs) => getNodeAllEdges(store, args), sticky: throwUnimplementedError('edge.sticky'), delete: throwUnimplementedError('edge.delete'), diff --git a/truthsayer/src/App.tsx b/truthsayer/src/App.tsx index edd78b23..59715e50 100644 --- a/truthsayer/src/App.tsx +++ b/truthsayer/src/App.tsx @@ -1,6 +1,6 @@ /** @jsxImportSource @emotion/react */ -import React, { useCallback, useContext } from 'react' +import React, { useCallback } from 'react' import { BrowserRouter as Router, @@ -326,7 +326,7 @@ function PageviewEventTracker({ analytics }: { analytics: PostHog }) { // See https://posthog.com/docs/integrate/client/js#one-page-apps-and-page-views // for more information about pageview events in PostHog analytics.capture('$pageview') - }, []) + }, [analytics]) const history = useHistory() React.useEffect(() => { diff --git a/truthsayer/src/lib/global.tsx b/truthsayer/src/lib/global.tsx index e5ea3947..52ea251e 100644 --- a/truthsayer/src/lib/global.tsx +++ b/truthsayer/src/lib/global.tsx @@ -5,7 +5,6 @@ import { PostHog } from 'posthog-js' import { jcss } from 'elementary' import { - AccountInterface, makeAlwaysThrowingStorageApi, makeDatacenterStorageApi, makeMsgProxyStorageApi,