anear-js-api 0.5.2 → 0.5.4

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.
@@ -43,6 +43,10 @@ confirmMove: {
43
43
  return [{
44
44
  participantId: movingParticipantId,
45
45
  view: 'participant/MoveConfirmation', // A view confirming their move was received
46
+ props: {
47
+ message: 'Your move has been recorded. Waiting for opponent...',
48
+ move: event.moveData
49
+ },
46
50
  timeout: 2000 // A short timeout for the confirmation display
47
51
  }];
48
52
  }
@@ -137,94 +141,4 @@ services: {
137
141
  The `DisplayEventProcessor` loops through each `displayEvent` and, based on the target, decides what to do.
138
142
 
139
143
  * **File:** `/Users/machvee/dev/anear-js-api/lib/utils/DisplayEventProcessor.js`
140
- * **Description:** For an `eachParticipant` target, it doesn't publish to a public channel. Instead, it performs a critical handoff: it finds the specific participant's own state machine (`AnearParticipantMachine`) and sends a *private* `RENDER_DISPLAY` event directly to that machine. This is the key to sending a message to only one person.
141
- * **Code Reference (lines 142-156 and 229-256):**
142
-
143
- ```javascript
144
- // ... inside DisplayEventProcessor.js _processSingle
145
- case 'eachParticipant':
146
- // ...
147
- // It determines we need to send to a specific participant
148
- publishPromise = this._processSelectiveParticipantDisplay(
149
- template,
150
- templateRenderContext,
151
- null,
152
- participantId, // e.g., 'participant-123'
153
- displayTimeout // e.g., 10000
154
- );
155
- // ...
156
-
157
- // ... inside _sendPrivateDisplay
158
- _sendPrivateDisplay(participantMachine, participantId, template, templateRenderContext, timeoutFn) {
159
- // ... it compiles the pug template into HTML ...
160
- const privateHtml = template(privateRenderContext);
161
-
162
- const renderMessage = { content: privateHtml };
163
- if (timeout !== null) {
164
- renderMessage.timeout = timeout; // Attaches the 10000ms timeout
165
- }
166
-
167
- // CRITICAL: It sends an event to the specific participant's machine, NOT to Ably.
168
- participantMachine.send('RENDER_DISPLAY', renderMessage);
169
- }
170
- ```
171
- ---
172
-
173
- ### Part 4: The Target & Delivery (`AnearParticipantMachine.js`)
174
-
175
- **Context:** Each participant in an event has their own instance of the `AnearParticipantMachine` (APM). This machine manages their connection, timeouts, and, most importantly, their private Ably channel.
176
-
177
- #### Lifecycle Step 5: Receiving the Private Display Command
178
-
179
- The target participant's APM receives the `RENDER_DISPLAY` event from the `DisplayEventProcessor`.
180
-
181
- * **File:** `/Users/machvee/dev/anear-js-api/lib/state_machines/AnearParticipantMachine.js`
182
- * **Description:** The APM transitions to its own `renderDisplay` state, where it invokes its `publishPrivateDisplay` service. The payload it received (`renderMessage`) contains the final HTML and the 10-second timeout.
183
- * **Code Reference (lines 96-98 and 147-160):**
184
-
185
- ```javascript
186
- // ... inside AnearParticipantMachine.js
187
- idle: {
188
- on: {
189
- RENDER_DISPLAY: {
190
- target: '#renderDisplay'
191
- },
192
- // ...
193
- renderDisplay: {
194
- id: 'renderDisplay',
195
- invoke: {
196
- src: 'publishPrivateDisplay', // This is the final step
197
- onDone: [
198
- // After publishing, it starts waiting for the user's ACTION
199
- { cond: 'hasActionTimeout', actions: 'updateActionTimeout', target: 'waitParticipantResponse' },
200
- { target: 'idle', internal: true }
201
- ],
202
- // ...
203
- ```
204
-
205
- #### Lifecycle Step 6: Sending the Ably Message
206
-
207
- This is the final hop. The `publishPrivateDisplay` service does one thing: it publishes the HTML to the participant's private channel.
208
-
209
- * **File:** `/Users/machvee/dev/anear-js-api/lib/state_machines/AnearParticipantMachine.js`
210
- * **Description:** It uses the `RealtimeMessaging` utility to send the message over Ably. The participant's browser, which is subscribed to this unique channel, receives the message and updates the DOM.
211
- * **Code Reference (lines 348-358):**
212
-
213
- ```javascript
214
- // ... inside AnearParticipantMachine.js
215
- services: {
216
- publishPrivateDisplay: async (context, event) => {
217
- // event.content is the final HTML from the DisplayEventProcessor
218
- const displayMessage = { content: event.content };
219
-
220
- await RealtimeMessaging.publish(
221
- context.privateChannel, // The participant's unique channel
222
- 'PRIVATE_DISPLAY',
223
- displayMessage
224
- );
225
-
226
- // It returns the timeout so the onDone transition knows to start the timer
227
- return { timeout: event.timeout };
228
- },
229
- // ...
230
- ```
144
+ * **Description:** For an `eachParticipant`
@@ -26,7 +26,11 @@ const Config = {
26
26
  meta: {
27
27
  eachParticipant: {
28
28
  view: 'PlayableGameBoard',
29
- timeout: calcParticipantTimeout
29
+ timeout: calcParticipantTimeout,
30
+ props: {
31
+ title: "Your Move!",
32
+ highlightLastMove: true
33
+ }
30
34
  },
31
35
  spectators: 'ViewableGameBoard'
32
36
  }
@@ -135,7 +139,7 @@ const actions = {
135
139
  - `null`: No timeout
136
140
  - `number`: Fixed timeout in milliseconds
137
141
  - `Function`: Dynamic timeout function `(appContext, participantId) => msecs`
138
- - **`props`** (Object): Additional properties merged into meta (optional)
142
+ - **`props`** (Object): Additional properties made available at the root of the pug template's render context (optional)
139
143
 
140
144
  ## When to Use Each Approach
141
145
 
@@ -79,6 +79,28 @@ class AnearApi extends ApiService {
79
79
  logger.debug('getAppFontAssetsUploadUrls response:', attrs)
80
80
  return attrs
81
81
  }
82
+
83
+ async saveAppEventContext(eventId, appmContext) {
84
+ logger.debug(`API: POST developer events/${eventId}/app_event_context`)
85
+ const path = `events/${eventId}/app_event_context`
86
+ return this.postRaw(path, { appm_context: appmContext })
87
+ }
88
+
89
+ async getLatestAppEventContext(eventId) {
90
+ logger.debug(`API: GET developer events/${eventId}/app_event_context`)
91
+ const path = `events/${eventId}/app_event_context`
92
+ const json = await this.get(path)
93
+ const attrs = json.data && json.data.attributes ? json.data.attributes : {}
94
+ const eventIdAttr = attrs['event-id']
95
+ const raw = attrs['appm-context']
96
+ let appmContext = null
97
+ try {
98
+ appmContext = typeof raw === 'string' ? JSON.parse(raw) : raw
99
+ } catch (e) {
100
+ // leave appmContext as null if parsing fails
101
+ }
102
+ return { eventId: eventIdAttr, appmContext }
103
+ }
82
104
  }
83
105
 
84
106
  // Instantiate and export the API immediately
@@ -56,6 +56,18 @@ class ApiService {
56
56
  return this.issueRequest(request)
57
57
  }
58
58
 
59
+ postRaw(path, body={}) {
60
+ const urlString = `${this.api_base_url}/${path}`
61
+ const request = new fetch.Request(
62
+ urlString, {
63
+ method: 'POST',
64
+ headers: this.default_headers,
65
+ body: JSON.stringify(body)
66
+ }
67
+ )
68
+ return this.issueRequest(request)
69
+ }
70
+
59
71
  put(resource, id, attributes, relationships={}) {
60
72
  const payload = this.formatPayload(resource, attributes, relationships)
61
73
  const urlString = `${this.api_base_url}/${resource}/${id}`
@@ -1,5 +1,6 @@
1
1
  "use strict"
2
2
  const logger = require('../utils/Logger')
3
+ const anearApi = require('../api/AnearApi')
3
4
 
4
5
  const JsonApiResource = require('./JsonApiResource')
5
6
 
@@ -46,6 +47,20 @@ class AnearEvent extends JsonApiResource {
46
47
  this.send("CLOSE")
47
48
  }
48
49
 
50
+ pauseEvent(context, resumeEvent = { type: 'RESUME' }) {
51
+ // Persist via AEM and acknowledge with PAUSED
52
+ this.send('PAUSE', { appmContext: { context, resumeEvent } })
53
+ }
54
+
55
+ saveEvent(context, resumeEvent = { type: 'RESUME' }) {
56
+ // Delegate save to the AEM; AEM will persist via ANAPI and acknowledge with SAVED
57
+ this.send('SAVE', { appmContext: { context, resumeEvent } })
58
+ }
59
+
60
+ bootParticipant(participantId, reason) {
61
+ this.send("BOOT_PARTICIPANT", { participantId, reason })
62
+ }
63
+
49
64
  render(viewPath, displayType, appContext, event, timeout = null, props = {}) {
50
65
  // Explicit render method for guaranteed rendering control
51
66
  // This complements the meta: {} approach for when you need explicit control
@@ -80,14 +95,6 @@ class AnearEvent extends JsonApiResource {
80
95
  })
81
96
  }
82
97
 
83
- pauseEvent() {
84
- this.send("PAUSE")
85
- }
86
-
87
- resumeEvent() {
88
- this.send("RESUME")
89
- }
90
-
91
98
  getClonedFromEvent() {
92
99
  // if the current event was a clone of previous event, fetch if from
93
100
  // Peristence and return
@@ -138,6 +145,10 @@ class AnearEvent extends JsonApiResource {
138
145
  return this.app.attributes["participant-timeout"]
139
146
  }
140
147
 
148
+ get appIconUrl() {
149
+ return this.app.attributes["icon-url"]
150
+ }
151
+
141
152
  hasFlag(flagName) {
142
153
  return this.attributes.flags.includes(flagName)
143
154
  }
@@ -134,7 +134,7 @@ const AnearCoreServiceMachineConfig = appId => ({
134
134
  }
135
135
  },
136
136
  waitAnearEventLifecycleCommand: {
137
- // The Anear API backend will send CREATE_EVENT messages with the event JSON data
137
+ // The Anear API backend will send CREATE_EVENT or LOAD_EVENT messages with the event JSON data
138
138
  // to this createEventMessages Channel when it needs to create a new instance of an
139
139
  // Event
140
140
  entry: (context) => logger.debug(`Waiting on ${context.appData.data.attributes['short-name']} lifecycle command`),
@@ -142,6 +142,9 @@ const AnearCoreServiceMachineConfig = appId => ({
142
142
  CREATE_EVENT: {
143
143
  actions: ['startNewEventMachine']
144
144
  },
145
+ LOAD_EVENT: {
146
+ actions: ['startNewEventMachine']
147
+ },
145
148
  EVENT_MACHINE_EXIT: {
146
149
  actions: [
147
150
  'cleanupEventMachine'
@@ -234,6 +237,11 @@ const AnearCoreServiceMachineFunctions = {
234
237
  context.coreServiceMachine,
235
238
  'CREATE_EVENT'
236
239
  )
240
+ RealtimeMessaging.subscribe(
241
+ context.newEventCreationChannel,
242
+ context.coreServiceMachine,
243
+ 'LOAD_EVENT'
244
+ )
237
245
  },
238
246
  startNewEventMachine: assign(
239
247
  {
@@ -242,11 +250,12 @@ const AnearCoreServiceMachineFunctions = {
242
250
  const anearEvent = new AnearEvent(eventJSON)
243
251
 
244
252
  if (context.anearEventMachines[anearEvent.id]) {
245
- logger.info(`[ACSM] Event machine for ${anearEvent.id} already exists. Ignoring CREATE_EVENT.`)
253
+ logger.info(`[ACSM] Event machine for ${anearEvent.id} already exists. Ignoring ${event.type}.`)
246
254
  return context.anearEventMachines
247
255
  }
248
256
 
249
- const service = AnearEventMachine(anearEvent, context)
257
+ const isLoadEvent = event.type === 'LOAD_EVENT'
258
+ const service = AnearEventMachine(anearEvent, { ...context, rehydrate: isLoadEvent })
250
259
 
251
260
  return {
252
261
  ...context.anearEventMachines,