anear-js-api 0.4.29 → 0.4.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -30,6 +30,7 @@ const AnearParticipantMachine = require('../state_machines/AnearParticipantMachi
30
30
  const AnearParticipant = require('../models/AnearParticipant')
31
31
  const RealtimeMessaging = require('../utils/RealtimeMessaging')
32
32
  const MetaProcessing = require('../utils/MetaProcessing')
33
+ const DisplayEventProcessor = require('../utils/DisplayEventProcessor')
33
34
  const PugHelpers = require('../utils/PugHelpers')
34
35
  const C = require('../utils/Constants')
35
36
 
@@ -180,7 +181,7 @@ const ActiveEventStatesConfig = {
180
181
  ],
181
182
  on: {
182
183
  RENDER_DISPLAY: {
183
- target: '#rendering'
184
+ target: '.rendering'
184
185
  }
185
186
  },
186
187
  states: {
@@ -196,7 +197,6 @@ const ActiveEventStatesConfig = {
196
197
  }
197
198
  },
198
199
  rendering: {
199
- id: 'rendering',
200
200
  deferred: DeferredStatesPlus('ANNOUNCE', 'START'),
201
201
  invoke: {
202
202
  src: 'renderDisplay',
@@ -220,7 +220,7 @@ const ActiveEventStatesConfig = {
220
220
  initial: 'transitioning',
221
221
  on: {
222
222
  RENDER_DISPLAY: {
223
- target: '#rendering'
223
+ target: '.rendering'
224
224
  },
225
225
  PARTICIPANT_LEAVE: {
226
226
  // creator browser refresh or MIA. Send disconnect events
@@ -273,7 +273,6 @@ const ActiveEventStatesConfig = {
273
273
  }
274
274
  },
275
275
  rendering: {
276
- id: 'rendering',
277
276
  deferred: DeferredStatesPlus('START'),
278
277
  invoke: {
279
278
  src: 'renderDisplay',
@@ -792,87 +791,10 @@ const AnearEventMachineFunctions = ({
792
791
  }
793
792
  },
794
793
  services: {
795
- renderDisplay: async (context, event) => {
796
- const processDisplayEvent = (context, displayEvent) => {
797
- const { displayType, viewPath, timeout, appExecutionContext } = displayEvent
798
-
799
- logger.debug("processDisplayEvent - displayType: ", displayType, ", viewPath: ", viewPath)
800
-
801
- const renderedDisplayContent = (template, additionalExecutionContext, timeout) => {
802
- const renderedMessage = template(
803
- {
804
- displayType,
805
- ...appExecutionContext,
806
- ...additionalExecutionContext,
807
- participants: context.participants,
808
- ...context.pugHelpers
809
- }
810
- )
811
- const displayContent = { content: renderedMessage }
812
- if (timeout) {
813
- displayContent['timeout'] = timeout
814
- }
815
- return displayContent
816
- }
817
-
818
- const privateParticipantSend = participantMachine => {
819
- const participantState = participantMachine.state
820
- const participantContext = participantState.context
821
- const { appParticipantMachine, anearParticipant } = participantContext
822
-
823
- // TODO: don't send to participants that aren't in an active state
824
-
825
- const appParticipantContext =
826
- appParticipantMachine ? participantContext.appParticipantMachine.state.context : {}
827
-
828
- const privateContext = {
829
- anearParticipant: anearParticipant.participantInfo,
830
- participantContext: appParticipantContext
831
- }
794
+ renderDisplay: (context, event) => {
795
+ const displayEventProcessor = new DisplayEventProcessor(context)
832
796
 
833
- const displayEventPayload = renderedDisplayContent(template, privateContext, timeout)
834
-
835
- participantMachine.send('PRIVATE_DISPLAY', displayEventPayload)
836
- }
837
-
838
- const normalizedPath = viewPath.endsWith(C.PugSuffix) ? viewPath : `${viewPath}${C.PugSuffix}`
839
- const template = context.pugTemplates[normalizedPath]
840
- if (!template) {
841
- throw new Error(`Template not found for path "${normalizedPath}".`)
842
- }
843
- let displayMessage
844
-
845
- switch (displayType) {
846
- case 'participants':
847
- displayMessage = renderedDisplayContent(template, {}, timeout)
848
- return RealtimeMessaging.publish(
849
- context.participantsDisplayChannel,
850
- 'PARTICIPANTS_DISPLAY',
851
- displayMessage
852
- )
853
- break
854
- case 'spectators':
855
- displayMessage = renderedDisplayContent(template, {})
856
- return RealtimeMessaging.publish(
857
- context.spectatorsDisplayChannel,
858
- 'SPECTATORS_DISPLAY',
859
- displayMessage
860
- )
861
- break
862
- case 'participant':
863
- // For private displays, iterate over active active participant machines.
864
- Object.values(context.participantMachines).forEach(privateParticipantSend)
865
- return Promise.resolve()
866
- break
867
- default:
868
- throw new Error(`Unknown display type: ${displayType}`)
869
- }
870
- }
871
-
872
- const publishPromises = event.displayEvents.map(
873
- displayEvent => processDisplayEvent(context, displayEvent)
874
- )
875
- return Promise.all(publishPromises)
797
+ return displayEventProcessor.processAndPublish(event.displayEvents)
876
798
  },
877
799
  getAttachedCreatorOrHost: async (context, event) => {
878
800
  logger.debug("getAttachedCreatorOrHost() invoked")
@@ -22,7 +22,7 @@ const CurrentDateTimestamp = _ => new Date().getTime()
22
22
  const MISSING_TIMEOUT = 10000 // msecs
23
23
 
24
24
  const DeferredStates = [
25
- 'PRIVATE_DISPLAY',
25
+ 'RENDER_DISPLAY',
26
26
  'PARTICIPANT_EXIT',
27
27
  'PARTICIPANT_DISCONNECT',
28
28
  'PARTICIPANT_RECONNECT'
@@ -92,8 +92,8 @@ const AnearParticipantMachineConfig = participantId => ({
92
92
  idle: {
93
93
  id: 'idle',
94
94
  on: {
95
- PRIVATE_DISPLAY: {
96
- target: '#privateDisplay'
95
+ RENDER_DISPLAY: {
96
+ target: '#renderDisplay'
97
97
  },
98
98
  PARTICIPANT_DISCONNECT: {
99
99
  target: 'missing'
@@ -116,11 +116,11 @@ const AnearParticipantMachineConfig = participantId => ({
116
116
  }
117
117
  }
118
118
  },
119
- privateDisplay: {
120
- id: 'privateDisplay',
119
+ renderDisplay: {
120
+ id: 'renderDisplay',
121
121
  deferred: DeferredStates,
122
122
  invoke: {
123
- src: 'privateParticipantDisplay',
123
+ src: 'participantDisplay',
124
124
  onDone: [
125
125
  {
126
126
  cond: 'hasMetaTimeout',
@@ -233,7 +233,7 @@ const AnearParticipantMachineFunctions = {
233
233
  }
234
234
  },
235
235
  services: {
236
- privateParticipantDisplay: async (context, event) => {
236
+ participantDisplay: async (context, event) => {
237
237
  await RealtimeMessaging.publish(
238
238
  context.privateChannel,
239
239
  'PRIVATE_DISPLAY',
@@ -248,7 +248,7 @@ const AnearParticipantMachineFunctions = {
248
248
  },
249
249
  guards: {
250
250
  hasAppParticipantMachine: context => context.appParticipantMachineFactory !== null,
251
- hasMetaTimeout: (context, event) => event.data.timeout.msecs > 0
251
+ hasMetaTimeout: (context, event) => event.data.timeout?.msecs > 0
252
252
  },
253
253
  delays: {
254
254
  timeoutMsecsAfterNoResponse: context => context.noResponseTimeout
@@ -258,7 +258,7 @@ const AnearParticipantMachineFunctions = {
258
258
  // The AnearParticipantMachine:
259
259
  // 1. maintains the presence and geo-location for a Participant in an Event
260
260
  // 2. instantiates the XState Machine return by the (optional) appParticipantMachineFactory
261
- // 3. creates a private display ChannelMachine to which any private display messages get published
261
+ // 3. creates a private display ChannelMachine to which any participant displayType messages get published
262
262
  // 4. handles activity state, response timeouts, idle state
263
263
  // 5. receives ACTION events relayed by the AnearEventMachine
264
264
  // 6. relays all relevant events to the participant XState Machine for Application-specific handling
@@ -0,0 +1,120 @@
1
+ "use strict"
2
+ /**
3
+ * --------------------------------------------------------------------------
4
+ * Pug templates receive the following context at render:
5
+ *
6
+ * {
7
+ * displayType: // 'participants' | 'spectators' | 'participant'
8
+ * context: // AppEventMachine's XState context for this transition
9
+ * state: // Stringified state name (e.g., 'live.registration.waitForOpponentToJoin')
10
+ * event: // The triggering event for this transition
11
+ * participants: // Map of all participants for this event
12
+ *
13
+ * // When 'participant' displayType
14
+ * participant // current participant
15
+ * participantContext // current participant private context if AppParticipantMachine
16
+ *
17
+ * // PLUS all helpers from PugHelpers:
18
+ * cdnImg(filename) // Resolves CDN image path
19
+ * action(payload) // JSON-encodes click action payloads for data attributes
20
+ * }
21
+ *
22
+ * Example usage in Pug:
23
+ *
24
+ * img(src=cdnImg('logo.png'))
25
+ * .sq(data-anear-click=action({MOVE: {x, y}}))
26
+ * each participant in Object.values(participants)
27
+ * li= participant.name
28
+ *
29
+ * --------------------------------------------------------------------------
30
+ */
31
+
32
+ const logger = require('./Logger')
33
+ const RealtimeMessaging = require('./RealtimeMessaging')
34
+ const C = require('./Constants')
35
+
36
+ class DisplayEventProcessor {
37
+ constructor(anearEventMachineContext) {
38
+ this.pugTemplates = anearEventMachineContext.pugTemplates
39
+ this.pugHelpers = anearEventMachineContext.pugHelpers
40
+ this.participants = anearEventMachineContext.participants
41
+ this.participantMachines = anearEventMachineContext.participantMachines
42
+ this.participantsDisplayChannel = anearEventMachineContext.participantsDisplayChannel
43
+ this.spectatorsDisplayChannel = anearEventMachineContext.spectatorsDisplayChannel
44
+ }
45
+
46
+ processAndPublish(displayEvents) {
47
+ return Promise.all(displayEvents.map(event => this._processSingle(event)))
48
+ }
49
+
50
+ _processSingle(displayEvent) {
51
+ const { displayType, viewPath, timeout, appExecutionContext } = displayEvent
52
+
53
+ const normalizedPath = viewPath.endsWith(C.PugSuffix) ? viewPath : `${viewPath}${C.PugSuffix}`
54
+ const template = this.pugTemplates[normalizedPath]
55
+
56
+ if (!template) {
57
+ throw new Error(`Template not found: ${normalizedPath}`)
58
+ }
59
+
60
+ const renderContext = {
61
+ displayType,
62
+ ...appExecutionContext,
63
+ participants: this.participants,
64
+ ...this.pugHelpers
65
+ }
66
+
67
+ const renderedContent = template(renderContext)
68
+ const message = { content: renderedContent }
69
+ if (timeout) message.timeout = timeout
70
+
71
+ switch (displayType) {
72
+ case 'participants':
73
+ logger.debug(`[DisplayEventProcessor] Publishing PARTICIPANTS_DISPLAY`)
74
+ return RealtimeMessaging.publish(
75
+ this.participantsDisplayChannel,
76
+ 'PARTICIPANTS_DISPLAY',
77
+ message.content
78
+ )
79
+
80
+ case 'spectators':
81
+ logger.debug(`[DisplayEventProcessor] Publishing SPECTATORS_DISPLAY`)
82
+ return RealtimeMessaging.publish(
83
+ this.spectatorsDisplayChannel,
84
+ 'SPECTATORS_DISPLAY',
85
+ message.content
86
+ )
87
+
88
+ case 'participant':
89
+ logger.debug(`[DisplayEventProcessor] Processing RENDER_DISPLAY for each participant`)
90
+ return this._processPrivateParticipantDisplays(template, renderContext, timeout)
91
+
92
+ default:
93
+ throw new Error(`Unknown displayType: ${displayType}`)
94
+ }
95
+ }
96
+
97
+ _processPrivateParticipantDisplays(template, renderContextBase, timeout) {
98
+ Object.values(this.participantMachines).forEach(participantMachine => {
99
+ const participantState = participantMachine.state
100
+ const { anearParticipant, appParticipantMachine } = participantState.context
101
+
102
+ const privateRenderContext = {
103
+ ...renderContextBase,
104
+ participant: anearParticipant.participantInfo,
105
+ participantContext: appParticipantMachine?.state?.context || {}
106
+ }
107
+
108
+ const privateHtml = template(privateRenderContext)
109
+
110
+ const renderMessage = { content: privateHtml }
111
+ if (timeout) renderMessage.timeout = timeout
112
+
113
+ participantMachine.send('RENDER_DISPLAY', renderMessage)
114
+ })
115
+
116
+ return Promise.resolve()
117
+ }
118
+ }
119
+
120
+ module.exports = DisplayEventProcessor
@@ -2,146 +2,94 @@
2
2
 
3
3
  const logger = require('./Logger')
4
4
 
5
- // Helper functions for processing meta configurations
6
- const buildTimeout = (timeoutType, timeoutConfig, appExecutionContext) => {
7
- let msecs = null
8
- let participantId = null
9
-
10
- switch (typeof timeoutConfig) {
11
- case 'function':
12
- [msecs, participantId] = timeoutConfig(appExecutionContext)
13
- break
14
- case 'number':
15
- msecs = timeoutConfig
16
- break
17
- default:
18
- throw new Error(`unknown timeout config ${typeof timeoutConfig}`)
19
- }
20
-
21
- switch (timeoutType) {
22
- case 'participant':
23
- return { type: timeoutType, msecs, participantId }
24
- case 'participants':
25
- return { type: timeoutType, msecs }
26
- default:
27
- throw new Error(`unknown timeout type ${timeoutType}`)
28
- }
29
- }
30
-
31
- const metaConfig = (meta, timeoutType, appExecutionContext) => {
32
- let timeout = {msecs: 0}
33
- let view = null
34
-
35
- switch (typeof meta) {
36
- case 'string':
37
- view = meta
38
- break
39
- case 'object':
40
- view = meta.view
41
- if (meta.timeout) {
42
- timeout = buildTimeout(timeoutType, meta.timeout, appExecutionContext)
43
- }
44
-
45
- break
46
- default:
47
- throw new Error(`unknown ${timeoutType} meta format ${meta}`)
48
- }
49
- return [view, timeout]
50
- }
51
-
52
- const participantsMetaConfig = (participantsMeta, appExecutionContext) => {
53
- return metaConfig(participantsMeta, 'participants', appExecutionContext)
54
- }
55
-
56
- const participantMetaConfig = (participantMeta, appExecutionContext) => {
57
- return metaConfig(participantMeta, 'participant', appExecutionContext)
58
- }
59
-
60
- const spectatorMetaConfig = config => {
61
- if (typeof config === 'string')
62
- return config
63
- else if (typeof config === 'object')
64
- return config.view
65
- else
66
- throw new Error(`unknown spectator meta format ${config}`)
67
- }
68
-
69
- const Stringified = (stateValue) => {
70
- switch (typeof stateValue) {
71
- case 'string':
72
- return stateValue
73
- case 'object':
74
- return Object.entries(stateValue)
75
- .map(([key, nested]) => `${key}.${Stringified(nested)}`)
76
- .join('.')
77
- default:
78
- throw new Error(`unknown Xstate state value type ${typeof stateValue}`)
79
- }
80
- }
81
-
82
- const MetaProcessing = anearEvent => {
83
- return appEventMachineState => {
84
- const { context: appContext, meta: rawMeta } = appEventMachineState
85
- const appStateName = Stringified(appEventMachineState.value)
86
- const displayEvents = []
87
-
88
- //logger.debug("MetaProcessing invoked on transition to: ", appStateName)
89
- //logger.debug("stateMeta: ", appEventMachineState.meta)
90
-
91
- if (Object.keys(appEventMachineState.meta).length === 0) {
92
- return
93
- }
94
-
95
- logger.debug("App Context: ", appContext)
96
-
97
- const sendDisplayMessage = () => {
98
- logger.debug(`${appStateName} 'RENDER_DISPLAY' to ${anearEvent.id} with ${displayEvents.length} msgs`)
99
-
100
- // sends a RENDER_DISPLAY event with one or more viewPaths and appExecutionContext in the displayEvents array
101
- anearEvent.send('RENDER_DISPLAY', { displayEvents })
102
- }
103
-
104
- const appendDisplayEvent = (displayType, viewPath, timeout = null) => {
105
- displayEvents.push(
106
- {
107
- displayType,
108
- viewPath,
109
- timeout,
110
- appExecutionContext
111
- }
112
- )
113
- }
5
+ /**
6
+ * MetaProcessing:
7
+ * - Runs inside your appEventMachine onTransition.
8
+ * - Knows how to parse the XState meta shape (participants, participant, spectators).
9
+ * - Normalizes viewPath + timeout.
10
+ * - Emits displayEvents → sends to AnearEventMachine for rendering.
11
+ */
12
+ const MetaProcessing = (anearEvent) => {
13
+ return (appEventMachineState) => {
14
+ const { meta: rawMeta, context: appContext, value, event } = appEventMachineState
15
+ const [meta] = Object.values(rawMeta)
16
+
17
+ if (!meta) return
18
+
19
+ const appStateName = _stringifiedState(value)
114
20
 
115
21
  const appExecutionContext = {
116
22
  context: appContext,
117
23
  state: appStateName,
118
- event: appEventMachineState.event
24
+ event
119
25
  }
120
26
 
121
- const [meta] = Object.values(appEventMachineState.meta)
122
-
123
- logger.debug("meta: ", meta)
124
-
125
- // Only proceed if there's at least one display meta defined.
126
- if (!meta.participants && !meta.participant && !meta.spectators) return
27
+ const displayEvents = []
127
28
 
128
29
  if (meta.participants) {
129
- const [viewPath, timeout] = participantsMetaConfig(meta.participants, appExecutionContext)
130
- appendDisplayEvent('participants', viewPath, timeout)
30
+ const viewPath = _getViewPath(meta.participants)
31
+ const timeout = _buildTimeout(meta.participants.timeout, appExecutionContext)
32
+ displayEvents.push({
33
+ displayType: 'participants',
34
+ viewPath,
35
+ timeout,
36
+ appExecutionContext
37
+ })
131
38
  }
132
39
 
133
40
  if (meta.participant) {
134
- const [viewPath, timeout] = participantMetaConfig(meta.participant, appExecutionContext)
135
- appendDisplayEvent('participant', viewPath, timeout)
41
+ const viewPath = _getViewPath(meta.participant)
42
+ const timeout = _buildTimeout(meta.participant.timeout, appExecutionContext)
43
+ displayEvents.push({
44
+ displayType: 'participant',
45
+ viewPath,
46
+ timeout,
47
+ appExecutionContext
48
+ })
136
49
  }
137
50
 
138
51
  if (meta.spectators) {
139
- const viewPath = spectatorMetaConfig(meta.spectators)
140
- appendDisplayEvent('spectators', viewPath)
52
+ const viewPath = _getViewPath(meta.spectators)
53
+ displayEvents.push({
54
+ displayType: 'spectators',
55
+ viewPath,
56
+ timeout: null,
57
+ appExecutionContext
58
+ })
59
+ }
60
+
61
+ if (displayEvents.length > 0) {
62
+ logger.debug(`[MetaProcessing] sending RENDER_DISPLAY with ${displayEvents.length} displayEvents`)
63
+ anearEvent.send('RENDER_DISPLAY', { displayEvents })
141
64
  }
65
+ }
66
+ }
67
+
68
+ const _getViewPath = (config) => {
69
+ if (!config) return null
70
+ if (typeof config === 'string') return config
71
+ if (typeof config === 'object') return config.view
72
+ throw new Error(`Unknown meta format: ${JSON.stringify(config)}`)
73
+ }
74
+
75
+ const _buildTimeout = (timeoutConfig, appExecutionContext) => {
76
+ if (!timeoutConfig) return null
142
77
 
143
- sendDisplayMessage()
78
+ if (typeof timeoutConfig === 'function') {
79
+ const [msecs, participantId] = timeoutConfig(appExecutionContext)
80
+ return { msecs, participantId }
144
81
  }
82
+
83
+ if (typeof timeoutConfig === 'number') {
84
+ return { msecs: timeoutConfig }
85
+ }
86
+
87
+ throw new Error(`Unknown timeout config: ${typeof timeoutConfig}`)
88
+ }
89
+
90
+ const _stringifiedState = (stateValue) => {
91
+ if (typeof stateValue === 'string') return stateValue
92
+ return Object.entries(stateValue).map(([k, v]) => `${k}.${_stringifiedState(v)}`).join('.')
145
93
  }
146
94
 
147
95
  module.exports = MetaProcessing
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anear-js-api",
3
- "version": "0.4.29",
3
+ "version": "0.4.31",
4
4
  "description": "Javascript Developer API for Anear Apps",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {