anear-js-api 0.4.7 → 0.4.8
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.
|
@@ -137,7 +137,6 @@ class AnearMessaging {
|
|
|
137
137
|
|
|
138
138
|
if (!eventExists) {
|
|
139
139
|
const messagingInitializedCallback = async () => {
|
|
140
|
-
await anearEvent.createdEventCallback()
|
|
141
140
|
await anearEvent.persist()
|
|
142
141
|
// start the state machine before initializing Realtime Messaging
|
|
143
142
|
// as REFRESH events come in and the state machine should be ready
|
|
@@ -214,7 +213,7 @@ class AnearMessaging {
|
|
|
214
213
|
this.subscribePresenceEvent(
|
|
215
214
|
spectatorsChannel,
|
|
216
215
|
PRESENCE_ENTER,
|
|
217
|
-
async message => await this.
|
|
216
|
+
async message => await this.spectatorViewMessagingCallback(anearEvent, message)
|
|
218
217
|
)
|
|
219
218
|
|
|
220
219
|
this.subscribePresenceEvent(
|
|
@@ -351,12 +350,11 @@ class AnearMessaging {
|
|
|
351
350
|
})
|
|
352
351
|
}
|
|
353
352
|
|
|
354
|
-
|
|
353
|
+
spectatorViewMessagingCallback(anearEvent, message) {
|
|
355
354
|
const userId = message.clientId
|
|
355
|
+
logger.debug(`**** ENTER SPECTATOR FOR VIEWING **** event: ${anearEvent.id}, user: ${userId}`)
|
|
356
356
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
await anearEvent.refreshSpectator()
|
|
357
|
+
anearEvent.spectatorView(userId)
|
|
360
358
|
}
|
|
361
359
|
|
|
362
360
|
async spectatorLeaveMessagingCallback(anearEvent, message) {
|
|
@@ -450,8 +448,8 @@ class AnearMessaging {
|
|
|
450
448
|
logger.debug(`setupPrivatePublishingChannel(${participant.privateChannelName}) state ${privateChannel.state}`)
|
|
451
449
|
}
|
|
452
450
|
|
|
453
|
-
|
|
454
|
-
return
|
|
451
|
+
attachChannel(channel) {
|
|
452
|
+
return channel.attach()
|
|
455
453
|
}
|
|
456
454
|
|
|
457
455
|
subscribeEventMessages(channel, messageType, callback) {
|
package/lib/models/AnearEvent.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
const { Mutex } = require('async-mutex')
|
|
4
4
|
|
|
5
5
|
const AnearXstate = require('../utils/AnearXstate')
|
|
6
|
-
const { DefaultConfigFunc, DefaultOptionsFunc } = require('../utils/AnearXstateDefaults')
|
|
7
6
|
|
|
8
7
|
const JsonApiResource = require('./JsonApiResource')
|
|
9
8
|
const Participants = require('../utils/Participants')
|
|
@@ -40,12 +39,12 @@ class AnearEvent extends JsonApiResource {
|
|
|
40
39
|
|
|
41
40
|
stateMachineConfig() {
|
|
42
41
|
// override in subclass with custom Xstate config
|
|
43
|
-
return
|
|
42
|
+
return {}
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
stateMachineOptions() {
|
|
47
46
|
// override in subclass with custom Xstate options
|
|
48
|
-
return
|
|
47
|
+
return {}
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
initStateMachine(previousState) {
|
|
@@ -128,48 +127,9 @@ class AnearEvent extends JsonApiResource {
|
|
|
128
127
|
return await this.messaging.getSpectatorCount(this)
|
|
129
128
|
}
|
|
130
129
|
|
|
131
|
-
async createdEventCallback(participantCreator) {
|
|
132
|
-
// You may implement createdEventCallback() in your AnearEvent sub-class
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async participantEnterEventCallback(participant) {
|
|
136
|
-
throw new Error('You must implement an async participantEnterEventCallback() in your AnearEvent sub-class');
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
async participantRefreshEventCallback(participant, remainingTimeout = null) {
|
|
140
|
-
// You may implement an async participantRefreshEventCallback() in your AnearEvent sub-class
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async spectatorRefreshEventCallback() {
|
|
144
|
-
// You may implement an async spectatorRefreshEventCallback() in your AnearEvent sub-class
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
async participantExitEventCallback(participant) {
|
|
148
|
-
throw new Error('You must implement an async participantExitEventCallback() in your AnearEvent sub-class');
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
async participantActionEventCallback(participant, actionEventName, message) {
|
|
152
|
-
throw new Error('You must implement an async participantActionEventCallback() in your AnearEvent sub-class');
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async participantTimedOutEventCallback(participant) {
|
|
156
|
-
// You may implement participantTimedOutEventCallback() in your AnearEvent sub-class'
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async eventBroadcastEventCallback(message) {
|
|
160
|
-
// You may implement eventBroadcastEventCallback() in your AnearEvent sub-class'
|
|
161
|
-
}
|
|
162
|
-
|
|
163
130
|
isParticipantEventCreator(RParticipant) {
|
|
164
131
|
return participant.userId === this.userId
|
|
165
132
|
}
|
|
166
|
-
async refreshParticipant(participant) {
|
|
167
|
-
await this.participantRefreshEventCallback(participant)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
async refreshSpectator() {
|
|
171
|
-
await this.spectatorRefreshEventCallback()
|
|
172
|
-
}
|
|
173
133
|
|
|
174
134
|
async publishEventParticipantsMessage(message, timeoutMsecs=0) {
|
|
175
135
|
await this.messaging.publishEventParticipantsMessage(
|
|
@@ -254,6 +214,10 @@ class AnearEvent extends JsonApiResource {
|
|
|
254
214
|
await this.participantPurge(participant)
|
|
255
215
|
}
|
|
256
216
|
|
|
217
|
+
spectatorView(userId) {
|
|
218
|
+
this.anearStateMachine.sendSpectatorViewEvent({userId})
|
|
219
|
+
}
|
|
220
|
+
|
|
257
221
|
async participantPurge(participant) {
|
|
258
222
|
this.participants.purge(participant)
|
|
259
223
|
await participant.remove()
|
|
@@ -267,10 +231,6 @@ class AnearEvent extends JsonApiResource {
|
|
|
267
231
|
this.anearStateMachine.sendTimeoutEvent({ participant })
|
|
268
232
|
}
|
|
269
233
|
|
|
270
|
-
async eventBroadcast(message) {
|
|
271
|
-
await this.eventBroadcastEventCallback(message)
|
|
272
|
-
}
|
|
273
|
-
|
|
274
234
|
async transitionEvent(eventName='next') {
|
|
275
235
|
//
|
|
276
236
|
// Allows the app/game to transition the remote AnearEvent which can un/hide and event
|
package/lib/utils/AnearXstate.js
CHANGED
|
@@ -3,7 +3,8 @@ const { createMachine, interpret, State } = require('xstate')
|
|
|
3
3
|
|
|
4
4
|
const logger = require('../utils/Logger')
|
|
5
5
|
|
|
6
|
-
const JoinEvent = '
|
|
6
|
+
const JoinEvent = 'PARTICIPANT_JOIN'
|
|
7
|
+
const SpectatorViewEvent = 'SPECTATOR_VIEW'
|
|
7
8
|
const ParticipantExitEvent = 'PARTICIPANT_EXIT'
|
|
8
9
|
const RefreshEvent = 'REFRESH'
|
|
9
10
|
const CloseEvent = 'EVENT_CLOSE'
|
|
@@ -60,6 +61,10 @@ class AnearXstate {
|
|
|
60
61
|
this.send(ParticipantExitEvent, params)
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
sendSpectatorViewEvent(params) {
|
|
65
|
+
this.send(SpectatorViewEvent, params)
|
|
66
|
+
}
|
|
67
|
+
|
|
63
68
|
sendTimeoutEvent(params) {
|
|
64
69
|
this.send(TimeoutEvent, params)
|
|
65
70
|
}
|
package/package.json
CHANGED
package/tests/AnearEvent.test.js
CHANGED
|
@@ -5,6 +5,7 @@ const AnearParticipant = require('../lib/models/AnearParticipant')
|
|
|
5
5
|
const MockMessaging = require('../lib/messaging/__mocks__/AnearMessaging')
|
|
6
6
|
|
|
7
7
|
const mockParticipantEnterHandler = jest.fn()
|
|
8
|
+
const mockSpectatorViewHandler = jest.fn()
|
|
8
9
|
const mockParticipantRefreshHandler = jest.fn()
|
|
9
10
|
const mockParticipantExitHandler = jest.fn()
|
|
10
11
|
const mockParticipantActionHandler = jest.fn()
|
|
@@ -15,15 +16,19 @@ const TicTacToeMachineConfig = anearEvent => ({
|
|
|
15
16
|
states: {
|
|
16
17
|
waitingForHost: {
|
|
17
18
|
on: {
|
|
18
|
-
|
|
19
|
+
PARTICIPANT_JOIN: {
|
|
19
20
|
actions: 'enterHandler',
|
|
20
21
|
target: 'waitingForOpponent'
|
|
22
|
+
},
|
|
23
|
+
SPECTATOR_VIEW: {
|
|
24
|
+
actions: 'viewHandler',
|
|
25
|
+
target: 'waitingForHost'
|
|
21
26
|
}
|
|
22
27
|
}
|
|
23
28
|
},
|
|
24
29
|
waitingForOpponent: {
|
|
25
30
|
on: {
|
|
26
|
-
|
|
31
|
+
PARTICIPANT_JOIN: {
|
|
27
32
|
actions: 'enterHandler',
|
|
28
33
|
target: 'gameStart'
|
|
29
34
|
},
|
|
@@ -37,6 +42,9 @@ const TicTacToeMachineConfig = anearEvent => ({
|
|
|
37
42
|
},
|
|
38
43
|
gameStart: {
|
|
39
44
|
on: {
|
|
45
|
+
TEST_ACTION: {
|
|
46
|
+
actions: 'testActionHandler'
|
|
47
|
+
},
|
|
40
48
|
BULLSEYE: {
|
|
41
49
|
actions: 'actionHandler'
|
|
42
50
|
},
|
|
@@ -59,10 +67,20 @@ const TicTacToeMachineOptions = anearEvent => ({
|
|
|
59
67
|
refreshHandler: (context, event) => {
|
|
60
68
|
anearEvent.myParticipantRefreshHandler(event.participant)
|
|
61
69
|
},
|
|
70
|
+
viewHandler: (context, event) => {
|
|
71
|
+
anearEvent.mySpectatorViewHandler(event.userId)
|
|
72
|
+
},
|
|
62
73
|
participantExitHandler: (context, event) => {
|
|
63
74
|
anearEvent.myParticipantExitHandler(event.participant)
|
|
64
75
|
},
|
|
65
76
|
actionHandler: assign({score: (context, event) => context.score + event.payload.points}),
|
|
77
|
+
testActionHandler: (context, event) => {
|
|
78
|
+
anearEvent.myParticipantActionHandler(
|
|
79
|
+
event.participant.id,
|
|
80
|
+
event.type,
|
|
81
|
+
event.payload
|
|
82
|
+
)
|
|
83
|
+
}
|
|
66
84
|
}
|
|
67
85
|
})
|
|
68
86
|
|
|
@@ -81,6 +99,9 @@ class TestEvent extends AnearEvent {
|
|
|
81
99
|
return TicTacToeMachineOptions(this)
|
|
82
100
|
}
|
|
83
101
|
|
|
102
|
+
async mySpectatorViewHandler(...args) {
|
|
103
|
+
return mockSpectatorViewHandler(...args)
|
|
104
|
+
}
|
|
84
105
|
async myParticipantEnterHandler(...args) {
|
|
85
106
|
return mockParticipantEnterHandler(...args)
|
|
86
107
|
}
|
|
@@ -90,37 +111,12 @@ class TestEvent extends AnearEvent {
|
|
|
90
111
|
async myParticipantRefreshHandler(...args) {
|
|
91
112
|
return mockParticipantRefreshHandler(...args)
|
|
92
113
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
class TestEventWithDefaultXState extends AnearEvent {
|
|
97
|
-
initContext() {
|
|
98
|
-
return {
|
|
99
|
-
playerScores: [83, 22]
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
participantEnterEventCallback(participant) {
|
|
104
|
-
mockParticipantEnterHandler(participant)
|
|
105
|
-
return Promise.resolve()
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
participantRefreshEventCallback(participant) {
|
|
109
|
-
mockParticipantRefreshHandler(participant)
|
|
110
|
-
return Promise.resolve()
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
participantExitEventCallback(participant) {
|
|
114
|
-
mockParticipantExitHandler(participant)
|
|
115
|
-
return Promise.resolve()
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
participantActionEventCallback(participant, actionEventName, payload) {
|
|
119
|
-
mockParticipantActionHandler(participant, actionEventName, payload)
|
|
120
|
-
return Promise.resolve()
|
|
114
|
+
async myParticipantActionHandler(...args) {
|
|
115
|
+
return mockParticipantActionHandler(...args)
|
|
121
116
|
}
|
|
122
117
|
}
|
|
123
118
|
|
|
119
|
+
|
|
124
120
|
class TestPlayer extends AnearParticipant {
|
|
125
121
|
initContext() {
|
|
126
122
|
return {
|
|
@@ -148,23 +144,18 @@ const newTestEvent = (hosted = false) => {
|
|
|
148
144
|
return t
|
|
149
145
|
}
|
|
150
146
|
|
|
151
|
-
|
|
152
|
-
const t =
|
|
153
|
-
t.startStateMachine()
|
|
154
|
-
return t
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
test('participant enter with Default Xstate Config', async () => {
|
|
158
|
-
const t = newTestEventWithDefaultXState(chatEvent)
|
|
147
|
+
test('participant enter', async () => {
|
|
148
|
+
const t = newTestEvent()
|
|
159
149
|
|
|
160
150
|
const id = t.id
|
|
161
151
|
expect(t.id).toBe(chatEvent.data.id)
|
|
162
152
|
expect(t.relationships.user.data.type).toBe("users")
|
|
163
|
-
expect(t.anearStateMachine.currentState.value).toBe("
|
|
164
|
-
expect(t.stateMachineContext.
|
|
153
|
+
expect(t.anearStateMachine.currentState.value).toBe("waitingForHost")
|
|
154
|
+
expect(t.stateMachineContext.score).toBe(90)
|
|
165
155
|
const p1 = new TestPlayer(chatParticipant1, t)
|
|
166
156
|
|
|
167
157
|
await t.participantEnter(p1)
|
|
158
|
+
expect(t.anearStateMachine.currentState.value).toBe("waitingForOpponent")
|
|
168
159
|
await t.persist()
|
|
169
160
|
await t.remove()
|
|
170
161
|
|
|
@@ -176,10 +167,25 @@ test('participant enter with Default Xstate Config', async () => {
|
|
|
176
167
|
await p1.remove()
|
|
177
168
|
})
|
|
178
169
|
|
|
179
|
-
test('
|
|
180
|
-
const t =
|
|
170
|
+
test('spectator viewer', async () => {
|
|
171
|
+
const t = newTestEvent()
|
|
172
|
+
const userId = 999837834
|
|
173
|
+
|
|
174
|
+
const id = t.id
|
|
175
|
+
expect(t.anearStateMachine.currentState.value).toBe("waitingForHost")
|
|
176
|
+
await t.spectatorView(userId)
|
|
177
|
+
expect(t.anearStateMachine.currentState.value).toBe("waitingForHost")
|
|
178
|
+
|
|
179
|
+
expect(mockSpectatorViewHandler).toHaveBeenCalledTimes(1)
|
|
180
|
+
expect(mockSpectatorViewHandler).toHaveBeenCalledWith(userId)
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
test('participant close', async () => {
|
|
185
|
+
const t = newTestEvent()
|
|
181
186
|
|
|
182
187
|
const p1 = new TestPlayer(chatParticipant1, t)
|
|
188
|
+
await t.participantEnter(p1)
|
|
183
189
|
|
|
184
190
|
await t.participantExit(p1)
|
|
185
191
|
await t.update()
|
|
@@ -191,29 +197,20 @@ test('participant close with Default Xstate Config', async () => {
|
|
|
191
197
|
await t.remove()
|
|
192
198
|
})
|
|
193
199
|
|
|
194
|
-
test('participant
|
|
195
|
-
const t =
|
|
200
|
+
test('participant action', async () => {
|
|
201
|
+
const t = newTestEvent()
|
|
196
202
|
const p1 = new TestPlayer(chatParticipant1, t)
|
|
203
|
+
const p2 = new TestPlayer(chatParticipant2, t)
|
|
204
|
+
await t.participantEnter(p1)
|
|
205
|
+
await t.participantEnter(p2)
|
|
197
206
|
|
|
198
|
-
await t.refreshParticipant(p1)
|
|
199
|
-
await t.update()
|
|
200
|
-
|
|
201
|
-
expect(mockParticipantRefreshHandler).toHaveBeenCalledWith(p1)
|
|
202
|
-
expect(mockParticipantRefreshHandler).toHaveBeenCalledTimes(1)
|
|
203
|
-
await p1.remove()
|
|
204
|
-
await t.remove()
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
test('participant action with Default Xstate Config', async () => {
|
|
208
|
-
const t = newTestEventWithDefaultXState(chatEvent)
|
|
209
|
-
const p1 = new TestPlayer(chatParticipant1, t)
|
|
210
207
|
const eventName = "TEST_ACTION"
|
|
211
208
|
const payload = {x: 1, y: 99}
|
|
212
209
|
|
|
213
210
|
await t.participantAction(p1, eventName, payload)
|
|
214
211
|
await t.update()
|
|
215
212
|
|
|
216
|
-
expect(mockParticipantActionHandler).toHaveBeenCalledWith(p1, eventName, payload)
|
|
213
|
+
expect(mockParticipantActionHandler).toHaveBeenCalledWith(p1.id, eventName, payload)
|
|
217
214
|
expect(mockParticipantActionHandler).toHaveBeenCalledTimes(1)
|
|
218
215
|
await p1.remove()
|
|
219
216
|
await t.remove()
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
"use strict"
|
|
2
|
-
|
|
3
|
-
// Default configuration and options funcs provide
|
|
4
|
-
// a simple default state machine for an anear Event. This is
|
|
5
|
-
// so we don't mandate that app developers use xState to drive
|
|
6
|
-
// their app's state transitions. Devs simply provide callbacks
|
|
7
|
-
// override implementations in their AnearEvent subclass.
|
|
8
|
-
// The xState context is simply the anearEvent.context
|
|
9
|
-
//
|
|
10
|
-
// If a developer wants an xState machine to drive the applications
|
|
11
|
-
// state transitions, the developer should override stateMachineConfig()
|
|
12
|
-
// and stateMachineOptions() in their AnearEvent subclass.
|
|
13
|
-
//
|
|
14
|
-
const DefaultConfigFunc = (anearEvent) => {
|
|
15
|
-
|
|
16
|
-
const PromiseResolveReject = {
|
|
17
|
-
onDone: {
|
|
18
|
-
target: 'eventActive'
|
|
19
|
-
},
|
|
20
|
-
onError: {
|
|
21
|
-
actions: 'logError',
|
|
22
|
-
target: 'eventActive'
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return {
|
|
27
|
-
id: "defaultXstateConfig",
|
|
28
|
-
initial: 'eventActive',
|
|
29
|
-
states: {
|
|
30
|
-
eventActive: {
|
|
31
|
-
on: {
|
|
32
|
-
JOIN: {
|
|
33
|
-
target: 'join'
|
|
34
|
-
},
|
|
35
|
-
REFRESH: {
|
|
36
|
-
target: 'refresh'
|
|
37
|
-
},
|
|
38
|
-
PARTICIPANT_EXIT: {
|
|
39
|
-
target: 'participant_exit'
|
|
40
|
-
},
|
|
41
|
-
TIMEOUT: {
|
|
42
|
-
target: 'timeout'
|
|
43
|
-
},
|
|
44
|
-
'*': {
|
|
45
|
-
// default wildcard state is presumed to be a custom ACTION name
|
|
46
|
-
// embedded in a anear-action-click property in the app's HTML
|
|
47
|
-
target: 'action'
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
join: {
|
|
52
|
-
invoke: {
|
|
53
|
-
id: 'join',
|
|
54
|
-
src: 'joinEventHandler',
|
|
55
|
-
...PromiseResolveReject
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
refresh: {
|
|
59
|
-
invoke: {
|
|
60
|
-
id: 'refresh',
|
|
61
|
-
src: 'refreshEventHandler',
|
|
62
|
-
...PromiseResolveReject
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
participant_exit: {
|
|
66
|
-
invoke: {
|
|
67
|
-
id: 'participant_exit',
|
|
68
|
-
src: 'participantExitEventHandler',
|
|
69
|
-
...PromiseResolveReject
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
timeout: {
|
|
73
|
-
invoke: {
|
|
74
|
-
id: 'timeout',
|
|
75
|
-
src: 'timeoutEventHandler',
|
|
76
|
-
...PromiseResolveReject
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
action: {
|
|
80
|
-
invoke: {
|
|
81
|
-
id: 'action',
|
|
82
|
-
src: 'actionEventHandler',
|
|
83
|
-
...PromiseResolveReject
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const DefaultOptionsFunc = anearEvent => {
|
|
91
|
-
return {
|
|
92
|
-
actions: {
|
|
93
|
-
logError: (context, event) => logger.error(`error message: ${event.data}`)
|
|
94
|
-
},
|
|
95
|
-
services: {
|
|
96
|
-
joinEventHandler: (context, event) => anearEvent.participantEnterEventCallback(event.participant),
|
|
97
|
-
refreshEventHandler: (context, event) => anearEvent.participantRefreshEventCallback(event.participant, event.remainingTimeout),
|
|
98
|
-
participantExitEventHandler: (context, event) => anearEvent.participantExitEventCallback(event.participant),
|
|
99
|
-
timeoutEventHandler: (context, event) => anearEvent.participantTimedOutEventCallback(event.participant),
|
|
100
|
-
actionEventHandler: (context, event) => anearEvent.participantActionEventCallback(event.participant, event.type, event.payload)
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
module.exports = {
|
|
106
|
-
DefaultConfigFunc,
|
|
107
|
-
DefaultOptionsFunc
|
|
108
|
-
}
|