serverless-spy 0.0.32 → 0.0.34
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/.jsii +9 -9
- package/common/publishSpyEvent.ts +269 -0
- package/dist/releasetag.txt +1 -1
- package/extension/interceptor.ts +24 -13
- package/functions/onConnect.ts +2 -1
- package/functions/onDisconnect.ts +2 -1
- package/functions/sendMessage.ts +3 -268
- package/lib/common/publishSpyEvent.d.ts +4 -0
- package/lib/common/publishSpyEvent.js +211 -0
- package/lib/common/publishSpyEvent.mjs +205 -0
- package/lib/extension/dist/layer/nodejs/node_modules/interceptor.js +13890 -13
- package/lib/extension/dist/layer/nodejs/node_modules/interceptor.js.map +4 -4
- package/lib/listener/{SpyListener.d.ts → ServerlessSpyListener.d.ts} +12 -12
- package/lib/listener/ServerlessSpyListener.js +3 -0
- package/lib/listener/ServerlessSpyListener.mjs +2 -0
- package/lib/listener/ServerlessSpyListenerParams.d.ts +5 -0
- package/lib/listener/ServerlessSpyListenerParams.js +3 -0
- package/lib/listener/ServerlessSpyListenerParams.mjs +2 -0
- package/lib/listener/SpyHandlers.ts.d.ts +1 -1
- package/lib/listener/SpyHandlers.ts.js +1 -1
- package/lib/listener/SpyHandlers.ts.mjs +1 -1
- package/lib/listener/WaitForParams.d.ts +5 -0
- package/lib/listener/WaitForParams.js +3 -0
- package/lib/listener/WaitForParams.mjs +2 -0
- package/lib/listener/WsListener.d.ts +38 -0
- package/lib/listener/WsListener.js +185 -0
- package/lib/listener/WsListener.mjs +181 -0
- package/lib/listener/createServerlessSpyListener.d.ts +2 -13
- package/lib/listener/createServerlessSpyListener.js +5 -163
- package/lib/listener/createServerlessSpyListener.mjs +5 -163
- package/lib/listener/index.d.ts +1 -1
- package/lib/listener/index.js +2 -2
- package/lib/listener/index.mjs +2 -2
- package/lib/src/ServerlessSpy.d.ts +16 -8
- package/lib/src/ServerlessSpy.js +94 -53
- package/lib/src/ServerlessSpy.mjs +93 -52
- package/lib/src/common/envVariableNames.d.ts +8 -0
- package/lib/src/common/envVariableNames.js +15 -0
- package/lib/src/common/envVariableNames.mjs +12 -0
- package/listener/{SpyListener.ts → ServerlessSpyListener.ts} +12 -13
- package/listener/ServerlessSpyListenerParams.ts +6 -0
- package/listener/SpyHandlers.ts.ts +1 -1
- package/listener/WaitForParams.ts +6 -0
- package/listener/WsListener.ts +273 -0
- package/listener/createServerlessSpyListener.ts +5 -265
- package/listener/index.ts +1 -1
- package/package.json +1 -1
- package/lib/listener/SpyListener.js +0 -3
- package/lib/listener/SpyListener.mjs +0 -2
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { WebSocket } from 'ws';
|
|
2
|
+
import { getWebSocketUrl } from '../common/getWebSocketUrl';
|
|
3
|
+
import { FunctionRequestSpyEvent } from '../common/spyEvents/FunctionRequestSpyEvent';
|
|
4
|
+
import { SpyEvent } from '../common/spyEvents/SpyEvent';
|
|
5
|
+
import { SpyMessage } from '../common/spyEvents/SpyMessage';
|
|
6
|
+
import { ServerlessSpyListener } from './ServerlessSpyListener';
|
|
7
|
+
import { ServerlessSpyListenerParams } from './ServerlessSpyListenerParams';
|
|
8
|
+
import { WaitForParams } from './WaitForParams';
|
|
9
|
+
|
|
10
|
+
export class WsListener<TSpyEvents> {
|
|
11
|
+
private messages: SpyMessageStorage[] = [];
|
|
12
|
+
private trackers: Tracker[] = [];
|
|
13
|
+
|
|
14
|
+
private connectionOpenResolve?: () => void;
|
|
15
|
+
private waitForConnection?: Promise<void>;
|
|
16
|
+
private ws?: WebSocket;
|
|
17
|
+
private closed = true;
|
|
18
|
+
private functionPrefix = 'waitFor';
|
|
19
|
+
|
|
20
|
+
async start(params: ServerlessSpyListenerParams) {
|
|
21
|
+
this.waitForConnection = new Promise((resolve) => {
|
|
22
|
+
this.connectionOpenResolve = resolve;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const urlSigned = await getWebSocketUrl(
|
|
26
|
+
params.serverlessSpyWsUrl,
|
|
27
|
+
params.credentials
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
this.ws = new WebSocket(urlSigned);
|
|
31
|
+
this.closed = false;
|
|
32
|
+
this.ws.on('open', () => {
|
|
33
|
+
this.connectionOpenResolve!();
|
|
34
|
+
});
|
|
35
|
+
this.ws.on('message', (data) => {
|
|
36
|
+
if (this.closed) return;
|
|
37
|
+
|
|
38
|
+
const message = JSON.parse(data.toString()) as SpyMessageStorage;
|
|
39
|
+
|
|
40
|
+
message.serviceKeyForFunction = message.serviceKey.replace(/#/g, '');
|
|
41
|
+
|
|
42
|
+
if (message.serviceKey.startsWith('Function')) {
|
|
43
|
+
message.functionContextAwsRequestId = (
|
|
44
|
+
message.data as FunctionRequestSpyEvent
|
|
45
|
+
).context.awsRequestId;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.messages.push(message);
|
|
49
|
+
this.resolveOldTrackerWithNewMessage(message);
|
|
50
|
+
});
|
|
51
|
+
this.ws.on('close', () => {
|
|
52
|
+
this.closed = true;
|
|
53
|
+
//console.log('disconnected ' + new Date().toISOString());
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
await this.waitForConnection;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async stop() {
|
|
60
|
+
this.closed = true;
|
|
61
|
+
this.ws!.close();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
trackerMatchMessage(tracker: Tracker, message: SpyMessageStorage) {
|
|
65
|
+
if (tracker.finished) return;
|
|
66
|
+
|
|
67
|
+
if (
|
|
68
|
+
(tracker.serviceKey && tracker.serviceKey === message.serviceKey) ||
|
|
69
|
+
(tracker.serviceKeyForFunction &&
|
|
70
|
+
tracker.serviceKeyForFunction === message.serviceKeyForFunction)
|
|
71
|
+
) {
|
|
72
|
+
if (this.trackerMatchCondition(tracker, message)) {
|
|
73
|
+
tracker.finished = true;
|
|
74
|
+
|
|
75
|
+
const spyAndJestMatchers: any = {
|
|
76
|
+
getData: () => message.data,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const serviceKeyForFunction = tracker.serviceKeyForFunction;
|
|
80
|
+
if (
|
|
81
|
+
serviceKeyForFunction &&
|
|
82
|
+
serviceKeyForFunction.startsWith('Function') &&
|
|
83
|
+
(serviceKeyForFunction.endsWith('Request') ||
|
|
84
|
+
serviceKeyForFunction.endsWith('Console'))
|
|
85
|
+
) {
|
|
86
|
+
let serviceKeyForFunctionResponse = serviceKeyForFunction;
|
|
87
|
+
|
|
88
|
+
if (serviceKeyForFunctionResponse.endsWith('Request')) {
|
|
89
|
+
serviceKeyForFunctionResponse =
|
|
90
|
+
serviceKeyForFunctionResponse.substring(
|
|
91
|
+
0,
|
|
92
|
+
serviceKeyForFunctionResponse.length - 'Request'.length
|
|
93
|
+
);
|
|
94
|
+
} else if (serviceKeyForFunctionResponse.endsWith('Console')) {
|
|
95
|
+
serviceKeyForFunctionResponse =
|
|
96
|
+
serviceKeyForFunctionResponse.substring(
|
|
97
|
+
0,
|
|
98
|
+
serviceKeyForFunctionResponse.length - 'Console'.length
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
serviceKeyForFunctionResponse += 'Response';
|
|
103
|
+
|
|
104
|
+
spyAndJestMatchers.followedByResponse = (paramsW: WaitForParams) => {
|
|
105
|
+
return this.createWaitForXXXFunc(
|
|
106
|
+
serviceKeyForFunctionResponse,
|
|
107
|
+
(message.data as FunctionRequestSpyEvent).context.awsRequestId
|
|
108
|
+
)(paramsW);
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const proxy = new Proxy(spyAndJestMatchers, {
|
|
113
|
+
get: function (target: any, objectKey: string) {
|
|
114
|
+
if (target.hasOwnProperty(objectKey)) {
|
|
115
|
+
return target[objectKey];
|
|
116
|
+
} else if (objectKey !== 'then') {
|
|
117
|
+
return function () {
|
|
118
|
+
const jestFunctionToExecute = (expect(message.data) as any)[
|
|
119
|
+
objectKey
|
|
120
|
+
];
|
|
121
|
+
jestFunctionToExecute.apply(undefined, arguments);
|
|
122
|
+
return proxy;
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
tracker.promiseResolve(proxy);
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private resolveTrackerInOldMessages(tracker: Tracker) {
|
|
136
|
+
for (const message of this.messages) {
|
|
137
|
+
if (this.trackerMatchMessage(tracker, message)) {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private resolveOldTrackerWithNewMessage(message: SpyMessageStorage) {
|
|
146
|
+
for (let index = 0; index < this.trackers.length; index++) {
|
|
147
|
+
const tracker = this.trackers[index];
|
|
148
|
+
if (this.trackerMatchMessage(tracker, message)) {
|
|
149
|
+
this.trackers = this.trackers.splice(index, 1);
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private trackerMatchCondition(tracker: Tracker, message: SpyMessageStorage) {
|
|
158
|
+
const matchCondition =
|
|
159
|
+
(tracker.condition && tracker.condition(message.data)) ||
|
|
160
|
+
!tracker.condition;
|
|
161
|
+
|
|
162
|
+
const matchRequestId =
|
|
163
|
+
(tracker.functionContextAwsRequestId &&
|
|
164
|
+
tracker.functionContextAwsRequestId ===
|
|
165
|
+
message.functionContextAwsRequestId) ||
|
|
166
|
+
!tracker.functionContextAwsRequestId;
|
|
167
|
+
|
|
168
|
+
if (matchCondition && matchRequestId) {
|
|
169
|
+
return true;
|
|
170
|
+
} else {
|
|
171
|
+
if (
|
|
172
|
+
!matchCondition &&
|
|
173
|
+
matchRequestId &&
|
|
174
|
+
!tracker.possibleSpyMessageDataForDebugging
|
|
175
|
+
) {
|
|
176
|
+
tracker.possibleSpyMessageDataForDebugging = message.data;
|
|
177
|
+
}
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
public createWaitForXXXFunc(
|
|
183
|
+
serviceKeyForFunction: string,
|
|
184
|
+
functionContextAwsRequestId?: string
|
|
185
|
+
) {
|
|
186
|
+
let tracker: Tracker;
|
|
187
|
+
|
|
188
|
+
const promise = new Promise((resolve, reject) => {
|
|
189
|
+
tracker = {
|
|
190
|
+
finished: false,
|
|
191
|
+
promiseResolve: resolve,
|
|
192
|
+
promiseReject: reject,
|
|
193
|
+
serviceKeyForFunction,
|
|
194
|
+
functionContextAwsRequestId,
|
|
195
|
+
};
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
//waitForXXXFunc
|
|
199
|
+
return (paramsW?: WaitForParams<SpyEvent>) => {
|
|
200
|
+
tracker.condition = paramsW?.condition;
|
|
201
|
+
|
|
202
|
+
const timer = setTimeout(() => {
|
|
203
|
+
if (tracker.finished) return;
|
|
204
|
+
tracker.finished = true;
|
|
205
|
+
let message = `Timeout waiting for Serverless Spy message ${serviceKeyForFunction}.`;
|
|
206
|
+
|
|
207
|
+
if (tracker.possibleSpyMessageDataForDebugging) {
|
|
208
|
+
message += ` Similar matching spy event data: ${JSON.stringify(
|
|
209
|
+
tracker.possibleSpyMessageDataForDebugging,
|
|
210
|
+
null,
|
|
211
|
+
2
|
|
212
|
+
)}`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
tracker.promiseReject(new Error(message));
|
|
216
|
+
}, paramsW?.timoutMs || 10000);
|
|
217
|
+
|
|
218
|
+
promise.finally(() => {
|
|
219
|
+
clearTimeout(timer);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
if (!this.resolveTrackerInOldMessages(tracker)) {
|
|
223
|
+
this.trackers.push(tracker);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return promise;
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
public createProxy() {
|
|
231
|
+
const spyListener = {} as ServerlessSpyListener<TSpyEvents>;
|
|
232
|
+
|
|
233
|
+
spyListener.stop = async () => {
|
|
234
|
+
await this.stop();
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const proxy = new Proxy(spyListener, {
|
|
238
|
+
get: (target: any, objectKey: string) => {
|
|
239
|
+
if (target.hasOwnProperty(objectKey)) {
|
|
240
|
+
return target[objectKey];
|
|
241
|
+
} else if (
|
|
242
|
+
typeof objectKey === 'string' &&
|
|
243
|
+
objectKey.startsWith(this.functionPrefix)
|
|
244
|
+
) {
|
|
245
|
+
const serviceKeyForFunction = objectKey.substring(
|
|
246
|
+
this.functionPrefix.length
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
return this.createWaitForXXXFunc(serviceKeyForFunction);
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
return proxy as ServerlessSpyListener<TSpyEvents>;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
type Tracker = {
|
|
259
|
+
promiseResolve: (data: any) => void;
|
|
260
|
+
promiseReject: (data: any) => void;
|
|
261
|
+
finished: boolean;
|
|
262
|
+
serviceKey?: string;
|
|
263
|
+
serviceKeyForFunction?: string;
|
|
264
|
+
condition?: (data: any) => boolean;
|
|
265
|
+
timoutMs?: number;
|
|
266
|
+
functionContextAwsRequestId?: string;
|
|
267
|
+
possibleSpyMessageDataForDebugging?: any;
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
type SpyMessageStorage = SpyMessage & {
|
|
271
|
+
serviceKeyForFunction: string;
|
|
272
|
+
functionContextAwsRequestId?: string;
|
|
273
|
+
};
|
|
@@ -1,271 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { getWebSocketUrl } from '../common/getWebSocketUrl';
|
|
4
|
-
import { FunctionRequestSpyEvent } from '../common/spyEvents/FunctionRequestSpyEvent';
|
|
5
|
-
import { SpyEvent } from '../common/spyEvents/SpyEvent';
|
|
6
|
-
import { SpyMessage } from '../common/spyEvents/SpyMessage';
|
|
7
|
-
import { SpyListener } from './SpyListener';
|
|
8
|
-
|
|
9
|
-
type ServerlessSpyListenerParams = {
|
|
10
|
-
serverlessSpyWsUrl: string;
|
|
11
|
-
credentials?: Credentials;
|
|
12
|
-
};
|
|
1
|
+
import { ServerlessSpyListenerParams } from './ServerlessSpyListenerParams';
|
|
2
|
+
import { WsListener } from './WsListener';
|
|
13
3
|
|
|
14
4
|
export async function createServerlessSpyListener<TSpyEvents>(
|
|
15
5
|
params: ServerlessSpyListenerParams
|
|
16
6
|
) {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
params.credentials
|
|
20
|
-
);
|
|
21
|
-
const ws = new WebSocket(urlSigned);
|
|
22
|
-
const messages: SpyMessageStorage[] = [];
|
|
23
|
-
let trackers: Tracker[] = [];
|
|
24
|
-
let closed = false;
|
|
25
|
-
const functionPrefix = 'waitFor';
|
|
26
|
-
let connectionOpenResolve: (value: unknown) => void;
|
|
27
|
-
|
|
28
|
-
let waitForConnection = new Promise((resolve) => {
|
|
29
|
-
connectionOpenResolve = resolve;
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
ws.on('open', () => {
|
|
33
|
-
//console.log('connected ' + new Date().toISOString());
|
|
34
|
-
connectionOpenResolve(undefined);
|
|
35
|
-
});
|
|
36
|
-
ws.on('message', (data) => {
|
|
37
|
-
if (closed) return;
|
|
38
|
-
|
|
39
|
-
//console.log(`From server: ${data}`);
|
|
40
|
-
|
|
41
|
-
const message = JSON.parse(data.toString()) as SpyMessageStorage;
|
|
42
|
-
|
|
43
|
-
message.serviceKeyForFunction = message.serviceKey.replace(/#/g, '');
|
|
44
|
-
|
|
45
|
-
if (message.serviceKey.startsWith('Function')) {
|
|
46
|
-
message.functionContextAwsRequestId = (
|
|
47
|
-
message.data as FunctionRequestSpyEvent
|
|
48
|
-
).context.awsRequestId;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
messages.push(message);
|
|
52
|
-
resolveOldTrackerWithNewMessage(message);
|
|
53
|
-
});
|
|
54
|
-
ws.on('close', () => {
|
|
55
|
-
closed = true;
|
|
56
|
-
// console.log("disconnected " + new Date().toISOString());
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
const trackerMatchMessage = (
|
|
60
|
-
tracker: Tracker,
|
|
61
|
-
message: SpyMessageStorage
|
|
62
|
-
) => {
|
|
63
|
-
if (tracker.finished) return;
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
(tracker.serviceKey && tracker.serviceKey === message.serviceKey) ||
|
|
67
|
-
(tracker.serviceKeyForFunction &&
|
|
68
|
-
tracker.serviceKeyForFunction === message.serviceKeyForFunction)
|
|
69
|
-
) {
|
|
70
|
-
if (trackerMatchCondition(tracker, message)) {
|
|
71
|
-
tracker.finished = true;
|
|
72
|
-
|
|
73
|
-
const spyAndJestMatchers: any = {
|
|
74
|
-
getData: () => message.data,
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const serviceKeyForFunction = tracker.serviceKeyForFunction;
|
|
78
|
-
if (
|
|
79
|
-
serviceKeyForFunction &&
|
|
80
|
-
serviceKeyForFunction.startsWith('Function') &&
|
|
81
|
-
(serviceKeyForFunction.endsWith('Request') ||
|
|
82
|
-
serviceKeyForFunction.endsWith('Console'))
|
|
83
|
-
) {
|
|
84
|
-
let serviceKeyForFunctionResponse = serviceKeyForFunction;
|
|
85
|
-
|
|
86
|
-
if (serviceKeyForFunctionResponse.endsWith('Request')) {
|
|
87
|
-
serviceKeyForFunctionResponse =
|
|
88
|
-
serviceKeyForFunctionResponse.substring(
|
|
89
|
-
0,
|
|
90
|
-
serviceKeyForFunctionResponse.length - 'Request'.length
|
|
91
|
-
);
|
|
92
|
-
} else if (serviceKeyForFunctionResponse.endsWith('Console')) {
|
|
93
|
-
serviceKeyForFunctionResponse =
|
|
94
|
-
serviceKeyForFunctionResponse.substring(
|
|
95
|
-
0,
|
|
96
|
-
serviceKeyForFunctionResponse.length - 'Console'.length
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
serviceKeyForFunctionResponse += 'Response';
|
|
101
|
-
|
|
102
|
-
spyAndJestMatchers.followedByResponse = (paramsW: WaitForParams) => {
|
|
103
|
-
return createWaitForXXXFunc(
|
|
104
|
-
serviceKeyForFunctionResponse,
|
|
105
|
-
paramsW,
|
|
106
|
-
(message.data as FunctionRequestSpyEvent).context.awsRequestId
|
|
107
|
-
)();
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const proxy = new Proxy(spyAndJestMatchers, {
|
|
112
|
-
get: function (target: any, objectKey: string) {
|
|
113
|
-
if (target.hasOwnProperty(objectKey)) {
|
|
114
|
-
return target[objectKey];
|
|
115
|
-
} else if (objectKey !== 'then') {
|
|
116
|
-
return function () {
|
|
117
|
-
const jestFunctionToExecute = (expect(message.data) as any)[
|
|
118
|
-
objectKey
|
|
119
|
-
];
|
|
120
|
-
jestFunctionToExecute.apply(undefined, arguments);
|
|
121
|
-
return proxy;
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
},
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
tracker.promiseResolve(proxy);
|
|
128
|
-
return true;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
return false;
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
const resolveTrackerInOldMessages = (tracker: Tracker) => {
|
|
135
|
-
for (const message of messages) {
|
|
136
|
-
if (trackerMatchMessage(tracker, message)) {
|
|
137
|
-
return true;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return false;
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
const resolveOldTrackerWithNewMessage = (message: SpyMessageStorage) => {
|
|
145
|
-
for (let index = 0; index < trackers.length; index++) {
|
|
146
|
-
const tracker = trackers[index];
|
|
147
|
-
if (trackerMatchMessage(tracker, message)) {
|
|
148
|
-
trackers = trackers.splice(index, 1);
|
|
149
|
-
return true;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return false;
|
|
154
|
-
};
|
|
7
|
+
const wsListener = new WsListener<TSpyEvents>();
|
|
8
|
+
await wsListener.start(params);
|
|
155
9
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
spyListener.stop = () => {
|
|
159
|
-
closed = true;
|
|
160
|
-
ws.close();
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
const proxy = new Proxy(spyListener, {
|
|
164
|
-
get: function (target: any, objectKey: string) {
|
|
165
|
-
if (target.hasOwnProperty(objectKey)) {
|
|
166
|
-
return target[objectKey];
|
|
167
|
-
} else if (
|
|
168
|
-
typeof objectKey === 'string' &&
|
|
169
|
-
objectKey.startsWith(functionPrefix)
|
|
170
|
-
) {
|
|
171
|
-
const paramsW = arguments[0] as WaitForParams;
|
|
172
|
-
const serviceKeyForFunction = objectKey.substring(
|
|
173
|
-
functionPrefix.length
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
return createWaitForXXXFunc(serviceKeyForFunction, paramsW);
|
|
177
|
-
}
|
|
178
|
-
},
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
await waitForConnection;
|
|
182
|
-
|
|
183
|
-
return proxy as SpyListener<TSpyEvents>;
|
|
184
|
-
|
|
185
|
-
function trackerMatchCondition(tracker: Tracker, message: SpyMessageStorage) {
|
|
186
|
-
const matchCondition =
|
|
187
|
-
(tracker.condition && tracker.condition(message.data)) ||
|
|
188
|
-
!tracker.condition;
|
|
189
|
-
|
|
190
|
-
const matchRequestId =
|
|
191
|
-
(tracker.functionContextAwsRequestId &&
|
|
192
|
-
tracker.functionContextAwsRequestId ===
|
|
193
|
-
message.functionContextAwsRequestId) ||
|
|
194
|
-
!tracker.functionContextAwsRequestId;
|
|
195
|
-
|
|
196
|
-
// if (tracker.functionContextAwsRequestId) {
|
|
197
|
-
// console.log(
|
|
198
|
-
// `${tracker.functionContextAwsRequestId} - ${message.functionContextAwsRequestId}`
|
|
199
|
-
// );
|
|
200
|
-
// }
|
|
201
|
-
|
|
202
|
-
return matchCondition && matchRequestId;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function createWaitForXXXFunc(
|
|
206
|
-
serviceKeyForFunction: string,
|
|
207
|
-
paramsW: WaitForParams<SpyEvent>,
|
|
208
|
-
functionContextAwsRequestId?: string
|
|
209
|
-
) {
|
|
210
|
-
let tracker: Tracker;
|
|
211
|
-
|
|
212
|
-
const promise = new Promise((resolve, reject) => {
|
|
213
|
-
tracker = {
|
|
214
|
-
finished: false,
|
|
215
|
-
promiseResolve: resolve,
|
|
216
|
-
promiseReject: reject,
|
|
217
|
-
serviceKeyForFunction,
|
|
218
|
-
functionContextAwsRequestId,
|
|
219
|
-
};
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
return function waitForXXXFunc() {
|
|
223
|
-
tracker.condition = paramsW?.condition;
|
|
224
|
-
|
|
225
|
-
const timer = setTimeout(() => {
|
|
226
|
-
if (tracker.finished) return;
|
|
227
|
-
tracker.finished = true;
|
|
228
|
-
tracker.promiseReject(
|
|
229
|
-
new Error(
|
|
230
|
-
`Timeout waiting for Serverless Spy message ${serviceKeyForFunction}. Received messages so far:\n ${JSON.stringify(
|
|
231
|
-
messages,
|
|
232
|
-
null,
|
|
233
|
-
2
|
|
234
|
-
)}`
|
|
235
|
-
)
|
|
236
|
-
);
|
|
237
|
-
}, paramsW?.timoutMs || 10000);
|
|
238
|
-
|
|
239
|
-
promise.finally(() => {
|
|
240
|
-
clearTimeout(timer);
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
if (!resolveTrackerInOldMessages(tracker)) {
|
|
244
|
-
trackers.push(tracker);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return promise;
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
export interface WaitForParams<TSpyEvent extends SpyEvent = SpyEvent> {
|
|
253
|
-
condition?: (event: TSpyEvent) => boolean;
|
|
254
|
-
timoutMs?: number;
|
|
10
|
+
return wsListener.createProxy();
|
|
255
11
|
}
|
|
256
|
-
|
|
257
|
-
type Tracker = {
|
|
258
|
-
promiseResolve: (data: any) => void;
|
|
259
|
-
promiseReject: (data: any) => void;
|
|
260
|
-
finished: boolean;
|
|
261
|
-
serviceKey?: string;
|
|
262
|
-
serviceKeyForFunction?: string;
|
|
263
|
-
condition?: (data: any) => boolean;
|
|
264
|
-
timoutMs?: number;
|
|
265
|
-
functionContextAwsRequestId?: string;
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
type SpyMessageStorage = SpyMessage & {
|
|
269
|
-
serviceKeyForFunction: string;
|
|
270
|
-
functionContextAwsRequestId?: string;
|
|
271
|
-
};
|
package/listener/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export * from './createServerlessSpyListener';
|
|
2
|
-
export * from './
|
|
2
|
+
export * from './ServerlessSpyListener';
|
package/package.json
CHANGED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU3B5TGlzdGVuZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9saXN0ZW5lci9TcHlMaXN0ZW5lci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgRHluYW1vREJTcHlFdmVudCB9IGZyb20gJy4vLi4vY29tbW9uL3NweUV2ZW50cy9EeW5hbW9EQlNweUV2ZW50JztcbmltcG9ydCB7IEV2ZW50QnJpZGdlUnVsZVNweUV2ZW50IH0gZnJvbSAnLi8uLi9jb21tb24vc3B5RXZlbnRzL0V2ZW50QnJpZGdlUnVsZVNweUV2ZW50JztcbmltcG9ydCB7IEV2ZW50QnJpZGdlU3B5RXZlbnQgfSBmcm9tICcuLy4uL2NvbW1vbi9zcHlFdmVudHMvRXZlbnRCcmlkZ2VTcHlFdmVudCc7XG5pbXBvcnQgeyBGdW5jdGlvbkNvbnNvbGVTcHlFdmVudCB9IGZyb20gJy4vLi4vY29tbW9uL3NweUV2ZW50cy9GdW5jdGlvbkNvbnNvbGVTcHlFdmVudCc7XG5pbXBvcnQgeyBGdW5jdGlvblJlcXVlc3RTcHlFdmVudCB9IGZyb20gJy4vLi4vY29tbW9uL3NweUV2ZW50cy9GdW5jdGlvblJlcXVlc3RTcHlFdmVudCc7XG5pbXBvcnQgeyBGdW5jdGlvblJlc3BvbnNlU3B5RXZlbnQgfSBmcm9tICcuLy4uL2NvbW1vbi9zcHlFdmVudHMvRnVuY3Rpb25SZXNwb25zZVNweUV2ZW50JztcbmltcG9ydCB7IFMzU3B5RXZlbnQgfSBmcm9tICcuLy4uL2NvbW1vbi9zcHlFdmVudHMvUzNTcHlFdmVudCc7XG5pbXBvcnQgeyBTbnNTdWJzY3JpcHRpb25TcHlFdmVudCB9IGZyb20gJy4vLi4vY29tbW9uL3NweUV2ZW50cy9TbnNTdWJzY3JpcHRpb25TcHlFdmVudCc7XG5pbXBvcnQgeyBTbnNUb3BpY1NweUV2ZW50IH0gZnJvbSAnLi8uLi9jb21tb24vc3B5RXZlbnRzL1Nuc1RvcGljU3B5RXZlbnQnO1xuaW1wb3J0IHsgU3FzU3B5RXZlbnQgfSBmcm9tICcuLy4uL2NvbW1vbi9zcHlFdmVudHMvU3FzU3B5RXZlbnQnO1xuXG5pbXBvcnQgeyBXYWl0Rm9yUGFyYW1zIH0gZnJvbSAnLi9jcmVhdGVTZXJ2ZXJsZXNzU3B5TGlzdGVuZXInO1xuaW1wb3J0IHsgUHJldHRpZnlGb3JEaXNwbGF5IH0gZnJvbSAnLi9QcmV0dGlmeUZvckRpc3BsYXknO1xuaW1wb3J0IHtcbiAgRHluYW1vREJTcHlIYW5kbGVyLFxuICBFdmVudEJyaWRnZVNweUhhbmRsZXIsXG4gIEV2ZW50QnJpZGdlUnVsZVNweUhhbmRsZXIsXG4gIFMzU3B5SGFuZGxlcixcbiAgU25zU3Vic2NyaXB0aW9uU3B5SGFuZGxlcixcbiAgU25zVG9waWNTcHlIYW5kbGVyLFxuICBTcXNTcHlIYW5kbGVyLFxuICBGdW5jdGlvblJlcXVlc3RTcHlIYW5kbGVyLFxuICBGdW5jdGlvbkNvbnNvbGVTcHlIYW5kbGVyLFxuICBGdW5jdGlvblJlc3BvbnNlU3B5SGFuZGxlcixcbn0gZnJvbSAnLi9TcHlIYW5kbGVycy50cyc7XG5cbmV4cG9ydCB0eXBlIFNweUxpc3RlbmVyPFRTcHlFdmVudHM+ID0ge1xuICBbUCBpbiBrZXlvZiBGaWx0ZXJDb25kaXRpb25hbGx5PFRTcHlFdmVudHMsIGBEeW5hbW9EQiMke2FueX1gPiAmXG4gICAgc3RyaW5nIGFzIGB3YWl0Rm9yJHtQfWBdOiA8VCA9IGFueT4oXG4gICAgcGFyYW0/OiBQcmV0dGlmeUZvckRpc3BsYXk8XG4gICAgICBXYWl0Rm9yUGFyYW1zPFByZXR0aWZ5Rm9yRGlzcGxheTxEeW5hbW9EQlNweUV2ZW50Pj5cbiAgICA+XG4gICkgPT4gUHJvbWlzZTxEeW5hbW9EQlNweUhhbmRsZXI8VD4+O1xufSAmIHtcbiAgW1AgaW4ga2V5b2YgRmlsdGVyQ29uZGl0aW9uYWxseTxUU3B5RXZlbnRzLCBgRXZlbnRCcmlkZ2UjJHthbnl9YD4gJlxuICAgIHN0cmluZyBhcyBgd2FpdEZvciR7UH1gXTogPFQgPSBhbnk+KFxuICAgIHBhcmFtPzogUHJldHRpZnlGb3JEaXNwbGF5PFxuICAgICAgV2FpdEZvclBhcmFtczxQcmV0dGlmeUZvckRpc3BsYXk8RXZlbnRCcmlkZ2VTcHlFdmVudDxUPj4+XG4gICAgPlxuICApID0+IFByb21pc2U8RXZlbnRCcmlkZ2VTcHlIYW5kbGVyPFQ+Pjtcbn0gJiB7XG4gIFtQIGluIGtleW9mIEZpbHRlckNvbmRpdGlvbmFsbHk8VFNweUV2ZW50cywgYEV2ZW50QnJpZGdlUnVsZSMke2FueX1gPiAmXG4gICAgc3RyaW5nIGFzIGB3YWl0Rm9yJHtQfWBdOiA8VCA9IGFueT4oXG4gICAgcGFyYW0/OiBQcmV0dGlmeUZvckRpc3BsYXk8XG4gICAgICBXYWl0Rm9yUGFyYW1zPFByZXR0aWZ5Rm9yRGlzcGxheTxFdmVudEJyaWRnZVJ1bGVTcHlFdmVudDxUPj4+XG4gICAgPlxuICApID0+IFByb21pc2U8RXZlbnRCcmlkZ2VSdWxlU3B5SGFuZGxlcjxUPj47XG59ICYge1xuICBbUCBpbiBrZXlvZiBGaWx0ZXJDb25kaXRpb25hbGx5PFRTcHlFdmVudHMsIGBTMyMke2FueX1gPiAmXG4gICAgc3RyaW5nIGFzIGB3YWl0Rm9yJHtQfWBdOiAoXG4gICAgcGFyYW0/OiBQcmV0dGlmeUZvckRpc3BsYXk8V2FpdEZvclBhcmFtczxQcmV0dGlmeUZvckRpc3BsYXk8UzNTcHlFdmVudD4+PlxuICApID0+IFByb21pc2U8UzNTcHlIYW5kbGVyPjtcbn0gJiB7XG4gIFtQIGluIGtleW9mIEZpbHRlckNvbmRpdGlvbmFsbHk8VFNweUV2ZW50cywgYFNuc1N1YnNjcmlwdGlvbiMke2FueX1gPiAmXG4gICAgc3RyaW5nIGFzIGB3YWl0Rm9yJHtQfWBdOiA8VCA9IGFueT4oXG4gICAgcGFyYW0/OiBQcmV0dGlmeUZvckRpc3BsYXk8XG4gICAgICBXYWl0Rm9yUGFyYW1zPFByZXR0aWZ5Rm9yRGlzcGxheTxTbnNTdWJzY3JpcHRpb25TcHlFdmVudDxUPj4+XG4gICAgPlxuICApID0+IFByb21pc2U8U25zU3Vic2NyaXB0aW9uU3B5SGFuZGxlcjxUPj47XG59ICYge1xuICBbUCBpbiBrZXlvZiBGaWx0ZXJDb25kaXRpb25hbGx5PFRTcHlFdmVudHMsIGBTbnNUb3BpYyMke2FueX1gPiAmXG4gICAgc3RyaW5nIGFzIGB3YWl0Rm9yJHtQfWBdOiA8VCA9IGFueT4oXG4gICAgcGFyYW0/OiBQcmV0dGlmeUZvckRpc3BsYXk8XG4gICAgICBXYWl0Rm9yUGFyYW1zPFByZXR0aWZ5Rm9yRGlzcGxheTxTbnNUb3BpY1NweUV2ZW50PFQ+Pj5cbiAgICA+XG4gICkgPT4gUHJvbWlzZTxTbnNUb3BpY1NweUhhbmRsZXI8VD4+O1xufSAmIHtcbiAgW1AgaW4ga2V5b2YgRmlsdGVyQ29uZGl0aW9uYWxseTxUU3B5RXZlbnRzLCBgU3FzIyR7YW55fWA+ICZcbiAgICBzdHJpbmcgYXMgYHdhaXRGb3Ike1B9YF06IDxUID0gYW55PihcbiAgICBwYXJhbT86IFByZXR0aWZ5Rm9yRGlzcGxheTxcbiAgICAgIFdhaXRGb3JQYXJhbXM8UHJldHRpZnlGb3JEaXNwbGF5PFNxc1NweUV2ZW50PFQ+Pj5cbiAgICA+XG4gICkgPT4gUHJvbWlzZTxTcXNTcHlIYW5kbGVyPFQ+Pjtcbn0gJiB7XG4gIFtQIGluIGtleW9mIEZpbHRlckNvbmRpdGlvbmFsbHk8VFNweUV2ZW50cywgYEZ1bmN0aW9uIyR7YW55fSNSZXF1ZXN0YD4gJlxuICAgIHN0cmluZyBhcyBgd2FpdEZvciR7UH1gXTogPFQgPSBhbnk+KFxuICAgIHBhcmFtPzogUHJldHRpZnlGb3JEaXNwbGF5PFxuICAgICAgV2FpdEZvclBhcmFtczxQcmV0dGlmeUZvckRpc3BsYXk8RnVuY3Rpb25SZXF1ZXN0U3B5RXZlbnQ8VD4+PlxuICAgID5cbiAgKSA9PiBQcm9taXNlPEZ1bmN0aW9uUmVxdWVzdFNweUhhbmRsZXI8VD4+O1xufSAmIHtcbiAgW1AgaW4ga2V5b2YgRmlsdGVyQ29uZGl0aW9uYWxseTxUU3B5RXZlbnRzLCBgRnVuY3Rpb24jJHthbnl9I0NvbnNvbGVgPiAmXG4gICAgc3RyaW5nIGFzIGB3YWl0Rm9yJHtQfWBdOiA8VCA9IGFueT4oXG4gICAgcGFyYW0/OiBQcmV0dGlmeUZvckRpc3BsYXk8XG4gICAgICBXYWl0Rm9yUGFyYW1zPFByZXR0aWZ5Rm9yRGlzcGxheTxGdW5jdGlvbkNvbnNvbGVTcHlFdmVudDxUPj4+XG4gICAgPlxuICApID0+IFByb21pc2U8RnVuY3Rpb25Db25zb2xlU3B5SGFuZGxlcjxUPj47XG59ICYge1xuICBbUCBpbiBrZXlvZiBGaWx0ZXJDb25kaXRpb25hbGx5PFRTcHlFdmVudHMsIGBGdW5jdGlvbiMke2FueX0jUmVzcG9uc2VgPiAmXG4gICAgc3RyaW5nIGFzIGB3YWl0Rm9yJHtQfWBdOiA8VCA9IGFueT4oXG4gICAgcGFyYW0/OiBQcmV0dGlmeUZvckRpc3BsYXk8XG4gICAgICBXYWl0Rm9yUGFyYW1zPFByZXR0aWZ5Rm9yRGlzcGxheTxGdW5jdGlvblJlc3BvbnNlU3B5RXZlbnQ8VD4+PlxuICAgID4gLy8gICAgICBmaWx0ZXI/OiAoZXZlbnQ6IEZ1bmN0aW9uUmVzcG9uc2VTcHlFdmVudDxUPikgPT4gYm9vbGVhblxuICApID0+IFByb21pc2U8RnVuY3Rpb25SZXNwb25zZVNweUhhbmRsZXI8VD4+O1xufSAmIHtcbiAgc3RvcDogKCkgPT4gdm9pZDtcbn07XG5cbnR5cGUgRmlsdGVyQ29uZGl0aW9uYWxseTxTb3VyY2UsIENvbmRpdGlvbj4gPSBQaWNrPFxuICBTb3VyY2UsXG4gIHtcbiAgICBbSyBpbiBrZXlvZiBTb3VyY2VdOiBTb3VyY2VbS10gZXh0ZW5kcyBDb25kaXRpb24gPyBLIDogbmV2ZXI7XG4gIH1ba2V5b2YgU291cmNlXVxuPjtcbiJdfQ==
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
export {};
|
|
2
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU3B5TGlzdGVuZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9saXN0ZW5lci9TcHlMaXN0ZW5lci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgRHluYW1vREJTcHlFdmVudCB9IGZyb20gJy4vLi4vY29tbW9uL3NweUV2ZW50cy9EeW5hbW9EQlNweUV2ZW50JztcbmltcG9ydCB7IEV2ZW50QnJpZGdlUnVsZVNweUV2ZW50IH0gZnJvbSAnLi8uLi9jb21tb24vc3B5RXZlbnRzL0V2ZW50QnJpZGdlUnVsZVNweUV2ZW50JztcbmltcG9ydCB7IEV2ZW50QnJpZGdlU3B5RXZlbnQgfSBmcm9tICcuLy4uL2NvbW1vbi9zcHlFdmVudHMvRXZlbnRCcmlkZ2VTcHlFdmVudCc7XG5pbXBvcnQgeyBGdW5jdGlvbkNvbnNvbGVTcHlFdmVudCB9IGZyb20gJy4vLi4vY29tbW9uL3NweUV2ZW50cy9GdW5jdGlvbkNvbnNvbGVTcHlFdmVudCc7XG5pbXBvcnQgeyBGdW5jdGlvblJlcXVlc3RTcHlFdmVudCB9IGZyb20gJy4vLi4vY29tbW9uL3NweUV2ZW50cy9GdW5jdGlvblJlcXVlc3RTcHlFdmVudCc7XG5pbXBvcnQgeyBGdW5jdGlvblJlc3BvbnNlU3B5RXZlbnQgfSBmcm9tICcuLy4uL2NvbW1vbi9zcHlFdmVudHMvRnVuY3Rpb25SZXNwb25zZVNweUV2ZW50JztcbmltcG9ydCB7IFMzU3B5RXZlbnQgfSBmcm9tICcuLy4uL2NvbW1vbi9zcHlFdmVudHMvUzNTcHlFdmVudCc7XG5pbXBvcnQgeyBTbnNTdWJzY3JpcHRpb25TcHlFdmVudCB9IGZyb20gJy4vLi4vY29tbW9uL3NweUV2ZW50cy9TbnNTdWJzY3JpcHRpb25TcHlFdmVudCc7XG5pbXBvcnQgeyBTbnNUb3BpY1NweUV2ZW50IH0gZnJvbSAnLi8uLi9jb21tb24vc3B5RXZlbnRzL1Nuc1RvcGljU3B5RXZlbnQnO1xuaW1wb3J0IHsgU3FzU3B5RXZlbnQgfSBmcm9tICcuLy4uL2NvbW1vbi9zcHlFdmVudHMvU3FzU3B5RXZlbnQnO1xuXG5pbXBvcnQgeyBXYWl0Rm9yUGFyYW1zIH0gZnJvbSAnLi9jcmVhdGVTZXJ2ZXJsZXNzU3B5TGlzdGVuZXInO1xuaW1wb3J0IHsgUHJldHRpZnlGb3JEaXNwbGF5IH0gZnJvbSAnLi9QcmV0dGlmeUZvckRpc3BsYXknO1xuaW1wb3J0IHtcbiAgRHluYW1vREJTcHlIYW5kbGVyLFxuICBFdmVudEJyaWRnZVNweUhhbmRsZXIsXG4gIEV2ZW50QnJpZGdlUnVsZVNweUhhbmRsZXIsXG4gIFMzU3B5SGFuZGxlcixcbiAgU25zU3Vic2NyaXB0aW9uU3B5SGFuZGxlcixcbiAgU25zVG9waWNTcHlIYW5kbGVyLFxuICBTcXNTcHlIYW5kbGVyLFxuICBGdW5jdGlvblJlcXVlc3RTcHlIYW5kbGVyLFxuICBGdW5jdGlvbkNvbnNvbGVTcHlIYW5kbGVyLFxuICBGdW5jdGlvblJlc3BvbnNlU3B5SGFuZGxlcixcbn0gZnJvbSAnLi9TcHlIYW5kbGVycy50cyc7XG5cbmV4cG9ydCB0eXBlIFNweUxpc3RlbmVyPFRTcHlFdmVudHM+ID0ge1xuICBbUCBpbiBrZXlvZiBGaWx0ZXJDb25kaXRpb25hbGx5PFRTcHlFdmVudHMsIGBEeW5hbW9EQiMke2FueX1gPiAmXG4gICAgc3RyaW5nIGFzIGB3YWl0Rm9yJHtQfWBdOiA8VCA9IGFueT4oXG4gICAgcGFyYW0/OiBQcmV0dGlmeUZvckRpc3BsYXk8XG4gICAgICBXYWl0Rm9yUGFyYW1zPFByZXR0aWZ5Rm9yRGlzcGxheTxEeW5hbW9EQlNweUV2ZW50Pj5cbiAgICA+XG4gICkgPT4gUHJvbWlzZTxEeW5hbW9EQlNweUhhbmRsZXI8VD4+O1xufSAmIHtcbiAgW1AgaW4ga2V5b2YgRmlsdGVyQ29uZGl0aW9uYWxseTxUU3B5RXZlbnRzLCBgRXZlbnRCcmlkZ2UjJHthbnl9YD4gJlxuICAgIHN0cmluZyBhcyBgd2FpdEZvciR7UH1gXTogPFQgPSBhbnk+KFxuICAgIHBhcmFtPzogUHJldHRpZnlGb3JEaXNwbGF5PFxuICAgICAgV2FpdEZvclBhcmFtczxQcmV0dGlmeUZvckRpc3BsYXk8RXZlbnRCcmlkZ2VTcHlFdmVudDxUPj4+XG4gICAgPlxuICApID0+IFByb21pc2U8RXZlbnRCcmlkZ2VTcHlIYW5kbGVyPFQ+Pjtcbn0gJiB7XG4gIFtQIGluIGtleW9mIEZpbHRlckNvbmRpdGlvbmFsbHk8VFNweUV2ZW50cywgYEV2ZW50QnJpZGdlUnVsZSMke2FueX1gPiAmXG4gICAgc3RyaW5nIGFzIGB3YWl0Rm9yJHtQfWBdOiA8VCA9IGFueT4oXG4gICAgcGFyYW0/OiBQcmV0dGlmeUZvckRpc3BsYXk8XG4gICAgICBXYWl0Rm9yUGFyYW1zPFByZXR0aWZ5Rm9yRGlzcGxheTxFdmVudEJyaWRnZVJ1bGVTcHlFdmVudDxUPj4+XG4gICAgPlxuICApID0+IFByb21pc2U8RXZlbnRCcmlkZ2VSdWxlU3B5SGFuZGxlcjxUPj47XG59ICYge1xuICBbUCBpbiBrZXlvZiBGaWx0ZXJDb25kaXRpb25hbGx5PFRTcHlFdmVudHMsIGBTMyMke2FueX1gPiAmXG4gICAgc3RyaW5nIGFzIGB3YWl0Rm9yJHtQfWBdOiAoXG4gICAgcGFyYW0/OiBQcmV0dGlmeUZvckRpc3BsYXk8V2FpdEZvclBhcmFtczxQcmV0dGlmeUZvckRpc3BsYXk8UzNTcHlFdmVudD4+PlxuICApID0+IFByb21pc2U8UzNTcHlIYW5kbGVyPjtcbn0gJiB7XG4gIFtQIGluIGtleW9mIEZpbHRlckNvbmRpdGlvbmFsbHk8VFNweUV2ZW50cywgYFNuc1N1YnNjcmlwdGlvbiMke2FueX1gPiAmXG4gICAgc3RyaW5nIGFzIGB3YWl0Rm9yJHtQfWBdOiA8VCA9IGFueT4oXG4gICAgcGFyYW0/OiBQcmV0dGlmeUZvckRpc3BsYXk8XG4gICAgICBXYWl0Rm9yUGFyYW1zPFByZXR0aWZ5Rm9yRGlzcGxheTxTbnNTdWJzY3JpcHRpb25TcHlFdmVudDxUPj4+XG4gICAgPlxuICApID0+IFByb21pc2U8U25zU3Vic2NyaXB0aW9uU3B5SGFuZGxlcjxUPj47XG59ICYge1xuICBbUCBpbiBrZXlvZiBGaWx0ZXJDb25kaXRpb25hbGx5PFRTcHlFdmVudHMsIGBTbnNUb3BpYyMke2FueX1gPiAmXG4gICAgc3RyaW5nIGFzIGB3YWl0Rm9yJHtQfWBdOiA8VCA9IGFueT4oXG4gICAgcGFyYW0/OiBQcmV0dGlmeUZvckRpc3BsYXk8XG4gICAgICBXYWl0Rm9yUGFyYW1zPFByZXR0aWZ5Rm9yRGlzcGxheTxTbnNUb3BpY1NweUV2ZW50PFQ+Pj5cbiAgICA+XG4gICkgPT4gUHJvbWlzZTxTbnNUb3BpY1NweUhhbmRsZXI8VD4+O1xufSAmIHtcbiAgW1AgaW4ga2V5b2YgRmlsdGVyQ29uZGl0aW9uYWxseTxUU3B5RXZlbnRzLCBgU3FzIyR7YW55fWA+ICZcbiAgICBzdHJpbmcgYXMgYHdhaXRGb3Ike1B9YF06IDxUID0gYW55PihcbiAgICBwYXJhbT86IFByZXR0aWZ5Rm9yRGlzcGxheTxcbiAgICAgIFdhaXRGb3JQYXJhbXM8UHJldHRpZnlGb3JEaXNwbGF5PFNxc1NweUV2ZW50PFQ+Pj5cbiAgICA+XG4gICkgPT4gUHJvbWlzZTxTcXNTcHlIYW5kbGVyPFQ+Pjtcbn0gJiB7XG4gIFtQIGluIGtleW9mIEZpbHRlckNvbmRpdGlvbmFsbHk8VFNweUV2ZW50cywgYEZ1bmN0aW9uIyR7YW55fSNSZXF1ZXN0YD4gJlxuICAgIHN0cmluZyBhcyBgd2FpdEZvciR7UH1gXTogPFQgPSBhbnk+KFxuICAgIHBhcmFtPzogUHJldHRpZnlGb3JEaXNwbGF5PFxuICAgICAgV2FpdEZvclBhcmFtczxQcmV0dGlmeUZvckRpc3BsYXk8RnVuY3Rpb25SZXF1ZXN0U3B5RXZlbnQ8VD4+PlxuICAgID5cbiAgKSA9PiBQcm9taXNlPEZ1bmN0aW9uUmVxdWVzdFNweUhhbmRsZXI8VD4+O1xufSAmIHtcbiAgW1AgaW4ga2V5b2YgRmlsdGVyQ29uZGl0aW9uYWxseTxUU3B5RXZlbnRzLCBgRnVuY3Rpb24jJHthbnl9I0NvbnNvbGVgPiAmXG4gICAgc3RyaW5nIGFzIGB3YWl0Rm9yJHtQfWBdOiA8VCA9IGFueT4oXG4gICAgcGFyYW0/OiBQcmV0dGlmeUZvckRpc3BsYXk8XG4gICAgICBXYWl0Rm9yUGFyYW1zPFByZXR0aWZ5Rm9yRGlzcGxheTxGdW5jdGlvbkNvbnNvbGVTcHlFdmVudDxUPj4+XG4gICAgPlxuICApID0+IFByb21pc2U8RnVuY3Rpb25Db25zb2xlU3B5SGFuZGxlcjxUPj47XG59ICYge1xuICBbUCBpbiBrZXlvZiBGaWx0ZXJDb25kaXRpb25hbGx5PFRTcHlFdmVudHMsIGBGdW5jdGlvbiMke2FueX0jUmVzcG9uc2VgPiAmXG4gICAgc3RyaW5nIGFzIGB3YWl0Rm9yJHtQfWBdOiA8VCA9IGFueT4oXG4gICAgcGFyYW0/OiBQcmV0dGlmeUZvckRpc3BsYXk8XG4gICAgICBXYWl0Rm9yUGFyYW1zPFByZXR0aWZ5Rm9yRGlzcGxheTxGdW5jdGlvblJlc3BvbnNlU3B5RXZlbnQ8VD4+PlxuICAgID4gLy8gICAgICBmaWx0ZXI/OiAoZXZlbnQ6IEZ1bmN0aW9uUmVzcG9uc2VTcHlFdmVudDxUPikgPT4gYm9vbGVhblxuICApID0+IFByb21pc2U8RnVuY3Rpb25SZXNwb25zZVNweUhhbmRsZXI8VD4+O1xufSAmIHtcbiAgc3RvcDogKCkgPT4gdm9pZDtcbn07XG5cbnR5cGUgRmlsdGVyQ29uZGl0aW9uYWxseTxTb3VyY2UsIENvbmRpdGlvbj4gPSBQaWNrPFxuICBTb3VyY2UsXG4gIHtcbiAgICBbSyBpbiBrZXlvZiBTb3VyY2VdOiBTb3VyY2VbS10gZXh0ZW5kcyBDb25kaXRpb24gPyBLIDogbmV2ZXI7XG4gIH1ba2V5b2YgU291cmNlXVxuPjtcbiJdfQ==
|