diff --git a/apps/movex-demo/pages/local/rps-v2/Game.tsx b/apps/movex-demo/pages/local/rps-v2/Game.tsx new file mode 100644 index 00000000..5e7612a0 --- /dev/null +++ b/apps/movex-demo/pages/local/rps-v2/Game.tsx @@ -0,0 +1,104 @@ +import * as React from 'react'; +import { MovexBoundResource } from 'movex-react'; +import movexConfig from './movex.config'; +import { initialState } from './movex'; +import { useState } from 'react'; +import { ResourceIdentifier, toResourceIdentifierStr } from 'movex-core-util'; +import { GameUI } from './GameUI'; +// import { MovexStoreItem } from "movex"; +import { MovexLocalInstance } from 'movex-react-local-master'; +import { MovexStoreItem } from 'movex-store'; + +type Props = { + masterStore?: MovexStoreItem; +}; + +export function Game(props: Props) { + const [rpsRid, setRpsRid] = useState>(); + const [masterStateUpdated, setMasterStateUpdated] = useState(false); + + return ( +
+ { + const reg = movex.register('rps'); + + reg.create(initialState).map(({ rid }) => { + setRpsRid(rid); + + setTimeout(() => { + // HACK. Fake the Maser State Update in order to fix a current issue with working with + // master state values, instead of local ones. + // See https://github.com/movesthatmatter/movex/issues/9 for more info. + // This feature witll be added in the close future + setMasterStateUpdated(true); + }, 10); + }); + }} + > + {rpsRid && ( + ( +
+ +
+ )} + /> + )} +
+
+
+ + 🆚 + +
+ {props.masterStore && ( +
+
+

+ Master Movex State +

+
+                
+                  {JSON.stringify(
+                    {
+                      submissions: props.masterStore.state[0].submissions,
+                      winner: props.masterStore.state[0].winner,
+                    },
+                    null,
+                    1
+                  )}
+                
+              
+
+
+ )} +
+ {rpsRid && masterStateUpdated && ( + + ( +
+ +
+ )} + /> +
+ )} +
+ ); +} diff --git a/apps/movex-demo/pages/local/rps-v2/GameUI.tsx b/apps/movex-demo/pages/local/rps-v2/GameUI.tsx new file mode 100644 index 00000000..60947fff --- /dev/null +++ b/apps/movex-demo/pages/local/rps-v2/GameUI.tsx @@ -0,0 +1,263 @@ +import React from "react"; +import { MovexBoundResourceFromConfig } from "movex-react"; +import { useCallback, useEffect, useMemo } from "react"; +import { globalLogsy } from "movex-core-util"; +import movexConfig from "./movex.config"; +import { + PlayerLabel, + toOppositeLabel, + RPS, + selectAvailableLabels +} from "./movex"; + +type Props = { + boundResource: MovexBoundResourceFromConfig< + typeof movexConfig["resources"], + "rps" + >; + userId: string; + buttonClassName?: string; + containerClassName?: string; + backgroundColor?: string; +}; + +export const GameUI: React.FC = ({ + boundResource, + userId, + ...props +}) => { + const { state, dispatch, dispatchPrivate } = boundResource; + const { players } = state; + + const myPlayerLabel = useMemo((): PlayerLabel | undefined => { + if (!(players.playerA && players.playerB)) { + return undefined; + } + + if (players.playerA.id === userId) { + return "playerA"; + } + + if (players.playerB.id === userId) { + return "playerB"; + } + + return undefined; + }, [players, userId]); + + const oppnentPlayerLabel = useMemo(() => { + return myPlayerLabel ? toOppositeLabel(myPlayerLabel) : undefined; + }, [myPlayerLabel]); + + // Add Player + useEffect(() => { + // TODO: here there is a major issue, as selectAvailableLables works with local state + // but it needs to check on the actual (master) state. How to solve this? + // add an api to be able to read master state seperately? + + // Or in this case change the strategy altogether, and work with master generated values, in which case + // the local optimistic state udate gets turned off by default, so that means it will wait for the real state to update. + // Kinda like a dispatchAndWait + const availableLabels = selectAvailableLabels(state); + + if ( + state.players.playerA?.id === userId || + state.players.playerB?.id === userId + ) { + return; + } + + if (availableLabels.length === 0) { + globalLogsy.warn("Player Slots taken"); + + return; + } + + dispatch({ + type: "addPlayer", + payload: { + id: userId, + playerLabel: availableLabels[0], + atTimestamp: new Date().getTime() + } + }); + }, [userId, state, dispatch]); + + const submit = useCallback( + (play: RPS) => { + if (!myPlayerLabel) { + console.warn("Not A Player"); + return; + } + + dispatchPrivate( + { + type: "submit", + payload: { + playerLabel: myPlayerLabel, + rps: play + }, + isPrivate: true + }, + { + type: "setReadySubmission", + payload: { + playerLabel: myPlayerLabel + } + } + ); + }, + [dispatchPrivate, myPlayerLabel] + ); + + const winner = useMemo(() => { + if (!state.winner) { + return undefined; + } + + if (state.winner === "1/2") { + return "1/2"; + } + + const { + submissions: { playerA } + } = state; + + if (playerA.play === state.winner) { + return state.players.playerA.label; + } + + return state.players.playerB.label; + }, [state.winner]); + + return ( +
+
+
{userId}
+ {state.winner ? ( + <> +

+ {winner === "1/2" + ? "Draw 😫" + : winner === myPlayerLabel + ? "You Won 🎉" + : "You Lost 😢"} +

+ + + ) : ( + <> +
+
+ +
+
+ +
+
+ +
+
+
+ {oppnentPlayerLabel && + state.submissions[oppnentPlayerLabel]?.play && ( +
+ Opponent Submitted{" "} + + ⌛️⏰ + +
+ )} +
+ + )} +
+
+
+

{userId} Movex State

+
+            
+              {JSON.stringify(
+                {
+                  submissions: state.submissions,
+                  winner: state.winner
+                },
+                null,
+                1
+              )}
+            
+          
+
+
+
+ ); +}; diff --git a/apps/movex-demo/pages/local/rps-v2/index.tsx b/apps/movex-demo/pages/local/rps-v2/index.tsx new file mode 100644 index 00000000..0efdd323 --- /dev/null +++ b/apps/movex-demo/pages/local/rps-v2/index.tsx @@ -0,0 +1,28 @@ +import { useState } from 'react'; +import movexConfig from './movex.config'; +import { Game } from './Game'; +import { MovexLocalMasterProvider } from 'movex-react-local-master'; +import { MovexStoreItem } from 'movex-store'; + +export default function App() { + const [masterStore, setMasterStore] = useState>(); + + return ( + /** + * This is using the Local Provider in order to simulate + * multiple players in the same browser instance + */ + { + // console.log('event', method, prefix, message, payload) + console[method](prefix + ' ' + message, payload); + }, + }} + > + + + ); +} diff --git a/apps/movex-demo/pages/local/rps-v2/movex.config.ts b/apps/movex-demo/pages/local/rps-v2/movex.config.ts new file mode 100644 index 00000000..d558b418 --- /dev/null +++ b/apps/movex-demo/pages/local/rps-v2/movex.config.ts @@ -0,0 +1,10 @@ +import { reducer as rpsReducer } from "./movex"; + +// The idea of this is so both the server and the client can read it +// Obviosuly being a node/js env helps a lot :) + +export default { + resources: { + rps: rpsReducer + } +}; diff --git a/apps/movex-demo/pages/local/rps-v2/movex.ts b/apps/movex-demo/pages/local/rps-v2/movex.ts new file mode 100644 index 00000000..2dc0c8c3 --- /dev/null +++ b/apps/movex-demo/pages/local/rps-v2/movex.ts @@ -0,0 +1,241 @@ +// import { Action } from "movex"; +// import { MovexClient } from "movex-core-util"; + +import { Action, MovexClient } from 'movex-core-util'; + +export type PlayerId = MovexClient["id"]; +export type Color = string; + +export type RPS = "rock" | "paper" | "scissors"; + +export const playerLabels = ["playerA", "playerB"] as const; +export type PlayerLabel = "playerA" | "playerB"; + +export type State = Game; + +export const initialState: State = { + players: { + playerA: null, + playerB: null + }, + winner: null, + submissions: { + playerA: null, + playerB: null + } +}; + +export const selectAvailableLabels = (state: State): PlayerLabel[] => { + return playerLabels.filter((l) => state.players[l] === null); +}; + +export function toOppositeLabel( + c: L +): L extends "playerA" ? "playerB" : "playerA"; +export function toOppositeLabel(l: L) { + return l === "playerA" ? "playerB" : "playerA"; +} + +export const getRPSWinner = ([a, b]: [ + RPS | "$SECRET" | null | undefined, + RPS | "$SECRET" | null | undefined +]): RPS | "1/2" | null => { + if (!a || a === "$SECRET" || !b || b === "$SECRET") { + return null; + } + + if (a === b) { + return "1/2"; + } + + if (a === "paper") { + if (b === "rock") { + return a; + } + + return b; + } else if (a === "rock") { + if (b === "scissors") { + return a; + } + + return b; + } else if (b === "paper") { + return a; + } + + return b; +}; + +type Player = { + id: PlayerId; + label: PlayerLabel; +}; + +type RevealedSubmission = { + play: RPS; +}; + +type SecretSubmission = { + play: "$SECRET"; +}; + +export type Submission = RevealedSubmission | SecretSubmission; + +export type GameInProgress = { + players: { + playerA: Player | null; + playerB: Player | null; + }; + submissions: { + playerA: Submission | null; + playerB: Submission | null; + }; + winner: null; +}; + +export type GameCompleted = { + players: { + playerA: Player; + playerB: Player; + }; + submissions: { + playerA: Submission; + playerB: Submission; + }; + winner: RPS | "1/2"; +}; + +export type Game = GameInProgress | GameCompleted; + +export type Actions = + | Action< + "addPlayer", + { + id: PlayerId; + playerLabel: PlayerLabel; + atTimestamp: number; + } + > + | Action<"playAgain"> + | Action< + "submit", + { + playerLabel: PlayerLabel; + rps: RPS; + } + > + | Action< + "setReadySubmission", + { + playerLabel: PlayerLabel; + } + >; + +export const reducer = (state = initialState, action: Actions): State => { + if (action.type === "playAgain") { + return { + ...state, + players: state.players, + winner: null, + submissions: { + playerA: null, + playerB: null + } + }; + } + + if (action.type === "addPlayer") { + // If already taken return + if (state.players[action.payload.playerLabel] !== null) { + return state; + } + + return { + ...state, + + players: { + // TODO: This is just stupid needing a recast b/c it cannot determine if the game is completed or inProgress, but at this point I care not + ...(state.players as any), + [action.payload.playerLabel]: { + label: action.payload.playerLabel, + id: action.payload.id + } + } + }; + } + + if (action.type === "submit") { + // If game is completed + if (state.winner !== null) { + return state; + } + + const oppositeLabel = toOppositeLabel(action.payload.playerLabel); + + // 1st submission + if (state.submissions[oppositeLabel] === null) { + return { + ...state, + submissions: { + ...(action.payload.playerLabel === "playerA" + ? { + playerA: { + play: action.payload.rps + }, + playerB: null + } + : { + playerB: { + play: action.payload.rps + }, + playerA: null + }) + } + }; + } else { + // final submission: game gets completed + const nextSubmission = { + ...state.submissions, + [action.payload.playerLabel]: { + play: action.payload.rps + } + }; + + const nextWinner = getRPSWinner([ + nextSubmission.playerA?.play, + nextSubmission.playerB?.play + ]); + + return { + ...state, + submissions: nextSubmission, + winner: nextWinner as any + }; + } + } else if (action.type === "setReadySubmission") { + // If game is completed + if (state.winner !== null) { + return state; + } + + const nextSubmission = { + ...state.submissions, + [action.payload.playerLabel]: { + play: "$SECRET" + } + }; + + return { + ...state, + submissions: nextSubmission + }; + } + + return state; +}; + +reducer.$canReconcileState = (state: State) => + state.submissions.playerA !== null && state.submissions.playerB !== null; + +export default reducer; diff --git a/libs/movex-core-util/package.json b/libs/movex-core-util/package.json index 505952b7..738f7d27 100644 --- a/libs/movex-core-util/package.json +++ b/libs/movex-core-util/package.json @@ -1,6 +1,6 @@ { "name": "movex-core-util", - "version": "0.1.5-0", + "version": "0.1.5-1", "description": "Movex Core Util is the library of utilities for Movex", "license": "MIT", "author": { diff --git a/libs/movex-core-util/src/lib/Logsy.ts b/libs/movex-core-util/src/lib/Logsy.ts index 8f3a8745..f68925c1 100644 --- a/libs/movex-core-util/src/lib/Logsy.ts +++ b/libs/movex-core-util/src/lib/Logsy.ts @@ -1,5 +1,7 @@ // const movexLogsy = Logger.get('Movex'); +import { Pubsy } from 'ts-pubsy'; + type LogsyMethods = | 'log' | 'info' @@ -9,98 +11,70 @@ type LogsyMethods = | 'groupEnd' | 'debug'; -const globalDisabled = false; - -const globalLogsyConfigWrapper = { - config: { - disabled: false, - verbose: false, - }, +export type LoggingEvent = { + method: LogsyMethods; + prefix?: string; + message?: unknown; + payload?: LogsyPayload; }; -// Depreacte from here! maybe make own library +export type LogsyPayload = Record; class Logsy { - constructor( - public prefix: string = '', - disabled = false, - private globalConfig?: typeof globalLogsyConfigWrapper - ) { - // This is needed - if (disabled) { - this.disable(); - } - } - - private handler = ( - method: LogsyMethods, - message?: unknown, - ...optionalParams: unknown[] - ) => { - if (!this.ON || globalLogsyConfigWrapper.config.disabled) { - return; - } + private pubsy = new Pubsy<{ + onLog: LoggingEvent; + }>(); - // If verbose is false, we don't want to log, info or debug - if ( - (method === 'log' || method === 'info' || method === 'debug') && - !globalLogsyConfigWrapper.config.verbose - ) { - return; - } + constructor(public prefix: string = '') {} - const prefix = this.hasGroupOpen() ? '' : this.prefix; + onLog = (fn: (event: LoggingEvent) => void) => + this.pubsy.subscribe('onLog', fn); - if (typeof message === 'string') { - console[method](prefix + ' ' + message, ...optionalParams); - } else { - console[method](prefix, message, ...optionalParams); - } + // To be overriden + // public onLog: (event: LoggingEvent) => void = (event: LoggingEvent) => { + // this.pubsy.publish('onLog', { + // ...event, + // }); + // }; + + private handler = (event: LoggingEvent) => { + const prefix = this.hasGroupOpen() ? '' : this.prefix; + this.pubsy.publish('onLog', { + ...event, + prefix, + }); + // this.onLog({ ...event, prefix }); }; - public ON: boolean = globalDisabled || true; + // public ON: boolean = globalDisabled || true; private activeGroups = 0; - enable() { - this.ON = true; - - if (this.globalConfig) { - this.globalConfig.config.disabled = false; - } - } - - disable() { - this.ON = false; - - if (this.globalConfig) { - this.globalConfig.config.disabled = true; - } - } - - log = (message?: any, ...optionalParams: any[]) => { - this.handler('log', message, ...optionalParams); + log = (message?: string, payload?: LogsyPayload) => { + this.handler({ method: 'log', message, payload }); }; - info = (message?: any, ...optionalParams: any[]) => { - this.handler('info', message, ...optionalParams); + info = (message?: string, payload?: LogsyPayload) => { + this.handler({ method: 'info', message, payload }); }; - warn = (message?: any, ...optionalParams: any[]) => { - this.handler('warn', message, ...optionalParams); + warn = (message?: string, payload?: LogsyPayload) => { + this.handler({ method: 'warn', message, payload }); }; - error = (message?: any, ...optionalParams: any[]) => { - this.handler('error', message, ...optionalParams); + error = (message?: string, payload?: LogsyPayload) => { + this.handler({ method: 'error', message, payload }); }; - group = (message?: any, ...optionalParams: any[]) => { - this.handler('group', message, ...optionalParams); + group = (message?: string, payload?: LogsyPayload) => { + this.handler({ method: 'group', message, payload }); this.openGroup(); }; - groupEnd = () => { - // console.groupEnd() - this.handler('groupEnd'); + groupEnd = (message?: string, payload?: LogsyPayload) => { + if (message) { + this.handler({ method: 'log', message, payload }); + } + this.handler({ method: 'groupEnd' }); this.closeGroup(); }; @@ -118,14 +92,25 @@ class Logsy { private hasGroupOpen = () => this.activeGroups > 0; - debug = (message?: any, ...optionalParams: any[]) => { - this.handler('debug', message, ...optionalParams); + debug = (message?: any, payload?: LogsyPayload) => { + this.handler({ method: 'debug', message, payload }); }; withNamespace = (s: string) => { - return new Logsy(this.prefix + s); + const next = new Logsy(this.prefix + s); + + // next.onLog((...args) => this.onLog(...args)); + // // this.onLog(); + // this.onLog((event) => { + // this.pubsy.publish('onLog', event); + // }); + // next.onLog = (event) => this.pubsy.publish('onLog', event); + next.onLog((event) => { + this.pubsy.publish('onLog', event); + }); + + return next; }; } -export const globalLogsy = new Logsy('', false, globalLogsyConfigWrapper); -export const logsy = globalLogsy; \ No newline at end of file +export const globalLogsy = new Logsy(); diff --git a/libs/movex-core-util/src/lib/ScketIOEmitter.ts b/libs/movex-core-util/src/lib/ScketIOEmitter.ts index 7028621c..4f93cfc0 100644 --- a/libs/movex-core-util/src/lib/ScketIOEmitter.ts +++ b/libs/movex-core-util/src/lib/ScketIOEmitter.ts @@ -1,5 +1,5 @@ import { Err, Ok } from 'ts-results'; -import { logsy } from './Logsy'; +import { globalLogsy } from './Logsy'; import type { Socket as ServerSocket } from 'socket.io'; import type { Socket as ClientSocket } from 'socket.io-client'; import type { EventMap } from 'typed-emitter'; @@ -8,18 +8,17 @@ import type { UnsubscribeFn, WsResponseResultPayload } from './core-types'; export type SocketIO = ServerSocket | ClientSocket; +const logsy = globalLogsy.withNamespace('[SocketIOEmitter]'); + export class SocketIOEmitter implements EventEmitter { - private logger: typeof console; constructor( private socket: SocketIO, private config: { - logger?: typeof console; waitForResponseMs?: number; } = {} ) { - this.logger = config.logger || console; this.config.waitForResponseMs = this.config.waitForResponseMs || 15 * 1000; } @@ -67,7 +66,7 @@ export class SocketIOEmitter acknowledgeCb?: (response: ReturnType) => void ): boolean { const reqId = `${event as string}(${String(Math.random()).slice(-3)})`; - logsy.debug('[ServerSocketEmitter]', reqId, 'Emit:', event, request); + logsy.debug('Emit', { reqId, event, request }); this.socket.emit( event as string, @@ -76,10 +75,10 @@ export class SocketIOEmitter withTimeout( (res: WsResponseResultPayload) => { if (res.ok) { - logsy.debug('[ServerSocketEmitter]', reqId, 'Response Ok:', res); + logsy.debug('Emit Response Ok', { reqId, event, res }); acknowledgeCb(new Ok(res.val) as ReturnType); } else { - logsy.warn('[ServerSocketEmitter]', reqId, 'Response Err:', res); + logsy.debug('Emit Response Err', { reqId, event, res }); acknowledgeCb(new Err(res.val) as ReturnType); } }, @@ -106,13 +105,11 @@ export class SocketIOEmitter ): Promise> { return new Promise((resolve, reject) => { const reqId = `${event as string}(${String(Math.random()).slice(-3)})`; - logsy.debug( - '[ServerSocketEmitter]', + logsy.debug('EmitAndAcknowledge', { reqId, - 'EmitAndAcknowledge:', event, - request - ); + request, + }); this.socket.emit( event as string, @@ -120,20 +117,30 @@ export class SocketIOEmitter withTimeout( (res: WsResponseResultPayload) => { if (res.ok) { - logsy.debug('[ServerSocketEmitter]', reqId, 'Response Ok:', res); + logsy.debug('EmitAndAcknowledge Response Ok', { + reqId, + res, + request, + event, + }); resolve(new Ok(res.val)); } else { - logsy.warn('[ServerSocketEmitter]', reqId, 'Response Err:', res); + logsy.debug('EmitAndAcknowledge Response Err', { + reqId, + res, + request, + event, + }); reject(new Err(res.val)); } }, () => { - logsy.warn( - '[ServerSocketEmitter]', + logsy.error('EmitAndAcknowledge Request Timeout', { + reqId, + request, event, - 'Request Timeout:', - request - ); + }); + // TODO This error could be typed better using a result error reject(new Err('RequestTimeout')); }, diff --git a/libs/movex-master/package.json b/libs/movex-master/package.json index 6096e5e5..2f50b8ca 100644 --- a/libs/movex-master/package.json +++ b/libs/movex-master/package.json @@ -1,6 +1,6 @@ { "name": "movex-master", - "version": "0.1.5-0", + "version": "0.1.5-1", "license": "MIT", "description": "Movex-master defines the master that wil be used on movex-server and movex-react-local-master", "author": { diff --git a/libs/movex-master/src/lib/MockConnectionEmitter.ts b/libs/movex-master/src/lib/MockConnectionEmitter.ts index ee8bde91..c2bb1fa9 100644 --- a/libs/movex-master/src/lib/MockConnectionEmitter.ts +++ b/libs/movex-master/src/lib/MockConnectionEmitter.ts @@ -1,13 +1,16 @@ -import type { +import { AnyAction, EventEmitter, UnsubscribeFn, IOEvents, -} from 'movex-core-util'; + globalLogsy, +} from 'movex-core-util'; import { Pubsy } from 'ts-pubsy'; import { getRandomInt, getUuid } from './util'; +const logsy = globalLogsy.withNamespace('MockConnectionEmitter'); + export class MockConnectionEmitter< TState extends any = any, TAction extends AnyAction = AnyAction, @@ -163,14 +166,12 @@ export class MockConnectionEmitter< // TODO: Need a way for this to call the unsubscriber this.ackPubsy.subscribe(ackId, (ackMsg) => { - console.log( - '[MockConnectionEmitter] emit', + logsy.log('Emit', { event, request, - 'response:', - ackMsg - ); - console.trace('[MockConnectionEmitter] Trace', event, request); + response: ackMsg, + }); + acknowledgeCb( ackMsg as ReturnType[E]> ); diff --git a/libs/movex-master/src/lib/MovexMasterServer.ts b/libs/movex-master/src/lib/MovexMasterServer.ts index fd2aa564..9d100412 100644 --- a/libs/movex-master/src/lib/MovexMasterServer.ts +++ b/libs/movex-master/src/lib/MovexMasterServer.ts @@ -99,7 +99,10 @@ export class MovexMasterServer { // Forwardable objectKeys(peerActions.byClientId).forEach((peerId) => { if (!peerActions.byClientId[peerId]) { - logsy.error('Inexistant Peer Connection for peerId:', peerId); + logsy.error('Inexistant Peer Connection for peerId:', { + peerId, + peerActionsByClientId: peerActions.byClientId, + }); return; } @@ -261,11 +264,10 @@ export class MovexMasterServer { const peerConnection = this.clientConnectionsByClientId[peerId]; if (!peerConnection) { - logsy.error( - 'onAddResourceSubscriber: Peer Connection not found for', + logsy.error('OnAddResourceSubscriber PeerConnectionNotFound', { peerId, - this.clientConnectionsByClientId - ); + clientId: this.clientConnectionsByClientId, + }); return; } @@ -312,12 +314,10 @@ export class MovexMasterServer { >, }; - logsy.log( - '[MovexMasterServer] Added Connection Succesfully:', - clientConnection.clientId, - '| Connections', - Object.keys(this.clientConnectionsByClientId).length - ); + logsy.info('Connection Added Succesfully', { + clientId: clientConnection.clientId, + connectionsCount: Object.keys(this.clientConnectionsByClientId).length, + }); // Unsubscribe return () => { @@ -343,39 +343,34 @@ export class MovexMasterServer { this.clientConnectionsByClientId = restOfConnections; - logsy.log( - '[MovexMasterServer] Removed Connection Succesfully for Client:', + logsy.info('Connection Removed', { clientId, - '| Connections Left:', - Object.keys(this.clientConnectionsByClientId).length - ); + connectionsLeft: Object.keys(this.clientConnectionsByClientId).length, + }); } private unsubscribeClientFromResources(clientId: MovexClient['id']) { - const clientSubscricptions = this.subscribersToRidsMap[clientId]; + const clientSubscriptions = this.subscribersToRidsMap[clientId]; - if (!clientSubscricptions) { - logsy.log('No Resource Subscription'); - return; - } - - const subscribedRidsList = objectKeys(clientSubscricptions); + if (clientSubscriptions) { + const subscribedRidsList = objectKeys(clientSubscriptions); - subscribedRidsList.forEach(async (rid) => { - await this.removeResourceSubscriberAndNotifyPeersOfClientUnsubscription( - rid, - clientId - ); + subscribedRidsList.forEach(async (rid) => { + await this.removeResourceSubscriberAndNotifyPeersOfClientUnsubscription( + rid, + clientId + ); - // TODO: should this wait for the client to ack or smtg? probably not needed + // TODO: should this wait for the client to ack or smtg? probably not needed - // Remove the rid from the client's record - const { [rid]: removed, ...rest } = this.subscribersToRidsMap[clientId]; + // Remove the rid from the client's record + const { [rid]: removed, ...rest } = this.subscribersToRidsMap[clientId]; - this.subscribersToRidsMap[clientId] = rest; - }); + this.subscribersToRidsMap[clientId] = rest; + }); + } - logsy.log('Removed client subscriptions ok'); + logsy.info('Client Subscriptions Removed', { clientId }); } private async removeResourceSubscriberAndNotifyPeersOfClientUnsubscription( @@ -387,7 +382,10 @@ export class MovexMasterServer { const masterResource = this.masterResourcesByType[resourceType]; if (!masterResource) { - logsy.error('MasterResourceInexistent', resourceType); + logsy.error( + 'RemoveResourceSubscriberAndNotifyPeersOfClientUnsubscription MasterResourceInexistent', + { resourceType } + ); return; } @@ -399,13 +397,6 @@ export class MovexMasterServer { await masterResource .getSubscribers(rid) .map((clientIds) => { - console.log( - 'notifyPeersOfClientUnsubscription resource', - rid, - 'subscribers', - clientIds - ); - objectKeys(clientIds) .filter((clientId) => clientId !== subscriberClientId) .forEach((peerId) => { @@ -413,9 +404,11 @@ export class MovexMasterServer { if (!peerConnection) { logsy.error( - 'notifyPeersOfClientUnsubscription Peer Connection not found for', - peerId, - this.clientConnectionsByClientId + 'RemoveResourceSubscriberAndNotifyPeersOfClientUnsubscription Peer Connection not found for', + { + peerId, + resourceType, + } ); return; } diff --git a/libs/movex-master/src/lib/orchestrator.ts b/libs/movex-master/src/lib/orchestrator.ts index 7d033aa0..3ef989d5 100644 --- a/libs/movex-master/src/lib/orchestrator.ts +++ b/libs/movex-master/src/lib/orchestrator.ts @@ -2,19 +2,21 @@ import { MovexClient, ResourceIdentifier, invoke, - logsy, + globalLogsy, AnyAction, MovexReducer, ConnectionToMaster, BaseMovexDefinitionResourcesMap, MovexDefinition, ConnectionToClient, -} from 'movex-core-util'; +} from 'movex-core-util'; import { Movex, MovexFromDefintion } from 'movex'; import { MovexMasterResource, MovexMasterServer } from 'movex-master'; import { MemoryMovexStore } from 'movex-store'; import { MockConnectionEmitter } from './MockConnectionEmitter'; +const logsy = globalLogsy.withNamespace('[MovexClientMasterOrchestrator]'); + export const movexClientMasterOrchestrator = () => { let unsubscribe = async () => {}; @@ -136,13 +138,13 @@ export const orchestrateDefinedMovex = < const unsubscribers = [ emitterOnClient._onEmitted((r, ackCb) => { - logsy.log('[Orchestrator] emitterOnClient _onEmitted', r); + logsy.debug('EmitterOnClient _onEmitted', r); // Calling the master with the given event from the client in order to process it emitterOnMaster._publish(r.event, r.payload, ackCb); }), emitterOnMaster._onEmitted((r, ackCb) => { - logsy.log('[Orchestrator] emitterOnMaster _onEmitted', r); - // Calling the client with the given event from the client in order to process it + logsy.debug('EmitterOnMaster _onEmitted', r); + // Calling the client with the given event from the master in order to process it emitterOnClient._publish(r.event, r.payload, ackCb); }), ]; diff --git a/libs/movex-react-local-master/package.json b/libs/movex-react-local-master/package.json index fb864314..a90c6404 100644 --- a/libs/movex-react-local-master/package.json +++ b/libs/movex-react-local-master/package.json @@ -1,6 +1,6 @@ { "name": "movex-react-local-master", - "version": "0.1.5-0", + "version": "0.1.5-1", "license": "MIT", "description": "Run the movex-master locally with react", "author": { diff --git a/libs/movex-react-local-master/src/lib/MovexLocalMasterProvider.tsx b/libs/movex-react-local-master/src/lib/MovexLocalMasterProvider.tsx index db648fa7..11dcdb1a 100644 --- a/libs/movex-react-local-master/src/lib/MovexLocalMasterProvider.tsx +++ b/libs/movex-react-local-master/src/lib/MovexLocalMasterProvider.tsx @@ -1,18 +1,21 @@ import React, { useEffect, useState } from 'react'; import { invoke, - logsy, + globalLogsy, noop, emptyFn, type StringKeys, type GetReducerState, type BaseMovexDefinitionResourcesMap, type MovexDefinition, -} from 'movex-core-util'; + LoggingEvent, +} from 'movex-core-util'; import { MemoryMovexStore, type MovexStoreItem } from 'movex-store'; import { MovexMasterServer, initMovexMaster } from 'movex-master'; import { MovexLocalContext, MovexLocalContextProps } from './MovexLocalContext'; +const logsy = globalLogsy.withNamespace('[MovexLocalMasterProvider]'); + type Props = React.PropsWithChildren<{ movexDefinition: MovexDefinition; @@ -26,6 +29,9 @@ type Props = >, updateKind: 'create' | 'update' ) => void; + logger?: { + onLog: (event: LoggingEvent) => void; + }; }>; /** @@ -36,7 +42,7 @@ type Props = */ export const MovexLocalMasterProvider: React.FC< Props -> = ({ onInit = noop, onMasterResourceUpdated = noop, ...props }) => { +> = ({ onInit = noop, onMasterResourceUpdated = noop, logger, ...props }) => { const [contextState, setContextState] = useState(); useEffect(() => { @@ -59,21 +65,15 @@ export const MovexLocalMasterProvider: React.FC< >(); const unsubscribers = [ - localStore.onCreated((s) => { - logsy.group('[Master.LocalStore] onCreated'); - logsy.log('Item', s); - logsy.log('All Store', localStore.all()); - logsy.groupEnd(); + localStore.onCreated((item) => { + logsy.info('onCreated', { item }); - onMasterResourceUpdated(s as any, 'create'); + onMasterResourceUpdated(item as any, 'create'); }), - localStore.onUpdated((s) => { - logsy.group('[Master.LocalStore] onUpdated'); - logsy.log('Item', s); - logsy.log('All Store', localStore.all()); - logsy.groupEnd(); + localStore.onUpdated((item) => { + logsy.info('onUpdated', { item }); - onMasterResourceUpdated(s as any, 'update'); + onMasterResourceUpdated(item as any, 'update'); }), ]; @@ -95,6 +95,14 @@ export const MovexLocalMasterProvider: React.FC< } }, [contextState]); + useEffect(() => { + if (logger) { + return globalLogsy.onLog(logger.onLog); + } + + return () => {}; + }, [logger]); + if (!contextState) { return null; } diff --git a/libs/movex-react-local-master/src/lib/MovexLocalProvider.tsx b/libs/movex-react-local-master/src/lib/MovexLocalProvider.tsx index 720bc0f4..7cefed12 100644 --- a/libs/movex-react-local-master/src/lib/MovexLocalProvider.tsx +++ b/libs/movex-react-local-master/src/lib/MovexLocalProvider.tsx @@ -2,9 +2,11 @@ import React from 'react'; import { invoke, ConnectionToClient, - type MovexClient, + type MovexClient as MovexClientUser, type MovexDefinition, type BaseMovexDefinitionResourcesMap, + StringKeys, + ResourceIdentifier, } from 'movex-core-util'; import { MovexMasterServer, @@ -12,13 +14,18 @@ import { orchestrateDefinedMovex, getUuid, // This can actually be mocked here as it's just client only! } from 'movex-master'; -import { MovexReactContext, MovexReactContextProps } from 'movex-react'; +import { MovexClient } from 'movex'; +import { + MovexReactContext, + MovexReactContextProps, + MovexResourceObservablesRegistry, +} from 'movex-react'; import { MovexLocalContextConsumerProvider } from './MovexLocalContextConsumer'; type Props = React.PropsWithChildren<{ movexDefinition: MovexDefinition; - clientId?: MovexClient['id']; + clientId?: MovexClientUser['id']; onConnected?: ( state: Extract, { connected: true }> ) => void; @@ -77,12 +84,28 @@ export class MovexLocalProvider< emitterOnMaster ); + // This resets each time movex re-initiates + const resourceRegistry = new MovexResourceObservablesRegistry( + mockedMovex.movex + ); + const nextState = { connected: true, movex: mockedMovex.movex, clientId: mockedMovex.movex.getClientId(), movexDefinition: this.props.movexDefinition, - bindResource: () => () => {}, // Added this on Apr 01 2024, w/o testing - not sure it doesn't create more issues but needed it for tsc + bindResource: >( + rid: ResourceIdentifier, + onStateUpdate: (p: MovexClient.MovexBoundResource) => void + ) => { + const $resource = resourceRegistry.register(rid); + + onStateUpdate(new MovexClient.MovexBoundResource($resource)); + + return $resource.onUpdate(() => { + onStateUpdate(new MovexClient.MovexBoundResource($resource)); + }); + }, } as const; this.setState({ diff --git a/libs/movex-react/package.json b/libs/movex-react/package.json index b3ea220e..23a360d2 100644 --- a/libs/movex-react/package.json +++ b/libs/movex-react/package.json @@ -1,6 +1,6 @@ { "name": "movex-react", - "version": "0.1.5-0", + "version": "0.1.5-3", "license": "MIT", "description": "Movex React is the library of React components for Movex", "author": { diff --git a/libs/movex-react/src/index.ts b/libs/movex-react/src/index.ts index d4507102..dca97359 100644 --- a/libs/movex-react/src/index.ts +++ b/libs/movex-react/src/index.ts @@ -8,5 +8,6 @@ export { } from './lib/MovexContext'; export { MovexBoundResourceComponent as MovexBoundResource } from './lib/MovexBoundResourceComponent'; +export { ResourceObservablesRegistry as MovexResourceObservablesRegistry } from './lib/ResourceObservableRegistry'; export * from './lib/MovexConnection'; export * from './lib/hooks'; diff --git a/libs/movex-react/src/lib/MovexBoundResourceComponent.tsx b/libs/movex-react/src/lib/MovexBoundResourceComponent.tsx index 381a483f..a4c639ee 100644 --- a/libs/movex-react/src/lib/MovexBoundResourceComponent.tsx +++ b/libs/movex-react/src/lib/MovexBoundResourceComponent.tsx @@ -1,6 +1,6 @@ import React from 'react'; import type { MovexClient } from 'movex'; -import type { +import { GetReducerState, GetReducerAction, ResourceIdentifier, @@ -9,7 +9,6 @@ import type { BaseMovexDefinitionResourcesMap, MovexDefinition, UnsubscribeFn, - DistributiveOmit, } from 'movex-core-util'; import { invoke, isSameResourceIdentifier } from 'movex-core-util'; import { MovexContextStateChange } from './MovexContextStateChange'; diff --git a/libs/movex-react/src/lib/MovexProvider.tsx b/libs/movex-react/src/lib/MovexProvider.tsx index 8991ad08..ca9eaaec 100644 --- a/libs/movex-react/src/lib/MovexProvider.tsx +++ b/libs/movex-react/src/lib/MovexProvider.tsx @@ -8,32 +8,38 @@ import { type MovexDefinition, ResourceIdentifier, StringKeys, + LoggingEvent, + globalLogsy, } from 'movex-core-util'; import { MovexClient } from 'movex'; import { ResourceObservablesRegistry } from './ResourceObservableRegistry'; -type Props = - React.PropsWithChildren<{ - movexDefinition: MovexDefinition; - endpointUrl: string; - clientId?: MovexClientUser['id']; - onConnected?: ( - state: Extract< - MovexContextProps, - { connected: true } - > - ) => void; - onDisconnected?: ( - state: Extract< - MovexContextProps, - { connected: false } - > - ) => void; - }>; +export type MovexProviderProps< + TMovexConfigResourcesMap extends BaseMovexDefinitionResourcesMap +> = React.PropsWithChildren<{ + movexDefinition: MovexDefinition; + endpointUrl: string; + clientId?: MovexClientUser['id']; + onConnected?: ( + state: Extract< + MovexContextProps, + { connected: true } + > + ) => void; + onDisconnected?: ( + state: Extract< + MovexContextProps, + { connected: false } + > + ) => void; + logger?: { + onLog: (event: LoggingEvent) => void; + }; +}>; export const MovexProvider: React.FC< - Props -> = ({ onConnected = noop, onDisconnected = noop, ...props }) => { + MovexProviderProps +> = ({ onConnected = noop, onDisconnected = noop, logger, ...props }) => { type TResourcesMap = typeof props['movexDefinition']['resources']; const [contextState, setContextState] = useState< @@ -108,6 +114,14 @@ export const MovexProvider: React.FC< } }, [contextState.connected, onDisconnected]); + useEffect(() => { + if (logger) { + return globalLogsy.onLog(logger.onLog); + } + + return () => {}; + }, [logger]); + return ( {props.children} diff --git a/libs/movex-react/src/lib/MovexProviderClass.tsx b/libs/movex-react/src/lib/MovexProviderClass.tsx index 5c1846a8..80c95159 100644 --- a/libs/movex-react/src/lib/MovexProviderClass.tsx +++ b/libs/movex-react/src/lib/MovexProviderClass.tsx @@ -4,25 +4,28 @@ import type { StringKeys, MovexDefinition, BaseMovexDefinitionResourcesMap, -} from 'movex-core-util'; +} from 'movex-core-util'; import type { MovexContextProps } from './MovexContext'; -import { MovexProvider } from './MovexProvider'; +import { MovexProvider, MovexProviderProps } from './MovexProvider'; type Props< TResourcesMap extends BaseMovexDefinitionResourcesMap, TResourceType extends Extract -> = React.PropsWithChildren<{ - movexDefinition: MovexDefinition; - endpointUrl: string; - clientId?: MovexClient['id']; - onConnected?: ( - state: Extract< - MovexContextProps, - { connected: true } - >['movex'] - ) => void; - onDisconnected?: () => void; -}>; +> = React.PropsWithChildren< + Omit< + MovexProviderProps, + 'movexDefinition' | 'onConnected' | 'onDisconnected' + > & { + movexDefinition: MovexDefinition; + onConnected?: ( + state: Extract< + MovexContextProps, + { connected: true } + >['movex'] + ) => void; + onDisconnected?: () => void; + } +>; type State = { clientId?: MovexClient['id']; @@ -36,36 +39,21 @@ export class MovexProviderClass< TResourcesMap extends BaseMovexDefinitionResourcesMap, TResourceType extends StringKeys > extends React.Component, State> { - constructor(props: Props) { - super(props); - - this.state = {}; - } - override render() { + const { onConnected, ...props } = this.props; + return ( { - this.setState({ clientId: r.clientId }); - - this.props.onConnected?.( + onConnected?.( r.movex as Extract< MovexContextProps, { connected: true } >['movex'] ); }} - // onDisconnected={() => { - // this.setState({ clientId: undefined }); - - // this.props.onDisconnected?.(); - // }} - > - {this.props.children} - + {...props} + /> ); } } diff --git a/libs/movex-server/package.json b/libs/movex-server/package.json index e4aab595..3b6a1736 100644 --- a/libs/movex-server/package.json +++ b/libs/movex-server/package.json @@ -1,6 +1,6 @@ { "name": "movex-server", - "version": "0.1.5-0", + "version": "0.1.5-1", "license": "MIT", "type": "commonjs", "description": "Movex Server is the backend runtime for Movex", diff --git a/libs/movex-server/src/lib/movex-server.ts b/libs/movex-server/src/lib/movex-server.ts index 8d8e97ed..b14bc6df 100644 --- a/libs/movex-server/src/lib/movex-server.ts +++ b/libs/movex-server/src/lib/movex-server.ts @@ -3,7 +3,7 @@ import { Server as SocketServer } from 'socket.io'; import express from 'express'; import cors from 'cors'; import { - logsy, + globalLogsy, SocketIOEmitter, ConnectionToClient, type MovexDefinition, @@ -16,6 +16,8 @@ import { MemoryMovexStore, MovexStore } from 'movex-store'; import { initMovexMaster } from 'movex-master'; import { isOneOf } from './util'; +const logsy = globalLogsy.withNamespace('[MovexServer]'); + export const movexServer = ( { httpServer = http.createServer(), @@ -53,7 +55,7 @@ export const movexServer = ( socket.on('connection', (io) => { const clientId = getClientId(io.handshake.query['clientId'] as string); - logsy.log('[MovexServer] Client Connected', clientId); + logsy.info('Client Connected', { clientId }); const connectionToClient = new ConnectionToClient( clientId, @@ -65,7 +67,7 @@ export const movexServer = ( movexMaster.addClientConnection(connectionToClient); io.on('disconnect', () => { - logsy.log('[MovexServer] Client Disconnected', clientId); + logsy.info('Client Disconnected', { clientId }); movexMaster.removeConnection(clientId); }); @@ -133,10 +135,10 @@ export const movexServer = ( const address = httpServer.address(); if (typeof address !== 'string') { - logsy.info( - `Movex Server started on port ${address?.port} for definition`, - Object.keys(definition.resources) - ); + logsy.info('Server started', { + port, + definitionResources: Object.keys(definition.resources), + }); } }); }; diff --git a/libs/movex-service/package.json b/libs/movex-service/package.json index cd0dd2f6..73a09518 100644 --- a/libs/movex-service/package.json +++ b/libs/movex-service/package.json @@ -1,6 +1,6 @@ { "name": "movex-service", - "version": "0.1.5-0", + "version": "0.1.5-1", "license": "MIT", "type": "commonjs", "description": "Movex Service is the CLI for Movex", diff --git a/libs/movex-store/package.json b/libs/movex-store/package.json index f926d2ec..4377810f 100644 --- a/libs/movex-store/package.json +++ b/libs/movex-store/package.json @@ -1,6 +1,6 @@ { "name": "movex-store", - "version": "0.1.5-0", + "version": "0.1.5-1", "license": "MIT", "description": "Movex-store defines the store interface and comes with a MemoryStore", "author": { diff --git a/libs/movex-store/src/lib/MemoryMovexStore/MemoryMovexStore.ts b/libs/movex-store/src/lib/MemoryMovexStore/MemoryMovexStore.ts index c2f0774e..d027f2fe 100644 --- a/libs/movex-store/src/lib/MemoryMovexStore/MemoryMovexStore.ts +++ b/libs/movex-store/src/lib/MemoryMovexStore/MemoryMovexStore.ts @@ -31,7 +31,7 @@ import type { } from '../MovexStore'; import { toResultError } from '../ResultError'; -const logsy = globalLogsy.withNamespace('[LocalMovexStore]'); +const logsy = globalLogsy.withNamespace('[MemoryMovexStore]'); export class MemoryMovexStore< TResourcesMap extends BaseMovexDefinitionResourcesMap = BaseMovexDefinitionResourcesMap @@ -304,8 +304,8 @@ export class MemoryMovexStore< ) ); }).mapErr( - AsyncResult.passThrough((e) => { - logsy.error('Update Error', e, rid); + AsyncResult.passThrough((error) => { + logsy.error('Update Error', { error, rid }); }) ); } diff --git a/libs/movex/package.json b/libs/movex/package.json index 3bb2beb1..88347046 100644 --- a/libs/movex/package.json +++ b/libs/movex/package.json @@ -1,6 +1,6 @@ { "name": "movex", - "version": "0.1.5-0", + "version": "0.1.5-1", "license": "MIT", "description": "Movex is a Multiplayer (Game) State Synchronization Library using Deterministic Action Propagation without the need to write Server Specific Code.", "author": { diff --git a/libs/movex/src/lib/ConnectionToMasterResource.ts b/libs/movex/src/lib/ConnectionToMasterResource.ts index e0fd303e..dbae0148 100644 --- a/libs/movex/src/lib/ConnectionToMasterResource.ts +++ b/libs/movex/src/lib/ConnectionToMasterResource.ts @@ -89,13 +89,7 @@ export class ConnectionToMasterResource< rid: ResourceIdentifier; clientId: MovexClient['id']; }) => { - console.log('ConnectionToMaster onRemoveResourceSubscriberHandler', p); if (toResourceIdentifierObj(p.rid).resourceType !== resourceType) { - console.log( - 'ConnectionToMaster onRemoveResourceSubscriberHandler', - 'nooooope', - p - ); return; } @@ -108,14 +102,7 @@ export class ConnectionToMasterResource< rid: ResourceIdentifier; clientId: MovexClient['id']; }) => { - console.log('ConnectionToMaster onAddResourceSubscriberHandler', p); - if (toResourceIdentifierObj(p.rid).resourceType !== resourceType) { - console.log( - 'ConnectionToMaster onAddResourceSubscriberHandler', - p, - 'noooope' - ); return; } diff --git a/libs/movex/src/lib/MovexResource.ts b/libs/movex/src/lib/MovexResource.ts index ab3bbad1..d78f391b 100644 --- a/libs/movex/src/lib/MovexResource.ts +++ b/libs/movex/src/lib/MovexResource.ts @@ -2,34 +2,22 @@ import type { ResourceIdentifier, ResourceIdentifierStr, UnsubscribeFn, - ActionWithAnyPayload, AnyAction, CheckedReconciliatoryActions, MovexReducer, IOConnection, } from 'movex-core-util'; import { - logsy as rawLogsy, + globalLogsy, toResourceIdentifierObj, toResourceIdentifierStr, - isAction, invoke, } from 'movex-core-util'; import { ConnectionToMasterResource } from './ConnectionToMasterResource'; import { MovexResourceObservable } from './MovexResourceObservable'; import * as deepObject from 'deep-object-diff'; -// TODO: Take away from here as it's adding to the size -const logUnimportantStyle = 'color: grey;'; -const logImportantStyle = 'font-weight: bold;'; -const logIncomingStyle = 'color: #4CAF50; font-weight: bold;'; -const logOutgoingStyle = 'color: #1EA7FD; font-weight: bold;'; -const logOpenConnectionStyle = 'color: #EF5FA0; font-weight: bold'; -const logClosedConnectionStyle = 'color: #DF9D04; font-weight: bold'; -const logErrorStyle = 'color: red; font-weight: bold;'; - -const logsy = rawLogsy.withNamespace('[Movex][MovexResource]'); -// const logsy = rawLogsy; +const logsy = globalLogsy.withNamespace('[Movex][MovexResource]'); export class MovexResource< S, @@ -120,21 +108,14 @@ export class MovexResource< const prevCheckedState = resourceObservable.state; return syncLocalState().map((masterCheckState) => { - logsy.group('State Resynch-ed Warning'); - logsy.log( - '%cPrev (Local) State', - logUnimportantStyle, - prevCheckedState - ); - logsy.log('%cNext (Master) State', logIncomingStyle, masterCheckState); - logsy.debug( - 'Diff', - deepObject.detailedDiff(prevCheckedState, masterCheckState) - ); - logsy.warn( + logsy.warn('State Resynch-ed', { + prevCheckedState, + masterCheckState, + diff: deepObject.detailedDiff(prevCheckedState, masterCheckState), + }); + console.warn( "This shouldn't happen too often! If it does, make sure there's no way around it! See this for more https://github.com/movesthatmatter/movex/issues/8" ); - logsy.groupEnd(); return masterCheckState; }); @@ -151,66 +132,37 @@ export class MovexResource< resourceObservable.syncState(res.state); resourceObservable.updateSubscribers(res.subscribers); }) - .mapErr((e) => { - logsy.error('Add Resource Subscriber Error', e); + .mapErr((error) => { + logsy.error('Add Resource Subscriber Error', { error }); }); const onReconciliateActionsHandler = ( p: CheckedReconciliatoryActions ) => { const prevState = resourceObservable.getCheckedState(); - - logsy.group( - `%c\u{25BC} %cReconciliatory Actions Received (${p.actions.length}). FinalCheckum ${p.finalChecksum}`, - logIncomingStyle, - logUnimportantStyle, - 'Client:', - this.connectionToMaster.clientId - ); - logsy.log('%cPrev state', logUnimportantStyle, prevState[0]); - const nextState = resourceObservable.applyMultipleActions( p.actions ).checkedState; - p.actions.forEach((action, i) => { - logsy.log( - `%cAction(${i + 1}/${p.actions.length}): %c${action.type}`, - logOutgoingStyle, - logImportantStyle, - (action as ActionWithAnyPayload).payload, - action - ); + logsy.log('Reconciliatory Actions Received', { + ...p, + actionsCount: p.actions.length, + clientId: this.connectionToMaster.clientId, + nextState, + prevState, }); - logsy.log('%cNextState', logIncomingStyle, nextState[0]); - logsy.log( - '%cChecksums', - logUnimportantStyle, - prevState[1], - '>', - nextState[1] - ); - - if (nextState[1] === p.finalChecksum) { - logsy.log( - '%cFinal Checksum Matches', - logUnimportantStyle, - p.finalChecksum - ); - } else { + if (nextState[1] !== p.finalChecksum) { // If the checksums are different then it this case it's needed to resync. // See this https://github.com/movesthatmatter/movex/issues/8 resyncLocalState(); // Here is where this happens!!! - logsy.warn( - '%cLocal and Final Master Checksum Mismatch', - logErrorStyle, - nextState[1], - p.finalChecksum - ); + logsy.warn('Local and Final Master Checksum Mismatch', { + ...p, + nextState: nextState[1], + }); } logsy.groupEnd(); @@ -245,33 +197,23 @@ export class MovexResource< // When the checksums are not the same, need to resync the state! // this is expensive and ideally doesn't happen too much. - logsy.group( - `[Movex] Dispatch Ack Error: "Checksums MISMATCH"\n`, - `client: '${this.connectionToMaster.clientId}',\n`, - 'action:', - action - ); + logsy.error(`Dispatch Ack Error: "Checksums MISMATCH"`, { + action, + clientId: this.connectionToMaster.clientId, + }); await resyncLocalState() .map((masterState) => { - logsy.log( - 'Master State:', - JSON.stringify(masterState[0], null, 2), - masterState[1] - ); - logsy.log( - 'Local State:', - JSON.stringify(nextLocalCheckedState[0], null, 2), - nextLocalCheckedState[1] - ); - logsy.log( - 'Diff', - deepObject.detailedDiff(masterState, nextLocalCheckedState) - ); + logsy.info('Resynched Result', { + masterState, + nextLocalCheckedState, + diff: deepObject.detailedDiff( + masterState, + nextLocalCheckedState + ), + }); }) .resolve(); - - logsy.groupEnd(); }); } ), @@ -282,35 +224,12 @@ export class MovexResource< const nextState = resourceObservable.getCheckedState(); - logsy.group( - `%c\u{25BC} %cForwarded Action Received: %c${p.action.type}`, - logIncomingStyle, - logUnimportantStyle, - logImportantStyle, - 'Client:', - this.connectionToMaster.clientId - ); - logsy.log('%cPrev State', logUnimportantStyle, prevState[0]); - logsy.log( - `%cAction: %c${p.action.type}`, - logOutgoingStyle, - logImportantStyle, - (p.action as ActionWithAnyPayload).payload - ); - logsy.log('%cNext State', logIncomingStyle, nextState[0]); - if (prevState[1] !== nextState[1]) { - logsy.log( - '%cchecksums', - logUnimportantStyle, - prevState[1], - '>', - nextState[1] - ); - } else { - logsy.log('%cNo Diff', logErrorStyle, prevState[1]); - } - - logsy.groupEnd(); + logsy.info('Forwarded Action Received', { + ...p, + clientId: this.connectionToMaster.clientId, + prevState, + nextState, + }); }), this.connectionToMasterResource.onReconciliatoryActions( rid, @@ -319,7 +238,7 @@ export class MovexResource< // Subscribers this.connectionToMasterResource.onSubscriberAdded(rid, (clientId) => { - logsy.log('Subscriber Added', clientId); + logsy.info('Subscriber Added', { clientId }); resourceObservable.updateSubscribers((prev) => ({ ...prev, @@ -327,7 +246,7 @@ export class MovexResource< })); }), this.connectionToMasterResource.onSubscriberRemoved(rid, (clientId) => { - logsy.log('Subscriber Removed', clientId); + logsy.info('Subscriber Removed', { clientId }); resourceObservable.updateSubscribers((prev) => { const { [clientId]: removed, ...rest } = prev; @@ -351,66 +270,12 @@ export class MovexResource< next: nextLocalCheckedState, prev: prevLocalCheckedState, }) => { - const [nextLocalState, nextLocalChecksum] = nextLocalCheckedState; - - logsy.group( - `%c\u{25B2} %cAction Dispatched: %c${ - isAction(action) - ? action.type - : action[0].type + ' + ' + action[1].type - }`, - logOutgoingStyle, - logUnimportantStyle, - logImportantStyle, - 'Client:', - this.connectionToMaster.clientId - ); - logsy.log( - '%cPrev state', - logUnimportantStyle, - prevLocalCheckedState[0] - ); - if (isAction(action)) { - logsy.log( - `%cPublic Action: %c${action.type}`, - logOutgoingStyle, - logImportantStyle, - (action as ActionWithAnyPayload).payload - ); - } else { - const [privateAction, publicAction] = action; - logsy.log( - `%cPrivate Action: %c${privateAction.type}`, - logOpenConnectionStyle, - logImportantStyle, - (privateAction as ActionWithAnyPayload).payload - ); - logsy.log( - `%cPublic Action payload: %c${publicAction.type}`, - logOutgoingStyle, - logImportantStyle, - (publicAction as ActionWithAnyPayload).payload - ); - } - logsy.log('%cNext State', logIncomingStyle, nextLocalState); - - if (prevLocalCheckedState[1] !== nextLocalChecksum) { - logsy.log( - '%cChecksums', - logUnimportantStyle, - prevLocalCheckedState[1], - '>', - nextLocalChecksum - ); - } else { - logsy.log( - '%cNo Diff', - logUnimportantStyle, - prevLocalCheckedState[1] - ); - } - - logsy.groupEnd(); + logsy.info('Action Dispatched', { + action, + clientId: this.connectionToMaster.clientId, + prevState: prevLocalCheckedState, + nextLocalState: nextLocalCheckedState, + }); } ), ]; diff --git a/libs/movex/src/lib/MovexResourceObservable.spec.ts b/libs/movex/src/lib/MovexResourceObservable.spec.ts index 98de4d36..16bc56d0 100644 --- a/libs/movex/src/lib/MovexResourceObservable.spec.ts +++ b/libs/movex/src/lib/MovexResourceObservable.spec.ts @@ -13,14 +13,6 @@ import { const rid: ResourceIdentifier = 'counter:test-id'; -beforeAll(() => { - globalLogsy.disable(); -}); - -afterAll(() => { - globalLogsy.enable(); -}); - test('Dispatch Local Actions', async () => { const $resource = new MovexResourceObservable( 'test-client', diff --git a/libs/movex/src/lib/MovexResourceObservable.ts b/libs/movex/src/lib/MovexResourceObservable.ts index 8256d845..96a7aa28 100644 --- a/libs/movex/src/lib/MovexResourceObservable.ts +++ b/libs/movex/src/lib/MovexResourceObservable.ts @@ -2,7 +2,7 @@ import { Pubsy } from 'ts-pubsy'; import { Err, Ok, Result } from 'ts-results'; import { invoke, - logsy, + globalLogsy, computeCheckedState, isAction, Observable, @@ -24,6 +24,8 @@ import type { import { createDispatcher, DispatchedEvent } from './dispatch'; import { PromiseDelegate } from 'promise-delegate'; +const logsy = globalLogsy.withNamespace('[MovexResourceObservable]'); + type ObservedItem = { subscribers: Record; checkedState: CheckedState; @@ -106,7 +108,7 @@ export class MovexResourceObservable< this.dispatcher = (...args: Parameters) => { if (!this.isSynchedPromiseDelegate.settled) { - logsy.info('[Movex] Attempt to dispatch before sync!', ...args); + logsy.warn('Attempt to dispatch before sync!', { args }); } this.isSynchedPromiseDelegate.promise.then(() => { diff --git a/package.json b/package.json index a583d1c8..e39a9628 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "movex", - "version": "0.1.5-0", + "version": "0.1.5-1", "license": "MIT", "author": { "name": "Gabriel C. Troia",