UNPKG

postgraphile

Version:

A GraphQL schema created by reflection over a PostgreSQL schema 🐘 (previously known as PostGraphQL)

410 lines 37.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.enhanceHttpServerWithWebSockets = void 0; const http_1 = require("http"); const graphql_1 = require("graphql"); const WebSocket = require("ws"); const subscriptions_transport_ws_1 = require("subscriptions-transport-ws"); const graphql_ws_1 = require("graphql-ws"); const ws_1 = require("graphql-ws/lib/use/ws"); const parseUrl = require("parseurl"); const pluginHook_1 = require("../pluginHook"); const createPostGraphileHttpRequestHandler_1 = require("./createPostGraphileHttpRequestHandler"); const liveSubscribe_1 = require("./liveSubscribe"); function lowerCaseKeys(obj) { return Object.keys(obj).reduce((memo, key) => { memo[key.toLowerCase()] = obj[key]; return memo; }, {}); } function deferred() { let resolve; let reject; const promise = new Promise((_resolve, _reject) => { resolve = _resolve; reject = _reject; }); // tslint:disable-next-line prefer-object-spread return Object.assign(promise, { // @ts-ignore This isn't used before being defined. resolve, // @ts-ignore This isn't used before being defined. reject, }); } async function enhanceHttpServerWithWebSockets(websocketServer, postgraphileMiddleware, subscriptionServerOptions) { if (websocketServer['__postgraphileSubscriptionsEnabled']) { return; } websocketServer['__postgraphileSubscriptionsEnabled'] = true; const { options, getGraphQLSchema, withPostGraphileContextFromReqRes, handleErrors, } = postgraphileMiddleware; const pluginHook = pluginHook_1.pluginHookFromOptions(options); const liveSubscribe = liveSubscribe_1.makeLiveSubscribe({ pluginHook, options }); const graphqlRoute = (subscriptionServerOptions && subscriptionServerOptions.graphqlRoute) || (options.externalUrlBase || '') + (options.graphqlRoute || '/graphql'); const { subscriptions, live, websockets = subscriptions || live ? ['v0', 'v1'] : [] } = options; // enhance with WebSockets shouldnt be called if there are no websocket versions if (!websockets?.length) { throw new Error(`Invalid value for \`websockets\` option: '${JSON.stringify(websockets)}'`); } const schema = await getGraphQLSchema(); const keepalivePromisesByContextKey = {}; // IMPORTANT: if you change this, be sure to change `releaseAllContextsForSocket` too const contextKey = (ws, opId) => ws['postgraphileId'] + '|' + opId; const releaseAllContextsForSocket = (ws) => { const prefix = ws['postgraphileId'] + '|'; for (const [key, promise] of Object.entries(keepalivePromisesByContextKey)) { if (key.startsWith(prefix)) { promise.resolve(); delete keepalivePromisesByContextKey[key]; } } }; const releaseContextForSocketAndOpId = (ws, opId) => { const promise = keepalivePromisesByContextKey[contextKey(ws, opId)]; if (promise) { promise.resolve(); delete keepalivePromisesByContextKey[contextKey(ws, opId)]; } }; const addContextForSocketAndOpId = (context, ws, opId) => { releaseContextForSocketAndOpId(ws, opId); const promise = deferred(); promise['context'] = context; keepalivePromisesByContextKey[contextKey(ws, opId)] = promise; return promise; }; const applyMiddleware = async (middlewares = [], req, res) => { for (const middleware of middlewares) { // TODO: add Koa support await new Promise((resolve, reject) => { middleware(req, res, err => (err ? reject(err) : resolve())); }); } }; const reqResFromSocket = async (socket) => { const req = socket['__postgraphileReq']; if (!req) { throw new Error('req could not be extracted'); } let dummyRes = socket['__postgraphileRes']; if (req.res) { throw new Error("Please get in touch with Benjie; we weren't expecting req.res to be present but we want to reserve it for future usage."); } if (!dummyRes) { dummyRes = new http_1.ServerResponse(req); dummyRes.writeHead = (statusCode, _statusMessage, headers) => { if (statusCode && statusCode > 200) { // tslint:disable-next-line no-console console.error(`Something used 'writeHead' to write a '${statusCode}' error for websockets - check the middleware you're passing!`); socket.close(); } else if (headers) { // tslint:disable-next-line no-console console.error("Passing headers to 'writeHead' is not supported with websockets currently - check the middleware you're passing"); socket.close(); } return dummyRes; }; await applyMiddleware(options.websocketMiddlewares, req, dummyRes); // reqResFromSocket is only called once per socket, so there's no race condition here // eslint-disable-next-line require-atomic-updates socket['__postgraphileRes'] = dummyRes; } return { req, res: dummyRes }; }; const getContext = (socket, opId, isSubscription) => { const singleStatement = isSubscription; return new Promise((resolve, reject) => { reqResFromSocket(socket) .then(({ req, res }) => withPostGraphileContextFromReqRes(req, res, { singleStatement }, context => { const promise = addContextForSocketAndOpId(context, socket, opId); resolve(promise['context']); return promise; })) .then(null, reject); }); }; const staticValidationRules = pluginHook('postgraphile:validationRules:static', graphql_1.specifiedRules, { options, }); let socketId = 0; let v0Wss = null; if (websockets.includes('v0')) { v0Wss = new WebSocket.Server({ noServer: true }); subscriptions_transport_ws_1.SubscriptionServer.create({ schema, validationRules: staticValidationRules, execute: options.websocketOperations === 'all' ? graphql_1.execute : () => { throw new Error('Only subscriptions are allowed over websocket transport'); }, subscribe: options.live ? liveSubscribe : graphql_1.subscribe, onConnect(connectionParams, _socket, connectionContext) { const { socket, request } = connectionContext; socket['postgraphileId'] = ++socketId; if (!request) { throw new Error('No request!'); } const normalizedConnectionParams = lowerCaseKeys(connectionParams); request['connectionParams'] = connectionParams; request['normalizedConnectionParams'] = normalizedConnectionParams; socket['__postgraphileReq'] = request; if (!request.headers.authorization && normalizedConnectionParams['authorization']) { /* * Enable JWT support through connectionParams. * * For other headers you'll need to do this yourself for security * reasons (e.g. we don't want to allow overriding of Origin / * Referer / etc) */ request.headers.authorization = String(normalizedConnectionParams['authorization']); } socket['postgraphileHeaders'] = { ...normalizedConnectionParams, // The original headers must win (for security) ...request.headers, }; }, // tslint:disable-next-line no-any async onOperation(message, params, socket) { const opId = message.id; // Override schema (for --watch) params.schema = await getGraphQLSchema(); const { req, res } = await reqResFromSocket(socket); const meta = {}; const formatResponse = (response) => { if (response.errors) { response.errors = handleErrors(response.errors, req, res); } if (!createPostGraphileHttpRequestHandler_1.isEmpty(meta)) { response['meta'] = meta; } return response; }; // onOperation is only called once per params object, so there's no race condition here // eslint-disable-next-line require-atomic-updates params.formatResponse = formatResponse; const hookedParams = pluginHook ? pluginHook('postgraphile:ws:onOperation', params, { message, params, socket, options, }) : params; const finalParams = { ...hookedParams, query: typeof hookedParams.query !== 'string' ? hookedParams.query : graphql_1.parse(hookedParams.query), }; if (!finalParams.query) { return Promise.reject(new Error('Must provide document')); } const operation = graphql_1.getOperationAST(finalParams.query, finalParams.operationName); if (!operation) { return Promise.reject(new Error('Unable to identify operation')); } const isSubscription = operation.operation === 'subscription'; // We used to call `getContext` here, now we just persist this side effect instead. await reqResFromSocket(socket); // You are strongly encouraged to use // `postgraphile:validationRules:static` if possible - you should // only use this one if you need access to variables. const moreValidationRules = pluginHook('postgraphile:validationRules', [], { options, req, res, variables: params.variables, operationName: params.operationName, meta, }); if (moreValidationRules.length) { const validationErrors = graphql_1.validate(params.schema, finalParams.query, moreValidationRules); if (validationErrors.length) { const error = new Error('Query validation failed: \n' + validationErrors.map(e => e.message).join('\n')); error['errors'] = validationErrors; return Promise.reject(error); } } const context = await getContext(socket, opId, isSubscription); Object.assign(params.context, context); return finalParams; }, onOperationComplete(socket, opId) { releaseContextForSocketAndOpId(socket, opId); }, onDisconnect(socket) { releaseAllContextsForSocket(socket); }, /* * Heroku times out after 55s: * https://devcenter.heroku.com/articles/error-codes#h15-idle-connection * * The subscriptions-transport-ws client times out by default 30s after last keepalive: * https://github.com/apollographql/subscriptions-transport-ws/blob/52758bfba6190169a28078ecbafd2e457a2ff7a8/src/defaults.ts#L1 * * GraphQL Playground times out after 20s: * https://github.com/prisma/graphql-playground/blob/fa91e1b6d0488e6b5563d8b472682fe728ee0431/packages/graphql-playground-react/src/state/sessions/fetchingSagas.ts#L81 * * Pick a number under these ceilings. */ keepAlive: 15000, ...subscriptionServerOptions, }, v0Wss); } let v1Wss = null; if (websockets.includes('v1')) { v1Wss = new WebSocket.Server({ noServer: true }); ws_1.useServer({ schema, execute: options.websocketOperations === 'all' ? graphql_1.execute : () => { throw new Error('Only subscriptions are allowed over WebSocket transport'); }, subscribe: options.live ? liveSubscribe : graphql_1.subscribe, onConnect(ctx) { const { socket, request } = ctx.extra; socket['postgraphileId'] = ++socketId; socket['__postgraphileReq'] = request; const normalizedConnectionParams = lowerCaseKeys(ctx.connectionParams || {}); request['connectionParams'] = ctx.connectionParams || {}; request['normalizedConnectionParams'] = normalizedConnectionParams; if (!request.headers.authorization && normalizedConnectionParams['authorization']) { /* * Enable JWT support through connectionParams. * * For other headers you'll need to do this yourself for security * reasons (e.g. we don't want to allow overriding of Origin / * Referer / etc) */ request.headers.authorization = String(normalizedConnectionParams['authorization']); } socket['postgraphileHeaders'] = { ...normalizedConnectionParams, // The original headers must win (for security) ...request.headers, }; }, async onSubscribe(ctx, msg) { // Override schema (for --watch) const schema = await getGraphQLSchema(); const { payload } = msg; const args = { schema, contextValue: {}, operationName: payload.operationName, document: payload.query ? graphql_1.parse(payload.query) : null, variableValues: payload.variables, }; // for supplying custom execution arguments. if not already // complete, the pluginHook should fill in the gaps const hookedArgs = (pluginHook ? pluginHook('postgraphile:ws:onSubscribe', args, { context: ctx, message: msg, options, }) : args); if (!args.document) { return [ // same error that graphql.validate would throw if the document is missing new graphql_1.GraphQLError('Must provide document'), ]; } const operation = graphql_1.getOperationAST(args.document, hookedArgs.operationName); if (!operation) { return [new graphql_1.GraphQLError('Unable to identify operation')]; } const isSubscription = operation.operation === 'subscription'; // We used to call `getContext` here, now we just persist this side effect instead. await reqResFromSocket(ctx.extra.socket); // when supplying custom execution args from the // onSubscribe, you're trusted to do the validation const validationErrors = graphql_1.validate(hookedArgs.schema, hookedArgs.document, staticValidationRules); if (validationErrors.length) { return validationErrors; } // You are strongly encouraged to use // `postgraphile:validationRules:static` if possible - you should // only use this one if you need access to variables. const { req, res } = await reqResFromSocket(ctx.extra.socket); const moreValidationRules = pluginHook('postgraphile:validationRules', [], { options, req, res, variables: hookedArgs.variableValues, operationName: hookedArgs.operationName, }); if (moreValidationRules.length) { const moreValidationErrors = graphql_1.validate(hookedArgs.schema, hookedArgs.document, moreValidationRules); if (moreValidationErrors.length) { return moreValidationErrors; } } const context = await getContext(ctx.extra.socket, msg.id, isSubscription); Object.assign(hookedArgs.contextValue, context); return hookedArgs; }, async onError(ctx, msg, errors) { // errors returned from onSubscribe releaseContextForSocketAndOpId(ctx.extra.socket, msg.id); const { req, res } = await reqResFromSocket(ctx.extra.socket); return handleErrors(errors, req, res); }, async onNext(ctx, _msg, _args, result) { if (result.errors) { // operation execution errors const { req, res } = await reqResFromSocket(ctx.extra.socket); result.errors = handleErrors(result.errors, req, res); return result; } }, onComplete(ctx, msg) { releaseContextForSocketAndOpId(ctx.extra.socket, msg.id); }, onClose(ctx) { releaseAllContextsForSocket(ctx.extra.socket); }, }, v1Wss, /* * Heroku times out after 55s: * https://devcenter.heroku.com/articles/error-codes#h15-idle-connection * * GraphQL Playground times out after 20s: * https://github.com/prisma/graphql-playground/blob/fa91e1b6d0488e6b5563d8b472682fe728ee0431/packages/graphql-playground-react/src/state/sessions/fetchingSagas.ts#L81 * * Pick a number under these ceilings. */ subscriptionServerOptions?.keepAlive); } // listen for upgrades and delegate requests according to the WS subprotocol websocketServer.on('upgrade', (req, socket, head) => { const { pathname = '' } = parseUrl(req) || {}; const isGraphqlRoute = pathname === graphqlRoute; if (isGraphqlRoute) { const protocol = req.headers['sec-websocket-protocol']; const protocols = Array.isArray(protocol) ? protocol : protocol?.split(',').map(p => p.trim()); const wss = v0Wss && protocols?.includes('graphql-ws') && !protocols.includes(graphql_ws_1.GRAPHQL_TRANSPORT_WS_PROTOCOL) ? v0Wss : // v1 will welcome its own subprotocol `graphql-transport-ws` // and gracefully reject invalid ones. if the client supports // both v0 and v1, v1 will prevail v1Wss; if (wss) { wss.handleUpgrade(req, socket, head, ws => { wss.emit('connection', ws, req); }); } } }); } exports.enhanceHttpServerWithWebSockets = enhanceHttpServerWithWebSockets; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3Vic2NyaXB0aW9ucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9wb3N0Z3JhcGhpbGUvaHR0cC9zdWJzY3JpcHRpb25zLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLCtCQUFvRjtBQUVwRixxQ0FXaUI7QUFDakIsZ0NBQWdDO0FBQ2hDLDJFQUFvRztBQUNwRywyQ0FBMkQ7QUFDM0QsOENBQWtEO0FBQ2xELHFDQUFzQztBQUN0Qyw4Q0FBc0Q7QUFDdEQsaUdBQWlFO0FBQ2pFLG1EQUFvRDtBQU9wRCxTQUFTLGFBQWEsQ0FBQyxHQUF3QjtJQUM3QyxPQUFPLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsSUFBSSxFQUFFLEdBQUcsRUFBRSxFQUFFO1FBQzNDLElBQUksQ0FBQyxHQUFHLENBQUMsV0FBVyxFQUFFLENBQUMsR0FBRyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDbkMsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUM7QUFDVCxDQUFDO0FBRUQsU0FBUyxRQUFRO0lBQ2YsSUFBSSxPQUF5RCxDQUFDO0lBQzlELElBQUksTUFBOEIsQ0FBQztJQUNuQyxNQUFNLE9BQU8sR0FBRyxJQUFJLE9BQU8sQ0FBSSxDQUFDLFFBQVEsRUFBRSxPQUFPLEVBQVEsRUFBRTtRQUN6RCxPQUFPLEdBQUcsUUFBUSxDQUFDO1FBQ25CLE1BQU0sR0FBRyxPQUFPLENBQUM7SUFDbkIsQ0FBQyxDQUFDLENBQUM7SUFDSCxnREFBZ0Q7SUFDaEQsT0FBTyxNQUFNLENBQUMsTUFBTSxDQUFDLE9BQU8sRUFBRTtRQUM1QixtREFBbUQ7UUFDbkQsT0FBTztRQUNQLG1EQUFtRDtRQUNuRCxNQUFNO0tBQ1AsQ0FBQyxDQUFDO0FBQ0wsQ0FBQztBQUVNLEtBQUssVUFBVSwrQkFBK0IsQ0FJbkQsZUFBdUIsRUFDdkIsc0JBQTBDLEVBQzFDLHlCQUdDO0lBRUQsSUFBSSxlQUFlLENBQUMsb0NBQW9DLENBQUMsRUFBRTtRQUN6RCxPQUFPO0tBQ1I7SUFDRCxlQUFlLENBQUMsb0NBQW9DLENBQUMsR0FBRyxJQUFJLENBQUM7SUFDN0QsTUFBTSxFQUNKLE9BQU8sRUFDUCxnQkFBZ0IsRUFDaEIsaUNBQWlDLEVBQ2pDLFlBQVksR0FDYixHQUFHLHNCQUFzQixDQUFDO0lBQzNCLE1BQU0sVUFBVSxHQUFHLGtDQUFxQixDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQ2xELE1BQU0sYUFBYSxHQUFHLGlDQUFpQixDQUFDLEVBQUUsVUFBVSxFQUFFLE9BQU8sRUFBRSxDQUFDLENBQUM7SUFDakUsTUFBTSxZQUFZLEdBQ2hCLENBQUMseUJBQXlCLElBQUkseUJBQXlCLENBQUMsWUFBWSxDQUFDO1FBQ3JFLENBQUMsT0FBTyxDQUFDLGVBQWUsSUFBSSxFQUFFLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxZQUFZLElBQUksVUFBVSxDQUFDLENBQUM7SUFDekUsTUFBTSxFQUFFLGFBQWEsRUFBRSxJQUFJLEVBQUUsVUFBVSxHQUFHLGFBQWEsSUFBSSxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsR0FBRyxPQUFPLENBQUM7SUFFaEcsZ0ZBQWdGO0lBQ2hGLElBQUksQ0FBQyxVQUFVLEVBQUUsTUFBTSxFQUFFO1FBQ3ZCLE1BQU0sSUFBSSxLQUFLLENBQUMsNkNBQTZDLElBQUksQ0FBQyxTQUFTLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxDQUFDO0tBQzdGO0lBRUQsTUFBTSxNQUFNLEdBQUcsTUFBTSxnQkFBZ0IsRUFBRSxDQUFDO0lBRXhDLE1BQU0sNkJBQTZCLEdBQTZDLEVBQUUsQ0FBQztJQUVuRixxRkFBcUY7SUFDckYsTUFBTSxVQUFVLEdBQUcsQ0FBQyxFQUFhLEVBQUUsSUFBWSxFQUFVLEVBQUUsQ0FBQyxFQUFFLENBQUMsZ0JBQWdCLENBQUMsR0FBRyxHQUFHLEdBQUcsSUFBSSxDQUFDO0lBRTlGLE1BQU0sMkJBQTJCLEdBQUcsQ0FBQyxFQUFhLEVBQVEsRUFBRTtRQUMxRCxNQUFNLE1BQU0sR0FBRyxFQUFFLENBQUMsZ0JBQWdCLENBQUMsR0FBRyxHQUFHLENBQUM7UUFDMUMsS0FBSyxNQUFNLENBQUMsR0FBRyxFQUFFLE9BQU8sQ0FBQyxJQUFJLE1BQU0sQ0FBQyxPQUFPLENBQUMsNkJBQTZCLENBQUMsRUFBRTtZQUMxRSxJQUFJLEdBQUcsQ0FBQyxVQUFVLENBQUMsTUFBTSxDQUFDLEVBQUU7Z0JBQzFCLE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQztnQkFDbEIsT0FBTyw2QkFBNkIsQ0FBQyxHQUFHLENBQUMsQ0FBQzthQUMzQztTQUNGO0lBQ0gsQ0FBQyxDQUFDO0lBRUYsTUFBTSw4QkFBOEIsR0FBRyxDQUFDLEVBQWEsRUFBRSxJQUFZLEVBQVEsRUFBRTtRQUMzRSxNQUFNLE9BQU8sR0FBRyw2QkFBNkIsQ0FBQyxVQUFVLENBQUMsRUFBRSxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUM7UUFDcEUsSUFBSSxPQUFPLEVBQUU7WUFDWCxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDbEIsT0FBTyw2QkFBNkIsQ0FBQyxVQUFVLENBQUMsRUFBRSxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUM7U0FDNUQ7SUFDSCxDQUFDLENBQUM7SUFFRixNQUFNLDBCQUEwQixHQUFHLENBQ2pDLE9BQWMsRUFDZCxFQUFhLEVBQ2IsSUFBWSxFQUNJLEVBQUU7UUFDbEIsOEJBQThCLENBQUMsRUFBRSxFQUFFLElBQUksQ0FBQyxDQUFDO1FBQ3pDLE1BQU0sT0FBTyxHQUFHLFFBQVEsRUFBRSxDQUFDO1FBQzNCLE9BQU8sQ0FBQyxTQUFTLENBQUMsR0FBRyxPQUFPLENBQUM7UUFDN0IsNkJBQTZCLENBQUMsVUFBVSxDQUFDLEVBQUUsRUFBRSxJQUFJLENBQUMsQ0FBQyxHQUFHLE9BQU8sQ0FBQztRQUM5RCxPQUFPLE9BQU8sQ0FBQztJQUNqQixDQUFDLENBQUM7SUFFRixNQUFNLGVBQWUsR0FBRyxLQUFLLEVBQzNCLGNBQW9ELEVBQUUsRUFDdEQsR0FBWSxFQUNaLEdBQWEsRUFDRSxFQUFFO1FBQ2pCLEtBQUssTUFBTSxVQUFVLElBQUksV0FBVyxFQUFFO1lBQ3BDLHdCQUF3QjtZQUN4QixNQUFNLElBQUksT0FBTyxDQUFPLENBQUMsT0FBTyxFQUFFLE1BQU0sRUFBUSxFQUFFO2dCQUNoRCxVQUFVLENBQUMsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLENBQUMsRUFBRSxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUMsQ0FBQztZQUMvRCxDQUFDLENBQUMsQ0FBQztTQUNKO0lBQ0gsQ0FBQyxDQUFDO0lBRUYsTUFBTSxnQkFBZ0IsR0FBRyxLQUFLLEVBQzVCLE1BQWlCLEVBSWhCLEVBQUU7UUFDSCxNQUFNLEdBQUcsR0FBRyxNQUFNLENBQUMsbUJBQW1CLENBQUMsQ0FBQztRQUN4QyxJQUFJLENBQUMsR0FBRyxFQUFFO1lBQ1IsTUFBTSxJQUFJLEtBQUssQ0FBQyw0QkFBNEIsQ0FBQyxDQUFDO1NBQy9DO1FBQ0QsSUFBSSxRQUFRLEdBQXlCLE1BQU0sQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDO1FBQ2pFLElBQUksR0FBRyxDQUFDLEdBQUcsRUFBRTtZQUNYLE1BQU0sSUFBSSxLQUFLLENBQ2IseUhBQXlILENBQzFILENBQUM7U0FDSDtRQUNELElBQUksQ0FBQyxRQUFRLEVBQUU7WUFDYixRQUFRLEdBQUcsSUFBSSxxQkFBYyxDQUFDLEdBQUcsQ0FBYSxDQUFDO1lBQy9DLFFBQVEsQ0FBQyxTQUFTLEdBQUcsQ0FDbkIsVUFBa0IsRUFDbEIsY0FBeUQsRUFDekQsT0FBeUMsRUFDL0IsRUFBRTtnQkFDWixJQUFJLFVBQVUsSUFBSSxVQUFVLEdBQUcsR0FBRyxFQUFFO29CQUNsQyxzQ0FBc0M7b0JBQ3RDLE9BQU8sQ0FBQyxLQUFLLENBQ1gsMENBQTBDLFVBQVUsK0RBQStELENBQ3BILENBQUM7b0JBQ0YsTUFBTSxDQUFDLEtBQUssRUFBRSxDQUFDO2lCQUNoQjtxQkFBTSxJQUFJLE9BQU8sRUFBRTtvQkFDbEIsc0NBQXNDO29CQUN0QyxPQUFPLENBQUMsS0FBSyxDQUNYLGlIQUFpSCxDQUNsSCxDQUFDO29CQUNGLE1BQU0sQ0FBQyxLQUFLLEVBQUUsQ0FBQztpQkFDaEI7Z0JBQ0QsT0FBTyxRQUFTLENBQUM7WUFDbkIsQ0FBQyxDQUFDO1lBQ0YsTUFBTSxlQUFlLENBQUMsT0FBTyxDQUFDLG9CQUFvQixFQUFFLEdBQUcsRUFBRSxRQUFRLENBQUMsQ0FBQztZQUVuRSxxRkFBcUY7WUFDckYsa0RBQWtEO1lBQ2xELE1BQU0sQ0FBQyxtQkFBbUIsQ0FBQyxHQUFHLFFBQVEsQ0FBQztTQUN4QztRQUNELE9BQU8sRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLFFBQVEsRUFBRSxDQUFDO0lBQ2hDLENBQUMsQ0FBQztJQUVGLE1BQU0sVUFBVSxHQUFHLENBQUMsTUFBaUIsRUFBRSxJQUFZLEVBQUUsY0FBdUIsRUFBa0IsRUFBRTtRQUM5RixNQUFNLGVBQWUsR0FBRyxjQUFjLENBQUM7UUFDdkMsT0FBTyxJQUFJLE9BQU8sQ0FBQyxDQUFDLE9BQU8sRUFBRSxNQUFNLEVBQVEsRUFBRTtZQUMzQyxnQkFBZ0IsQ0FBQyxNQUFNLENBQUM7aUJBQ3JCLElBQUksQ0FBQyxDQUFDLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxFQUFFLEVBQUUsQ0FDckIsaUNBQWlDLENBQUMsR0FBRyxFQUFFLEdBQUcsRUFBRSxFQUFFLGVBQWUsRUFBRSxFQUFFLE9BQU8sQ0FBQyxFQUFFO2dCQUN6RSxNQUFNLE9BQU8sR0FBRywwQkFBMEIsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLElBQUksQ0FBQyxDQUFDO2dCQUNsRSxPQUFPLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUM7Z0JBQzVCLE9BQU8sT0FBTyxDQUFDO1lBQ2pCLENBQUMsQ0FBQyxDQUNIO2lCQUNBLElBQUksQ0FBQyxJQUFJLEVBQUUsTUFBTSxDQUFDLENBQUM7UUFDeEIsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDLENBQUM7SUFFRixNQUFNLHFCQUFxQixHQUFHLFVBQVUsQ0FBQyxxQ0FBcUMsRUFBRSx3QkFBYyxFQUFFO1FBQzlGLE9BQU87S0FDUixDQUFDLENBQUM7SUFFSCxJQUFJLFFBQVEsR0FBRyxDQUFDLENBQUM7SUFFakIsSUFBSSxLQUFLLEdBQTRCLElBQUksQ0FBQztJQUMxQyxJQUFJLFVBQVUsQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLEVBQUU7UUFDN0IsS0FBSyxHQUFHLElBQUksU0FBUyxDQUFDLE1BQU0sQ0FBQyxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDO1FBQ2pELCtDQUFrQixDQUFDLE1BQU0sQ0FDdkI7WUFDRSxNQUFNO1lBQ04sZUFBZSxFQUFFLHFCQUFxQjtZQUN0QyxPQUFPLEVBQ0wsT0FBTyxDQUFDLG1CQUFtQixLQUFLLEtBQUs7Z0JBQ25DLENBQUMsQ0FBQyxpQkFBTztnQkFDVCxDQUFDLENBQUMsR0FBRyxFQUFFO29CQUNILE1BQU0sSUFBSSxLQUFLLENBQUMseURBQXlELENBQUMsQ0FBQztnQkFDN0UsQ0FBQztZQUNQLFNBQVMsRUFBRSxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxhQUFhLENBQUMsQ0FBQyxDQUFDLG1CQUFnQjtZQUMxRCxTQUFTLENBQ1AsZ0JBQXFDLEVBQ3JDLE9BQWtCLEVBQ2xCLGlCQUFvQztnQkFFcEMsTUFBTSxFQUFFLE1BQU0sRUFBRSxPQUFPLEVBQUUsR0FBRyxpQkFBaUIsQ0FBQztnQkFDOUMsTUFBTSxDQUFDLGdCQUFnQixDQUFDLEdBQUcsRUFBRSxRQUFRLENBQUM7Z0JBQ3RDLElBQUksQ0FBQyxPQUFPLEVBQUU7b0JBQ1osTUFBTSxJQUFJLEtBQUssQ0FBQyxhQUFhLENBQUMsQ0FBQztpQkFDaEM7Z0JBQ0QsTUFBTSwwQkFBMEIsR0FBRyxhQUFhLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztnQkFDbkUsT0FBTyxDQUFDLGtCQUFrQixDQUFDLEdBQUcsZ0JBQWdCLENBQUM7Z0JBQy9DLE9BQU8sQ0FBQyw0QkFBNEIsQ0FBQyxHQUFHLDBCQUEwQixDQUFDO2dCQUNuRSxNQUFNLENBQUMsbUJBQW1CLENBQUMsR0FBRyxPQUFPLENBQUM7Z0JBQ3RDLElBQUksQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLGFBQWEsSUFBSSwwQkFBMEIsQ0FBQyxlQUFlLENBQUMsRUFBRTtvQkFDakY7Ozs7Ozt1QkFNRztvQkFDSCxPQUFPLENBQUMsT0FBTyxDQUFDLGFBQWEsR0FBRyxNQUFNLENBQUMsMEJBQTBCLENBQUMsZUFBZSxDQUFDLENBQUMsQ0FBQztpQkFDckY7Z0JBRUQsTUFBTSxDQUFDLHFCQUFxQixDQUFDLEdBQUc7b0JBQzlCLEdBQUcsMEJBQTBCO29CQUM3QiwrQ0FBK0M7b0JBQy9DLEdBQUcsT0FBTyxDQUFDLE9BQU87aUJBQ25CLENBQUM7WUFDSixDQUFDO1lBQ0Qsa0NBQWtDO1lBQ2xDLEtBQUssQ0FBQyxXQUFXLENBQUMsT0FBWSxFQUFFLE1BQXVCLEVBQUUsTUFBaUI7Z0JBQ3hFLE1BQU0sSUFBSSxHQUFHLE9BQU8sQ0FBQyxFQUFFLENBQUM7Z0JBRXhCLGdDQUFnQztnQkFDaEMsTUFBTSxDQUFDLE1BQU0sR0FBRyxNQUFNLGdCQUFnQixFQUFFLENBQUM7Z0JBRXpDLE1BQU0sRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsTUFBTSxnQkFBZ0IsQ0FBQyxNQUFNLENBQUMsQ0FBQztnQkFDcEQsTUFBTSxJQUFJLEdBQUcsRUFBRSxDQUFDO2dCQUNoQixNQUFNLGNBQWMsR0FBRyxDQUNyQixRQUEwQixFQUNSLEVBQUU7b0JBQ3BCLElBQUksUUFBUSxDQUFDLE1BQU0sRUFBRTt3QkFDbkIsUUFBUSxDQUFDLE1BQU0sR0FBRyxZQUFZLENBQUMsUUFBUSxDQUFDLE1BQU0sRUFBRSxHQUFHLEVBQUUsR0FBRyxDQUFDLENBQUM7cUJBQzNEO29CQUNELElBQUksQ0FBQyw4Q0FBTyxDQUFDLElBQUksQ0FBQyxFQUFFO3dCQUNsQixRQUFRLENBQUMsTUFBTSxDQUFDLEdBQUcsSUFBSSxDQUFDO3FCQUN6QjtvQkFFRCxPQUFPLFFBQVEsQ0FBQztnQkFDbEIsQ0FBQyxDQUFDO2dCQUNGLHVGQUF1RjtnQkFDdkYsa0RBQWtEO2dCQUNsRCxNQUFNLENBQUMsY0FBYyxHQUFHLGNBQWMsQ0FBQztnQkFDdkMsTUFBTSxZQUFZLEdBQUcsVUFBVTtvQkFDN0IsQ0FBQyxDQUFDLFVBQVUsQ0FBQyw2QkFBNkIsRUFBRSxNQUFNLEVBQUU7d0JBQ2hELE9BQU87d0JBQ1AsTUFBTTt3QkFDTixNQUFNO3dCQUNOLE9BQU87cUJBQ1IsQ0FBQztvQkFDSixDQUFDLENBQUMsTUFBTSxDQUFDO2dCQUNYLE1BQU0sV0FBVyxHQUFrRDtvQkFDakUsR0FBRyxZQUFZO29CQUNmLEtBQUssRUFDSCxPQUFPLFlBQVksQ0FBQyxLQUFLLEtBQUssUUFBUTt3QkFDcEMsQ0FBQyxDQUFDLFlBQVksQ0FBQyxLQUFLO3dCQUNwQixDQUFDLENBQUMsZUFBSyxDQUFDLFlBQVksQ0FBQyxLQUFLLENBQUM7aUJBQ2hDLENBQUM7Z0JBQ0YsSUFBSSxDQUFDLFdBQVcsQ0FBQyxLQUFLLEVBQUU7b0JBQ3RCLE9BQU8sT0FBTyxDQUFDLE1BQU0sQ0FBQyxJQUFJLEtBQUssQ0FBQyx1QkFBdUIsQ0FBQyxDQUFDLENBQUM7aUJBQzNEO2dCQUVELE1BQU0sU0FBUyxHQUFHLHlCQUFlLENBQUMsV0FBVyxDQUFDLEtBQUssRUFBRSxXQUFXLENBQUMsYUFBYSxDQUFDLENBQUM7Z0JBQ2hGLElBQUksQ0FBQyxTQUFTLEVBQUU7b0JBQ2QsT0FBTyxPQUFPLENBQUMsTUFBTSxDQUFDLElBQUksS0FBSyxDQUFDLDhCQUE4QixDQUFDLENBQUMsQ0FBQztpQkFDbEU7Z0JBQ0QsTUFBTSxjQUFjLEdBQUcsU0FBUyxDQUFDLFNBQVMsS0FBSyxjQUFjLENBQUM7Z0JBRTlELG1GQUFtRjtnQkFDbkYsTUFBTSxnQkFBZ0IsQ0FBQyxNQUFNLENBQUMsQ0FBQztnQkFFL0IscUNBQXFDO2dCQUNyQyxpRUFBaUU7Z0JBQ2pFLHFEQUFxRDtnQkFDckQsTUFBTSxtQkFBbUIsR0FBRyxVQUFVLENBQUMsOEJBQThCLEVBQUUsRUFBRSxFQUFFO29CQUN6RSxPQUFPO29CQUNQLEdBQUc7b0JBQ0gsR0FBRztvQkFDSCxTQUFTLEVBQUUsTUFBTSxDQUFDLFNBQVM7b0JBQzNCLGFBQWEsRUFBRSxNQUFNLENBQUMsYUFBYTtvQkFDbkMsSUFBSTtpQkFDTCxDQUFDLENBQUM7Z0JBQ0gsSUFBSSxtQkFBbUIsQ0FBQyxNQUFNLEVBQUU7b0JBQzlCLE1BQU0sZ0JBQWdCLEdBQWdDLGtCQUFRLENBQzVELE1BQU0sQ0FBQyxNQUFNLEVBQ2IsV0FBVyxDQUFDLEtBQUssRUFDakIsbUJBQW1CLENBQ3BCLENBQUM7b0JBQ0YsSUFBSSxnQkFBZ0IsQ0FBQyxNQUFNLEVBQUU7d0JBQzNCLE1BQU0sS0FBSyxHQUFHLElBQUksS0FBSyxDQUNyQiw2QkFBNkIsR0FBRyxnQkFBZ0IsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUNoRixDQUFDO3dCQUNGLEtBQUssQ0FBQyxRQUFRLENBQUMsR0FBRyxnQkFBZ0IsQ0FBQzt3QkFDbkMsT0FBTyxPQUFPLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO3FCQUM5QjtpQkFDRjtnQkFFRCxNQUFNLE9BQU8sR0FBRyxNQUFNLFVBQVUsQ0FBQyxNQUFNLEVBQUUsSUFBSSxFQUFFLGNBQWMsQ0FBQyxDQUFDO2dCQUMvRCxNQUFNLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxPQUFPLEVBQUUsT0FBTyxDQUFDLENBQUM7Z0JBRXZDLE9BQU8sV0FBVyxDQUFDO1lBQ3JCLENBQUM7WUFDRCxtQkFBbUIsQ0FBQyxNQUFpQixFQUFFLElBQVk7Z0JBQ2pELDhCQUE4QixDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsQ0FBQztZQUMvQyxDQUFDO1lBQ0QsWUFBWSxDQUFDLE1BQWlCO2dCQUM1QiwyQkFBMkIsQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUN0QyxDQUFDO1lBRUQ7Ozs7Ozs7Ozs7O2VBV0c7WUFDSCxTQUFTLEVBQUUsS0FBSztZQUNoQixHQUFHLHlCQUF5QjtTQUM3QixFQUNELEtBQUssQ0FDTixDQUFDO0tBQ0g7SUFFRCxJQUFJLEtBQUssR0FBNEIsSUFBSSxDQUFDO0lBQzFDLElBQUksVUFBVSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsRUFBRTtRQUM3QixLQUFLLEdBQUcsSUFBSSxTQUFTLENBQUMsTUFBTSxDQUFDLEVBQUUsUUFBUSxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7UUFDakQsY0FBUyxDQUNQO1lBQ0UsTUFBTTtZQUNOLE9BQU8sRUFDTCxPQUFPLENBQUMsbUJBQW1CLEtBQUssS0FBSztnQkFDbkMsQ0FBQyxDQUFDLGlCQUFPO2dCQUNULENBQUMsQ0FBQyxHQUFHLEVBQUU7b0JBQ0gsTUFBTSxJQUFJLEtBQUssQ0FBQyx5REFBeUQsQ0FBQyxDQUFDO2dCQUM3RSxDQUFDO1lBQ1AsU0FBUyxFQUFFLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsbUJBQWdCO1lBQzFELFNBQVMsQ0FBQyxHQUFHO2dCQUNYLE1BQU0sRUFBRSxNQUFNLEVBQUUsT0FBTyxFQUFFLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQztnQkFDdEMsTUFBTSxDQUFDLGdCQUFnQixDQUFDLEdBQUcsRUFBRSxRQUFRLENBQUM7Z0JBQ3RDLE1BQU0sQ0FBQyxtQkFBbUIsQ0FBQyxHQUFHLE9BQU8sQ0FBQztnQkFFdEMsTUFBTSwwQkFBMEIsR0FBRyxhQUFhLENBQUMsR0FBRyxDQUFDLGdCQUFnQixJQUFJLEVBQUUsQ0FBQyxDQUFDO2dCQUM3RSxPQUFPLENBQUMsa0JBQWtCLENBQUMsR0FBRyxHQUFHLENBQUMsZ0JBQWdCLElBQUksRUFBRSxDQUFDO2dCQUN6RCxPQUFPLENBQUMsNEJBQTRCLENBQUMsR0FBRywwQkFBMEIsQ0FBQztnQkFFbkUsSUFBSSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsYUFBYSxJQUFJLDBCQUEwQixDQUFDLGVBQWUsQ0FBQyxFQUFFO29CQUNqRjs7Ozs7O3VCQU1HO29CQUNILE9BQU8sQ0FBQyxPQUFPLENBQUMsYUFBYSxHQUFHLE1BQU0sQ0FBQywwQkFBMEIsQ0FBQyxlQUFlLENBQUMsQ0FBQyxDQUFDO2lCQUNyRjtnQkFFRCxNQUFNLENBQUMscUJBQXFCLENBQUMsR0FBRztvQkFDOUIsR0FBRywwQkFBMEI7b0JBQzdCLCtDQUErQztvQkFDL0MsR0FBRyxPQUFPLENBQUMsT0FBTztpQkFDbkIsQ0FBQztZQUNKLENBQUM7WUFDRCxLQUFLLENBQUMsV0FBVyxDQUFDLEdBQUcsRUFBRSxHQUFHO2dCQUN4QixnQ0FBZ0M7Z0JBQ2hDLE1BQU0sTUFBTSxHQUFHLE1BQU0sZ0JBQWdCLEVBQUUsQ0FBQztnQkFFeEMsTUFBTSxFQUFFLE9BQU8sRUFBRSxHQUFHLEdBQUcsQ0FBQztnQkFDeEIsTUFBTSxJQUFJLEdBQUc7b0JBQ1gsTUFBTTtvQkFDTixZQUFZLEVBQUUsRUFBRTtvQkFDaEIsYUFBYSxFQUFFLE9BQU8sQ0FBQyxhQUFhO29CQUNwQyxRQUFRLEVBQUUsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsZUFBSyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSTtvQkFDckQsY0FBYyxFQUFFLE9BQU8sQ0FBQyxTQUFTO2lCQUNsQyxDQUFDO2dCQUVGLDJEQUEyRDtnQkFDM0QsbURBQW1EO2dCQUNuRCxNQUFNLFVBQVUsR0FBRyxDQUFDLFVBQVU7b0JBQzVCLENBQUMsQ0FBQyxVQUFVLENBQUMsNkJBQTZCLEVBQUUsSUFBSSxFQUFFO3dCQUM5QyxPQUFPLEVBQUUsR0FBRzt3QkFDWixPQUFPLEVBQUUsR0FBRzt3QkFDWixPQUFPO3FCQUNSLENBQUM7b0JBQ0osQ0FBQyxDQUFDLElBQUksQ0FBa0IsQ0FBQztnQkFDM0IsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUU7b0JBQ2xCLE9BQU87d0JBQ0wsMEVBQTBFO3dCQUMxRSxJQUFJLHNCQUFZLENBQUMsdUJBQXVCLENBQUM7cUJBQzFDLENBQUM7aUJBQ0g7Z0JBRUQsTUFBTSxTQUFTLEdBQUcseUJBQWUsQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLFVBQVUsQ0FBQyxhQUFhLENBQUMsQ0FBQztnQkFDM0UsSUFBSSxDQUFDLFNBQVMsRUFBRTtvQkFDZCxPQUFPLENBQUMsSUFBSSxzQkFBWSxDQUFDLDhCQUE4QixDQUFDLENBQUMsQ0FBQztpQkFDM0Q7Z0JBQ0QsTUFBTSxjQUFjLEdBQUcsU0FBUyxDQUFDLFNBQVMsS0FBSyxjQUFjLENBQUM7Z0JBRTlELG1GQUFtRjtnQkFDbkYsTUFBTSxnQkFBZ0IsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDO2dCQUV6QyxnREFBZ0Q7Z0JBQ2hELG1EQUFtRDtnQkFDbkQsTUFBTSxnQkFBZ0IsR0FBRyxrQkFBUSxDQUMvQixVQUFVLENBQUMsTUFBTSxFQUNqQixVQUFVLENBQUMsUUFBUSxFQUNuQixxQkFBcUIsQ0FDdEIsQ0FBQztnQkFDRixJQUFJLGdCQUFnQixDQUFDLE1BQU0sRUFBRTtvQkFDM0IsT0FBTyxnQkFBZ0IsQ0FBQztpQkFDekI7Z0JBRUQscUNBQXFDO2dCQUNyQyxpRUFBaUU7Z0JBQ2pFLHFEQUFxRDtnQkFDckQsTUFBTSxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxNQUFNLGdCQUFnQixDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLENBQUM7Z0JBQzlELE1BQU0sbUJBQW1CLEdBQUcsVUFBVSxDQUFDLDhCQUE4QixFQUFFLEVBQUUsRUFBRTtvQkFDekUsT0FBTztvQkFDUCxHQUFHO29CQUNILEdBQUc7b0JBQ0gsU0FBUyxFQUFFLFVBQVUsQ0FBQyxjQUFjO29CQUNwQyxhQUFhLEVBQUUsVUFBVSxDQUFDLGFBQWE7aUJBSXhDLENBQUMsQ0FBQztnQkFDSCxJQUFJLG1CQUFtQixDQUFDLE1BQU0sRUFBRTtvQkFDOUIsTUFBTSxvQkFBb0IsR0FBRyxrQkFBUSxDQUNuQyxVQUFVLENBQUMsTUFBTSxFQUNqQixVQUFVLENBQUMsUUFBUSxFQUNuQixtQkFBbUIsQ0FDcEIsQ0FBQztvQkFDRixJQUFJLG9CQUFvQixDQUFDLE1BQU0sRUFBRTt3QkFDL0IsT0FBTyxvQkFBb0IsQ0FBQztxQkFDN0I7aUJBQ0Y7Z0JBRUQsTUFBTSxPQUFPLEdBQUcsTUFBTSxVQUFVLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxNQUFNLEVBQUUsR0FBRyxDQUFDLEVBQUUsRUFBRSxjQUFjLENBQUMsQ0FBQztnQkFDM0UsTUFBTSxDQUFDLE1BQU0sQ0FBQyxVQUFVLENBQUMsWUFBWSxFQUFFLE9BQU8sQ0FBQyxDQUFDO2dCQUVoRCxPQUFPLFVBQVUsQ0FBQztZQUNwQixDQUFDO1lBQ0QsS0FBSyxDQUFDLE9BQU8sQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLE1BQU07Z0JBQzVCLG1DQUFtQztnQkFDbkMsOEJBQThCLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxNQUFNLEVBQUUsR0FBRyxDQUFDLEVBQUUsQ0FBQyxDQUFDO2dCQUN6RCxNQUFNLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLE1BQU0sZ0JBQWdCLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQztnQkFDOUQsT0FBTyxZQUFZLENBQUMsTUFBTSxFQUFFLEdBQUcsRUFBRSxHQUFHLENBQUMsQ0FBQztZQUN4QyxDQUFDO1lBQ0QsS0FBSyxDQUFDLE1BQU0sQ0FBQyxHQUFHLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxNQUFNO2dCQUNuQyxJQUFJLE1BQU0sQ0FBQyxNQUFNLEVBQUU7b0JBQ2pCLDZCQUE2QjtvQkFDN0IsTUFBTSxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxNQUFNLGdCQUFnQixDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLENBQUM7b0JBQzlELE1BQU0sQ0FBQyxNQUFNLEdBQUcsWUFBWSxDQUFDLE1BQU0sQ0FBQyxNQUFNLEVBQUUsR0FBRyxFQUFFLEdBQUcsQ0FBQyxDQUFDO29CQUN0RCxPQUFPLE1BQU0sQ0FBQztpQkFDZjtZQUNILENBQUM7WUFDRCxVQUFVLENBQUMsR0FBRyxFQUFFLEdBQUc7Z0JBQ2pCLDhCQUE4QixDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsTUFBTSxFQUFFLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUMzRCxDQUFDO1lBQ0QsT0FBTyxDQUFDLEdBQUc7Z0JBQ1QsMkJBQTJCLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUNoRCxDQUFDO1NBQ0YsRUFDRCxLQUFLO1FBQ0w7Ozs7Ozs7O1dBUUc7UUFDSCx5QkFBeUIsRUFBRSxTQUFTLENBQ3JDLENBQUM7S0FDSDtJQUVELDRFQUE0RTtJQUM1RSxlQUFlLENBQUMsRUFBRSxDQUFDLFNBQVMsRUFBRSxDQUFDLEdBQW9CLEVBQUUsTUFBTSxFQUFFLElBQUksRUFBRSxFQUFFO1FBQ25FLE1BQU0sRUFBRSxRQUFRLEdBQUcsRUFBRSxFQUFFLEdBQUcsUUFBUSxDQUFDLEdBQUcsQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUM5QyxNQUFNLGNBQWMsR0FBRyxRQUFRLEtBQUssWUFBWSxDQUFDO1FBQ2pELElBQUksY0FBYyxFQUFFO1lBQ2xCLE1BQU0sUUFBUSxHQUFHLEdBQUcsQ0FBQyxPQUFPLENBQUMsd0JBQXdCLENBQUMsQ0FBQztZQUN2RCxNQUFNLFNBQVMsR0FBRyxLQUFLLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQztnQkFDdkMsQ0FBQyxDQUFDLFFBQVE7Z0JBQ1YsQ0FBQyxDQUFDLFFBQVEsRUFBRSxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDLENBQUM7WUFFNUMsTUFBTSxHQUFHLEdBQ1AsS0FBSztnQkFDTCxTQUFTLEVBQUUsUUFBUSxDQUFDLFlBQVksQ0FBQztnQkFDakMsQ0FBQyxTQUFTLENBQUMsUUFBUSxDQUFDLDBDQUE2QixDQUFDO2dCQUNoRCxDQUFDLENBQUMsS0FBSztnQkFDUCxDQUFDLENBQUMsNkRBQTZEO29CQUM3RCw2REFBNkQ7b0JBQzdELGtDQUFrQztvQkFDbEMsS0FBSyxDQUFDO1lBQ1osSUFBSSxHQUFHLEVBQUU7Z0JBQ1AsR0FBRyxDQUFDLGFBQWEsQ0FBQyxHQUFHLEVBQUUsTUFBYSxFQUFFLElBQUksRUFBRSxFQUFFLENBQUMsRUFBRTtvQkFDL0MsR0FBRyxDQUFDLElBQUksQ0FBQyxZQUFZLEVBQUUsRUFBRSxFQUFFLEdBQUcsQ0FBQyxDQUFDO2dCQUNsQyxDQUFDLENBQUMsQ0FBQzthQUNKO1NBQ0Y7SUFDSCxDQUFDLENBQUMsQ0FBQztBQUNMLENBQUM7QUFwZUQsMEVBb2VDIn0=