"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const event_provider_1 = __importDefault(require("../request-pipeline/request-hooks/events/event-provider")); const state_snapshot_1 = __importDefault(require("./state-snapshot")); const mustache_1 = __importDefault(require("mustache")); const read_file_relative_1 = require("read-file-relative"); const url_1 = require("url"); const cookies_1 = __importDefault(require("./cookies")); const storage_1 = __importDefault(require("../upload/storage")); const command_1 = __importDefault(require("./command")); const generate_unique_id_1 = __importDefault(require("../utils/generate-unique-id")); const service_routes_1 = __importDefault(require("../proxy/service-routes")); const configure_response_event_options_1 = __importDefault(require("../request-pipeline/request-hooks/events/configure-response-event-options")); const cookie_1 = require("../utils/cookie"); const injectables_1 = require("./injectables"); const events_1 = require("events"); const TASK_TEMPLATE = (0, read_file_relative_1.readSync)('../client/task.js.mustache'); class Session extends events_1.EventEmitter { constructor(uploadRoots, options) { super(); this.id = (0, generate_unique_id_1.default)(); this.proxy = null; this.externalProxySettings = null; this.pageLoadCount = 0; this.pendingStateSnapshot = null; this.injectable = { scripts: [...injectables_1.SCRIPTS], styles: [], userScripts: [] }; this._recordMode = false; this._disableHttp2 = false; this._disableCrossDomain = false; this.uploadStorage = new storage_1.default(uploadRoots); this.options = this._getOptions(options); this.cookies = this.createCookies(); this.requestHookEventProvider = new event_provider_1.default(); } _getOptions(options = {}) { return Object.assign({ disablePageCaching: false, allowMultipleWindows: false, windowId: '', }, options); } createCookies() { return new cookies_1.default(); } // State getStateSnapshot() { return new state_snapshot_1.default(this.cookies.serializeJar(), null); } useStateSnapshot(snapshot) { if (!snapshot) throw new Error('"snapshot" parameter cannot be null. Use StateSnapshot.empty() instead of it.'); // NOTE: we don't perform state switch immediately, since there might be // pending requests from current page. Therefore, we perform switch in // onPageRequest handler when new page is requested. this.pendingStateSnapshot = snapshot; } async handleServiceMessage(msg, serverInfo) { if (this[msg.cmd]) return await this[msg.cmd](msg, serverInfo); throw new Error('Malformed service message or message handler is not implemented'); } takePendingSyncCookies() { return this.cookies.takePendingSyncCookies().map(syncCookie => (0, cookie_1.formatSyncCookie)(Object.assign(Object.assign({}, syncCookie), { sid: this.id, isServerSync: true, domain: syncCookie.domain || '', path: syncCookie.path || '', lastAccessed: new Date(), syncKey: '' }))); } _fillTaskScriptTemplate({ serverInfo, isFirstPageLoad, referer, cookie, iframeTaskScriptTemplate, payloadScript, allowMultipleWindows, isRecordMode, windowId }) { var _a, _b; referer = referer && JSON.stringify(referer) || '{{{referer}}}'; cookie = cookie || '{{{cookie}}}'; iframeTaskScriptTemplate = iframeTaskScriptTemplate || '{{{iframeTaskScriptTemplate}}}'; if ((_a = this.proxy) === null || _a === void 0 ? void 0 : _a.options.proxyless) { referer = '""'; cookie = '""'; } const { domain, crossDomainPort } = serverInfo; return mustache_1.default.render(TASK_TEMPLATE, { sessionId: this.id, serviceMsgUrl: domain + service_routes_1.default.messaging, transportWorkerUrl: domain + service_routes_1.default.transportWorker, forceProxySrcForImage: this.requestHookEventProvider.hasRequestEventListeners(), crossDomainPort, isFirstPageLoad, referer, cookie, iframeTaskScriptTemplate, payloadScript, allowMultipleWindows, isRecordMode, windowId: windowId || '', proxyless: ((_b = this.proxy) === null || _b === void 0 ? void 0 : _b.options.proxyless) || false, disableCrossDomain: this.isCrossDomainDisabled() || false, }); } async getIframeTaskScriptTemplate(serverInfo) { const taskScriptTemplate = this._fillTaskScriptTemplate({ serverInfo, isFirstPageLoad: false, referer: null, cookie: null, iframeTaskScriptTemplate: null, payloadScript: await this.getIframePayloadScript(true), allowMultipleWindows: this.options.allowMultipleWindows, isRecordMode: this._recordMode, }); return JSON.stringify(taskScriptTemplate); } async getTaskScript({ referer, cookieUrl, serverInfo, isIframe, withPayload, windowId }) { const cookies = JSON.stringify(this.cookies.getClientString(cookieUrl)); let payloadScript = ''; if (withPayload) payloadScript = isIframe ? await this.getIframePayloadScript(false) : await this.getPayloadScript(); const taskScript = this._fillTaskScriptTemplate({ serverInfo, isFirstPageLoad: this.pageLoadCount === 0, referer, cookie: cookies, iframeTaskScriptTemplate: await this.getIframeTaskScriptTemplate(serverInfo), payloadScript, allowMultipleWindows: this.options.allowMultipleWindows, isRecordMode: this._recordMode, windowId, }); this.pageLoadCount++; return taskScript; } setExternalProxySettings(proxySettings) { if (typeof proxySettings === 'string') proxySettings = { url: proxySettings }; if (!proxySettings || !proxySettings.url) return; const { url, bypassRules } = proxySettings; const parsedUrl = (0, url_1.parse)('http://' + url); let settings = null; if (parsedUrl && parsedUrl.host) { settings = { host: parsedUrl.host, hostname: parsedUrl.hostname || '', }; if (bypassRules) settings.bypassRules = bypassRules; if (parsedUrl.port) settings.port = parsedUrl.port; if (parsedUrl.auth) { settings.proxyAuth = parsedUrl.auth; settings.authHeader = 'Basic ' + Buffer.from(parsedUrl.auth).toString('base64'); } } this.externalProxySettings = settings; } onPageRequest(ctx) { if (!this.pendingStateSnapshot) return; this.cookies.setJar(this.pendingStateSnapshot.cookies); if (this.pendingStateSnapshot.storages) ctx.restoringStorages = this.pendingStateSnapshot.storages; this.pendingStateSnapshot = null; } // Request hooks _ensureConfigureResponseEventData(eventId) { let eventData = this.requestHookEventProvider.requestHookEventData.configureResponse.get(eventId); if (!eventData) { eventData = { opts: configure_response_event_options_1.default.DEFAULT, setHeaders: [], removedHeaders: [], }; } return eventData; } _updateConfigureResponseEventData(eventId, updateFn) { const eventData = this._ensureConfigureResponseEventData(eventId); updateFn(eventData); this.requestHookEventProvider.requestHookEventData.configureResponse.set(eventId, eventData); } removeConfigureResponseEventData(eventId) { this.requestHookEventProvider.requestHookEventData.configureResponse.delete(eventId); } async setConfigureResponseEventOptions(eventId, opts) { this._updateConfigureResponseEventData(eventId, eventData => { eventData.opts = opts; }); } async setHeaderOnConfigureResponseEvent(eventId, headerName, headerValue) { this._updateConfigureResponseEventData(eventId, eventData => { eventData.setHeaders.push({ name: headerName, value: headerValue }); }); } async removeHeaderOnConfigureResponseEvent(eventId, headerName) { this._updateConfigureResponseEventData(eventId, eventData => { eventData.removedHeaders.push(headerName); }); } setRecordMode() { this._recordMode = true; } disableHttp2() { this._disableHttp2 = true; } isHttp2Disabled() { return this._disableHttp2; } disableCrossDomain() { this._disableCrossDomain = true; } isCrossDomainDisabled() { return this._disableCrossDomain; } // Service message handlers async [command_1.default.uploadFiles](msg) { return await this.uploadStorage.store(msg.fileNames, msg.data); } async [command_1.default.getUploadedFiles](msg) { return await this.uploadStorage.get(msg.filePaths); } } exports.default = Session;module.exports = exports.default;