diff --git a/public/config.json b/public/config.json new file mode 100644 index 0000000..e8e39a9 --- /dev/null +++ b/public/config.json @@ -0,0 +1,3 @@ +{ + "SIGIT_BLOSSOM": "https://blossom.sigit.io" +} diff --git a/src/controllers/AuthController.ts b/src/controllers/AuthController.ts index 9cdf85a..1af3c0d 100644 --- a/src/controllers/AuthController.ts +++ b/src/controllers/AuthController.ts @@ -17,6 +17,8 @@ import { saveAuthToken, unixNow } from '../utils' +import { getFileServerMap } from '../utils/file-servers.ts' +import { setServerMapAction } from '../store/servers/action.ts' export class AuthController { private nostrController: NostrController @@ -78,16 +80,30 @@ export class AuthController { }) ) - const relayMap = await getRelayMap(pubkey) + const relayMapPromise = getRelayMap(pubkey) + const serverMapPromise = getFileServerMap(pubkey) + const [relayMap, serverMap] = await Promise.all([ + relayMapPromise, + serverMapPromise + ]) + + // Navigate user to relays page if relay map is empty if (Object.keys(relayMap).length < 1) { - // Navigate user to relays page if relay map is empty return Promise.resolve(appPrivateRoutes.relays) } + // Navigate user to servers page if server map is empty + if (Object.keys(serverMap).length < 1) { + return Promise.resolve(appPrivateRoutes.servers) + } + if (store.getState().auth.loggedIn) { if (!compareObjects(store.getState().relays?.map, relayMap.map)) store.dispatch(setRelayMapAction(relayMap.map)) + + if (!compareObjects(store.getState().servers?.map, serverMap.map)) + store.dispatch(setServerMapAction(serverMap.map)) } /** diff --git a/src/main.tsx b/src/main.tsx index a8b1898..404c8cc 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -18,7 +18,8 @@ store.subscribe( auth: store.getState().auth, metadata: store.getState().metadata, userRobotImage: store.getState().userRobotImage, - relays: store.getState().relays + relays: store.getState().relays, + servers: store.getState().servers }) }, 1000) ) diff --git a/src/pages/settings/servers/index.tsx b/src/pages/settings/servers/index.tsx index a5fb842..6160518 100644 --- a/src/pages/settings/servers/index.tsx +++ b/src/pages/settings/servers/index.tsx @@ -17,10 +17,10 @@ import { useState } from 'react' import { toast } from 'react-toastify' import { FileServerMap, KeyboardCode } from '../../../types' import { - getFileServers, + getFileServerMap, publishFileServer } from '../../../utils/file-servers.ts' -import { useAppSelector } from '../../../hooks/store.ts' +import { useAppSelector } from '../../../hooks' import { useDidMount } from '../../../hooks' import { SIGIT_BLOSSOM } from '../../../utils' import axios from 'axios' @@ -48,7 +48,7 @@ export const ServersPage = () => { const fetchFileServers = async () => { if (usersPubkey) { - await getFileServers(usersPubkey).then((res) => { + await getFileServerMap(usersPubkey).then((res) => { if (res.map) { if (Object.keys(res.map).length === 0) { serverRequirementWarning() diff --git a/src/services/config/index.ts b/src/services/config/index.ts new file mode 100644 index 0000000..624aba6 --- /dev/null +++ b/src/services/config/index.ts @@ -0,0 +1,54 @@ +import axios from 'axios' +import { ILocalConfig } from '../../types/config.ts' + +class LocalConfig { + // Static property to hold the single instance of LocalCache + private static instance: LocalConfig | null = null + + private config: ILocalConfig + + // Private constructor to prevent direct instantiation + private constructor() { + // Set default config + this.config = { + SIGIT_BLOSSOM: 'https://blossom.sigit.io' + } + } + + // Method to initialize the database + private async init() { + axios + .get('/config.json') + .then((response) => { + console.log('response', response) + + if (typeof response.data === 'object') { + this.config = response.data + } else { + throw 'Failed to load config.json: File not found' + } + }) + .catch((error) => { + console.error('Failed to load config.json:', error) + console.warn('Default config will be used.') + }) + } + + // Static method to get the single instance of LocalCache + public static getInstance(): LocalConfig { + // If the instance doesn't exist, create it + if (!LocalConfig.instance) { + LocalConfig.instance = new LocalConfig() + LocalConfig.instance.init() + } + // Return the single instance of LocalCache + return LocalConfig.instance + } + + public getConfig() { + return this.config + } +} + +// Export the single instance of LocalCache +export const localConfig = LocalConfig.getInstance() diff --git a/src/store/actionTypes.ts b/src/store/actionTypes.ts index 90fa99b..bc77195 100644 --- a/src/store/actionTypes.ts +++ b/src/store/actionTypes.ts @@ -11,6 +11,9 @@ export const SET_METADATA_EVENT = 'SET_METADATA_EVENT' export const SET_USER_ROBOT_IMAGE = 'SET_USER_ROBOT_IMAGE' +export const SET_SERVER_MAP = 'SET_SERVER_MAP' +export const SET_SERVER_MAP_UPDATED = 'SET_SERVER_MAP_UPDATED' + export const SET_RELAY_MAP = 'SET_RELAY_MAP' export const SET_RELAY_INFO = 'SET_RELAY_INFO' export const SET_RELAY_MAP_UPDATED = 'SET_RELAY_MAP_UPDATED' diff --git a/src/store/rootReducer.ts b/src/store/rootReducer.ts index 61b2837..895e333 100644 --- a/src/store/rootReducer.ts +++ b/src/store/rootReducer.ts @@ -9,15 +9,18 @@ import relaysReducer from './relays/reducer' import { RelaysDispatchTypes, RelaysState } from './relays/types' import UserAppDataReducer from './userAppData/reducer' import userRobotImageReducer from './userRobotImage/reducer' +import serversReducer from './servers/reducer' import { MetadataDispatchTypes } from './metadata/types' import { UserAppDataDispatchTypes } from './userAppData/types' import { UserRobotImageDispatchTypes } from './userRobotImage/types' +import { ServersDispatchTypes, ServersState } from './servers/types.ts' export interface State { auth: AuthState metadata?: Event userRobotImage?: string relays: RelaysState + servers: ServersState userAppData?: UserAppData } @@ -26,6 +29,7 @@ type AppActions = | MetadataDispatchTypes | UserRobotImageDispatchTypes | RelaysDispatchTypes + | ServersDispatchTypes | UserAppDataDispatchTypes export const appReducer = combineReducers({ @@ -33,6 +37,7 @@ export const appReducer = combineReducers({ metadata: metadataReducer, userRobotImage: userRobotImageReducer, relays: relaysReducer, + servers: serversReducer, userAppData: UserAppDataReducer }) diff --git a/src/store/servers/action.ts b/src/store/servers/action.ts new file mode 100644 index 0000000..9ef9e33 --- /dev/null +++ b/src/store/servers/action.ts @@ -0,0 +1,12 @@ +import * as ActionTypes from '../actionTypes' +import { SetServerMapAction, SetServerMapUpdatedAction } from './types' +import { ServerMap } from '../../types' + +export const setServerMapAction = (payload: ServerMap): SetServerMapAction => ({ + type: ActionTypes.SET_SERVER_MAP, + payload +}) + +export const setServerMapUpdatedAction = (): SetServerMapUpdatedAction => ({ + type: ActionTypes.SET_SERVER_MAP_UPDATED +}) diff --git a/src/store/servers/reducer.ts b/src/store/servers/reducer.ts new file mode 100644 index 0000000..1f06a44 --- /dev/null +++ b/src/store/servers/reducer.ts @@ -0,0 +1,28 @@ +import * as ActionTypes from '../actionTypes' +import { ServersDispatchTypes, ServersState } from './types' + +const initialState: ServersState = { + map: undefined, + mapUpdated: undefined +} + +const reducer = ( + state = initialState, + action: ServersDispatchTypes +): ServersState => { + switch (action.type) { + case ActionTypes.SET_SERVER_MAP: + return { ...state, map: action.payload, mapUpdated: Date.now() } + + case ActionTypes.SET_SERVER_MAP_UPDATED: + return { ...state, mapUpdated: Date.now() } + + case ActionTypes.RESTORE_STATE: + return action.payload.servers || initialState + + default: + return state + } +} + +export default reducer diff --git a/src/store/servers/types.ts b/src/store/servers/types.ts new file mode 100644 index 0000000..41bda0a --- /dev/null +++ b/src/store/servers/types.ts @@ -0,0 +1,22 @@ +import * as ActionTypes from '../actionTypes' +import { RestoreState } from '../actions' +import { ServerMap } from '../../types' + +export type ServersState = { + map?: ServerMap + mapUpdated?: number +} + +export interface SetServerMapAction { + type: typeof ActionTypes.SET_SERVER_MAP + payload: ServerMap +} + +export interface SetServerMapUpdatedAction { + type: typeof ActionTypes.SET_SERVER_MAP_UPDATED +} + +export type ServersDispatchTypes = + | SetServerMapAction + | SetServerMapUpdatedAction + | RestoreState diff --git a/src/types/config.ts b/src/types/config.ts new file mode 100644 index 0000000..fc76a08 --- /dev/null +++ b/src/types/config.ts @@ -0,0 +1,3 @@ +export interface ILocalConfig { + SIGIT_BLOSSOM: string +} diff --git a/src/types/index.ts b/src/types/index.ts index 342c880..e0203ed 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -5,4 +5,5 @@ export * from './profile' export * from './relay' export * from './zip' export * from './event' +export * from './server' export * from './file-server.ts' diff --git a/src/types/server.ts b/src/types/server.ts new file mode 100644 index 0000000..db386bc --- /dev/null +++ b/src/types/server.ts @@ -0,0 +1,6 @@ +export type ServerMap = { + [key: string]: { + read: boolean + write: boolean + } +} diff --git a/src/utils/const.ts b/src/utils/const.ts index 2b8e822..426909a 100644 --- a/src/utils/const.ts +++ b/src/utils/const.ts @@ -1,7 +1,12 @@ +import { localConfig } from '../services/config' +import { ILocalConfig } from '../types/config.ts' + export const EMPTY: string = '' export const ARRAY_BUFFER = 'arraybuffer' export const DEFLATE = 'DEFLATE' +const config: ILocalConfig = localConfig.getConfig() + /** * Number of milliseconds in one week. */ @@ -14,7 +19,7 @@ export const ONE_DAY_IN_MS = 24 * 60 * 60 * 1000 export const SIGIT_RELAY = 'wss://relay.sigit.io' -export const SIGIT_BLOSSOM = 'https://blossom.sigit.io' +export const SIGIT_BLOSSOM = config.SIGIT_BLOSSOM export const DEFAULT_LOOK_UP_RELAY_LIST = [ SIGIT_RELAY, diff --git a/src/utils/file-servers.ts b/src/utils/file-servers.ts index 900c6f1..387db50 100644 --- a/src/utils/file-servers.ts +++ b/src/utils/file-servers.ts @@ -8,7 +8,7 @@ import { Filter, UnsignedEvent, kinds } from 'nostr-tools' * Fetches the relays to get preferred file servers for the given npub * @param npub hex pubkey */ -const getFileServers = async ( +const getFileServerMap = async ( npub: string ): Promise<{ map: FileServerMap; mapUpdated?: number }> => { // More info about this kind of event available https://github.com/nostr-protocol/nips/blob/master/96.md @@ -112,4 +112,4 @@ const getDefaultFileServerMap = (): FileServerMap => ({ [SIGIT_BLOSSOM]: { write: true, read: true } }) -export { getFileServers, publishFileServer } +export { getFileServerMap, publishFileServer }