@dcl/sdk 7.29.1-35862852104.commit-1f44e80 → 7.29.1-35901015152.commit-d1b7b96
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.
- package/internal/transports/rendererTransport.js +2 -5
- package/network/binary-message-bus.d.ts +3 -6
- package/network/binary-message-bus.js +5 -9
- package/network/index.d.ts +2 -8
- package/network/index.js +3 -16
- package/network/message-bus-sync.d.ts +3 -16
- package/network/message-bus-sync.js +103 -161
- package/network/state.js +5 -3
- package/observables.d.ts +0 -4
- package/observables.js +1 -6
- package/package.json +6 -6
- package/platform/index.d.ts +0 -16
- package/platform/index.js +1 -42
- package/players/index.d.ts +1 -0
- package/players/index.js +4 -6
- package/src/internal/transports/rendererTransport.ts +1 -4
- package/src/network/binary-message-bus.ts +4 -9
- package/src/network/index.ts +3 -40
- package/src/network/message-bus-sync.ts +112 -176
- package/src/network/state.ts +4 -3
- package/src/observables.ts +0 -10
- package/src/platform/index.ts +0 -47
- package/src/players/index.ts +4 -5
- package/src/testing/runtime.ts +0 -3
- package/testing/runtime.js +1 -4
- package/atom.d.ts +0 -19
- package/atom.js +0 -83
- package/future.d.ts +0 -8
- package/future.js +0 -26
- package/internal/language-events.d.ts +0 -1
- package/internal/language-events.js +0 -8
- package/network/chunking.d.ts +0 -5
- package/network/chunking.js +0 -38
- package/network/events/implementation.d.ts +0 -93
- package/network/events/implementation.js +0 -221
- package/network/events/index.d.ts +0 -42
- package/network/events/index.js +0 -43
- package/network/events/protocol.d.ts +0 -27
- package/network/events/protocol.js +0 -66
- package/network/events/registry.d.ts +0 -8
- package/network/events/registry.js +0 -3
- package/network/server/index.d.ts +0 -14
- package/network/server/index.js +0 -219
- package/network/server/utils.d.ts +0 -18
- package/network/server/utils.js +0 -135
- package/server/env-var.d.ts +0 -15
- package/server/env-var.js +0 -31
- package/server/index.d.ts +0 -2
- package/server/index.js +0 -3
- package/server/storage/constants.d.ts +0 -79
- package/server/storage/constants.js +0 -19
- package/server/storage/index.d.ts +0 -45
- package/server/storage/index.js +0 -51
- package/server/storage/player.d.ts +0 -45
- package/server/storage/player.js +0 -198
- package/server/storage/scene.d.ts +0 -40
- package/server/storage/scene.js +0 -188
- package/server/storage/value-cache.d.ts +0 -1
- package/server/storage/value-cache.js +0 -52
- package/server/storage/write-queue.d.ts +0 -1
- package/server/storage/write-queue.js +0 -71
- package/server/storage-url.d.ts +0 -13
- package/server/storage-url.js +0 -42
- package/server/utils.d.ts +0 -35
- package/server/utils.js +0 -61
- package/src/atom.ts +0 -98
- package/src/future.ts +0 -38
- package/src/internal/language-events.ts +0 -8
- package/src/network/chunking.ts +0 -45
- package/src/network/events/implementation.ts +0 -271
- package/src/network/events/index.ts +0 -48
- package/src/network/events/protocol.ts +0 -94
- package/src/network/events/registry.ts +0 -18
- package/src/network/server/index.ts +0 -301
- package/src/network/server/utils.ts +0 -189
- package/src/server/env-var.ts +0 -36
- package/src/server/index.ts +0 -12
- package/src/server/storage/constants.ts +0 -111
- package/src/server/storage/index.ts +0 -74
- package/src/server/storage/player.ts +0 -289
- package/src/server/storage/scene.ts +0 -270
- package/src/server/storage/value-cache.ts +0 -92
- package/src/server/storage/write-queue.ts +0 -131
- package/src/server/storage-url.ts +0 -49
- package/src/server/utils.ts +0 -76
|
@@ -1,53 +1,27 @@
|
|
|
1
|
-
import { IEngine, Transport, RealmInfo } from '@dcl/ecs'
|
|
2
|
-
import type
|
|
1
|
+
import { IEngine, Transport, RealmInfo, PlayerIdentityData } from '@dcl/ecs'
|
|
2
|
+
import { type SendBinaryRequest, type SendBinaryResponse } from '~system/CommunicationsController'
|
|
3
3
|
|
|
4
4
|
import { syncFilter } from './filter'
|
|
5
5
|
import { engineToCrdt } from './state'
|
|
6
|
-
import { BinaryMessageBus, CommsMessage } from './binary-message-bus'
|
|
6
|
+
import { BinaryMessageBus, CommsMessage, decodeString, encodeString } from './binary-message-bus'
|
|
7
7
|
import { fetchProfile } from './utils'
|
|
8
8
|
import { entityUtils } from './entities'
|
|
9
|
-
import {
|
|
10
|
-
import type { GetUserDataRequest, GetUserDataResponse } from '~system/UserIdentity'
|
|
9
|
+
import { GetUserDataRequest, GetUserDataResponse } from '~system/UserIdentity'
|
|
11
10
|
import { definePlayerHelper } from '../players'
|
|
12
11
|
import { serializeCrdtMessages } from '../internal/transports/logger'
|
|
13
|
-
import { IsServerRequest, IsServerResponse } from '~system/EngineApi'
|
|
14
|
-
import { Atom } from '../atom'
|
|
15
|
-
import { setGlobalRoom, Room } from './events/implementation'
|
|
16
12
|
|
|
17
13
|
export type IProfile = { networkId: number; userId: string }
|
|
18
14
|
// user that we asked for the inital crdt state
|
|
19
|
-
export const AUTH_SERVER_PEER_ID = 'authoritative-server'
|
|
20
|
-
export const DEBUG_NETWORK_MESSAGES = () => (globalThis as any).DEBUG_NETWORK_MESSAGES ?? false
|
|
21
|
-
|
|
22
|
-
// Test environment detection without 'as any'
|
|
23
|
-
const isTestEnvironment = (): boolean => {
|
|
24
|
-
try {
|
|
25
|
-
if (typeof globalThis === 'undefined') return false
|
|
26
|
-
const globalWithProcess = globalThis as unknown as { process?: { env?: { NODE_ENV?: string } } }
|
|
27
|
-
return globalWithProcess.process?.env?.NODE_ENV === 'test'
|
|
28
|
-
} catch {
|
|
29
|
-
return false
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
15
|
export function addSyncTransport(
|
|
34
16
|
engine: IEngine,
|
|
35
17
|
sendBinary: (msg: SendBinaryRequest) => Promise<SendBinaryResponse>,
|
|
36
|
-
getUserData: (value: GetUserDataRequest) => Promise<GetUserDataResponse
|
|
37
|
-
isServerFn: (request: IsServerRequest) => Promise<IsServerResponse>,
|
|
38
|
-
name: string
|
|
18
|
+
getUserData: (value: GetUserDataRequest) => Promise<GetUserDataResponse>
|
|
39
19
|
) {
|
|
20
|
+
const DEBUG_NETWORK_MESSAGES = () => (globalThis as any).DEBUG_NETWORK_MESSAGES ?? false
|
|
40
21
|
// Profile Info
|
|
41
22
|
const myProfile: IProfile = {} as IProfile
|
|
42
23
|
fetchProfile(myProfile!, getUserData)
|
|
43
24
|
|
|
44
|
-
const isServerAtom = Atom<boolean>()
|
|
45
|
-
const isRoomReadyAtom = Atom<boolean>(false)
|
|
46
|
-
|
|
47
|
-
void isServerFn({}).then(($: IsServerResponse) => {
|
|
48
|
-
return isServerAtom.swap(!!$.isServer)
|
|
49
|
-
})
|
|
50
|
-
|
|
51
25
|
// Entity utils
|
|
52
26
|
const entityDefinitions = entityUtils(engine, myProfile)
|
|
53
27
|
|
|
@@ -66,190 +40,108 @@ export function addSyncTransport(
|
|
|
66
40
|
const players = definePlayerHelper(engine)
|
|
67
41
|
|
|
68
42
|
let stateIsSyncronized = false
|
|
43
|
+
let transportInitialzed = false
|
|
69
44
|
|
|
70
|
-
/**
|
|
71
|
-
* We need to wait till 2 ticks that is when the engine is ready to send new messages.
|
|
72
|
-
* The first tick is for the client engine processing the CRDT messages,
|
|
73
|
-
* and the second one are the messages created by the main() function.
|
|
74
|
-
* So to avoid sending those messages, that all the clients have, through the network we put this validation here.
|
|
75
|
-
*/
|
|
76
|
-
let tick = 0
|
|
77
|
-
const TRANSPORT_INITIALIZED_NUMBER = isTestEnvironment() ? 0 : 2
|
|
78
45
|
// Add Sync Transport
|
|
79
46
|
const transport: Transport = {
|
|
80
47
|
filter: syncFilter(engine),
|
|
81
48
|
send: async (messages) => {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (message.byteLength) {
|
|
49
|
+
for (const message of [messages].flat()) {
|
|
50
|
+
if (message.byteLength && transportInitialzed) {
|
|
85
51
|
DEBUG_NETWORK_MESSAGES() &&
|
|
86
52
|
console.log(...Array.from(serializeCrdtMessages('[NetworkMessage sent]:', message, engine)))
|
|
87
|
-
|
|
88
|
-
// Convert regular messages to network messages for broadcasting with chunking
|
|
89
|
-
for (const chunk of serverValidator.convertRegularToNetworkMessage(message)) {
|
|
90
|
-
binaryMessageBus.emit(CommsMessage.CRDT, chunk)
|
|
91
|
-
}
|
|
53
|
+
binaryMessageBus.emit(CommsMessage.CRDT, message)
|
|
92
54
|
}
|
|
93
55
|
}
|
|
94
56
|
const peerMessages = getMessagesToSend()
|
|
57
|
+
let totalSize = 0
|
|
58
|
+
for (const message of peerMessages) {
|
|
59
|
+
for (const data of message.data) {
|
|
60
|
+
totalSize += data.byteLength
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (totalSize) {
|
|
64
|
+
DEBUG_NETWORK_MESSAGES() && console.log('Sending network messages: ', totalSize / 1024, 'KB')
|
|
65
|
+
}
|
|
95
66
|
const response = await sendBinary({ data: [], peerData: peerMessages })
|
|
96
67
|
binaryMessageBus.__processMessages(response.data)
|
|
68
|
+
transportInitialzed = true
|
|
97
69
|
},
|
|
98
|
-
type:
|
|
70
|
+
type: 'network'
|
|
99
71
|
}
|
|
100
|
-
|
|
101
|
-
// Server validation setup
|
|
102
|
-
const serverValidator = createServerValidator({
|
|
103
|
-
engine,
|
|
104
|
-
binaryMessageBus
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
// Initialize Event Bus with registered schemas
|
|
108
|
-
const eventBus = new Room(engine, binaryMessageBus, isServerAtom, isRoomReadyAtom)
|
|
109
|
-
|
|
110
|
-
// Set global eventBus instance
|
|
111
|
-
setGlobalRoom(eventBus)
|
|
112
|
-
|
|
113
72
|
engine.addTransport(transport)
|
|
114
73
|
// End add sync transport
|
|
115
74
|
|
|
116
75
|
// Receive & Process CRDT_STATE
|
|
117
|
-
binaryMessageBus.on(CommsMessage.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (chunks.length === 0) {
|
|
121
|
-
DEBUG_NETWORK_MESSAGES() && console.log('[Emiting empty state:]', sender, Date.now())
|
|
122
|
-
binaryMessageBus.emit(CommsMessage.RES_CRDT_STATE, new Uint8Array(), [sender])
|
|
123
|
-
} else {
|
|
124
|
-
for (const chunk of chunks) {
|
|
125
|
-
DEBUG_NETWORK_MESSAGES() && console.log('[Emiting:]', sender, Date.now())
|
|
126
|
-
binaryMessageBus.emit(CommsMessage.RES_CRDT_STATE, chunk, [sender])
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
})
|
|
130
|
-
binaryMessageBus.on(CommsMessage.RES_CRDT_STATE, async (data, sender) => {
|
|
131
|
-
requestingState = false
|
|
132
|
-
elapsedTimeSinceRequest = 0
|
|
133
|
-
if (isServerAtom.getOrNull() || sender !== AUTH_SERVER_PEER_ID) return
|
|
76
|
+
binaryMessageBus.on(CommsMessage.RES_CRDT_STATE, (value) => {
|
|
77
|
+
const { sender, data } = decodeCRDTState(value)
|
|
78
|
+
if (sender !== myProfile.userId) return
|
|
134
79
|
DEBUG_NETWORK_MESSAGES() && console.log('[Processing CRDT State]', data.byteLength / 1024, 'KB')
|
|
135
|
-
|
|
136
|
-
transport.onmessage!(serverValidator.processClientMessages(data, sender))
|
|
137
|
-
}
|
|
80
|
+
transport.onmessage!(data)
|
|
138
81
|
stateIsSyncronized = true
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
// Answer to REQ_CRDT_STATE
|
|
85
|
+
binaryMessageBus.on(CommsMessage.REQ_CRDT_STATE, async (_, userId) => {
|
|
86
|
+
DEBUG_NETWORK_MESSAGES() && console.log(`Sending CRDT State to: ${userId}`)
|
|
139
87
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const realmInfo = RealmInfo.getOrNull(engine.RootEntity)
|
|
143
|
-
if (realmInfo) {
|
|
144
|
-
DEBUG_NETWORK_MESSAGES() && console.log('[isRoomReady] Marking room as ready after state sync')
|
|
145
|
-
isRoomReadyAtom.swap(true)
|
|
88
|
+
for (const chunk of engineToCrdt(engine)) {
|
|
89
|
+
binaryMessageBus.emit(CommsMessage.RES_CRDT_STATE, encodeCRDTState(userId, chunk), [userId])
|
|
146
90
|
}
|
|
147
91
|
})
|
|
148
92
|
|
|
149
|
-
//
|
|
150
|
-
binaryMessageBus.on(CommsMessage.CRDT, (value
|
|
151
|
-
const isServer = isServerAtom.getOrNull()
|
|
93
|
+
// Process CRDT messages here
|
|
94
|
+
binaryMessageBus.on(CommsMessage.CRDT, (value) => {
|
|
152
95
|
DEBUG_NETWORK_MESSAGES() &&
|
|
153
|
-
console.log(
|
|
154
|
-
|
|
155
|
-
...Array.from(serializeCrdtMessages('[NetworkMessage received]:', value, engine)),
|
|
156
|
-
isServer
|
|
157
|
-
)
|
|
158
|
-
if (isServer) {
|
|
159
|
-
transport.onmessage!(serverValidator.processServerMessages(value, sender))
|
|
160
|
-
} else if (sender === AUTH_SERVER_PEER_ID) {
|
|
161
|
-
// Process network messages from server and convert to regular messages
|
|
162
|
-
transport.onmessage!(serverValidator.processClientMessages(value, sender))
|
|
163
|
-
}
|
|
96
|
+
console.log(Array.from(serializeCrdtMessages('[NetworkMessage received]:', value, engine)))
|
|
97
|
+
transport.onmessage!(value)
|
|
164
98
|
})
|
|
165
99
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
100
|
+
async function requestState(retryCount: number = 1) {
|
|
101
|
+
let players = Array.from(engine.getEntitiesWith(PlayerIdentityData))
|
|
102
|
+
DEBUG_NETWORK_MESSAGES() && console.log(`Requesting state. Players connected: ${players.length - 1}`)
|
|
103
|
+
|
|
104
|
+
if (!RealmInfo.getOrNull(engine.RootEntity)?.isConnectedSceneRoom) {
|
|
105
|
+
DEBUG_NETWORK_MESSAGES() && console.log(`Aborting Requesting state?. Disconnected`)
|
|
106
|
+
return
|
|
107
|
+
}
|
|
170
108
|
|
|
171
|
-
|
|
172
|
-
console.log('[AUTHORITATIVE] Received authoritative message from server:', value.byteLength, 'bytes')
|
|
109
|
+
binaryMessageBus.emit(CommsMessage.REQ_CRDT_STATE, new Uint8Array())
|
|
173
110
|
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
const authoritativeBuffer = serverValidator.processClientMessages(value, sender, true)
|
|
177
|
-
if (authoritativeBuffer.byteLength > 0) {
|
|
178
|
-
// Apply authoritative message through normal transport, but the server's messages
|
|
179
|
-
// should be processed as authoritative with special timestamp handling
|
|
180
|
-
transport.onmessage!(authoritativeBuffer)
|
|
111
|
+
// Wait ~5s for the response.
|
|
112
|
+
await sleep(5000)
|
|
181
113
|
|
|
182
|
-
|
|
114
|
+
players = Array.from(engine.getEntitiesWith(PlayerIdentityData))
|
|
115
|
+
|
|
116
|
+
if (!stateIsSyncronized) {
|
|
117
|
+
if (players.length > 1 && retryCount <= 2) {
|
|
118
|
+
DEBUG_NETWORK_MESSAGES() &&
|
|
119
|
+
console.log(`Requesting state again ${retryCount} (no response). Players connected: ${players.length - 1}`)
|
|
120
|
+
void requestState(retryCount + 1)
|
|
121
|
+
} else {
|
|
122
|
+
DEBUG_NETWORK_MESSAGES() && console.log('No active players. State syncronized')
|
|
123
|
+
stateIsSyncronized = true
|
|
124
|
+
}
|
|
183
125
|
}
|
|
184
|
-
}
|
|
126
|
+
}
|
|
185
127
|
|
|
186
128
|
players.onEnterScene((player) => {
|
|
187
129
|
DEBUG_NETWORK_MESSAGES() && console.log('[onEnterScene]', player.userId)
|
|
188
|
-
if (!isServerAtom.getOrNull() && myProfile.userId === player.userId) {
|
|
189
|
-
requestState()
|
|
190
|
-
}
|
|
191
130
|
})
|
|
192
131
|
|
|
193
132
|
// Asks for the REQ_CRDT_STATE when its connected to comms
|
|
194
133
|
RealmInfo.onChange(engine.RootEntity, (value) => {
|
|
195
|
-
const isServer = isServerAtom.getOrNull()
|
|
196
|
-
|
|
197
134
|
if (!value?.isConnectedSceneRoom) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
DEBUG_NETWORK_MESSAGES() && console.log('Disconnected from comms')
|
|
201
|
-
isRoomReadyAtom.swap(false)
|
|
202
|
-
if (!isServer) {
|
|
203
|
-
stateIsSyncronized = false
|
|
204
|
-
}
|
|
205
|
-
}
|
|
135
|
+
DEBUG_NETWORK_MESSAGES() && console.log('Disconnected from comms')
|
|
136
|
+
stateIsSyncronized = false
|
|
206
137
|
}
|
|
207
138
|
|
|
208
139
|
if (value?.isConnectedSceneRoom) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
// For servers, mark as ready immediately when connected
|
|
212
|
-
// (servers don't need to sync state from anyone)
|
|
213
|
-
if (isServer && isRoomReadyAtom.getOrNull() === false) {
|
|
214
|
-
DEBUG_NETWORK_MESSAGES() && console.log('[isRoomReady] Server marking room as ready')
|
|
215
|
-
isRoomReadyAtom.swap(true)
|
|
216
|
-
}
|
|
217
|
-
// For clients, room will be marked ready after receiving CRDT state (above)
|
|
218
|
-
}
|
|
219
|
-
})
|
|
220
|
-
|
|
221
|
-
let requestingState = false
|
|
222
|
-
let elapsedTimeSinceRequest = 0
|
|
223
|
-
const STATE_REQUEST_RETRY_INTERVAL = 2.0 // seconds
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Why we have to request the state if we have a server that can send us the state when we joined?
|
|
227
|
-
* The thing is that when the server detects a new JOIN_PARTICIPANT on livekit room, it sends automatically the state to that peer.
|
|
228
|
-
* But in unity, it takes more time, so that message is not being delivered to the client.
|
|
229
|
-
* So instead, when we are finally connected to the room, we request the state, and then the server answers with the state :)
|
|
230
|
-
*
|
|
231
|
-
* If no response is received within 2 seconds, the request is automatically retried.
|
|
232
|
-
*/
|
|
233
|
-
function requestState() {
|
|
234
|
-
if (isServerAtom.getOrNull()) return
|
|
235
|
-
if (RealmInfo.getOrNull(engine.RootEntity)?.isConnectedSceneRoom && !requestingState) {
|
|
236
|
-
requestingState = true
|
|
237
|
-
elapsedTimeSinceRequest = 0
|
|
238
|
-
DEBUG_NETWORK_MESSAGES() && console.log('Requesting state...')
|
|
239
|
-
binaryMessageBus.emit(CommsMessage.REQ_CRDT_STATE, new Uint8Array())
|
|
140
|
+
DEBUG_NETWORK_MESSAGES() && console.log('Connected to comms')
|
|
240
141
|
}
|
|
241
|
-
}
|
|
242
142
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
if (requestingState && !stateIsSyncronized) {
|
|
246
|
-
elapsedTimeSinceRequest += dt
|
|
247
|
-
if (elapsedTimeSinceRequest >= STATE_REQUEST_RETRY_INTERVAL) {
|
|
248
|
-
DEBUG_NETWORK_MESSAGES() && console.log('State request timed out, retrying...')
|
|
249
|
-
elapsedTimeSinceRequest = 0
|
|
250
|
-
requestingState = false
|
|
251
|
-
requestState()
|
|
252
|
-
}
|
|
143
|
+
if (value?.isConnectedSceneRoom && !stateIsSyncronized) {
|
|
144
|
+
void requestState()
|
|
253
145
|
}
|
|
254
146
|
})
|
|
255
147
|
|
|
@@ -261,12 +153,56 @@ export function addSyncTransport(
|
|
|
261
153
|
return stateIsSyncronized
|
|
262
154
|
}
|
|
263
155
|
|
|
156
|
+
function sleep(ms: number) {
|
|
157
|
+
return new Promise<void>((resolve) => {
|
|
158
|
+
let timer = 0
|
|
159
|
+
function sleepSystem(dt: number) {
|
|
160
|
+
timer += dt
|
|
161
|
+
if (timer * 1000 >= ms) {
|
|
162
|
+
engine.removeSystem(sleepSystem)
|
|
163
|
+
resolve()
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
engine.addSystem(sleepSystem)
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
|
|
264
170
|
return {
|
|
265
171
|
...entityDefinitions,
|
|
266
172
|
myProfile,
|
|
267
|
-
isStateSyncronized
|
|
268
|
-
binaryMessageBus,
|
|
269
|
-
eventBus,
|
|
270
|
-
isRoomReadyAtom
|
|
173
|
+
isStateSyncronized
|
|
271
174
|
}
|
|
272
175
|
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Messages Protocol Encoding
|
|
179
|
+
*
|
|
180
|
+
* CRDT: Plain Uint8Array
|
|
181
|
+
*
|
|
182
|
+
* CRDT_STATE_RES { sender: string, data: Uint8Array}
|
|
183
|
+
*/
|
|
184
|
+
function decodeCRDTState(data: Uint8Array) {
|
|
185
|
+
let offset = 0
|
|
186
|
+
const r = new Uint8Array(data)
|
|
187
|
+
const view = new DataView(r.buffer)
|
|
188
|
+
const senderLength = view.getUint8(offset)
|
|
189
|
+
offset += 1
|
|
190
|
+
const sender = decodeString(data.subarray(1, senderLength + 1))
|
|
191
|
+
offset += senderLength
|
|
192
|
+
const state = r.subarray(offset)
|
|
193
|
+
|
|
194
|
+
return { sender, data: state }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function encodeCRDTState(address: string, data: Uint8Array) {
|
|
198
|
+
// address to uint8array
|
|
199
|
+
const addressBuffer = encodeString(address)
|
|
200
|
+
const addressOffset = 1
|
|
201
|
+
const messageLength = addressOffset + addressBuffer.byteLength + data.byteLength
|
|
202
|
+
|
|
203
|
+
const serializedMessage = new Uint8Array(messageLength)
|
|
204
|
+
serializedMessage.set(new Uint8Array([addressBuffer.byteLength]), 0)
|
|
205
|
+
serializedMessage.set(addressBuffer, 1)
|
|
206
|
+
serializedMessage.set(data, addressBuffer.byteLength + 1)
|
|
207
|
+
return serializedMessage
|
|
208
|
+
}
|
package/src/network/state.ts
CHANGED
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
TriggerAreaResult,
|
|
29
29
|
ComponentDefinition
|
|
30
30
|
} from '@dcl/ecs'
|
|
31
|
-
import { LIVEKIT_MAX_SIZE } from '
|
|
31
|
+
import { LIVEKIT_MAX_SIZE } from '@dcl/ecs/dist/systems/crdt'
|
|
32
32
|
|
|
33
33
|
export const NOT_SYNC_COMPONENTS: ComponentDefinition<unknown>[] = [
|
|
34
34
|
VideoEvent,
|
|
@@ -79,9 +79,9 @@ export function engineToCrdt(engine: IEngine): Uint8Array[] {
|
|
|
79
79
|
if (!shouldSyncComponent(itComponentDefinition)) {
|
|
80
80
|
continue
|
|
81
81
|
}
|
|
82
|
-
|
|
83
82
|
itComponentDefinition.dumpCrdtStateToBuffer(crdtBuffer, (entity) => {
|
|
84
|
-
|
|
83
|
+
const isNetworkEntity = NetworkEntity.has(entity)
|
|
84
|
+
return isNetworkEntity
|
|
85
85
|
})
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -103,6 +103,7 @@ export function engineToCrdt(engine: IEngine): Uint8Array[] {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
// If the message itself is larger than the limit, we need to handle it specially
|
|
106
|
+
// For now, we'll skip it to prevent infinite loops
|
|
106
107
|
if (messageSize / 1024 > LIVEKIT_MAX_SIZE) {
|
|
107
108
|
console.error(
|
|
108
109
|
`Message too large (${messageSize} bytes), skipping component ${message.componentId} for entity ${message.entityId}`
|
package/src/observables.ts
CHANGED
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
} from '@dcl/ecs'
|
|
13
13
|
import { ManyEntityAction, SendBatchResponse, subscribe } from '~system/EngineApi'
|
|
14
14
|
import players from './players'
|
|
15
|
-
import { localeChangedEvents } from './internal/language-events'
|
|
16
15
|
|
|
17
16
|
/// --- EVENTS ---
|
|
18
17
|
|
|
@@ -50,11 +49,6 @@ export interface IEvents {
|
|
|
50
49
|
message: string
|
|
51
50
|
}
|
|
52
51
|
|
|
53
|
-
/** Triggered when the player switches the client language. `locale` is a BCP-47 tag */
|
|
54
|
-
localeChanged: {
|
|
55
|
-
locale: string
|
|
56
|
-
}
|
|
57
|
-
|
|
58
52
|
/**
|
|
59
53
|
* This is triggered once the scene should start.
|
|
60
54
|
*/
|
|
@@ -221,10 +215,6 @@ export async function pollEvents(sendBatch: (body: ManyEntityAction) => Promise<
|
|
|
221
215
|
onCommsMessage.notifyObservers(data as IEvents['comms'])
|
|
222
216
|
break
|
|
223
217
|
}
|
|
224
|
-
case 'localeChanged': {
|
|
225
|
-
localeChangedEvents.notifyObservers(data as IEvents['localeChanged'])
|
|
226
|
-
break
|
|
227
|
-
}
|
|
228
218
|
}
|
|
229
219
|
}
|
|
230
220
|
}
|
package/src/platform/index.ts
CHANGED
|
@@ -1,49 +1,12 @@
|
|
|
1
1
|
import { getExplorerInformation } from '~system/Runtime'
|
|
2
|
-
import { subscribe } from '~system/EngineApi'
|
|
3
|
-
import { Observable } from '../internal/Observable'
|
|
4
|
-
import { localeChangedEvents } from '../internal/language-events'
|
|
5
2
|
|
|
6
3
|
export type Platform = 'mobile' | 'desktop' | 'web'
|
|
7
4
|
|
|
8
5
|
const VALID_PLATFORMS: Set<string> = new Set<string>(['mobile', 'desktop', 'web'])
|
|
9
6
|
|
|
10
|
-
const DEFAULT_LANGUAGE = 'en'
|
|
11
|
-
const BCP47_TAG = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$/
|
|
12
|
-
|
|
13
7
|
let platform: Platform | null = null
|
|
14
|
-
let language: string = DEFAULT_LANGUAGE
|
|
15
|
-
let languageSetByEvent = false
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Triggered when the player switches the client language mid-session.
|
|
19
|
-
* Older clients never emit it.
|
|
20
|
-
* @public
|
|
21
|
-
*/
|
|
22
|
-
export const onPlayerLanguageChanged = new Observable<{ language: string }>()
|
|
23
|
-
|
|
24
|
-
function normalizeLocale(locale: string | undefined): string {
|
|
25
|
-
const tag = (locale ?? '').trim().replace(/_/g, '-')
|
|
26
|
-
return BCP47_TAG.test(tag) ? tag : DEFAULT_LANGUAGE
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function setLanguage(next: string) {
|
|
30
|
-
if (next === language) return
|
|
31
|
-
language = next
|
|
32
|
-
onPlayerLanguageChanged.notifyObservers({ language })
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
localeChangedEvents.add((event) => {
|
|
36
|
-
languageSetByEvent = true
|
|
37
|
-
setLanguage(normalizeLocale(event.locale))
|
|
38
|
-
})
|
|
39
|
-
subscribe({ eventId: 'localeChanged' }).catch(console.error)
|
|
40
|
-
|
|
41
8
|
void getExplorerInformation({})
|
|
42
9
|
.then((response) => {
|
|
43
|
-
// a mid-session change that arrived first is more recent than this response
|
|
44
|
-
if (!languageSetByEvent) {
|
|
45
|
-
setLanguage(normalizeLocale(response.configurations?.['locale']))
|
|
46
|
-
}
|
|
47
10
|
const normalized = response.platform.toLowerCase()
|
|
48
11
|
if (VALID_PLATFORMS.has(normalized)) {
|
|
49
12
|
platform = normalized as Platform
|
|
@@ -70,13 +33,3 @@ export function isDesktop(): boolean {
|
|
|
70
33
|
export function isWeb(): boolean {
|
|
71
34
|
return getPlatform() === 'web'
|
|
72
35
|
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Player's client language as a BCP-47 tag (e.g. `'es'`, `'pt-BR'`).
|
|
76
|
-
* Returns `'en'` until the explorer responds, and on clients that don't report a locale.
|
|
77
|
-
* Use `onPlayerLanguageChanged` to react to mid-session changes.
|
|
78
|
-
* @public
|
|
79
|
-
*/
|
|
80
|
-
export function getPlayerLanguage(): string {
|
|
81
|
-
return language
|
|
82
|
-
}
|
package/src/players/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ type GetPlayerDataRes = {
|
|
|
19
19
|
avatar?: PBAvatarBase
|
|
20
20
|
wearables: PBAvatarEquippedData['wearableUrns']
|
|
21
21
|
emotes: PBAvatarEquippedData['emoteUrns']
|
|
22
|
+
forceRender: PBAvatarEquippedData['forceRender']
|
|
22
23
|
position: TransformType['position'] | undefined
|
|
23
24
|
}
|
|
24
25
|
|
|
@@ -92,11 +93,9 @@ export function definePlayerHelper(engine: IEngine) {
|
|
|
92
93
|
isGuest: !!playerData?.isGuest,
|
|
93
94
|
userId: playerData?.address ?? '',
|
|
94
95
|
avatar: avatarData ?? undefined,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
wearables: [...(wearablesData?.wearableUrns ?? [])],
|
|
99
|
-
emotes: [...(wearablesData?.emoteUrns ?? [])],
|
|
96
|
+
wearables: wearablesData?.wearableUrns ?? [],
|
|
97
|
+
emotes: wearablesData?.emoteUrns ?? [],
|
|
98
|
+
forceRender: wearablesData?.forceRender ?? [],
|
|
100
99
|
position: Transform.getOrNull(userEntity)?.position
|
|
101
100
|
}
|
|
102
101
|
}
|
package/src/testing/runtime.ts
CHANGED
|
@@ -50,7 +50,6 @@ export function createTestRuntime(testingModule: TestingModule, engine: IEngine)
|
|
|
50
50
|
// continue to run until it reaches a yield point
|
|
51
51
|
function scheduleValue(value: any, env: RunnerEnvironment) {
|
|
52
52
|
if (value && typeof value === 'object' && typeof value.then === 'function') {
|
|
53
|
-
console.log('⏱️ yield promise')
|
|
54
53
|
// if the value is a promise, schedule it to be awaited after the current frame is finished
|
|
55
54
|
nextTickFuture.push(async () => {
|
|
56
55
|
try {
|
|
@@ -60,14 +59,12 @@ export function createTestRuntime(testingModule: TestingModule, engine: IEngine)
|
|
|
60
59
|
}
|
|
61
60
|
})
|
|
62
61
|
} else if (typeof value === 'function') {
|
|
63
|
-
console.log('⏱️ yield function')
|
|
64
62
|
// if the value is a function, schedule it to be called on the next frame
|
|
65
63
|
nextTickFuture.push(() => {
|
|
66
64
|
scheduleValue(value(), env)
|
|
67
65
|
})
|
|
68
66
|
return
|
|
69
67
|
} else if (typeof value === 'undefined' || value === null) {
|
|
70
|
-
console.log('⏱️ yield')
|
|
71
68
|
// if the value is undefined or null, continue processing the generator the next frame
|
|
72
69
|
nextTickFuture.push(() => {
|
|
73
70
|
consumeGenerator(env)
|
package/testing/runtime.js
CHANGED
|
@@ -33,7 +33,6 @@ export function createTestRuntime(testingModule, engine) {
|
|
|
33
33
|
// continue to run until it reaches a yield point
|
|
34
34
|
function scheduleValue(value, env) {
|
|
35
35
|
if (value && typeof value === 'object' && typeof value.then === 'function') {
|
|
36
|
-
console.log('⏱️ yield promise');
|
|
37
36
|
// if the value is a promise, schedule it to be awaited after the current frame is finished
|
|
38
37
|
nextTickFuture.push(async () => {
|
|
39
38
|
try {
|
|
@@ -45,7 +44,6 @@ export function createTestRuntime(testingModule, engine) {
|
|
|
45
44
|
});
|
|
46
45
|
}
|
|
47
46
|
else if (typeof value === 'function') {
|
|
48
|
-
console.log('⏱️ yield function');
|
|
49
47
|
// if the value is a function, schedule it to be called on the next frame
|
|
50
48
|
nextTickFuture.push(() => {
|
|
51
49
|
scheduleValue(value(), env);
|
|
@@ -53,7 +51,6 @@ export function createTestRuntime(testingModule, engine) {
|
|
|
53
51
|
return;
|
|
54
52
|
}
|
|
55
53
|
else if (typeof value === 'undefined' || value === null) {
|
|
56
|
-
console.log('⏱️ yield');
|
|
57
54
|
// if the value is undefined or null, continue processing the generator the next frame
|
|
58
55
|
nextTickFuture.push(() => {
|
|
59
56
|
consumeGenerator(env);
|
|
@@ -194,4 +191,4 @@ function globalFail(error) {
|
|
|
194
191
|
// for now, the failure is only writing to the console.error.
|
|
195
192
|
console.error(error);
|
|
196
193
|
}
|
|
197
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnVudGltZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy90ZXN0aW5nL3J1bnRpbWUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFFSCxPQUFPLEVBQVcsU0FBUyxFQUFFLE1BQU0sVUFBVSxDQUFBO0FBQzdDLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxVQUFVLENBQUE7QUFHdkMsaUZBQWlGO0FBQ2pGLG9JQUFvSTtBQUNwSSxnRkFBZ0Y7QUFDaEYsZUFBZTtBQUNmLE1BQU0sVUFBVSxpQkFBaUIsQ0FBQyxhQUE0QixFQUFFLE1BQWU7SUFTN0Usc0RBQXNEO0lBQ3RELElBQUksYUFBYSxHQUFHLEtBQUssQ0FBQTtJQUV6QixJQUFJLG1CQUFtQixHQUFHLENBQUMsQ0FBQTtJQUMzQixJQUFJLGdCQUFnQixHQUFHLENBQUMsQ0FBQTtJQUV4QixvQ0FBb0M7SUFDcEMsTUFBTSxjQUFjLEdBQW9CLEVBQUUsQ0FBQTtJQUUxQyxpR0FBaUc7SUFDakcsTUFBTSxjQUFjLEdBQWdDLEVBQUUsQ0FBQTtJQUV0RCxrRUFBa0U7SUFDbEUsS0FBSyxVQUFVLFFBQVE7UUFDckIsT0FBTyxJQUFJLE9BQU8sQ0FBUyxDQUFDLE9BQU8sRUFBRSxFQUFFO1lBQ3JDLGNBQWMsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUE7UUFDOUIsQ0FBQyxDQUFDLENBQUE7SUFDSixDQUFDO0lBRUQsc0ZBQXNGO0lBQ3RGLE1BQU0sQ0FBQyxTQUFTLENBQUMsU0FBUywrQkFBK0IsQ0FBQyxFQUFFO1FBQzFELG1CQUFtQixFQUFFLENBQUE7UUFDckIsZ0JBQWdCLElBQUksRUFBRSxDQUFBO1FBQ3RCLGdDQUFnQztRQUNoQyxjQUFjLENBQUMsTUFBTSxDQUFDLENBQUMsRUFBRSxjQUFjLENBQUMsTUFBTSxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQTtJ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SUFDNUIsNkRBQTZEO0lBQzdELE9BQU8sQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUE7QUFDdEIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogVGhpcyBtb2R1bGUgcHJvdmlkZXMgYSBjcmVhdGVUZXN0UnVudGltZSBmdW5jdGlvbiB0aGF0IHJldHVybnMgYW4gb2JqZWN0IHdpdGggYSB0ZXN0IGZ1bmN0aW9uIHRoYXQgY2FuIGJlIHVzZWQgdG8gZGVmaW5lIHRlc3RzLlxuICovXG5cbmltcG9ydCB7IElFbmdpbmUsIFRyYW5zZm9ybSB9IGZyb20gJ0BkY2wvZWNzJ1xuaW1wb3J0IHsgYXNzZXJ0RXF1YWxzIH0gZnJvbSAnLi9hc3NlcnQnXG5pbXBvcnQgdHlwZSB7IFRlc3RpbmdNb2R1bGUsIFRlc3RGdW5jdGlvbiwgVGVzdEhlbHBlcnMgfSBmcm9tICcuL3R5cGVzJ1xuXG4vLyBUaGlzIGZ1bmN0aW9uIGNyZWF0ZXMgYSB0ZXN0IHJ1bnRpbWUgdGhhdCBjYW4gYmUgdXNlZCB0byBkZWZpbmUgYW5kIHJ1biB0ZXN0cy5cbi8vIEl0IHRha2VzIGEgYFRlc3RpbmdNb2R1bGVgIGluc3RhbmNlIChsb2FkZWQgZnJvbSByZXF1aXJlKCd+c3lzdGVtL1Rlc3RpbmcnKSkgYW5kIGFuIGBJRW5naW5lYCBpbnN0YW5jZSAoZnJvbSBEZWNlbnRyYWxhbmQncyBTREspLlxuLy8gSXQgcmV0dXJucyBhbiBvYmplY3Qgd2l0aCBhIGB0ZXN0YCBmdW5jdGlvbiB0aGF0IGNhbiBiZSB1c2VkIHRvIGRlZmluZSB0ZXN0cy5cbi8qIEBfX1BVUkVfXyAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZVRlc3RSdW50aW1lKHRlc3RpbmdNb2R1bGU6IFRlc3RpbmdNb2R1bGUsIGVuZ2luZTogSUVuZ2luZSkge1xuICB0eXBlIFRlc3RQbGFuRW50cnkgPSB7IG5hbWU6IHN0cmluZzsgZm46IFRlc3RGdW5jdGlvbiB9XG4gIHR5cGUgUnVubmVyRW52aXJvbm1lbnQgPSB7XG4gICAgcmVzb2x2ZTogKCkgPT4gdm9pZFxuICAgIHJlamVjdDogKGVycm9yOiBhbnkpID0+IHZvaWRcbiAgICBoZWxwZXJzOiBUZXN0SGVscGVyc1xuICAgIGdlbmVyYXRvcjogR2VuZXJhdG9yXG4gIH1cblxuICAvLyB0aGlzIGZsYWcgZW5zdXJlcyBubyB0ZXN0cyBhcmUgYWRkZWQgYXN5bmNocm9ub3VzbHlcbiAgbGV0IHJ1bnRpbWVGcm96ZW4gPSBmYWxzZVxuXG4gIGxldCBjdXJyZW50RnJhbWVDb3VudGVyID0gMFxuICBsZXQgY3VycmVudEZyYW1lVGltZSA9IDBcblxuICAvLyBhcnJheSB0byBob2xkIHRoZSBzY2hlZHVsZWQgdGVzdHNcbiAgY29uc3Qgc2NoZWR1bGVkVGVzdHM6IFRlc3RQbGFuRW50cnlbXSA9IFtdXG5cbiAgLy8gYW4gYXJyYXkgb2YgcHJvbWlzZXMgdGhhdCBhcmUgcmVzb2x2ZWQgb24gdGhlIG5leHQgZnJhbWUgKGFmdGVyIHRoZSBjdXJyZW50IGZyYW1lIGlzIGZpbmlzaGVkKVxuICBjb25zdCBuZXh0VGlja0Z1dHVyZTogQXJyYXk8KGR0OiBudW1iZXIpID0+IHZvaWQ+ID0gW11cblxuICAvLyB0aGlzIGZ1bmN0aW9uIHJldHVybnMgYSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgb24gdGhlIG5leHQgZnJhbWVcbiAgYXN5bmMgZnVuY3Rpb24gbmV4dFRpY2soKSB7XG4gICAgcmV0dXJuIG5ldyBQcm9taXNlPG51bWJlcj4oKHJlc29sdmUpID0+IHtcbiAgICAgIG5leHRUaWNrRnV0dXJlLnB1c2gocmVzb2x2ZSlcbiAgICB9KVxuICB9XG5cbiAgLy8gYWRkIGEgc3lzdGVtIHRvIHRoZSBlbmdpbmUgdGhhdCByZXNvbHZlcyBhbGwgcHJvbWlzZXMgaW4gdGhlIGBuZXh0VGlja0Z1dHVyZWAgYXJyYXlcbiAgZW5naW5lLmFkZFN5c3RlbShmdW5jdGlvbiBUZXN0aW5nRnJhbWV3b3JrQ29yb3V0aW5lUnVubmVyKGR0KSB7XG4gICAgY3VycmVudEZyYW1lQ291bnRlcisrXG4gICAgY3VycmVudEZyYW1lVGltZSArPSBkdFxuICAgIC8vIHJlc29sdmUgYWxsIG5leHRUaWNrIGZ1dHVyZXMuXG4gICAgbmV4dFRpY2tGdXR1cmUuc3BsaWNlKDAsIG5leHRUaWNrRnV0dXJlLmxlbmd0aCkuZm9yRWFjaCgoXykgPT4gXyhkdCkpXG4gIH0pXG5cbiAgLy8gdGhpcyBmdW5jdGlvbiBzY2hlZHVsZXMgYSB2YWx1ZSB0byBiZSBwcm9jZXNzZWQgb24gdGhlIG5leHQgZnJhbWUsIHRoZSB0ZXN0IHJ1bm5lciB3aWxsXG4gIC8vIGNvbnRpbnVlIHRvIHJ1biB1bnRpbCBpdCByZWFjaGVzIGEgeWllbGQgcG9pbnRcbiAgZnVuY3Rpb24gc2NoZWR1bGVWYWx1ZSh2YWx1ZTogYW55LCBlbnY6IFJ1bm5lckVudmlyb25tZW50KSB7XG4gICAgaWYgKHZhbHVlICYmIHR5cGVvZiB2YWx1ZSA9PT0gJ29iamVjdCcgJiYgdHlwZW9mIHZhbHVlLnRoZW4gPT09ICdmdW5jdGlvbicpIHtcbiAgICAgIGNvbnNvbGUubG9nKCfij7HvuI8geWllbGQgcHJvbWlzZScpXG4gICAgICAvLyBpZiB0aGUgdmFsdWUgaXMgYSBwcm9taXNlLCBzY2hlZHVsZSBpdCB0byBiZSBhd2FpdGVkIGFmdGVyIHRoZSBjdXJyZW50IGZyYW1lIGlzIGZpbmlzaGVkXG4gICAgICBuZXh0VGlja0Z1dHVyZS5wdXNoKGFzeW5jICgpID0+IHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICBzY2hlZHVsZVZhbHVlKGF3YWl0IHZhbHVlLCBlbnYpXG4gICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgIGVudi5yZWplY3QoZXJyKVxuICAgICAgICB9XG4gICAgICB9KVxuICAgIH0gZWxzZSBpZiAodHlwZW9mIHZhbHVlID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICBjb25zb2xlLmxvZygn4o+x77iPIHlpZWxkIGZ1bmN0aW9uJylcbiAgICAgIC8vIGlmIHRoZSB2YWx1ZSBpcyBhIGZ1bmN0aW9uLCBzY2hlZHVsZSBpdCB0byBiZSBjYWxsZWQgb24gdGhlIG5leHQgZnJhbWVcbiAgICAgIG5leHRUaWNrRnV0dXJlLnB1c2goKCkgPT4ge1xuICAgICAgICBzY2hlZHVsZVZhbHVlKHZhbHVlKCksIGVudilcbiAgICAgIH0pXG4gICAgICByZXR1cm5cbiAgICB9IGVsc2UgaWYgKHR5cGVvZiB2YWx1ZSA9PT0gJ3VuZGVmaW5lZCcgfHwgdmFsdWUgPT09IG51bGwpIHtcbiAgICAgIGNvbnNvbGUubG9nKCfij7HvuI8geWllbGQnKVxuICAgICAgLy8gaWYgdGhlIHZhbHVlIGlzIHVuZGVmaW5lZCBvciBudWxsLCBjb250aW51ZSBwcm9jZXNzaW5nIHRoZSBnZW5lcmF0b3IgdGhlIG5leHQgZnJhbWVcbiAgICAgIG5leHRUaWNrRnV0dXJlLnB1c2goKCkgPT4ge1xuICAgICAgICBjb25zdW1lR2VuZXJhdG9yKGVudilcbiAgICAgIH0pXG4gICAgfSBlbHNlIHRocm93IG5ldyBFcnJvcihgVW5leHBlY3RlZCB2YWx1ZSBmcm9tIHRlc3QgZ2VuZXJhdG9yOiAke3ZhbHVlfWApXG4gIH1cblxuICAvLyB0aGlzIGZ1bmN0aW9uIHByb2Nlc3NlcyBhIGdlbmVyYXRvciBmdW5jdGlvbiBieSBzY2hlZHVsaW5nIGl0cyB2YWx1ZXMgdG8gYmUgcHJvY2Vzc2VkIG9uIHRoZSBuZXh0IGZyYW1lXG4gIGZ1bmN0aW9uIGNvbnN1bWVHZW5lcmF0b3IoZW52OiBSdW5uZXJFbnZpcm9ubWVudCkge1xuICAgIHRyeSB7XG4gICAgICBjb25zdCByZXQgPSBlbnYuZ2VuZXJhdG9yLm5leHQoKVxuICAgICAgaWYgKCFyZXQuZG9uZSkge1xuICAgICAgICBzY2hlZHVsZVZhbHVlKHJldC52YWx1ZSwgZW52KVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgZW52LnJlc29sdmUoKVxuICAgICAgfVxuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgZW52LnJlamVjdChlcnIpXG4gICAgfVxuICB9XG5cbiAgLy8gdGhpcyBmdW5jdGlvbiBzY2hlZHVsZXMgYSB0ZXN0IHJ1biBvbiB0aGUgbmV4dCBmcmFtZVxuICBmdW5jdGlvbiBzY2hlZHVsZU5leHRSdW4oKSB7XG4gICAgaWYgKHNjaGVkdWxlZFRlc3RzLmxlbmd0aCkge1xuICAgICAgbmV4dFRpY2tGdXR1cmUucHVzaChydW5UZXN0cylcbiAgICB9XG4gIH1cblxuICAvLyB0aGlzIGZ1bmN0aW9uIHJ1bnMgdGhlIHNjaGVkdWxlZCB0ZXN0c1xuICBmdW5jdGlvbiBydW5UZXN0cygpIHtcbiAgICBpZiAoc2NoZWR1bGVkVGVzdHMubGVuZ3RoKSB7XG4gICAgICBjb25zdCBlbnRyeSA9IHNjaGVkdWxlZFRlc3RzLnNoaWZ0KCkhXG4gICAgICBjb25zdCBpbml0aWFsRnJhbWUgPSBjdXJyZW50RnJhbWVDb3VudGVyXG4gICAgICBjb25zdCBzdGFydFRpbWUgPSBjdXJyZW50RnJhbWVUaW1lXG5cbiAgICAgIGxldCByZXNvbHZlZCA9IGZhbHNlXG5cbiAgICAgIC8vIHRoaXMgZnVuY3Rpb24gc2hvdWxkIGJlIGNhbGxlZCBvbmx5IG9uY2UuIGl0IG1ha2VzIHRoZSBjdXJyZW50IHRlc3QgcGFzc1xuICAgICAgY29uc3QgcmVzb2x2ZSA9ICgpID0+IHtcbiAgICAgICAgaWYgKHJlc29sdmVkKSB0aHJvdyBuZXcgRXJyb3IoJ3Jlc29sdmVkIHR3aWNlJylcbiAgICAgICAgcmVzb2x2ZWQgPSB0cnVlXG5cbiAgICAgICAgY29uc29sZS5sb2coYPCfn6IgVGVzdCBwYXNzZWQgJHtlbnRyeS5uYW1lfWApXG5cbiAgICAgICAgdGVzdGluZ01vZHVsZVxuICAgICAgICAgIC5sb2dUZXN0UmVzdWx0KHtcbiAgICAgICAgICAgIG5hbWU6IGVudHJ5Lm5hbWUsXG4gICAgICAgICAgICBvazogdHJ1ZSxcbiAgICAgICAgICAgIHRvdGFsRnJhbWVzOiBjdXJyZW50RnJhbWVDb3VudGVyIC0gaW5pdGlhbEZyYW1lLFxuICAgICAgICAgICAgdG90YWxUaW1lOiBjdXJyZW50RnJhbWVUaW1lIC0gc3RhcnRUaW1lXG4gICAgICAgICAgfSlcbiAgICAgICAgICAuZmluYWxseShzY2hlZHVsZU5leHRSdW4pXG4gICAgICB9XG5cbiAgICAgIGNvbnN0IHJlamVjdCA9IChlcnI6IGFueSkgPT4ge1xuICAgICAgICBpZiAocmVzb2x2ZWQpIHRocm93IG5ldyBFcnJvcigncmVzb2x2ZWQgdHdpY2UnKVxuICAgICAgICByZXNvbHZlZCA9IHRydWVcblxuICAgICAgICBjb25zb2xlLmxvZyhg8J+UtCBUZXN0IGZhaWxlZCAke2VudHJ5Lm5hbWV9YClcbiAgICAgICAgY29uc29sZS5lcnJvcihlcnIpXG5cbiAgICAgICAgdGVzdGluZ01vZHVsZVxuICAgICAgICAgIC5sb2dUZXN0UmVzdWx0KHtcbiAgICAgICAgICAgIG5hbWU6IGVudHJ5Lm5hbWUsXG4gICAgICAgICAgICBvazogZmFsc2UsXG4gICAgICAgICAgICBlcnJvcjogZXJyLnRvU3RyaW5nKCksXG4gICAgICAgICAgICBzdGFjazogZXJyICYmIHR5cGVvZiBlcnIgPT09ICdvYmplY3QnICYmIGVyci5zdGFjayxcbiAgICAgICAgICAgIHRvdGFsRnJhbWVzOiBjdXJyZW50RnJhbWVDb3VudGVyIC0gaW5pdGlhbEZyYW1lLFxuICAgICAgICAgICAgdG90YWxUaW1lOiBjdXJyZW50RnJhbWVUaW1lIC0gc3RhcnRUaW1lXG4gICAgICAgICAgfSlcbiAgICAgICAgICAuZmluYWxseShzY2hlZHVsZU5leHRSdW4pXG4gICAgICB9XG5cbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnNvbGUubG9nKGDwn6eqIFJ1bm5pbmcgdGVzdCAke2VudHJ5Lm5hbWV9YClcblxuICAgICAgICBjb25zdCB0ZXN0SGVscGVyczogVGVzdEhlbHBlcnMgPSB7XG4gICAgICAgICAgYXN5bmMgc2V0Q2FtZXJhVHJhbnNmb3JtKHRyYW5zZm9ybSkge1xuICAgICAgICAgICAgYXdhaXQgdGVzdGluZ01vZHVsZS5zZXRDYW1lcmFUcmFuc2Zvcm0odHJhbnNmb3JtKVxuICAgICAgICAgICAgYXdhaXQgbmV4dFRpY2soKVxuXG4gICAgICAgICAgICBjb25zdCBUcmFuc2Zvcm1Db21wb25lbnQgPSBlbmdpbmUuZ2V0Q29tcG9uZW50KFRyYW5zZm9ybS5jb21wb25lbnRJZCkgYXMgdHlwZW9mIFRyYW5zZm9ybVxuICAgICAgICAgICAgY29uc3QgYWN0dWFsVHJhbnNmb3JtID0gVHJhbnNmb3JtQ29tcG9uZW50LmdldChlbmdpbmUuQ2FtZXJhRW50aXR5KVxuXG4gICAgICAgICAgICBhc3NlcnRFcXVhbHMoYWN0dWFsVHJhbnNmb3JtLnBvc2l0aW9uLCB0cmFuc2Zvcm0ucG9zaXRpb24sIFwicG9zaXRpb25zIGRvbid0IG1hdGNoXCIpXG4gICAgICAgICAgICBhc3NlcnRFcXVhbHMoYWN0dWFsVHJhbnNmb3JtLnJvdGF0aW9uLCB0cmFuc2Zvcm0ucm90YXRpb24sIFwicm90YXRpb25zIGRvbid0IG1hdGNoXCIpXG4gICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgY29uc3QgcmV0dXJuVmFsdWUgPSBlbnRyeS5mbih0ZXN0SGVscGVycylcblxuICAgICAgICBpZiAocmV0dXJuVmFsdWUgJiYgdHlwZW9mIHJldHVyblZhbHVlID09PSAnb2JqZWN0Jykge1xuICAgICAgICAgIGlmIChpc0dlbmVyYXRvcihyZXR1cm5WYWx1ZSkpIHtcbiAgICAgICAgICAgIGNvbnN0IGVudjogUnVubmVyRW52aXJvbm1lbnQgPSB7XG4gICAgICAgICAgICAgIGdlbmVyYXRvcjogcmV0dXJuVmFsdWUsXG4gICAgICAgICAgICAgIGhlbHBlcnM6IHRlc3RIZWxwZXJzLFxuICAgICAgICAgICAgICByZXNvbHZlLFxuICAgICAgICAgICAgICByZWplY3RcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGNvbnN1bWVHZW5lcmF0b3IoZW52KVxuICAgICAgICAgIH0gZWxzZSBpZiAoaXNQcm9taXNlKHJldHVyblZhbHVlKSkge1xuICAgICAgICAgICAgcmV0dXJuVmFsdWUudGhlbihyZXNvbHZlKS5jYXRjaChyZWplY3QpXG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgVW5rbm93biB0ZXN0IHJlc3VsdCB0eXBlOiAke3JldHVyblZhbHVlfWApXG4gICAgICAgICAgfVxuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHJlc29sdmUoKVxuICAgICAgICB9XG4gICAgICB9IGNhdGNoIChlcnI6IGFueSkge1xuICAgICAgICByZWplY3QoZXJyKVxuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8vIHNjaGVkdWxlIHRoZSB0ZXN0IHJ1bm5lciBzdGFydCBmb3IgdGhlIG5leHQgZnJhbWVcbiAgbmV4dFRpY2tGdXR1cmUucHVzaCgoKSA9PiB7XG4gICAgLy8gb25jZSB3ZSBydW4gdGhlIG5leHQgdGljaywgdGhlIHRlc3QgcnVudGltZSBiZWNvbWVzIGZyb3plbi4gdGhhdCBtZWFucyBubyBuZXdcbiAgICAvLyB0ZXN0IGRlZmluaXRpb25zIGFyZSBhY2NlcHRlZFxuICAgIHJ1bnRpbWVGcm96ZW4gPSB0cnVlXG5cbiAgICBpZiAoIXNjaGVkdWxlZFRlc3RzLmxlbmd0aCkgcmV0dXJuXG5cbiAgICAvLyBpbmZvcm0gdGhlIHRlc3QgcnVubmVyIGFib3V0IHRoZSBwbGFucyBmb3IgdGhpcyB0ZXN0IHJ1blxuICAgIHRlc3RpbmdNb2R1bGUucGxhbih7IHRlc3RzOiBzY2hlZHVsZWRUZXN0cyB9KS50aGVuKHNjaGVkdWxlTmV4dFJ1bikuY2F0Y2goZ2xvYmFsRmFpbClcbiAgfSlcblxuICAvLyB0aGlzIGlzIHRoZSBmdW5jdGlvbiB0aGF0IGlzIHVzZWQgdG8gcGxhbiBhIHRlc3QgZnVuY3Rpb25uXG4gIC8qIEBfX1BVUkVfXyAqL1xuICBmdW5jdGlvbiB0ZXN0KG5hbWU6IHN0cmluZywgZm46IFRlc3RGdW5jdGlvbikge1xuICAgIGlmIChydW50aW1lRnJvemVuKSB0aHJvdyBuZXcgRXJyb3IoXCJOZXcgdGVzdHMgY2FuJ3QgYmUgYWRkZWQgYXQgdGhpcyBzdGFnZS5cIilcblxuICAgIGlmIChzY2hlZHVsZWRUZXN0cy5zb21lKCgkKSA9PiAkLm5hbWUgPT09IG5hbWUpKSB0aHJvdyBuZXcgRXJyb3IoYFRlc3Qgd2l0aCBuYW1lICR7bmFtZX0gYWxyZWFkeSBleGlzdHNgKVxuXG4gICAgc2NoZWR1bGVkVGVzdHMucHVzaCh7IGZuLCBuYW1lIH0pXG4gIH1cblxuICByZXR1cm4ge1xuICAgIHRlc3RcbiAgfVxufVxuXG5mdW5jdGlvbiBpc0dlbmVyYXRvcih0OiBhbnkpOiB0IGlzIEdlbmVyYXRvciB7XG4gIHJldHVybiB0ICYmIHR5cGVvZiB0ID09PSAnb2JqZWN0JyAmJiB0eXBlb2YgdFtTeW1ib2wuaXRlcmF0b3JdID09PSAnZnVuY3Rpb24nXG59XG5cbmZ1bmN0aW9uIGlzUHJvbWlzZSh0OiBhbnkpOiB0IGlzIFByb21pc2U8dW5rbm93bj4ge1xuICByZXR1cm4gdCAmJiB0eXBlb2YgdCA9PT0gJ29iamVjdCcgJiYgdHlwZW9mIHQudGhlbiA9PT0gJ2Z1bmN0aW9uJ1xufVxuXG5mdW5jdGlvbiBnbG9iYWxGYWlsKGVycm9yOiBhbnkpIHtcbiAgLy8gZm9yIG5vdywgdGhlIGZhaWx1cmUgaXMgb25seSB3cml0aW5nIHRvIHRoZSBjb25zb2xlLmVycm9yLlxuICBjb25zb2xlLmVycm9yKGVycm9yKVxufVxuIl19
|
|
194
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnVudGltZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy90ZXN0aW5nL3J1bnRpbWUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFFSCxPQUFPLEVBQVcsU0FBUyxFQUFFLE1BQU0sVUFBVSxDQUFBO0FBQzdDLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxVQUFVLENBQUE7QUFHdkMsaUZBQWlGO0FBQ2pGLG9JQUFvSTtBQUNwSSxnRkFBZ0Y7QUFDaEYsZUFBZTtBQUNmLE1BQU0sVUFBVSxpQkFBaUIsQ0FBQyxhQUE0QixFQUFFLE1BQWU7SUFTN0Usc0RBQXNEO0lBQ3RELElBQUksYUFBYSxHQUFHLEtBQUssQ0FBQTtJQUV6QixJQUFJLG1CQUFtQixHQUFHLENBQUMsQ0FBQTtJQUMzQixJQUFJLGdCQUFnQixHQUFHLENBQUMsQ0FBQTtJQUV4QixvQ0FBb0M7SUFDcEMsTUFBTSxjQUFjLEdBQW9CLEVBQUUsQ0FBQTtJQUUxQyxpR0FBaUc7SUFDakcsTUFBTSxjQUFjLEdBQWdDLEVBQUUsQ0FBQTtJQUV0RCxrRUFBa0U7SUFDbEUsS0FBSyxVQUFVLFFBQVE7UUFDckIsT0FBTyxJQUFJLE9BQU8sQ0FBUyxDQUFDLE9BQU8sRUFBRSxFQUFFO1lBQ3JDLGNBQWMsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUE7UUFDOUIsQ0FBQyxDQUFDLENBQUE7SUFDSixDQUFDO0lBRUQsc0ZBQXNGO0lBQ3RGLE1BQU0sQ0FBQyxTQUFTLENBQUMsU0FBUywrQkFBK0IsQ0FBQyxFQUFFO1FBQzFELG1CQUFtQixFQUFFLENBQUE7UUFDckIsZ0JBQWdCLElBQUksRUFBRSxDQUFBO1FBQ3RCLGdDQUFnQztRQUNoQyxjQUFjLENBQUMsTUFBTSxDQUFDLENBQUMsRUFBRSxjQUFjLENBQUMsTUFBTSxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQTtJ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dW50aW1lIGZ1bmN0aW9uIHRoYXQgcmV0dXJucyBhbiBvYmplY3Qgd2l0aCBhIHRlc3QgZnVuY3Rpb24gdGhhdCBjYW4gYmUgdXNlZCB0byBkZWZpbmUgdGVzdHMuXG4gKi9cblxuaW1wb3J0IHsgSUVuZ2luZSwgVHJhbnNmb3JtIH0gZnJvbSAnQGRjbC9lY3MnXG5pbXBvcnQgeyBhc3NlcnRFcXVhbHMgfSBmcm9tICcuL2Fzc2VydCdcbmltcG9ydCB0eXBlIHsgVGVzdGluZ01vZHVsZSwgVGVzdEZ1bmN0aW9uLCBUZXN0SGVscGVycyB9IGZyb20gJy4vdHlwZXMnXG5cbi8vIFRoaXMgZnVuY3Rpb24gY3JlYXRlcyBhIHRlc3QgcnVudGltZSB0aGF0IGNhbiBiZSB1c2VkIHRvIGRlZmluZSBhbmQgcnVuIHRlc3RzLlxuLy8gSXQgdGFrZXMgYSBgVGVzdGluZ01vZHVsZWAgaW5zdGFuY2UgKGxvYWRlZCBmcm9tIHJlcXVpcmUoJ35zeXN0ZW0vVGVzdGluZycpKSBhbmQgYW4gYElFbmdpbmVgIGluc3RhbmNlIChmcm9tIERlY2VudHJhbGFuZCdzIFNESykuXG4vLyBJdCByZXR1cm5zIGFuIG9iamVjdCB3aXRoIGEgYHRlc3RgIGZ1bmN0aW9uIHRoYXQgY2FuIGJlIHVzZWQgdG8gZGVmaW5lIHRlc3RzLlxuLyogQF9fUFVSRV9fICovXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlVGVzdFJ1bnRpbWUodGVzdGluZ01vZHVsZTogVGVzdGluZ01vZHVsZSwgZW5naW5lOiBJRW5naW5lKSB7XG4gIHR5cGUgVGVzdFBsYW5FbnRyeSA9IHsgbmFtZTogc3RyaW5nOyBmbjogVGVzdEZ1bmN0aW9uIH1cbiAgdHlwZSBSdW5uZXJFbnZpcm9ubWVudCA9IHtcbiAgICByZXNvbHZlOiAoKSA9PiB2b2lkXG4gICAgcmVqZWN0OiAoZXJyb3I6IGFueSkgPT4gdm9pZFxuICAgIGhlbHBlcnM6IFRlc3RIZWxwZXJzXG4gICAgZ2VuZXJhdG9yOiBHZW5lcmF0b3JcbiAgfVxuXG4gIC8vIHRoaXMgZmxhZyBlbnN1cmVzIG5vIHRlc3RzIGFyZSBhZGRlZCBhc3luY2hyb25vdXNseVxuICBsZXQgcnVudGltZUZyb3plbiA9IGZhbHNlXG5cbiAgbGV0IGN1cnJlbnRGcmFtZUNvdW50ZXIgPSAwXG4gIGxldCBjdXJyZW50RnJhbWVUaW1lID0gMFxuXG4gIC8vIGFycmF5IHRvIGhvbGQgdGhlIHNjaGVkdWxlZCB0ZXN0c1xuICBjb25zdCBzY2hlZHVsZWRUZXN0czogVGVzdFBsYW5FbnRyeVtdID0gW11cblxuICAvLyBhbiBhcnJheSBvZiBwcm9taXNlcyB0aGF0IGFyZSByZXNvbHZlZCBvbiB0aGUgbmV4dCBmcmFtZSAoYWZ0ZXIgdGhlIGN1cnJlbnQgZnJhbWUgaXMgZmluaXNoZWQpXG4gIGNvbnN0IG5leHRUaWNrRnV0dXJlOiBBcnJheTwoZHQ6IG51bWJlcikgPT4gdm9pZD4gPSBbXVxuXG4gIC8vIHRoaXMgZnVuY3Rpb24gcmV0dXJucyBhIHByb21pc2UgdGhhdCByZXNvbHZlcyBvbiB0aGUgbmV4dCBmcmFtZVxuICBhc3luYyBmdW5jdGlvbiBuZXh0VGljaygpIHtcbiAgICByZXR1cm4gbmV3IFByb21pc2U8bnVtYmVyPigocmVzb2x2ZSkgPT4ge1xuICAgICAgbmV4dFRpY2tGdXR1cmUucHVzaChyZXNvbHZlKVxuICAgIH0pXG4gIH1cblxuICAvLyBhZGQgYSBzeXN0ZW0gdG8gdGhlIGVuZ2luZSB0aGF0IHJlc29sdmVzIGFsbCBwcm9taXNlcyBpbiB0aGUgYG5leHRUaWNrRnV0dXJlYCBhcnJheVxuICBlbmdpbmUuYWRkU3lzdGVtKGZ1bmN0aW9uIFRlc3RpbmdGcmFtZXdvcmtDb3JvdXRpbmVSdW5uZXIoZHQpIHtcbiAgICBjdXJyZW50RnJhbWVDb3VudGVyKytcbiAgICBjdXJyZW50RnJhbWVUaW1lICs9IGR0XG4gICAgLy8gcmVzb2x2ZSBhbGwgbmV4dFRpY2sgZnV0dXJlcy5cbiAgICBuZXh0VGlja0Z1dHVyZS5zcGxpY2UoMCwgbmV4dFRpY2tGdXR1cmUubGVuZ3RoKS5mb3JFYWNoKChfKSA9PiBfKGR0KSlcbiAgfSlcblxuICAvLyB0aGlzIGZ1bmN0aW9uIHNjaGVkdWxlcyBhIHZhbHVlIHRvIGJlIHByb2Nlc3NlZCBvbiB0aGUgbmV4dCBmcmFtZSwgdGhlIHRlc3QgcnVubmVyIHdpbGxcbiAgLy8gY29udGludWUgdG8gcnVuIHVudGlsIGl0IHJlYWNoZXMgYSB5aWVsZCBwb2ludFxuICBmdW5jdGlvbiBzY2hlZHVsZVZhbHVlKHZhbHVlOiBhbnksIGVudjogUnVubmVyRW52aXJvbm1lbnQpIHtcbiAgICBpZiAodmFsdWUgJiYgdHlwZW9mIHZhbHVlID09PSAnb2JqZWN0JyAmJiB0eXBlb2YgdmFsdWUudGhlbiA9PT0gJ2Z1bmN0aW9uJykge1xuICAgICAgLy8gaWYgdGhlIHZhbHVlIGlzIGEgcHJvbWlzZSwgc2NoZWR1bGUgaXQgdG8gYmUgYXdhaXRlZCBhZnRlciB0aGUgY3VycmVudCBmcmFtZSBpcyBmaW5pc2hlZFxuICAgICAgbmV4dFRpY2tGdXR1cmUucHVzaChhc3luYyAoKSA9PiB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgc2NoZWR1bGVWYWx1ZShhd2FpdCB2YWx1ZSwgZW52KVxuICAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgICBlbnYucmVqZWN0KGVycilcbiAgICAgICAgfVxuICAgICAgfSlcbiAgICB9IGVsc2UgaWYgKHR5cGVvZiB2YWx1ZSA9PT0gJ2Z1bmN0aW9uJykge1xuICAgICAgLy8gaWYgdGhlIHZhbHVlIGlzIGEgZnVuY3Rpb24sIHNjaGVkdWxlIGl0IHRvIGJlIGNhbGxlZCBvbiB0aGUgbmV4dCBmcmFtZVxuICAgICAgbmV4dFRpY2tGdXR1cmUucHVzaCgoKSA9PiB7XG4gICAgICAgIHNjaGVkdWxlVmFsdWUodmFsdWUoKSwgZW52KVxuICAgICAgfSlcbiAgICAgIHJldHVyblxuICAgIH0gZWxzZSBpZiAodHlwZW9mIHZhbHVlID09PSAndW5kZWZpbmVkJyB8fCB2YWx1ZSA9PT0gbnVsbCkge1xuICAgICAgLy8gaWYgdGhlIHZhbHVlIGlzIHVuZGVmaW5lZCBvciBudWxsLCBjb250aW51ZSBwcm9jZXNzaW5nIHRoZSBnZW5lcmF0b3IgdGhlIG5leHQgZnJhbWVcbiAgICAgIG5leHRUaWNrRnV0dXJlLnB1c2goKCkgPT4ge1xuICAgICAgICBjb25zdW1lR2VuZXJhdG9yKGVudilcbiAgICAgIH0pXG4gICAgfSBlbHNlIHRocm93IG5ldyBFcnJvcihgVW5leHBlY3RlZCB2YWx1ZSBmcm9tIHRlc3QgZ2VuZXJhdG9yOiAke3ZhbHVlfWApXG4gIH1cblxuICAvLyB0aGlzIGZ1bmN0aW9uIHByb2Nlc3NlcyBhIGdlbmVyYXRvciBmdW5jdGlvbiBieSBzY2hlZHVsaW5nIGl0cyB2YWx1ZXMgdG8gYmUgcHJvY2Vzc2VkIG9uIHRoZSBuZXh0IGZyYW1lXG4gIGZ1bmN0aW9uIGNvbnN1bWVHZW5lcmF0b3IoZW52OiBSdW5uZXJFbnZpcm9ubWVudCkge1xuICAgIHRyeSB7XG4gICAgICBjb25zdCByZXQgPSBlbnYuZ2VuZXJhdG9yLm5leHQoKVxuICAgICAgaWYgKCFyZXQuZG9uZSkge1xuICAgICAgICBzY2hlZHVsZVZhbHVlKHJldC52YWx1ZSwgZW52KVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgZW52LnJlc29sdmUoKVxuICAgICAgfVxuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgZW52LnJlamVjdChlcnIpXG4gICAgfVxuICB9XG5cbiAgLy8gdGhpcyBmdW5jdGlvbiBzY2hlZHVsZXMgYSB0ZXN0IHJ1biBvbiB0aGUgbmV4dCBmcmFtZVxuICBmdW5jdGlvbiBzY2hlZHVsZU5leHRSdW4oKSB7XG4gICAgaWYgKHNjaGVkdWxlZFRlc3RzLmxlbmd0aCkge1xuICAgICAgbmV4dFRpY2tGdXR1cmUucHVzaChydW5UZXN0cylcbiAgICB9XG4gIH1cblxuICAvLyB0aGlzIGZ1bmN0aW9uIHJ1bnMgdGhlIHNjaGVkdWxlZCB0ZXN0c1xuICBmdW5jdGlvbiBydW5UZXN0cygpIHtcbiAgICBpZiAoc2NoZWR1bGVkVGVzdHMubGVuZ3RoKSB7XG4gICAgICBjb25zdCBlbnRyeSA9IHNjaGVkdWxlZFRlc3RzLnNoaWZ0KCkhXG4gICAgICBjb25zdCBpbml0aWFsRnJhbWUgPSBjdXJyZW50RnJhbWVDb3VudGVyXG4gICAgICBjb25zdCBzdGFydFRpbWUgPSBjdXJyZW50RnJhbWVUaW1lXG5cbiAgICAgIGxldCByZXNvbHZlZCA9IGZhbHNlXG5cbiAgICAgIC8vIHRoaXMgZnVuY3Rpb24gc2hvdWxkIGJlIGNhbGxlZCBvbmx5IG9uY2UuIGl0IG1ha2VzIHRoZSBjdXJyZW50IHRlc3QgcGFzc1xuICAgICAgY29uc3QgcmVzb2x2ZSA9ICgpID0+IHtcbiAgICAgICAgaWYgKHJlc29sdmVkKSB0aHJvdyBuZXcgRXJyb3IoJ3Jlc29sdmVkIHR3aWNlJylcbiAgICAgICAgcmVzb2x2ZWQgPSB0cnVlXG5cbiAgICAgICAgY29uc29sZS5sb2coYPCfn6IgVGVzdCBwYXNzZWQgJHtlbnRyeS5uYW1lfWApXG5cbiAgICAgICAgdGVzdGluZ01vZHVsZVxuICAgICAgICAgIC5sb2dUZXN0UmVzdWx0KHtcbiAgICAgICAgICAgIG5hbWU6IGVudHJ5Lm5hbWUsXG4gICAgICAgICAgICBvazogdHJ1ZSxcbiAgICAgICAgICAgIHRvdGFsRnJhbWVzOiBjdXJyZW50RnJhbWVDb3VudGVyIC0gaW5pdGlhbEZyYW1lLFxuICAgICAgICAgICAgdG90YWxUaW1lOiBjdXJyZW50RnJhbWVUaW1lIC0gc3RhcnRUaW1lXG4gICAgICAgICAgfSlcbiAgICAgICAgICAuZmluYWxseShzY2hlZHVsZU5leHRSdW4pXG4gICAgICB9XG5cbiAgICAgIGNvbnN0IHJlamVjdCA9IChlcnI6IGFueSkgPT4ge1xuICAgICAgICBpZiAocmVzb2x2ZWQpIHRocm93IG5ldyBFcnJvcigncmVzb2x2ZWQgdHdpY2UnKVxuICAgICAgICByZXNvbHZlZCA9IHRydWVcblxuICAgICAgICBjb25zb2xlLmxvZyhg8J+UtCBUZXN0IGZhaWxlZCAke2VudHJ5Lm5hbWV9YClcbiAgICAgICAgY29uc29sZS5lcnJvcihlcnIpXG5cbiAgICAgICAgdGVzdGluZ01vZHVsZVxuICAgICAgICAgIC5sb2dUZXN0UmVzdWx0KHtcbiAgICAgICAgICAgIG5hbWU6IGVudHJ5Lm5hbWUsXG4gICAgICAgICAgICBvazogZmFsc2UsXG4gICAgICAgICAgICBlcnJvcjogZXJyLnRvU3RyaW5nKCksXG4gICAgICAgICAgICBzdGFjazogZXJyICYmIHR5cGVvZiBlcnIgPT09ICdvYmplY3QnICYmIGVyci5zdGFjayxcbiAgICAgICAgICAgIHRvdGFsRnJhbWVzOiBjdXJyZW50RnJhbWVDb3VudGVyIC0gaW5pdGlhbEZyYW1lLFxuICAgICAgICAgICAgdG90YWxUaW1lOiBjdXJyZW50RnJhbWVUaW1lIC0gc3RhcnRUaW1lXG4gICAgICAgICAgfSlcbiAgICAgICAgICAuZmluYWxseShzY2hlZHVsZU5leHRSdW4pXG4gICAgICB9XG5cbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnNvbGUubG9nKGDwn6eqIFJ1bm5pbmcgdGVzdCAke2VudHJ5Lm5hbWV9YClcblxuICAgICAgICBjb25zdCB0ZXN0SGVscGVyczogVGVzdEhlbHBlcnMgPSB7XG4gICAgICAgICAgYXN5bmMgc2V0Q2FtZXJhVHJhbnNmb3JtKHRyYW5zZm9ybSkge1xuICAgICAgICAgICAgYXdhaXQgdGVzdGluZ01vZHVsZS5zZXRDYW1lcmFUcmFuc2Zvcm0odHJhbnNmb3JtKVxuICAgICAgICAgICAgYXdhaXQgbmV4dFRpY2soKVxuXG4gICAgICAgICAgICBjb25zdCBUcmFuc2Zvcm1Db21wb25lbnQgPSBlbmdpbmUuZ2V0Q29tcG9uZW50KFRyYW5zZm9ybS5jb21wb25lbnRJZCkgYXMgdHlwZW9mIFRyYW5zZm9ybVxuICAgICAgICAgICAgY29uc3QgYWN0dWFsVHJhbnNmb3JtID0gVHJhbnNmb3JtQ29tcG9uZW50LmdldChlbmdpbmUuQ2FtZXJhRW50aXR5KVxuXG4gICAgICAgICAgICBhc3NlcnRFcXVhbHMoYWN0dWFsVHJhbnNmb3JtLnBvc2l0aW9uLCB0cmFuc2Zvcm0ucG9zaXRpb24sIFwicG9zaXRpb25zIGRvbid0IG1hdGNoXCIpXG4gICAgICAgICAgICBhc3NlcnRFcXVhbHMoYWN0dWFsVHJhbnNmb3JtLnJvdGF0aW9uLCB0cmFuc2Zvcm0ucm90YXRpb24sIFwicm90YXRpb25zIGRvbid0IG1hdGNoXCIpXG4gICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgY29uc3QgcmV0dXJuVmFsdWUgPSBlbnRyeS5mbih0ZXN0SGVscGVycylcblxuICAgICAgICBpZiAocmV0dXJuVmFsdWUgJiYgdHlwZW9mIHJldHVyblZhbHVlID09PSAnb2JqZWN0Jykge1xuICAgICAgICAgIGlmIChpc0dlbmVyYXRvcihyZXR1cm5WYWx1ZSkpIHtcbiAgICAgICAgICAgIGNvbnN0IGVudjogUnVubmVyRW52aXJvbm1lbnQgPSB7XG4gICAgICAgICAgICAgIGdlbmVyYXRvcjogcmV0dXJuVmFsdWUsXG4gICAgICAgICAgICAgIGhlbHBlcnM6IHRlc3RIZWxwZXJzLFxuICAgICAgICAgICAgICByZXNvbHZlLFxuICAgICAgICAgICAgICByZWplY3RcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGNvbnN1bWVHZW5lcmF0b3IoZW52KVxuICAgICAgICAgIH0gZWxzZSBpZiAoaXNQcm9taXNlKHJldHVyblZhbHVlKSkge1xuICAgICAgICAgICAgcmV0dXJuVmFsdWUudGhlbihyZXNvbHZlKS5jYXRjaChyZWplY3QpXG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgVW5rbm93biB0ZXN0IHJlc3VsdCB0eXBlOiAke3JldHVyblZhbHVlfWApXG4gICAgICAgICAgfVxuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHJlc29sdmUoKVxuICAgICAgICB9XG4gICAgICB9IGNhdGNoIChlcnI6IGFueSkge1xuICAgICAgICByZWplY3QoZXJyKVxuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8vIHNjaGVkdWxlIHRoZSB0ZXN0IHJ1bm5lciBzdGFydCBmb3IgdGhlIG5leHQgZnJhbWVcbiAgbmV4dFRpY2tGdXR1cmUucHVzaCgoKSA9PiB7XG4gICAgLy8gb25jZSB3ZSBydW4gdGhlIG5leHQgdGljaywgdGhlIHRlc3QgcnVudGltZSBiZWNvbWVzIGZyb3plbi4gdGhhdCBtZWFucyBubyBuZXdcbiAgICAvLyB0ZXN0IGRlZmluaXRpb25zIGFyZSBhY2NlcHRlZFxuICAgIHJ1bnRpbWVGcm96ZW4gPSB0cnVlXG5cbiAgICBpZiAoIXNjaGVkdWxlZFRlc3RzLmxlbmd0aCkgcmV0dXJuXG5cbiAgICAvLyBpbmZvcm0gdGhlIHRlc3QgcnVubmVyIGFib3V0IHRoZSBwbGFucyBmb3IgdGhpcyB0ZXN0IHJ1blxuICAgIHRlc3RpbmdNb2R1bGUucGxhbih7IHRlc3RzOiBzY2hlZHVsZWRUZXN0cyB9KS50aGVuKHNjaGVkdWxlTmV4dFJ1bikuY2F0Y2goZ2xvYmFsRmFpbClcbiAgfSlcblxuICAvLyB0aGlzIGlzIHRoZSBmdW5jdGlvbiB0aGF0IGlzIHVzZWQgdG8gcGxhbiBhIHRlc3QgZnVuY3Rpb25uXG4gIC8qIEBfX1BVUkVfXyAqL1xuICBmdW5jdGlvbiB0ZXN0KG5hbWU6IHN0cmluZywgZm46IFRlc3RGdW5jdGlvbikge1xuICAgIGlmIChydW50aW1lRnJvemVuKSB0aHJvdyBuZXcgRXJyb3IoXCJOZXcgdGVzdHMgY2FuJ3QgYmUgYWRkZWQgYXQgdGhpcyBzdGFnZS5cIilcblxuICAgIGlmIChzY2hlZHVsZWRUZXN0cy5zb21lKCgkKSA9PiAkLm5hbWUgPT09IG5hbWUpKSB0aHJvdyBuZXcgRXJyb3IoYFRlc3Qgd2l0aCBuYW1lICR7bmFtZX0gYWxyZWFkeSBleGlzdHNgKVxuXG4gICAgc2NoZWR1bGVkVGVzdHMucHVzaCh7IGZuLCBuYW1lIH0pXG4gIH1cblxuICByZXR1cm4ge1xuICAgIHRlc3RcbiAgfVxufVxuXG5mdW5jdGlvbiBpc0dlbmVyYXRvcih0OiBhbnkpOiB0IGlzIEdlbmVyYXRvciB7XG4gIHJldHVybiB0ICYmIHR5cGVvZiB0ID09PSAnb2JqZWN0JyAmJiB0eXBlb2YgdFtTeW1ib2wuaXRlcmF0b3JdID09PSAnZnVuY3Rpb24nXG59XG5cbmZ1bmN0aW9uIGlzUHJvbWlzZSh0OiBhbnkpOiB0IGlzIFByb21pc2U8dW5rbm93bj4ge1xuICByZXR1cm4gdCAmJiB0eXBlb2YgdCA9PT0gJ29iamVjdCcgJiYgdHlwZW9mIHQudGhlbiA9PT0gJ2Z1bmN0aW9uJ1xufVxuXG5mdW5jdGlvbiBnbG9iYWxGYWlsKGVycm9yOiBhbnkpIHtcbiAgLy8gZm9yIG5vdywgdGhlIGZhaWx1cmUgaXMgb25seSB3cml0aW5nIHRvIHRoZSBjb25zb2xlLmVycm9yLlxuICBjb25zb2xlLmVycm9yKGVycm9yKVxufVxuIl19
|