author | Gurzau Raul <rgurzau@mozilla.com> |
Wed, 25 Apr 2018 12:38:44 +0300 | |
changeset 415624 | 6696fd50114a8c105771f1e87e0b76cbf5761c26 |
parent 415623 | aa4185068f2e3ffb1377afa0e0d95b3c5ac0a04c (current diff) |
parent 415512 | a83a4ef50f6ca754ec451320dfefbffa707bad1a (diff) |
child 415625 | 37320f8b708c6aad8914e0d1da0b90affc1b613f |
push id | 33901 |
push user | apavel@mozilla.com |
push date | Thu, 26 Apr 2018 06:05:37 +0000 |
treeherder | mozilla-central@b62ad926cf2a [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 61.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/.taskcluster.yml +++ b/.taskcluster.yml @@ -107,27 +107,27 @@ tasks: ACTION_TASK_GROUP_ID: '${ownTaskId}' ACTION_TASK_ID: {$json: {$eval: 'taskId'}} ACTION_TASK: {$json: {$eval: 'task'}} ACTION_INPUT: {$json: {$eval: 'input'}} ACTION_CALLBACK: '${action.cb_name}' ACTION_PARAMETERS: {$json: {$eval: 'parameters'}} cache: - level-${repository.level}-checkouts-sparse-v1: /builds/worker/checkouts + level-${repository.level}-checkouts-sparse-v2: /builds/worker/checkouts features: taskclusterProxy: true chainOfTrust: true # Note: This task is built server side without the context or tooling that # exist in tree so we must hard code the hash # XXX Changing this will break Chain of Trust without an associated puppet and # scriptworker patch! - image: 'taskcluster/decision:2.0.0@sha256:4039fd878e5700b326d4a636e28c595c053fbcb53909c1db84ad1f513cf644ef' + image: 'taskcluster/decision:2.1.0@sha256:6db3b697d7a3c7aba440d72f04199331b872111cefff57206b8b8b1d53230360' maxRunTime: 1800 command: - /builds/worker/bin/run-task - '--vcs-checkout=/builds/worker/checkouts/gecko' - '--sparse-profile=build/sparse-profiles/taskgraph' - '--'
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1423,16 +1423,19 @@ pref("identity.mobilepromo.ios", "https: // Migrate any existing Firefox Account data from the default profile to the // Developer Edition profile. #ifdef MOZ_DEV_EDITION pref("identity.fxaccounts.migrateToDevEdition", true); #else pref("identity.fxaccounts.migrateToDevEdition", false); #endif +// If activated, send tab will use the new FxA messages backend. +pref("identity.fxaccounts.messages.enabled", false); + // On GTK, we now default to showing the menubar only when alt is pressed: #ifdef MOZ_WIDGET_GTK pref("ui.key.menuAccessKeyFocuses", true); #endif #ifdef NIGHTLY_BUILD pref("media.eme.vp9-in-mp4.enabled", true); #else
--- a/browser/base/content/browser-sidebar.js +++ b/browser/base/content/browser-sidebar.js @@ -184,16 +184,21 @@ var SidebarUI = { appcontent.ordinal = boxOrdinal; // Indicate we've switched ordering to the box this._box.setAttribute("positionend", true); } else { this._box.removeAttribute("positionend"); } this.hideSwitcherPanel(); + + let content = SidebarUI.browser.contentWindow; + if (content && content.updatePosition) { + content.updatePosition(); + } }, /** * Try and adopt the status of the sidebar from another window. * @param {Window} sourceWindow - Window to use as a source for sidebar status. * @return true if we adopted the state, or false if the caller should * initialize the state itself. */
--- a/browser/base/content/browser-sync.js +++ b/browser/base/content/browser-sync.js @@ -314,20 +314,44 @@ var gSync = { }, openSendToDevicePromo() { let url = this.PRODUCT_INFO_BASE_URL; url += "send-tabs/?utm_source=" + Services.appinfo.name.toLowerCase(); switchToTabHavingURI(url, true, { replaceQueryString: true }); }, - sendTabToDevice(url, clientId, title) { - Weave.Service.clientsEngine.sendURIToClientForDisplay(url, clientId, title).catch(e => { - console.error("Could not send tab to device", e); - }); + async sendTabToDevice(url, clients, title) { + let devices; + try { + devices = await fxAccounts.getDeviceList(); + } catch (e) { + console.error("Could not get the FxA device list", e); + devices = []; // We can still run in degraded mode. + } + const toSendMessages = []; + for (const client of clients) { + const device = devices.find(d => d.id == client.fxaDeviceId); + if (device && fxAccounts.messages.canReceiveSendTabMessages(device)) { + toSendMessages.push(device); + } else { + try { + await Weave.Service.clientsEngine.sendURIToClientForDisplay(url, client.id, title); + } catch (e) { + console.error("Could not send tab to device", e); + } + } + } + if (toSendMessages.length) { + try { + await fxAccounts.messages.sendTab(toSendMessages, {url, title}); + } catch (e) { + console.error("Could not send tab to device", e); + } + } }, populateSendTabToDevicesMenu(devicesPopup, url, title, createDeviceNodeFn) { if (!createDeviceNodeFn) { createDeviceNodeFn = (clientId, name, clientType, lastModified) => { let eltName = name ? "menuitem" : "menuseparator"; return document.createElement(eltName); }; @@ -358,28 +382,33 @@ var gSync = { this._appendSendTabVerify(fragment, createDeviceNodeFn); } else /* status is STATUS_NOT_CONFIGURED */ { this._appendSendTabUnconfigured(fragment, createDeviceNodeFn); } devicesPopup.appendChild(fragment); }, + // TODO: once our transition from the old-send tab world is complete, + // this list should be built using the FxA device list instead of the client + // collection. _appendSendTabDeviceList(fragment, createDeviceNodeFn, url, title) { + const onSendAllCommand = (event) => { + this.sendTabToDevice(url, this.remoteClients, title); + }; const onTargetDeviceCommand = (event) => { - let clients = event.target.getAttribute("clientId") ? - [event.target.getAttribute("clientId")] : - this.remoteClients.map(client => client.id); - - clients.forEach(clientId => this.sendTabToDevice(url, clientId, title)); + const clientId = event.target.getAttribute("clientId"); + const client = this.remoteClients.find(c => c.id == clientId); + this.sendTabToDevice(url, [client], title); }; function addTargetDevice(clientId, name, clientType, lastModified) { const targetDevice = createDeviceNodeFn(clientId, name, clientType, lastModified); - targetDevice.addEventListener("command", onTargetDeviceCommand, true); + targetDevice.addEventListener("command", clientId ? onTargetDeviceCommand : + onSendAllCommand, true); targetDevice.classList.add("sync-menuitem", "sendtab-target"); targetDevice.setAttribute("clientId", clientId); targetDevice.setAttribute("clientType", clientType); targetDevice.setAttribute("label", name); fragment.appendChild(targetDevice); } const clients = this.remoteClients;
--- a/browser/base/content/webext-panels.js +++ b/browser/base/content/webext-panels.js @@ -88,16 +88,27 @@ var gBrowser = { getTabModalPromptBox(browser) { if (!browser.tabModalPromptBox) { browser.tabModalPromptBox = new TabModalPromptBox(browser); } return browser.tabModalPromptBox; }, }; +function updatePosition() { + // We need both of these to make sure we update the position + // after any lower level updates have finished. + requestAnimationFrame(() => setTimeout(() => { + let browser = document.getElementById("webext-panels-browser"); + if (browser && browser.isRemoteBrowser) { + browser.frameLoader.requestUpdatePosition(); + } + }, 0)); +} + function loadPanel(extensionId, extensionUrl, browserStyle) { let browserEl = document.getElementById("webext-panels-browser"); if (browserEl) { if (browserEl.currentURI.spec === extensionUrl) { return; } // Forces runtime disconnect. Remove the stack (parent). browserEl.parentNode.remove();
--- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -439,16 +439,17 @@ BrowserGlue.prototype = { this._onVerifyLoginNotification(JSON.parse(data)); break; case "fxaccounts:device_disconnected": data = JSON.parse(data); if (data.isLocalDevice) { this._onDeviceDisconnected(); } break; + case "fxaccounts:messages:display-tabs": case "weave:engine:clients:display-uris": this._onDisplaySyncURIs(subject); break; case "session-save": this._setPrefToSaveSession(true); subject.QueryInterface(Ci.nsISupportsPRBool); subject.data = true; break; @@ -593,16 +594,17 @@ BrowserGlue.prototype = { os.addObserver(this, "browser-lastwindow-close-requested"); os.addObserver(this, "browser-lastwindow-close-granted"); } os.addObserver(this, "weave:service:ready"); os.addObserver(this, "fxaccounts:onverified"); os.addObserver(this, "fxaccounts:device_connected"); os.addObserver(this, "fxaccounts:verify_login"); os.addObserver(this, "fxaccounts:device_disconnected"); + os.addObserver(this, "fxaccounts:messages:display-tabs"); os.addObserver(this, "weave:engine:clients:display-uris"); os.addObserver(this, "session-save"); os.addObserver(this, "places-init-complete"); os.addObserver(this, "distribution-customization-complete"); os.addObserver(this, "handle-xul-text-link"); os.addObserver(this, "profile-before-change"); os.addObserver(this, "keyword-search"); os.addObserver(this, "browser-search-engine-modified"); @@ -635,16 +637,17 @@ BrowserGlue.prototype = { os.removeObserver(this, "browser-lastwindow-close-requested"); os.removeObserver(this, "browser-lastwindow-close-granted"); } os.removeObserver(this, "weave:service:ready"); os.removeObserver(this, "fxaccounts:onverified"); os.removeObserver(this, "fxaccounts:device_connected"); os.removeObserver(this, "fxaccounts:verify_login"); os.removeObserver(this, "fxaccounts:device_disconnected"); + os.removeObserver(this, "fxaccounts:messages:display-tabs"); os.removeObserver(this, "weave:engine:clients:display-uris"); os.removeObserver(this, "session-save"); if (this._bookmarksBackupIdleTime) { this._idleService.removeIdleObserver(this, this._bookmarksBackupIdleTime); delete this._bookmarksBackupIdleTime; } if (this._lateTasksIdleObserver) { this._idleService.removeIdleObserver(this._lateTasksIdleObserver, LATE_TASKS_IDLE_TIME_SEC); @@ -2538,17 +2541,17 @@ BrowserGlue.prototype = { tab.setAttribute("attention", true); return tab; }; const firstTab = await openTab(URIs[0]); await Promise.all(URIs.slice(1).map(URI => openTab(URI))); let title, body; - const deviceName = Weave.Service.clientsEngine.getClientName(URIs[0].clientId); + const deviceName = URIs[0].sender.name; const bundle = Services.strings.createBundle("chrome://browser/locale/accounts.properties"); if (URIs.length == 1) { // Due to bug 1305895, tabs from iOS may not have device information, so // we have separate strings to handle those cases. (See Also // unnamedTabsArrivingNotificationNoDevice.body below) if (deviceName) { title = bundle.formatStringFromName("tabArrivingNotificationWithDevice.title", [deviceName], 1); } else { @@ -2562,17 +2565,17 @@ BrowserGlue.prototype = { if (win.gURLBar) { body = win.gURLBar.trimValue(body); } if (wasTruncated) { body = bundle.formatStringFromName("singleTabArrivingWithTruncatedURL.body", [body], 1); } } else { title = bundle.GetStringFromName("multipleTabsArrivingNotification.title"); - const allSameDevice = URIs.every(URI => URI.clientId == URIs[0].clientId); + const allSameDevice = URIs.every(URI => URI.sender.id == URIs[0].sender.id); const unknownDevice = allSameDevice && !deviceName; let tabArrivingBody; if (unknownDevice) { tabArrivingBody = "unnamedTabsArrivingNotificationNoDevice.body"; } else if (allSameDevice) { tabArrivingBody = "unnamedTabsArrivingNotification2.body"; } else { tabArrivingBody = "unnamedTabsArrivingNotificationMultiple2.body";
--- a/browser/extensions/screenshots/bootstrap.js +++ b/browser/extensions/screenshots/bootstrap.js @@ -104,17 +104,17 @@ const LibraryButton = { // this will be null, and we bail out early. if (!libraryViewInsertionPoint) { return; } const parent = libraryViewInsertionPoint.parentNode; const {nextSibling} = libraryViewInsertionPoint; const item = win.document.createElement("toolbarbutton"); item.className = "subviewbutton subviewbutton-iconic"; - item.addEventListener("command", () => win.openTrustedLinkIn(this.PAGE_TO_OPEN, "tab")); + item.addEventListener("command", () => win.openWebLinkIn(this.PAGE_TO_OPEN, "tab")); item.id = this.ITEM_ID; const iconURL = this.ICON_URL; item.setAttribute("image", iconURL); item.setAttribute("label", this.LABEL); parent.insertBefore(item, nextSibling); }, };
--- a/browser/extensions/screenshots/install.rdf +++ b/browser/extensions/screenshots/install.rdf @@ -7,14 +7,14 @@ <em:targetApplication> <Description> <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <!--Firefox--> <em:minVersion>57.0a1</em:minVersion> <em:maxVersion>*</em:maxVersion> </Description> </em:targetApplication> <em:type>2</em:type> - <em:version>30.1.0</em:version> + <em:version>32.1.0</em:version> <em:bootstrap>true</em:bootstrap> <em:homepageURL>https://screenshots.firefox.com/</em:homepageURL> <em:multiprocessCompatible>true</em:multiprocessCompatible> </Description> </RDF>
--- a/browser/extensions/screenshots/moz.build +++ b/browser/extensions/screenshots/moz.build @@ -152,16 +152,28 @@ FINAL_TARGET_FILES.features['screenshots FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["fy_NL"] += [ 'webextension/_locales/fy_NL/messages.json' ] FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["ga_IE"] += [ 'webextension/_locales/ga_IE/messages.json' ] +FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["gd"] += [ + 'webextension/_locales/gd/messages.json' +] + +FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["gl"] += [ + 'webextension/_locales/gl/messages.json' +] + +FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["gn"] += [ + 'webextension/_locales/gn/messages.json' +] + FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["gu_IN"] += [ 'webextension/_locales/gu_IN/messages.json' ] FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["he"] += [ 'webextension/_locales/he/messages.json' ] @@ -240,32 +252,40 @@ FINAL_TARGET_FILES.features['screenshots FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["mk"] += [ 'webextension/_locales/mk/messages.json' ] FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["ml"] += [ 'webextension/_locales/ml/messages.json' ] +FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["mn"] += [ + 'webextension/_locales/mn/messages.json' +] + FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["mr"] += [ 'webextension/_locales/mr/messages.json' ] FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["ms"] += [ 'webextension/_locales/ms/messages.json' ] FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["my"] += [ 'webextension/_locales/my/messages.json' ] FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["nb_NO"] += [ 'webextension/_locales/nb_NO/messages.json' ] +FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["ne_NP"] += [ + 'webextension/_locales/ne_NP/messages.json' +] + FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["nl"] += [ 'webextension/_locales/nl/messages.json' ] FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["nn_NO"] += [ 'webextension/_locales/nn_NO/messages.json' ]
--- a/browser/extensions/screenshots/test/browser/browser_screenshots_ui_check.js +++ b/browser/extensions/screenshots/test/browser/browser_screenshots_ui_check.js @@ -10,62 +10,62 @@ function checkElements(expectPresent, l) async function togglePageActionPanel() { await promiseOpenPageActionPanel(); EventUtils.synthesizeMouseAtCenter(BrowserPageActions.mainButtonNode, {}); await promisePageActionPanelEvent("popuphidden"); } function promiseOpenPageActionPanel() { - let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); + const dwu = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); return BrowserTestUtils.waitForCondition(() => { // Wait for the main page action button to become visible. It's hidden for // some URIs, so depending on when this is called, it may not yet be quite // visible. It's up to the caller to make sure it will be visible. info("Waiting for main page action button to have non-0 size"); - let bounds = dwu.getBoundsWithoutFlushing(BrowserPageActions.mainButtonNode); + const bounds = dwu.getBoundsWithoutFlushing(BrowserPageActions.mainButtonNode); return bounds.width > 0 && bounds.height > 0; }).then(() => { // Wait for the panel to become open, by clicking the button if necessary. info("Waiting for main page action panel to be open"); - if (BrowserPageActions.panelNode.state == "open") { + if (BrowserPageActions.panelNode.state === "open") { return Promise.resolve(); } - let shownPromise = promisePageActionPanelEvent("popupshown"); + const shownPromise = promisePageActionPanelEvent("popupshown"); EventUtils.synthesizeMouseAtCenter(BrowserPageActions.mainButtonNode, {}); return shownPromise; }).then(() => { // Wait for items in the panel to become visible. return promisePageActionViewChildrenVisible(BrowserPageActions.mainViewNode); }); } function promisePageActionPanelEvent(eventType) { return new Promise(resolve => { - let panel = BrowserPageActions.panelNode; - if ((eventType == "popupshown" && panel.state == "open") || - (eventType == "popuphidden" && panel.state == "closed")) { + const panel = BrowserPageActions.panelNode; + if ((eventType === "popupshown" && panel.state === "open") || + (eventType === "popuphidden" && panel.state === "closed")) { executeSoon(resolve); return; } panel.addEventListener(eventType, () => { executeSoon(resolve); }, { once: true }); }); } function promisePageActionViewChildrenVisible(panelViewNode) { info("promisePageActionViewChildrenVisible waiting for a child node to be visible"); - let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); + const dwu = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); return BrowserTestUtils.waitForCondition(() => { - let bodyNode = panelViewNode.firstChild; - for (let childNode of bodyNode.childNodes) { - let bounds = dwu.getBoundsWithoutFlushing(childNode); + const bodyNode = panelViewNode.firstChild; + for (const childNode of bodyNode.childNodes) { + const bounds = dwu.getBoundsWithoutFlushing(childNode); if (bounds.width > 0 && bounds.height > 0) { return true; } } return false; }); } @@ -73,18 +73,18 @@ add_task(async function() { await promiseScreenshotsEnabled(); registerCleanupFunction(async function() { await promiseScreenshotsReset(); }); // Toggle the page action panel to get it to rebuild itself. An actionable // page must be opened first. - let url = "http://example.com/browser_screenshots_ui_check"; - await BrowserTestUtils.withNewTab(url, async () => { + const url = "http://example.com/browser_screenshots_ui_check"; + await BrowserTestUtils.withNewTab(url, async () => { // eslint-disable-line space-before-function-paren await togglePageActionPanel(); await BrowserTestUtils.waitForCondition( () => document.getElementById(BUTTON_ID), "Screenshots button should be present", 100, 100); checkElements(true, [BUTTON_ID]); });
--- a/browser/extensions/screenshots/webextension/_locales/ach/messages.json +++ b/browser/extensions/screenshots/webextension/_locales/ach/messages.json @@ -24,27 +24,41 @@ "message": "Gwok potbuk weng" }, "cancelScreenshot": { "message": "Juki" }, "downloadScreenshot": { "message": "Gam" }, + "downloadOnlyDetailsESR": { + "message": "Itye ka tic ki Firefox pi ESR." + }, "notificationLinkCopiedTitle": { "message": "Ki loko kakube" }, "notificationLinkCopiedDetails": { "message": "Ki loko kakube me cal mamegi i bao me coc. Dii $META_KEY$-V me mwono ne.", "placeholders": { "meta_key": { "content": "$1" } } }, + "copyScreenshot": { + "message": "Loki" + }, + "imageCroppedWarning": { + "message": "Ki ngolo cal ma odoko $PIXELS$px.", + "placeholders": { + "pixels": { + "content": "$1" + } + } + }, "requestErrorTitle": { "message": "Pe tye katic." }, "requestErrorDetails": { "message": "Timwa kica! Pe onongo wa twero gwoko cal mamegi. Tim ber item doki lacen." }, "connectionErrorTitle": { "message": "Pe watwero kube ki cal me wang kio mamegi."
--- a/browser/extensions/screenshots/webextension/_locales/bs/messages.json +++ b/browser/extensions/screenshots/webextension/_locales/bs/messages.json @@ -124,17 +124,17 @@ }, "tourBodyIntro": { "message": "Snimite, sačuvajte i dijelite snimke bez da napuštate Firefox." }, "tourHeaderPageAction": { "message": "Novi način da sačuvate" }, "tourBodyPageAction": { - "message": "Proširite meni radnji stranice u adresnoj traci svaki put kada želite snimiti ekran." + "message": "Proširite meni s radnjama stranice u adresnoj traci svaki put kada želite snimiti ekran." }, "tourHeaderClickAndDrag": { "message": "Uslikajte baš ono što želite" }, "tourBodyClickAndDrag": { "message": "Kliknite i povucite ako želite snimiti samo dio stranice. Također možete označiti vaš odabir tako da preko njega stavite pokazivač miša." }, "tourHeaderFullPage": {
--- a/browser/extensions/screenshots/webextension/_locales/ca/messages.json +++ b/browser/extensions/screenshots/webextension/_locales/ca/messages.json @@ -39,16 +39,22 @@ "message": "En una finestra de navegació privada." }, "downloadOnlyDetailsThirdParty": { "message": "S'han desactivat les galetes de tercers." }, "downloadOnlyDetailsNeverRemember": { "message": "S'ha activat l'opció «No recordarà mai l'historial»." }, + "downloadOnlyDetailsESR": { + "message": "Feu servir el Firefox ESR." + }, + "downloadOnlyDetailsNoUploadPref": { + "message": "Les pujades estan inhabilitades." + }, "notificationLinkCopiedTitle": { "message": "S'ha copiat l'enllaç" }, "notificationLinkCopiedDetails": { "message": "L'enllaç de la captura s'ha copiat al porta-retalls. Premeu $META_KEY$-V per enganxar-lo.", "placeholders": { "meta_key": { "content": "$1"
--- a/browser/extensions/screenshots/webextension/_locales/de/messages.json +++ b/browser/extensions/screenshots/webextension/_locales/de/messages.json @@ -39,16 +39,22 @@ "message": "In einem privaten Fenster." }, "downloadOnlyDetailsThirdParty": { "message": "Cookies von Drittanbietern sind deaktiviert." }, "downloadOnlyDetailsNeverRemember": { "message": "„Chronik niemals anlegen“ ist aktiviert." }, + "downloadOnlyDetailsESR": { + "message": "Sie verwenden Firefox ESR." + }, + "downloadOnlyDetailsNoUploadPref": { + "message": "Uploads wurden deaktiviert." + }, "notificationLinkCopiedTitle": { "message": "Link kopiert" }, "notificationLinkCopiedDetails": { "message": "Der Link zu Ihrem Bildschirmfoto wurde in die Zwischenablage kopiert. Drücken Sie $META_KEY$-V zum Einfügen.", "placeholders": { "meta_key": { "content": "$1"
--- a/browser/extensions/screenshots/webextension/_locales/el/messages.json +++ b/browser/extensions/screenshots/webextension/_locales/el/messages.json @@ -24,16 +24,19 @@ "message": "Αποθήκευση ολόκληρης σελίδας" }, "cancelScreenshot": { "message": "Ακύρωση" }, "downloadScreenshot": { "message": "Λήψη" }, + "downloadOnlyDetailsPrivate": { + "message": "Σε ένα παράθυρο ιδιωτικής περιήγησης." + }, "downloadOnlyDetailsThirdParty": { "message": "Τα cookies τρίτων είναι απενεργοποιημένα." }, "downloadOnlyDetailsESR": { "message": "Χρησιμοποιείτε το Firefox ESR." }, "downloadOnlyDetailsNoUploadPref": { "message": "Οι μεταφορτώσεις έχουν απενεργοποιηθεί."
--- a/browser/extensions/screenshots/webextension/_locales/et/messages.json +++ b/browser/extensions/screenshots/webextension/_locales/et/messages.json @@ -39,30 +39,47 @@ "message": "oled privaatse veebilehitsemise aknas" }, "downloadOnlyDetailsThirdParty": { "message": "kolmanda osapoole küpsised on keelatud" }, "downloadOnlyDetailsNeverRemember": { "message": "“ei säilita ajalugu” on lubatud" }, + "downloadOnlyDetailsESR": { + "message": "Sa kasutad Firefox ESRi." + }, + "downloadOnlyDetailsNoUploadPref": { + "message": "Üleslaadimised on keelatud." + }, "notificationLinkCopiedTitle": { "message": "Link kopeeriti" }, "notificationLinkCopiedDetails": { "message": "Link sinu pildile kopeeriti lõikepuhvrisse. Asetamiseks vajuta $META_KEY$-V.", "placeholders": { "meta_key": { "content": "$1" } } }, "copyScreenshot": { "message": "Kopeeri" }, + "notificationImageCopiedTitle": { + "message": "Pilt kopeeriti" + }, + "notificationImageCopiedDetails": { + "message": "Sinu pilt kopeeriti vahemällu. Asetamiseks vajuta $META_KEY$-V.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, "imageCroppedWarning": { "message": "See pilt on vähendatud $PIXELS$-le pikslile.", "placeholders": { "pixels": { "content": "$1" } } },
new file mode 100644 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/gd/messages.json @@ -0,0 +1,184 @@ +{ + "addonDescription": { + "message": "Tog cliopaichean is glacaidhean-sgrìn on lìon is sàbhail iad rè seal no gu buan." + }, + "addonAuthorsList": { + "message": "Mozilla <screenshots-feedback@mozilla.com>" + }, + "contextMenuLabel": { + "message": "Tog glacadh-sgrìn" + }, + "myShotsLink": { + "message": "Na glacaidhean agam" + }, + "screenshotInstructions": { + "message": "Dèan briogadh no slaodadh air an duilleag airson raon a thaghadh. Brùth ESC airson sgur dheth." + }, + "saveScreenshotSelectedArea": { + "message": "Sàbhail" + }, + "saveScreenshotVisibleArea": { + "message": "Sàbhail na tha ri fhaicinn" + }, + "saveScreenshotFullPage": { + "message": "Sàbhail an duilleag shlàn" + }, + "cancelScreenshot": { + "message": "Sguir dheth" + }, + "downloadScreenshot": { + "message": "Luchdaich a-nuas" + }, + "downloadOnlyNotice": { + "message": "Tha thu sa mhodh luchdaidh a-nuas a-mhàin." + }, + "downloadOnlyDetails": { + "message": "Bidh gleus glacaidhean-sgrìn Firefox sa mhodh luchdaidh a-nuas gu fèin-obrachail sna suidheachaidhean a leanas:" + }, + "downloadOnlyDetailsPrivate": { + "message": "Ann an uinneag brabhsaidh phrìobhaidich." + }, + "downloadOnlyDetailsThirdParty": { + "message": "Tha briosgaidean threas-phàrtaidhean à comas." + }, + "downloadOnlyDetailsNeverRemember": { + "message": "Tha “Na cuimhnich an eachdraidh idir” an comas." + }, + "downloadOnlyDetailsESR": { + "message": "Tha thu a’ cleachdadh Firefox ESR." + }, + "downloadOnlyDetailsNoUploadPref": { + "message": "Chaidh an luchdadh suas a chur à comas." + }, + "notificationLinkCopiedTitle": { + "message": "Chaidh lethbhreac a dhèanamh dhen cheangal" + }, + "notificationLinkCopiedDetails": { + "message": "Chaidh lethbhreac de cheangal a’ ghlacaidh agad a chur air an stòr-bhòrd. Brùth $META_KEY$-V airson a chur ann.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "copyScreenshot": { + "message": "Dèan lethbhreac" + }, + "notificationImageCopiedTitle": { + "message": "Chaidh lethbhreac a dhèanamh dhen ghlacadh" + }, + "notificationImageCopiedDetails": { + "message": "Chaidh lethbhreac dhen ghlacadh agad a chur air an stòr-bhòrd. Brùth $META_KEY$-V airson a chur ann.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "imageCroppedWarning": { + "message": "Chaidh an dealbh a bhearradh is tha e $PIXELS$px a-nis.", + "placeholders": { + "pixels": { + "content": "$1" + } + } + }, + "requestErrorTitle": { + "message": "Tuibriste." + }, + "requestErrorDetails": { + "message": "Tha sinn duilich! Cha b’ urrainn dhuinn an glacadh agad a shàbhaladh. Feuch ris a-rithist an ceann greis." + }, + "connectionErrorTitle": { + "message": "Cha b’ urrainn dhuinn na glacaidhean-sgrìn agad a shàbhaladh." + }, + "connectionErrorDetails": { + "message": "Thoir sùil air a’ cheangal agad ris an eadar-lìon. Ma tha ceangal agad ris, dh’fhaoidte gu bheil duilgheadas sealach aig seirbheis glacaidhean-sgrìn Firefox." + }, + "loginErrorDetails": { + "message": "Cha b’ urrainn dhuinn an glacadh agad a shàbhaladh air sgàth duilgheadas le seirbheis glacaidhean-sgrìn Firefox. Feuch ris a-rithist an ceann greis." + }, + "unshootablePageErrorTitle": { + "message": "Chan urrainn dhuinn glacadh-sgrìn a dhèanamh dhen duilleag seo." + }, + "unshootablePageErrorDetails": { + "message": "Chan eil duilleag-lìn àbhaisteach a tha seo ’s chan urrainn dhut glacadh-sgrìn a dhèanamh dheth." + }, + "selfScreenshotErrorTitle": { + "message": "Chan urrainn dhut glacadh a thogail de dhuilleag ghlacaidhean-sgrìn Firefox!" + }, + "emptySelectionErrorTitle": { + "message": "Tha na thagh thu ro bheag" + }, + "privateWindowErrorTitle": { + "message": "Tha gleus nan glacaidhean-sgrìn à comas ann am modh a’ bhrabhsaidh phrìobhaidich" + }, + "privateWindowErrorDetails": { + "message": "Tha sinn duilich mu dhèidhinn. Tha sinn ag obair air agus an dòchas gum bi e ri làimh a dh’aithghearr." + }, + "genericErrorTitle": { + "message": "Ìoc! Sin glacaidhean-sgrìn Firefox air feadh na fìdhle." + }, + "genericErrorDetails": { + "message": "Chan eil sinn cinnteach dè thachair. A bheil thu airson feuchainn ris a-rithist no glacadh a thogail de dhuilleag eile?" + }, + "tourBodyIntro": { + "message": "Tog, sàbhail is co-roinn glacadh-sgrìn gun Firefix fhàgail." + }, + "tourHeaderPageAction": { + "message": "Dòigh ùr airson sàbhaladh" + }, + "tourBodyPageAction": { + "message": "Leudaich clàr-taice gnìomhan na duilleige ann am bàr an t-seòlaidh uair sam bith a tha thu airson glacadh-sgrìn a thogail." + }, + "tourHeaderClickAndDrag": { + "message": "Na glac ach dìreach na tha a dhìth ort" + }, + "tourBodyClickAndDrag": { + "message": "Dèan briogadh is slaodadh airson earrann de dhuilleag a ghlacadh. ’S urrainn dhut fantainn os cionn rud cuideachd airson na thagh thu a shoillseachadh." + }, + "tourHeaderFullPage": { + "message": "Glac uinneagan no duilleagan slàna" + }, + "tourBodyFullPage": { + "message": "Tagh na putanan air an taobh deas gu h-àrd airson na tha ri fhaicinn san uinneag a ghlacadh no airson duilleag shlàn a ghlacadh." + }, + "tourHeaderDownloadUpload": { + "message": "Do thoil fhèin" + }, + "tourBodyDownloadUpload": { + "message": "Sàbhail na glacaidhean bearrte air an lìon ach am bi e furasta an co-roinneadh no luchdaich a-nuas iad dhan choimpiutair agad. ’S urrainn dhut briogadh air a’ phutan “Na glacaidhean agam” cuideachd is chì thu gach glacadh a thog thu." + }, + "tourSkip": { + "message": "LEUM SEACHAD" + }, + "tourNext": { + "message": "An ath-shleamhnag" + }, + "tourPrevious": { + "message": "An t-sleamhnag roimhe" + }, + "tourDone": { + "message": "Dèanta" + }, + "termsAndPrivacyNotice2": { + "message": "Ma chleachdas tu gleus nan glacaidhean-sgrìn aig Firefox, bidh thu ag aontachadh ris na $TERMSANDPRIVACYNOTICETERMSLINK$ agus $TERMSANDPRIVACYNOTICEPRIVACYLINK$ againn.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "na teirmichean" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "an aithris prìobhaideachd" + }, + "libraryLabel": { + "message": "Glacaidhean-sgrìn" + } +} \ No newline at end of file
new file mode 100644 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/gl/messages.json @@ -0,0 +1,64 @@ +{ + "myShotsLink": { + "message": "As miñas capturas" + }, + "saveScreenshotSelectedArea": { + "message": "Gardar" + }, + "saveScreenshotVisibleArea": { + "message": "Gardar a parte visible" + }, + "saveScreenshotFullPage": { + "message": "Gardar a páxina completa" + }, + "cancelScreenshot": { + "message": "Cancelar" + }, + "downloadScreenshot": { + "message": "Descargar" + }, + "downloadOnlyDetailsPrivate": { + "message": "Nunha xanela de navegación privada." + }, + "downloadOnlyDetailsThirdParty": { + "message": "Cando as cookies de terceiros están desactivadas." + }, + "downloadOnlyDetailsNeverRemember": { + "message": "Cando está activa a opción \"Nunca gardará o historial\"." + }, + "downloadOnlyDetailsESR": { + "message": "Está usando Firefox ESR." + }, + "notificationLinkCopiedTitle": { + "message": "Copiouse a ligazón" + }, + "copyScreenshot": { + "message": "Copiar" + }, + "notificationImageCopiedTitle": { + "message": "Copiouse a captura" + }, + "tourDone": { + "message": "Feito" + }, + "termsAndPrivacyNotice2": { + "message": "Ao usar Firefox Screenshots, vostede acepta os nosos $TERMSANDPRIVACYNOTICETERMSLINK$ e a $TERMSANDPRIVACYNOTICEPRIVACYLINK$.", + "placeholders": { + "termsandprivacynoticetermslink": { + "content": "$1" + }, + "termsandprivacynoticeprivacylink": { + "content": "$2" + } + } + }, + "termsAndPrivacyNoticeTermsLink": { + "message": "Termos" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "Política de privacidade" + }, + "libraryLabel": { + "message": "Capturas de pantalla" + } +} \ No newline at end of file
new file mode 100644 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/gn/messages.json @@ -0,0 +1,14 @@ +{ + "saveScreenshotSelectedArea": { + "message": "Ñongatu" + }, + "saveScreenshotVisibleArea": { + "message": "Ñongatu hechapy" + }, + "saveScreenshotFullPage": { + "message": "Kuatiarogue tuichavéva ñongatu" + }, + "cancelScreenshot": { + "message": "Heja" + } +} \ No newline at end of file
--- a/browser/extensions/screenshots/webextension/_locales/he/messages.json +++ b/browser/extensions/screenshots/webextension/_locales/he/messages.json @@ -24,27 +24,70 @@ "message": "שמירת הדף במלואו" }, "cancelScreenshot": { "message": "ביטול" }, "downloadScreenshot": { "message": "הורדה" }, + "downloadOnlyNotice": { + "message": "הינך כרגע במצב הורדה בלבד." + }, + "downloadOnlyDetails": { + "message": "Firefox Screenshots משתנה אוטומטית למצב הורדה בלבד במקרים הבאים:" + }, + "downloadOnlyDetailsPrivate": { + "message": "בחלון גלישה פרטית." + }, + "downloadOnlyDetailsThirdParty": { + "message": "כשעוגיות צד שלישי מנוטרלות." + }, + "downloadOnlyDetailsNeverRemember": { + "message": "„לא לזכור היסטוריה” פעיל." + }, + "downloadOnlyDetailsESR": { + "message": "הגרסה שלך היא Firefox ESR." + }, + "downloadOnlyDetailsNoUploadPref": { + "message": "ההעלאות הושבתו." + }, "notificationLinkCopiedTitle": { "message": "הקישור הועתק" }, "notificationLinkCopiedDetails": { "message": "הקישור לתמונה שלך הועתק ללוח. נא ללחוץ על $META_KEY$-V כדי להדביק.", "placeholders": { "meta_key": { "content": "$1" } } }, + "copyScreenshot": { + "message": "העתקה" + }, + "notificationImageCopiedTitle": { + "message": "צילום המסך הועתק" + }, + "notificationImageCopiedDetails": { + "message": "צילום המסך שלך הועתק ללוח העריכה. יש ללחוץ על $META_KEY$-V כדי להדביק.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, + "imageCroppedWarning": { + "message": "תמונה זו נחתכה לגודל של $PIXELS$ פיקסלים.", + "placeholders": { + "pixels": { + "content": "$1" + } + } + }, "requestErrorTitle": { "message": "לא תקין." }, "requestErrorDetails": { "message": "אנו מצטערים, אך לא ניתן היה לשמור את התמונה. נא לנסות שוב מאוחר יותר." }, "connectionErrorTitle": { "message": "לא ניתן היה להתחבר אל מאגר צילומי המסך שלך." @@ -80,16 +123,19 @@ "message": "אנחנו לא בטוחים מה קרה פה הרגע. אכפת לך לנסות שוב או לצלם דף אחר?" }, "tourBodyIntro": { "message": "צילום, שמירה ושיתוף של צילומי מסך מבלי לעזוב את Firefox." }, "tourHeaderPageAction": { "message": "דרך חדשה לשמירה" }, + "tourBodyPageAction": { + "message": "יש לפתוח את תפריט פעולות הדף בסרגל הכתובת בכל פעם שברצונך לצלם את המסך." + }, "tourHeaderClickAndDrag": { "message": "לצלם רק את מה שנחוץ לך" }, "tourBodyClickAndDrag": { "message": "ניתן ללחוץ ולגרור כדי לצלם רק מקטע מהעמוד. ניתן גם לרחף מעל כדי לסמן את הבחירה שלך." }, "tourHeaderFullPage": { "message": "לצלם חלונות או דפים שלמים"
--- a/browser/extensions/screenshots/webextension/_locales/hi_IN/messages.json +++ b/browser/extensions/screenshots/webextension/_locales/hi_IN/messages.json @@ -53,16 +53,30 @@ "notificationLinkCopiedDetails": { "message": "आपके शॉट के लिंक क्लिपबोर्ड पर कॉपी किए गए हैं. पेस्ट करने के लिए $META_KEY$-V दबाएँ.", "placeholders": { "meta_key": { "content": "$1" } } }, + "copyScreenshot": { + "message": "प्रतिलिपि बनाएँ" + }, + "notificationImageCopiedTitle": { + "message": "शॉट प्रतिलिपि बनाई गई" + }, + "notificationImageCopiedDetails": { + "message": "आपके शॉट के लिंक क्लिपबोर्ड पर कॉपी किए गए हैं. पेस्ट करने के लिए $META_KEY$-V दबाएँ.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, "imageCroppedWarning": { "message": "इस छवि को $PIXELS$px में छोटा किया गया है.", "placeholders": { "pixels": { "content": "$1" } } },
--- a/browser/extensions/screenshots/webextension/_locales/ko/messages.json +++ b/browser/extensions/screenshots/webextension/_locales/ko/messages.json @@ -39,16 +39,19 @@ "message": "개인정보 보호 창에 있을 때" }, "downloadOnlyDetailsThirdParty": { "message": "제 3자 쿠키가 비활성화되어 있을 때" }, "downloadOnlyDetailsNeverRemember": { "message": "“히스토리 기억 안함”이 활성화되어 있을 때" }, + "downloadOnlyDetailsESR": { + "message": "Firefox ESR을 사용중입니다." + }, "notificationLinkCopiedTitle": { "message": "링크 복사됨" }, "notificationLinkCopiedDetails": { "message": "방금 찍은 스냅샷의 링크가 클립보드에 저장됐습니다. 붙여넣으려면 $META_KEY$-V를 누르세요.", "placeholders": { "meta_key": { "content": "$1"
--- a/browser/extensions/screenshots/webextension/_locales/lij/messages.json +++ b/browser/extensions/screenshots/webextension/_locales/lij/messages.json @@ -35,16 +35,19 @@ "notificationLinkCopiedDetails": { "message": "O colegamento a l'inmagine o l'é stæto copiou inti aponti. Sciacca$META_KEY$-V pe incolalo.", "placeholders": { "meta_key": { "content": "$1" } } }, + "copyScreenshot": { + "message": "Còpia" + }, "requestErrorTitle": { "message": "Feua serviçio." }, "requestErrorDetails": { "message": "Ne spiaxe! No poemmo sarvâ l'inmagine. Pe piaxei preuva torna dòppo." }, "connectionErrorTitle": { "message": "No poemmo conetise a-e teu föto do schermo." @@ -68,16 +71,19 @@ "message": "Ahime mi! Firefox Screeshot o s'é ciantou." }, "genericErrorDetails": { "message": "Niatri no emmo ben acapio cöse l'é sucesso. Ti peu miga preuvâ co-ina pagina dispægia?" }, "tourBodyIntro": { "message": "Fanni, sarva e condividdi föto do schermo sensa sciortî da Firefox." }, + "tourHeaderPageAction": { + "message": "Un neuvo mòddo de sarvâ" + }, "tourHeaderClickAndDrag": { "message": "Catua solo quello che t'eu" }, "tourBodyClickAndDrag": { "message": "Sciacca e rebela pe catuâ solo 'na porçion de 'na pagina. Ti peu anche anâ co-o ratto sorvia l'area seleçionâ pe evidençiala." }, "tourHeaderFullPage": { "message": "Catua 'n barcon ò 'na pagina intrega" @@ -98,18 +104,18 @@ "message": "Pròscima schermâ" }, "tourPrevious": { "message": "Schermâ de primma" }, "tourDone": { "message": "Fæto" }, - "termsAndPrivacyNoticeCloudServices": { - "message": "Se ti deuvi Firefox Screenshots, ti e d'acordio con {termsAndPrivacyNoticeTermsLink} e {termsAndPrivacyNoticePrivacyLink} de Firefox Cloud Services." - }, "termsAndPrivacyNoticeTermsLink": { "message": "Termini" }, "termsAndPrivacyNoticyPrivacyLink": { "message": "Informativa in sciâ privacy" + }, + "libraryLabel": { + "message": "Föto do schermo" } } \ No newline at end of file
--- a/browser/extensions/screenshots/webextension/_locales/mk/messages.json +++ b/browser/extensions/screenshots/webextension/_locales/mk/messages.json @@ -24,27 +24,62 @@ "message": "Сними цела страница" }, "cancelScreenshot": { "message": "Откажи" }, "downloadScreenshot": { "message": "Преземи" }, + "downloadOnlyNotice": { + "message": "Во моментов сте во режим Само Преземање." + }, + "downloadOnlyDetails": { + "message": "Firefox Screenshots се префрла во режим Само Преземање во овие случаи:" + }, + "downloadOnlyDetailsPrivate": { + "message": "Во приватен прозорец." + }, + "downloadOnlyDetailsThirdParty": { + "message": "Колачињата од трети страни се оневозможени." + }, + "downloadOnlyDetailsNeverRemember": { + "message": "“Никогаш нема да ја памти историјата” е вклучена." + }, + "downloadOnlyDetailsESR": { + "message": "Користите Firefox ESR." + }, + "downloadOnlyDetailsNoUploadPref": { + "message": "Качувањето на датотеки е оневозможено." + }, "notificationLinkCopiedTitle": { "message": "Врската е ископирана" }, "notificationLinkCopiedDetails": { "message": "Врската до вашата слика е ископирана во меморија. Притиснете $META_KEY$-V за да ја вметнете.", "placeholders": { "meta_key": { "content": "$1" } } }, + "copyScreenshot": { + "message": "Копирај" + }, + "notificationImageCopiedTitle": { + "message": "Слика е ископирана" + }, + "notificationImageCopiedDetails": { + "message": "Вашата слика беше ископирана во меморија. Притиснете $META_KEY$-V за да ја вметнете.", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, "imageCroppedWarning": { "message": "Оваа слика е скратена до $PIXELS$px.", "placeholders": { "pixels": { "content": "$1" } } },
new file mode 100644 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/mn/messages.json @@ -0,0 +1,5 @@ +{ + "addonDescription": { + "message": "Вэбээс авсан клип болон дэлгэцийн зургийг аваад тэдгээрийг түр эсвэл бүрмөсөн хадгал." + } +} \ No newline at end of file
new file mode 100644 --- /dev/null +++ b/browser/extensions/screenshots/webextension/_locales/ne_NP/messages.json @@ -0,0 +1,20 @@ +{ + "addonDescription": { + "message": "वेबबाट फोटो र सिक्रिनसट या क्लिपहरू लिएर क्षणिक वा सदाको लागि सङ्ग्रह गर्न ।" + }, + "contextMenuLabel": { + "message": "स्क्रिनसट लिनुहोस्" + }, + "myShotsLink": { + "message": "मेरा सटहरू" + }, + "saveScreenshotSelectedArea": { + "message": "सङ्ग्रह गर्नुहोस्" + }, + "saveScreenshotVisibleArea": { + "message": "दृश्यात्मक सङ्ग्रह गर्नुहोस्" + }, + "cancelScreenshot": { + "message": "रद्द गर्नुहोस" + } +} \ No newline at end of file
--- a/browser/extensions/screenshots/webextension/_locales/pa_IN/messages.json +++ b/browser/extensions/screenshots/webextension/_locales/pa_IN/messages.json @@ -21,30 +21,83 @@ "message": "ਪੂਰੇ ਸਫ਼ੇ ਨੂੰ ਸੰਭਾਲੋ" }, "cancelScreenshot": { "message": "ਰੱਦ ਕਰੋ" }, "downloadScreenshot": { "message": "ਡਾਊਨਲੋਡ ਕਰੋ" }, + "downloadOnlyDetailsPrivate": { + "message": "ਇੱਕ ਨਿੱਜੀ ਬਰਾਊਜਿੰਗ ਵਿੰਡੋ ਵਿੱਚ" + }, + "downloadOnlyDetailsThirdParty": { + "message": "ਤੀਜੀ ਧਿਰ ਦੀਆਂ ਕੂਕੀਜ ਆਯੋਗ ਹਨ।" + }, + "downloadOnlyDetailsNeverRemember": { + "message": "\"ਅਤੀਤ ਕਦੇ ਵੀ ਯਾਦ ਨਾ ਰੱਖੋ\" ਆਯੋਗ ਕੀਤਾ ਗਿਆ ਹੈ।" + }, + "downloadOnlyDetailsESR": { + "message": "ਤੁਸੀਂ ESR ਫਾਇਰਫਾਕਸ ਵਰਤ ਰਹੇ ਹੋ।" + }, + "downloadOnlyDetailsNoUploadPref": { + "message": "ਅੱਪਲ੍ਹੋਡ ਆਯੋਗ ਕੀਤੇ ਗਏ ਹਨ।" + }, "notificationLinkCopiedTitle": { "message": "ਲਿੰਕ ਕਾਪੀ ਕੀਤਾ ਗਿਆ" }, + "copyScreenshot": { + "message": "ਕਾਪੀ" + }, + "notificationImageCopiedTitle": { + "message": "ਸ਼ਾਟ ਕਾਪੀ ਕੀਤਾ ਗਿਆ" + }, + "notificationImageCopiedDetails": { + "message": "ਤੁਹਾਡਾ ਸ਼ਾਟ ਕਲਿੱਪਬੋਰਡ ਤੋਂ ਕਾਪੀ ਕੀਤਾ ਗਿਆ ਹੈ। ਪੇਸਟ ਕਰਨ ਲਈ $META_KEY$-V ਨੂੰ ਦਬਾਓ।", + "placeholders": { + "meta_key": { + "content": "$1" + } + } + }, "requestErrorTitle": { "message": "ਖ਼ਰਾਬ ਹੈ।" }, + "connectionErrorTitle": { + "message": "ਅਸੀਂ ਤੁਹਾਡੇ ਸਕਰੀਨਸ਼ਾਟ ਨਾਲ ਕੁਨੈੱਕਟ ਨਹੀਂ ਕਰ ਸਕਦੇ।" + }, + "unshootablePageErrorTitle": { + "message": "ਅਸੀਂ ਇਸ ਸਫੇ ਦਾ ਸਕਰੀਨਸ਼ਾਟ ਨਹੀਂ ਲੈ ਸਕਦੇ।" + }, + "emptySelectionErrorTitle": { + "message": "ਤੁਹਾਡੀ ਚੋਣ ਬਹੁਤ ਛੋਟੀ ਹੈ" + }, + "privateWindowErrorTitle": { + "message": "ਨਿੱਜੀ ਬਰਾਊਜਿੰਗ ਮੋਡ ਵਿੱਚ ਸਕਰੀਨਸ਼ਾਟ ਆਯੋਗ ਹੋਇਆ" + }, + "genericErrorTitle": { + "message": "ਠਹਿਰੋ! ਫਾਇਰਫਾਕਸ ਸਕਰੀਨਸ਼ਾਟ ਲੈਣ 'ਚ ਸਮੱਸਿਆ ਆਈ" + }, + "tourBodyIntro": { + "message": "ਬਿਨਾਂ ਫਾਇਰਫਾਕਸ ਨੂੰ ਛੱਡੇ ਸਕਰੀਨਸ਼ਾਟ ਲਓ, ਸੰਭਾਲੋ, ਅਤੇ ਸਾਂਝਾ ਕਰੋ।" + }, "tourHeaderPageAction": { "message": "ਸੰਭਾਲਣ ਦਾ ਨਵਾਂ ਢੰਗ" }, + "tourHeaderClickAndDrag": { + "message": "ਕੈਪਚਰ ਕਰੋ ਤੁਸੀਂ ਕੀ ਚਾਹੁੰਦੇ ਹੋ" + }, "tourSkip": { "message": "ਛੱਡੋ" }, "tourNext": { "message": "ਅਗਲੀ ਸਲਾਈਡ" }, "tourPrevious": { "message": "ਪਿਛਲੀ ਸਲਾਈਡ" }, "tourDone": { "message": "ਮੁਕੰਮਲ" + }, + "termsAndPrivacyNoticyPrivacyLink": { + "message": "ਨਿੱਜੀ ਨੋਟਿਸ" } } \ No newline at end of file
--- a/browser/extensions/screenshots/webextension/_locales/ro/messages.json +++ b/browser/extensions/screenshots/webextension/_locales/ro/messages.json @@ -25,17 +25,17 @@ }, "cancelScreenshot": { "message": "Renunță" }, "downloadScreenshot": { "message": "Descarcă" }, "downloadOnlyNotice": { - "message": "Momentan sunteți în modul descărcare." + "message": "În prezent ești în modul numai de descărcare." }, "downloadOnlyDetails": { "message": "Firefox Screenshots se mută automat în modul descărcare în aceste situații:" }, "downloadOnlyDetailsPrivate": { "message": "În fereastra de navigare privată." }, "downloadOnlyDetailsThirdParty": {
--- a/browser/extensions/screenshots/webextension/_locales/te/messages.json +++ b/browser/extensions/screenshots/webextension/_locales/te/messages.json @@ -21,25 +21,37 @@ "message": "రద్దుచేయి" }, "downloadScreenshot": { "message": "దింపుకోండి" }, "downloadOnlyNotice": { "message": "మీరు ప్రస్తుతం దింపుకోలు-మాత్రమే రీతిలో ఉన్నారు." }, + "downloadOnlyDetailsPrivate": { + "message": "అంతరంగిక విహారణ కిటికీలో." + }, + "downloadOnlyDetailsThirdParty": { + "message": "మూడవ-పక్ష కుకీలు అచేతనమయ్యాయి." + }, + "downloadOnlyDetailsESR": { + "message": "మీరు Firefox ESR ఉపయోగిస్తున్నారు." + }, "downloadOnlyDetailsNoUploadPref": { "message": "ఎక్కింపులు అచేతమై ఉన్నాయి." }, "notificationLinkCopiedTitle": { "message": "లంకె కాపీ అయింది" }, "copyScreenshot": { "message": "కాపీచెయ్యి" }, + "notificationImageCopiedTitle": { + "message": "పట్టు కాపీఅయ్యింది" + }, "requestErrorTitle": { "message": "పని చెయుట లేదు." }, "requestErrorDetails": { "message": "క్షమిచండి! మీ తెరను భద్రపరచలేకపోయాం. దయచేసి కాసేపాగి మళ్ళీ ప్రయత్నించండి." }, "connectionErrorTitle": { "message": "మేము మీ స్క్రీన్షాట్లకు కనెక్ట్ చేయలేము."
--- a/browser/extensions/screenshots/webextension/_locales/th/messages.json +++ b/browser/extensions/screenshots/webextension/_locales/th/messages.json @@ -33,27 +33,36 @@ "message": "คุณกำลังอยู่ในโหมดดาวน์โหลดเท่านั้น" }, "downloadOnlyDetailsPrivate": { "message": "ในหน้าต่างการท่องเว็บแบบส่วนตัว" }, "downloadOnlyDetailsThirdParty": { "message": "คุกกี้บุคคลที่สามถูกปิดการใช้งาน" }, + "downloadOnlyDetailsESR": { + "message": "คุณกำลังใช้ Firefox ESR" + }, "notificationLinkCopiedTitle": { "message": "คัดลอกลิงก์แล้ว" }, "notificationLinkCopiedDetails": { "message": "คัดลอกลิงก์ไปยังภาพของคุณไปยังคลิปบอร์ดแล้ว กด $META_KEY$-V เพื่อวาง", "placeholders": { "meta_key": { "content": "$1" } } }, + "copyScreenshot": { + "message": "คัดลอก" + }, + "notificationImageCopiedTitle": { + "message": "คัดลอกภาพแล้ว" + }, "imageCroppedWarning": { "message": "ภาพนี้ถูกตัดเป็น $PIXELS$ พิกเซล", "placeholders": { "pixels": { "content": "$1" } } },
--- a/browser/extensions/screenshots/webextension/_locales/tr/messages.json +++ b/browser/extensions/screenshots/webextension/_locales/tr/messages.json @@ -124,35 +124,35 @@ }, "tourBodyIntro": { "message": "Firefox'tan çıkmadan ekran görüntüleri alın, kaydedin ve paylaşın." }, "tourHeaderPageAction": { "message": "Kaydetmenin yeni yolu" }, "tourBodyPageAction": { - "message": "Ekran görüntüsü almak istediğiniz zaman adres çubuğundaki sayfa eylemleri menüsünü açabilirsiniz." + "message": "Ekran görüntüsü almak istediğiniz zaman adres çubuğundaki sayfa eylemleri menüsünü açın." }, "tourHeaderClickAndDrag": { "message": "İstediğini yakala" }, "tourBodyClickAndDrag": { - "message": "Sayfanın belli bir kısmını yakalamak için işaretçiyi tıklayıp sürükleyin. Seçiminizi vurgulamak için fareyle üzerine gelebilirsiniz." + "message": "Sayfanın belli bir kısmını yakalamak için tıklayıp sürükleyin. Seçiminizi işaretlemek için fareyle üzerine de gidebilirsiniz." }, "tourHeaderFullPage": { "message": "Pencereleri veya sayfaların tamamını yakala" }, "tourBodyFullPage": { "message": "Yalnızca pencerede gördüğünüz alanı veya sayfanın tamamını yakalamak için sağ üstteki düğmelerden uygun olanı seçin." }, "tourHeaderDownloadUpload": { "message": "İstediğin gibi yakala" }, "tourBodyDownloadUpload": { - "message": "Ekran görüntülerinizi daha kolay paylaşmak veya bilgisayarınıza indirmek için web’e kaydedin. Kaydettiğiniz tüm görüntüleri bulmak için \"Ekran görüntülerim\" düğmesine tıklayabilirsiniz." + "message": "Ekran görüntülerinizi daha kolay paylaşmak web’e kaydedebilir veya bilgisayarınıza indirebilirsiniz. Kaydettiğiniz tüm görüntüleri bulmak için “Ekran görüntülerim” düğmesine tıklayabilirsiniz." }, "tourSkip": { "message": "GEÇ" }, "tourNext": { "message": "Sonraki slayt" }, "tourPrevious": {
--- a/browser/extensions/screenshots/webextension/_locales/ur/messages.json +++ b/browser/extensions/screenshots/webextension/_locales/ur/messages.json @@ -24,27 +24,33 @@ "message": "پورا صفحہ محفوظ کریں" }, "cancelScreenshot": { "message": "منسوخ کریں" }, "downloadScreenshot": { "message": "ڈاؤن لوڈ" }, + "downloadOnlyDetailsESR": { + "message": "آپ Firefox ESR استعمال کر رہے ہیں۔" + }, "notificationLinkCopiedTitle": { "message": "تبط نقل کر دیا گیا" }, "notificationLinkCopiedDetails": { "message": "آُپ کی شاٹس کا ربط و تختہ تراشہ پر نقل کر دیا گیا ہے۔ چسپاں کرنے کے لیئے $META_KEY$-V دبائِں۔", "placeholders": { "meta_key": { "content": "$1" } } }, + "copyScreenshot": { + "message": "نقل کریں" + }, "requestErrorTitle": { "message": "خراب ہے۔" }, "requestErrorDetails": { "message": "معاف کیجیئے گا! ہم آپ کی شاٹ محفوظ نہیں کر سکے۔ براہ مہربانی کچھ دیر بعد کوشش کریں۔" }, "connectionErrorTitle": { "message": "ہم آپ کی اسکرین شاٹس سے نہیں جڑ سکتے۔"
--- a/browser/extensions/screenshots/webextension/background/main.js +++ b/browser/extensions/screenshots/webextension/background/main.js @@ -6,28 +6,28 @@ this.main = (function() { const exports = {}; const pasteSymbol = (window.navigator.platform.match(/Mac/i)) ? "\u2318" : "Ctrl"; const { sendEvent } = analytics; const manifest = browser.runtime.getManifest(); let backend; - let hasSeenOnboarding; - - browser.storage.local.get(["hasSeenOnboarding"]).then((result) => { - hasSeenOnboarding = !!result.hasSeenOnboarding; - if (!hasSeenOnboarding) { + let hasSeenOnboarding = browser.storage.local.get(["hasSeenOnboarding"]).then((result) => { + const onboarded = !!result.hasSeenOnboarding; + if (!onboarded) { setIconActive(false, null); // Note that the branded name 'Firefox Screenshots' is not localized: startBackground.photonPageActionPort.postMessage({ type: "setProperties", title: "Firefox Screenshots" }); } + hasSeenOnboarding = Promise.resolve(onboarded); + return hasSeenOnboarding; }).catch((error) => { log.error("Error getting hasSeenOnboarding:", error); }); exports.setBackend = function(newBackend) { backend = newBackend; backend = backend.replace(/\/*$/, ""); }; @@ -88,44 +88,46 @@ this.main = (function() { } function shouldOpenMyShots(url) { return /^about:(?:newtab|blank|home)/i.test(url) || /^resource:\/\/activity-streams\//i.test(url); } // This is called by startBackground.js, directly in response to clicks on the Photon page action exports.onClicked = catcher.watchFunction((tab) => { - if (shouldOpenMyShots(tab.url)) { - if (!hasSeenOnboarding) { + catcher.watchPromise(hasSeenOnboarding.then(onboarded => { + if (shouldOpenMyShots(tab.url)) { + if (!onboarded) { + catcher.watchPromise(analytics.refreshTelemetryPref().then(() => { + sendEvent("goto-onboarding", "selection-button", {incognito: tab.incognito}); + return forceOnboarding(); + })); + return; + } catcher.watchPromise(analytics.refreshTelemetryPref().then(() => { - sendEvent("goto-onboarding", "selection-button", {incognito: tab.incognito}); - return forceOnboarding(); + sendEvent("goto-myshots", "about-newtab", {incognito: tab.incognito}); })); - return; + catcher.watchPromise( + auth.authHeaders() + .then(() => browser.tabs.update({url: backend + "/shots"}))); + } else { + catcher.watchPromise( + toggleSelector(tab) + .then(active => { + const event = active ? "start-shot" : "cancel-shot"; + sendEvent(event, "toolbar-button", {incognito: tab.incognito}); + }, (error) => { + if ((!onboarded) && error.popupMessage === "UNSHOOTABLE_PAGE") { + sendEvent("goto-onboarding", "selection-button", {incognito: tab.incognito}); + return forceOnboarding(); + } + throw error; + })); } - catcher.watchPromise(analytics.refreshTelemetryPref().then(() => { - sendEvent("goto-myshots", "about-newtab", {incognito: tab.incognito}); - })); - catcher.watchPromise( - auth.authHeaders() - .then(() => browser.tabs.update({url: backend + "/shots"}))); - } else { - catcher.watchPromise( - toggleSelector(tab) - .then(active => { - const event = active ? "start-shot" : "cancel-shot"; - sendEvent(event, "toolbar-button", {incognito: tab.incognito}); - }, (error) => { - if ((!hasSeenOnboarding) && error.popupMessage === "UNSHOOTABLE_PAGE") { - sendEvent("goto-onboarding", "selection-button", {incognito: tab.incognito}); - return forceOnboarding(); - } - throw error; - })); - } + })); }); function forceOnboarding() { return browser.tabs.create({url: getOnboardingUrl()}); } exports.onClickedContextMenu = catcher.watchFunction((info, tab) => { if (!tab) { @@ -268,18 +270,18 @@ this.main = (function() { }); }); communication.register("closeSelector", (sender) => { setIconActive(false, sender.tab.id); }); communication.register("hasSeenOnboarding", () => { - hasSeenOnboarding = true; - catcher.watchPromise(browser.storage.local.set({hasSeenOnboarding})); + hasSeenOnboarding = Promise.resolve(true); + catcher.watchPromise(browser.storage.local.set({hasSeenOnboarding: true})); setIconActive(false, null); startBackground.photonPageActionPort.postMessage({ type: "setProperties", title: browser.i18n.getMessage("contextMenuLabel") }); }); communication.register("abortStartShot", () => {
--- a/browser/extensions/screenshots/webextension/background/selectorLoader.js +++ b/browser/extensions/screenshots/webextension/background/selectorLoader.js @@ -61,34 +61,36 @@ this.selectorLoader = (function() { }).then(result => { return result && result[0]; }); }; const loadingTabs = new Set(); exports.loadModules = function(tabId, hasSeenOnboarding) { - loadingTabs.add(tabId); - let promise = downloadOnlyCheck(tabId); - if (hasSeenOnboarding) { - promise = promise.then(() => { - return executeModules(tabId, standardScripts.concat(selectorScripts)); + catcher.watchPromise(hasSeenOnboarding.then(onboarded => { + loadingTabs.add(tabId); + let promise = downloadOnlyCheck(tabId); + if (onboarded) { + promise = promise.then(() => { + return executeModules(tabId, standardScripts.concat(selectorScripts)); + }); + } else { + promise = promise.then(() => { + return executeModules(tabId, standardScripts.concat(onboardingScripts).concat(selectorScripts)); + }); + } + return promise.then((result) => { + loadingTabs.delete(tabId); + return result; + }, (error) => { + loadingTabs.delete(tabId); + throw error; }); - } else { - promise = promise.then(() => { - return executeModules(tabId, standardScripts.concat(onboardingScripts).concat(selectorScripts)); - }); - } - return promise.then((result) => { - loadingTabs.delete(tabId); - return result; - }, (error) => { - loadingTabs.delete(tabId); - throw error; - }); + })); }; // TODO: since bootstrap communication is now required, would this function // make more sense inside background/main? function downloadOnlyCheck(tabId) { return communication.sendToBootstrap("isHistoryEnabled").then((historyEnabled) => { return communication.sendToBootstrap("isUploadDisabled").then((uploadDisabled) => { return browser.tabs.get(tabId).then(tab => {
--- a/browser/extensions/screenshots/webextension/background/takeshot.js +++ b/browser/extensions/screenshots/webextension/background/takeshot.js @@ -186,23 +186,24 @@ this.takeshot = (function() { return auth.authHeaders().then((_headers) => { headers = _headers; if (blob) { const files = [ {fieldName: "blob", filename: "screenshot.png", blob} ]; if (thumbnail) { files.push({fieldName: "thumbnail", filename: "thumbnail.png", blob: thumbnail}); } return createMultipart( - {shot: JSON.stringify(shot.asJson())}, + {shot: JSON.stringify(shot)}, + files ); } return { "content-type": "application/json", - body: JSON.stringify(shot.asJson()) + body: JSON.stringify(shot) }; }).then((submission) => { headers["content-type"] = submission["content-type"]; sendEvent("upload", "started", {eventValue: Math.floor(submission.body.length / 1000)}); return fetch(shot.jsonUrl, { method: "PUT", mode: "cors",
--- a/browser/extensions/screenshots/webextension/build/raven.js +++ b/browser/extensions/screenshots/webextension/build/raven.js @@ -1,2587 +1,2942 @@ -/*! Raven.js 3.18.1 (2dca364) | github.com/getsentry/raven-js */ +/*! Raven.js 3.24.1 (f3b3500) | github.com/getsentry/raven-js */ /* * Includes TraceKit * https://github.com/getsentry/TraceKit * - * Copyright 2017 Matt Robenolt and other contributors + * Copyright 2018 Matt Robenolt and other contributors * Released under the BSD license * https://github.com/getsentry/raven-js/blob/master/LICENSE * */ -(function(f) { - if (typeof exports === 'object' && typeof module !== 'undefined') { - module.exports = f(); - } else if (typeof define === 'function' && define.amd) { - define([], f); - } else { - var g; - if (typeof window !== 'undefined') { - g = window; - } else if (typeof global !== 'undefined') { - g = global; - } else if (typeof self !== 'undefined') { - g = self; - } else { - g = this; - } - g.Raven = f(); +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Raven = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ +function RavenConfigError(message) { + this.name = 'RavenConfigError'; + this.message = message; +} +RavenConfigError.prototype = new Error(); +RavenConfigError.prototype.constructor = RavenConfigError; + +module.exports = RavenConfigError; + +},{}],2:[function(_dereq_,module,exports){ +var utils = _dereq_(5); + +var wrapMethod = function(console, level, callback) { + var originalConsoleLevel = console[level]; + var originalConsole = console; + + if (!(level in console)) { + return; } -})(function() { - var define, module, exports; - return (function e(t, n, r) { - function s(o, u) { - if (!n[o]) { - if (!t[o]) { - var a = typeof require == 'function' && require; - if (!u && a) return a(o, !0); - if (i) return i(o, !0); - var f = new Error("Cannot find module '" + o + "'"); - throw ((f.code = 'MODULE_NOT_FOUND'), f); - } - var l = (n[o] = {exports: {}}); - t[o][0].call( - l.exports, - function(e) { - var n = t[o][1][e]; - return s(n ? n : e); - }, - l, - l.exports, - e, - t, - n, - r - ); + + var sentryLevel = level === 'warn' ? 'warning' : level; + + console[level] = function() { + var args = [].slice.call(arguments); + + var msg = utils.safeJoin(args, ' '); + var data = {level: sentryLevel, logger: 'console', extra: {arguments: args}}; + + if (level === 'assert') { + if (args[0] === false) { + // Default browsers message + msg = + 'Assertion failed: ' + (utils.safeJoin(args.slice(1), ' ') || 'console.assert'); + data.extra.arguments = args.slice(1); + callback && callback(msg, data); } - return n[o].exports; + } else { + callback && callback(msg, data); + } + + // this fails for some browsers. :( + if (originalConsoleLevel) { + // IE9 doesn't allow calling apply on console functions directly + // See: https://stackoverflow.com/questions/5472938/does-ie9-support-console-log-and-is-it-a-real-function#answer-5473193 + Function.prototype.apply.call(originalConsoleLevel, originalConsole, args); } - var i = typeof require == 'function' && require; - for (var o = 0; o < r.length; o++) s(r[o]); - return s; - })( - { - 1: [ - function(_dereq_, module, exports) { - function RavenConfigError(message) { - this.name = 'RavenConfigError'; - this.message = message; - } - RavenConfigError.prototype = new Error(); - RavenConfigError.prototype.constructor = RavenConfigError; - - module.exports = RavenConfigError; - }, - {} - ], - 2: [ - function(_dereq_, module, exports) { - var wrapMethod = function(console, level, callback) { - var originalConsoleLevel = console[level]; - var originalConsole = console; - - if (!(level in console)) { - return; - } - - var sentryLevel = level === 'warn' ? 'warning' : level; - - console[level] = function() { - var args = [].slice.call(arguments); - - var msg = '' + args.join(' '); - var data = { - level: sentryLevel, - logger: 'console', - extra: {arguments: args} - }; - - if (level === 'assert') { - if (args[0] === false) { - // Default browsers message - msg = - 'Assertion failed: ' + (args.slice(1).join(' ') || 'console.assert'); - data.extra.arguments = args.slice(1); - callback && callback(msg, data); - } - } else { - callback && callback(msg, data); - } - - // this fails for some browsers. :( - if (originalConsoleLevel) { - // IE9 doesn't allow calling apply on console functions directly - // See: https://stackoverflow.com/questions/5472938/does-ie9-support-console-log-and-is-it-a-real-function#answer-5473193 - Function.prototype.apply.call( - originalConsoleLevel, - originalConsole, - args - ); - } - }; - }; - - module.exports = { - wrapMethod: wrapMethod - }; - }, - {} - ], - 3: [ - function(_dereq_, module, exports) { - (function(global) { - /*global XDomainRequest:false, __DEV__:false*/ - - var TraceKit = _dereq_(6); - var stringify = _dereq_(7); - var RavenConfigError = _dereq_(1); - var utils = _dereq_(5); - - var isError = utils.isError, - isObject = utils.isObject; - - var wrapConsoleMethod = _dereq_(2).wrapMethod; - - var dsnKeys = 'source protocol user pass host port path'.split(' '), - dsnPattern = /^(?:(\w+):)?\/\/(?:(\w+)(:\w+)?@)?([\w\.-]+)(?::(\d+))?(\/.*)/; - - function now() { - return +new Date(); - } - - // This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785) - var _window = - typeof window !== 'undefined' - ? window - : typeof global !== 'undefined' - ? global - : typeof self !== 'undefined' ? self : {}; - var _document = _window.document; - var _navigator = _window.navigator; - - function keepOriginalCallback(original, callback) { - return isFunction(callback) - ? function(data) { - return callback(data, original); - } - : callback; - } - - // First, check for JSON support - // If there is no JSON, we no-op the core features of Raven - // since JSON is required to encode the payload - function Raven() { - this._hasJSON = !!(typeof JSON === 'object' && JSON.stringify); - // Raven can run in contexts where there's no document (react-native) - this._hasDocument = !isUndefined(_document); - this._hasNavigator = !isUndefined(_navigator); - this._lastCapturedException = null; - this._lastData = null; - this._lastEventId = null; - this._globalServer = null; - this._globalKey = null; - this._globalProject = null; - this._globalContext = {}; - this._globalOptions = { - logger: 'javascript', - ignoreErrors: [], - ignoreUrls: [], - whitelistUrls: [], - includePaths: [], - collectWindowErrors: true, - maxMessageLength: 0, - - // By default, truncates URL values to 250 chars - maxUrlLength: 250, - stackTraceLimit: 50, - autoBreadcrumbs: true, - instrument: true, - sampleRate: 1 - }; - this._ignoreOnError = 0; - this._isRavenInstalled = false; - this._originalErrorStackTraceLimit = Error.stackTraceLimit; - // capture references to window.console *and* all its methods first - // before the console plugin has a chance to monkey patch - this._originalConsole = _window.console || {}; - this._originalConsoleMethods = {}; - this._plugins = []; - this._startTime = now(); - this._wrappedBuiltIns = []; - this._breadcrumbs = []; - this._lastCapturedEvent = null; - this._keypressTimeout; - this._location = _window.location; - this._lastHref = this._location && this._location.href; - this._resetBackoff(); - - // eslint-disable-next-line guard-for-in - for (var method in this._originalConsole) { - this._originalConsoleMethods[method] = this._originalConsole[method]; - } - } - - /* + }; +}; + +module.exports = { + wrapMethod: wrapMethod +}; + +},{"5":5}],3:[function(_dereq_,module,exports){ +(function (global){ +/*global XDomainRequest:false */ + +var TraceKit = _dereq_(6); +var stringify = _dereq_(7); +var md5 = _dereq_(8); +var RavenConfigError = _dereq_(1); + +var utils = _dereq_(5); +var isError = utils.isError; +var isObject = utils.isObject; +var isPlainObject = utils.isPlainObject; +var isErrorEvent = utils.isErrorEvent; +var isUndefined = utils.isUndefined; +var isFunction = utils.isFunction; +var isString = utils.isString; +var isArray = utils.isArray; +var isEmptyObject = utils.isEmptyObject; +var each = utils.each; +var objectMerge = utils.objectMerge; +var truncate = utils.truncate; +var objectFrozen = utils.objectFrozen; +var hasKey = utils.hasKey; +var joinRegExp = utils.joinRegExp; +var urlencode = utils.urlencode; +var uuid4 = utils.uuid4; +var htmlTreeAsString = utils.htmlTreeAsString; +var isSameException = utils.isSameException; +var isSameStacktrace = utils.isSameStacktrace; +var parseUrl = utils.parseUrl; +var fill = utils.fill; +var supportsFetch = utils.supportsFetch; +var supportsReferrerPolicy = utils.supportsReferrerPolicy; +var serializeKeysForMessage = utils.serializeKeysForMessage; +var serializeException = utils.serializeException; +var sanitize = utils.sanitize; + +var wrapConsoleMethod = _dereq_(2).wrapMethod; + +var dsnKeys = 'source protocol user pass host port path'.split(' '), + dsnPattern = /^(?:(\w+):)?\/\/(?:(\w+)(:\w+)?@)?([\w\.-]+)(?::(\d+))?(\/.*)/; + +function now() { + return +new Date(); +} + +// This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785) +var _window = + typeof window !== 'undefined' + ? window + : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; +var _document = _window.document; +var _navigator = _window.navigator; + +function keepOriginalCallback(original, callback) { + return isFunction(callback) + ? function(data) { + return callback(data, original); + } + : callback; +} + +// First, check for JSON support +// If there is no JSON, we no-op the core features of Raven +// since JSON is required to encode the payload +function Raven() { + this._hasJSON = !!(typeof JSON === 'object' && JSON.stringify); + // Raven can run in contexts where there's no document (react-native) + this._hasDocument = !isUndefined(_document); + this._hasNavigator = !isUndefined(_navigator); + this._lastCapturedException = null; + this._lastData = null; + this._lastEventId = null; + this._globalServer = null; + this._globalKey = null; + this._globalProject = null; + this._globalContext = {}; + this._globalOptions = { + // SENTRY_RELEASE can be injected by https://github.com/getsentry/sentry-webpack-plugin + release: _window.SENTRY_RELEASE && _window.SENTRY_RELEASE.id, + logger: 'javascript', + ignoreErrors: [], + ignoreUrls: [], + whitelistUrls: [], + includePaths: [], + headers: null, + collectWindowErrors: true, + captureUnhandledRejections: true, + maxMessageLength: 0, + // By default, truncates URL values to 250 chars + maxUrlLength: 250, + stackTraceLimit: 50, + autoBreadcrumbs: true, + instrument: true, + sampleRate: 1, + sanitizeKeys: [] + }; + this._fetchDefaults = { + method: 'POST', + keepalive: true, + // Despite all stars in the sky saying that Edge supports old draft syntax, aka 'never', 'always', 'origin' and 'default + // https://caniuse.com/#feat=referrer-policy + // It doesn't. And it throw exception instead of ignoring this parameter... + // REF: https://github.com/getsentry/raven-js/issues/1233 + referrerPolicy: supportsReferrerPolicy() ? 'origin' : '' + }; + this._ignoreOnError = 0; + this._isRavenInstalled = false; + this._originalErrorStackTraceLimit = Error.stackTraceLimit; + // capture references to window.console *and* all its methods first + // before the console plugin has a chance to monkey patch + this._originalConsole = _window.console || {}; + this._originalConsoleMethods = {}; + this._plugins = []; + this._startTime = now(); + this._wrappedBuiltIns = []; + this._breadcrumbs = []; + this._lastCapturedEvent = null; + this._keypressTimeout; + this._location = _window.location; + this._lastHref = this._location && this._location.href; + this._resetBackoff(); + + // eslint-disable-next-line guard-for-in + for (var method in this._originalConsole) { + this._originalConsoleMethods[method] = this._originalConsole[method]; + } +} + +/* * The core Raven singleton * * @this {Raven} */ - Raven.prototype = { - // Hardcode version string so that raven source can be loaded directly via - // webpack (using a build step causes webpack #1617). Grunt verifies that - // this value matches package.json during build. - // See: https://github.com/getsentry/raven-js/issues/465 - VERSION: '3.18.1', - - debug: false, - - TraceKit: TraceKit, // alias to TraceKit - - /* +Raven.prototype = { + // Hardcode version string so that raven source can be loaded directly via + // webpack (using a build step causes webpack #1617). Grunt verifies that + // this value matches package.json during build. + // See: https://github.com/getsentry/raven-js/issues/465 + VERSION: '3.24.1', + + debug: false, + + TraceKit: TraceKit, // alias to TraceKit + + /* * Configure Raven with a DSN and extra options * * @param {string} dsn The public Sentry DSN * @param {object} options Set of global options [optional] * @return {Raven} */ - config: function(dsn, options) { - var self = this; - - if (self._globalServer) { - this._logDebug('error', 'Error: Raven has already been configured'); - return self; - } - if (!dsn) return self; - - var globalOptions = self._globalOptions; - - // merge in options - if (options) { - each(options, function(key, value) { - // tags and extra are special and need to be put into context - if (key === 'tags' || key === 'extra' || key === 'user') { - self._globalContext[key] = value; - } else { - globalOptions[key] = value; - } - }); - } - - self.setDSN(dsn); - - // "Script error." is hard coded into browsers for errors that it can't read. - // this is the result of a script being pulled in from an external domain and CORS. - globalOptions.ignoreErrors.push(/^Script error\.?$/); - globalOptions.ignoreErrors.push( - /^Javascript error: Script error\.? on line 0$/ - ); - - // join regexp rules into one big rule - globalOptions.ignoreErrors = joinRegExp(globalOptions.ignoreErrors); - globalOptions.ignoreUrls = globalOptions.ignoreUrls.length - ? joinRegExp(globalOptions.ignoreUrls) - : false; - globalOptions.whitelistUrls = globalOptions.whitelistUrls.length - ? joinRegExp(globalOptions.whitelistUrls) - : false; - globalOptions.includePaths = joinRegExp(globalOptions.includePaths); - globalOptions.maxBreadcrumbs = Math.max( - 0, - Math.min(globalOptions.maxBreadcrumbs || 100, 100) - ); // default and hard limit is 100 - - var autoBreadcrumbDefaults = { - xhr: true, - console: true, - dom: true, - location: true - }; - - var autoBreadcrumbs = globalOptions.autoBreadcrumbs; - if ({}.toString.call(autoBreadcrumbs) === '[object Object]') { - autoBreadcrumbs = objectMerge(autoBreadcrumbDefaults, autoBreadcrumbs); - } else if (autoBreadcrumbs !== false) { - autoBreadcrumbs = autoBreadcrumbDefaults; - } - globalOptions.autoBreadcrumbs = autoBreadcrumbs; - - var instrumentDefaults = { - tryCatch: true - }; - - var instrument = globalOptions.instrument; - if ({}.toString.call(instrument) === '[object Object]') { - instrument = objectMerge(instrumentDefaults, instrument); - } else if (instrument !== false) { - instrument = instrumentDefaults; - } - globalOptions.instrument = instrument; - - TraceKit.collectWindowErrors = !!globalOptions.collectWindowErrors; - - // return for chaining - return self; - }, - - /* + config: function(dsn, options) { + var self = this; + + if (self._globalServer) { + this._logDebug('error', 'Error: Raven has already been configured'); + return self; + } + if (!dsn) return self; + + var globalOptions = self._globalOptions; + + // merge in options + if (options) { + each(options, function(key, value) { + // tags and extra are special and need to be put into context + if (key === 'tags' || key === 'extra' || key === 'user') { + self._globalContext[key] = value; + } else { + globalOptions[key] = value; + } + }); + } + + self.setDSN(dsn); + + // "Script error." is hard coded into browsers for errors that it can't read. + // this is the result of a script being pulled in from an external domain and CORS. + globalOptions.ignoreErrors.push(/^Script error\.?$/); + globalOptions.ignoreErrors.push(/^Javascript error: Script error\.? on line 0$/); + + // join regexp rules into one big rule + globalOptions.ignoreErrors = joinRegExp(globalOptions.ignoreErrors); + globalOptions.ignoreUrls = globalOptions.ignoreUrls.length + ? joinRegExp(globalOptions.ignoreUrls) + : false; + globalOptions.whitelistUrls = globalOptions.whitelistUrls.length + ? joinRegExp(globalOptions.whitelistUrls) + : false; + globalOptions.includePaths = joinRegExp(globalOptions.includePaths); + globalOptions.maxBreadcrumbs = Math.max( + 0, + Math.min(globalOptions.maxBreadcrumbs || 100, 100) + ); // default and hard limit is 100 + + var autoBreadcrumbDefaults = { + xhr: true, + console: true, + dom: true, + location: true, + sentry: true + }; + + var autoBreadcrumbs = globalOptions.autoBreadcrumbs; + if ({}.toString.call(autoBreadcrumbs) === '[object Object]') { + autoBreadcrumbs = objectMerge(autoBreadcrumbDefaults, autoBreadcrumbs); + } else if (autoBreadcrumbs !== false) { + autoBreadcrumbs = autoBreadcrumbDefaults; + } + globalOptions.autoBreadcrumbs = autoBreadcrumbs; + + var instrumentDefaults = { + tryCatch: true + }; + + var instrument = globalOptions.instrument; + if ({}.toString.call(instrument) === '[object Object]') { + instrument = objectMerge(instrumentDefaults, instrument); + } else if (instrument !== false) { + instrument = instrumentDefaults; + } + globalOptions.instrument = instrument; + + TraceKit.collectWindowErrors = !!globalOptions.collectWindowErrors; + + // return for chaining + return self; + }, + + /* * Installs a global window.onerror error handler * to capture and report uncaught exceptions. * At this point, install() is required to be called due * to the way TraceKit is set up. * * @return {Raven} */ - install: function() { - var self = this; - if (self.isSetup() && !self._isRavenInstalled) { - TraceKit.report.subscribe(function() { - self._handleOnErrorStackInfo.apply(self, arguments); - }); - if ( - self._globalOptions.instrument && - self._globalOptions.instrument.tryCatch - ) { - self._instrumentTryCatch(); - } - - if (self._globalOptions.autoBreadcrumbs) self._instrumentBreadcrumbs(); - - // Install all of the plugins - self._drainPlugins(); - - self._isRavenInstalled = true; - } - - Error.stackTraceLimit = self._globalOptions.stackTraceLimit; - return this; - }, - - /* + install: function() { + var self = this; + if (self.isSetup() && !self._isRavenInstalled) { + TraceKit.report.subscribe(function() { + self._handleOnErrorStackInfo.apply(self, arguments); + }); + + if (self._globalOptions.captureUnhandledRejections) { + self._attachPromiseRejectionHandler(); + } + + self._patchFunctionToString(); + + if (self._globalOptions.instrument && self._globalOptions.instrument.tryCatch) { + self._instrumentTryCatch(); + } + + if (self._globalOptions.autoBreadcrumbs) self._instrumentBreadcrumbs(); + + // Install all of the plugins + self._drainPlugins(); + + self._isRavenInstalled = true; + } + + Error.stackTraceLimit = self._globalOptions.stackTraceLimit; + return this; + }, + + /* * Set the DSN (can be called multiple time unlike config) * * @param {string} dsn The public Sentry DSN */ - setDSN: function(dsn) { - var self = this, - uri = self._parseDSN(dsn), - lastSlash = uri.path.lastIndexOf('/'), - path = uri.path.substr(1, lastSlash); - - self._dsn = dsn; - self._globalKey = uri.user; - self._globalSecret = uri.pass && uri.pass.substr(1); - self._globalProject = uri.path.substr(lastSlash + 1); - - self._globalServer = self._getGlobalServer(uri); - - self._globalEndpoint = - self._globalServer + - '/' + - path + - 'api/' + - self._globalProject + - '/store/'; - - // Reset backoff state since we may be pointing at a - // new project/server - this._resetBackoff(); - }, - - /* + setDSN: function(dsn) { + var self = this, + uri = self._parseDSN(dsn), + lastSlash = uri.path.lastIndexOf('/'), + path = uri.path.substr(1, lastSlash); + + self._dsn = dsn; + self._globalKey = uri.user; + self._globalSecret = uri.pass && uri.pass.substr(1); + self._globalProject = uri.path.substr(lastSlash + 1); + + self._globalServer = self._getGlobalServer(uri); + + self._globalEndpoint = + self._globalServer + '/' + path + 'api/' + self._globalProject + '/store/'; + + // Reset backoff state since we may be pointing at a + // new project/server + this._resetBackoff(); + }, + + /* * Wrap code within a context so Raven can capture errors * reliably across domains that is executed immediately. * * @param {object} options A specific set of options for this context [optional] * @param {function} func The callback to be immediately executed within the context * @param {array} args An array of arguments to be called with the callback [optional] */ - context: function(options, func, args) { - if (isFunction(options)) { - args = func || []; - func = options; - options = undefined; - } - - return this.wrap(options, func).apply(this, args); - }, - - /* + context: function(options, func, args) { + if (isFunction(options)) { + args = func || []; + func = options; + options = undefined; + } + + return this.wrap(options, func).apply(this, args); + }, + + /* * Wrap code within a context and returns back a new function to be executed * * @param {object} options A specific set of options for this context [optional] * @param {function} func The function to be wrapped in a new context * @param {function} func A function to call before the try/catch wrapper [optional, private] * @return {function} The newly wrapped functions with a context */ - wrap: function(options, func, _before) { - var self = this; - // 1 argument has been passed, and it's not a function - // so just return it - if (isUndefined(func) && !isFunction(options)) { - return options; - } - - // options is optional - if (isFunction(options)) { - func = options; - options = undefined; - } - - // At this point, we've passed along 2 arguments, and the second one - // is not a function either, so we'll just return the second argument. - if (!isFunction(func)) { - return func; - } - - // We don't wanna wrap it twice! - try { - if (func.__raven__) { - return func; - } - - // If this has already been wrapped in the past, return that - if (func.__raven_wrapper__) { - return func.__raven_wrapper__; - } - } catch (e) { - // Just accessing custom props in some Selenium environments - // can cause a "Permission denied" exception (see raven-js#495). - // Bail on wrapping and return the function as-is (defers to window.onerror). - return func; - } - - function wrapped() { - var args = [], - i = arguments.length, - deep = !options || (options && options.deep !== false); - - if (_before && isFunction(_before)) { - _before.apply(this, arguments); - } - - // Recursively wrap all of a function's arguments that are - // functions themselves. - while (i--) - args[i] = deep ? self.wrap(options, arguments[i]) : arguments[i]; - - try { - // Attempt to invoke user-land function - // NOTE: If you are a Sentry user, and you are seeing this stack frame, it - // means Raven caught an error invoking your application code. This is - // expected behavior and NOT indicative of a bug with Raven.js. - return func.apply(this, args); - } catch (e) { - self._ignoreNextOnError(); - self.captureException(e, options); - throw e; - } - } - - // copy over properties of the old function - for (var property in func) { - if (hasKey(func, property)) { - wrapped[property] = func[property]; - } - } - wrapped.prototype = func.prototype; - - func.__raven_wrapper__ = wrapped; - // Signal that this function has been wrapped already - // for both debugging and to prevent it to being wrapped twice - wrapped.__raven__ = true; - wrapped.__inner__ = func; - - return wrapped; - }, - - /* - * Uninstalls the global error handler. - * - * @return {Raven} - */ - uninstall: function() { - TraceKit.report.uninstall(); - - this._restoreBuiltIns(); - - Error.stackTraceLimit = this._originalErrorStackTraceLimit; - this._isRavenInstalled = false; - - return this; - }, - - /* - * Manually capture an exception and send it over to Sentry - * - * @param {error} ex An exception to be logged - * @param {object} options A specific set of options for this error [optional] - * @return {Raven} - */ - captureException: function(ex, options) { - // If not an Error is passed through, recall as a message instead - if (!isError(ex)) { - return this.captureMessage( - ex, - objectMerge( - { - trimHeadFrames: 1, - stacktrace: true // if we fall back to captureMessage, default to attempting a new trace - }, - options - ) - ); - } - - // Store the raw exception object for potential debugging and introspection - this._lastCapturedException = ex; - - // TraceKit.report will re-raise any exception passed to it, - // which means you have to wrap it in try/catch. Instead, we - // can wrap it here and only re-raise if TraceKit.report - // raises an exception different from the one we asked to - // report on. - try { - var stack = TraceKit.computeStackTrace(ex); - this._handleStackInfo(stack, options); - } catch (ex1) { - if (ex !== ex1) { - throw ex1; - } - } - - return this; - }, - - /* + wrap: function(options, func, _before) { + var self = this; + // 1 argument has been passed, and it's not a function + // so just return it + if (isUndefined(func) && !isFunction(options)) { + return options; + } + + // options is optional + if (isFunction(options)) { + func = options; + options = undefined; + } + + // At this point, we've passed along 2 arguments, and the second one + // is not a function either, so we'll just return the second argument. + if (!isFunction(func)) { + return func; + } + + // We don't wanna wrap it twice! + try { + if (func.__raven__) { + return func; + } + + // If this has already been wrapped in the past, return that + if (func.__raven_wrapper__) { + return func.__raven_wrapper__; + } + } catch (e) { + // Just accessing custom props in some Selenium environments + // can cause a "Permission denied" exception (see raven-js#495). + // Bail on wrapping and return the function as-is (defers to window.onerror). + return func; + } + + function wrapped() { + var args = [], + i = arguments.length, + deep = !options || (options && options.deep !== false); + + if (_before && isFunction(_before)) { + _before.apply(this, arguments); + } + + // Recursively wrap all of a function's arguments that are + // functions themselves. + while (i--) args[i] = deep ? self.wrap(options, arguments[i]) : arguments[i]; + + try { + // Attempt to invoke user-land function + // NOTE: If you are a Sentry user, and you are seeing this stack frame, it + // means Raven caught an error invoking your application code. This is + // expected behavior and NOT indicative of a bug with Raven.js. + return func.apply(this, args); + } catch (e) { + self._ignoreNextOnError(); + self.captureException(e, options); + throw e; + } + } + + // copy over properties of the old function + for (var property in func) { + if (hasKey(func, property)) { + wrapped[property] = func[property]; + } + } + wrapped.prototype = func.prototype; + + func.__raven_wrapper__ = wrapped; + // Signal that this function has been wrapped/filled already + // for both debugging and to prevent it to being wrapped/filled twice + wrapped.__raven__ = true; + wrapped.__orig__ = func; + + return wrapped; + }, + + /** + * Uninstalls the global error handler. + * + * @return {Raven} + */ + uninstall: function() { + TraceKit.report.uninstall(); + + this._detachPromiseRejectionHandler(); + this._unpatchFunctionToString(); + this._restoreBuiltIns(); + this._restoreConsole(); + + Error.stackTraceLimit = this._originalErrorStackTraceLimit; + this._isRavenInstalled = false; + + return this; + }, + + /** + * Callback used for `unhandledrejection` event + * + * @param {PromiseRejectionEvent} event An object containing + * promise: the Promise that was rejected + * reason: the value with which the Promise was rejected + * @return void + */ + _promiseRejectionHandler: function(event) { + this._logDebug('debug', 'Raven caught unhandled promise rejection:', event); + this.captureException(event.reason, { + extra: { + unhandledPromiseRejection: true + } + }); + }, + + /** + * Installs the global promise rejection handler. + * + * @return {raven} + */ + _attachPromiseRejectionHandler: function() { + this._promiseRejectionHandler = this._promiseRejectionHandler.bind(this); + _window.addEventListener && + _window.addEventListener('unhandledrejection', this._promiseRejectionHandler); + return this; + }, + + /** + * Uninstalls the global promise rejection handler. + * + * @return {raven} + */ + _detachPromiseRejectionHandler: function() { + _window.removeEventListener && + _window.removeEventListener('unhandledrejection', this._promiseRejectionHandler); + return this; + }, + + /** + * Manually capture an exception and send it over to Sentry + * + * @param {error} ex An exception to be logged + * @param {object} options A specific set of options for this error [optional] + * @return {Raven} + */ + captureException: function(ex, options) { + options = objectMerge({trimHeadFrames: 0}, options ? options : {}); + + if (isErrorEvent(ex) && ex.error) { + // If it is an ErrorEvent with `error` property, extract it to get actual Error + ex = ex.error; + } else if (isError(ex)) { + // we have a real Error object + ex = ex; + } else if (isPlainObject(ex)) { + // If it is plain Object, serialize it manually and extract options + // This will allow us to group events based on top-level keys + // which is much better than creating new group when any key/value change + options = this._getCaptureExceptionOptionsFromPlainObject(options, ex); + ex = new Error(options.message); + } else { + // If none of previous checks were valid, then it means that + // it's not a plain Object + // it's not a valid ErrorEvent (one with an error property) + // it's not an Error + // So bail out and capture it as a simple message: + return this.captureMessage( + ex, + objectMerge(options, { + stacktrace: true, // if we fall back to captureMessage, default to attempting a new trace + trimHeadFrames: options.trimHeadFrames + 1 + }) + ); + } + + // Store the raw exception object for potential debugging and introspection + this._lastCapturedException = ex; + + // TraceKit.report will re-raise any exception passed to it, + // which means you have to wrap it in try/catch. Instead, we + // can wrap it here and only re-raise if TraceKit.report + // raises an exception different from the one we asked to + // report on. + try { + var stack = TraceKit.computeStackTrace(ex); + this._handleStackInfo(stack, options); + } catch (ex1) { + if (ex !== ex1) { + throw ex1; + } + } + + return this; + }, + + _getCaptureExceptionOptionsFromPlainObject: function(currentOptions, ex) { + var exKeys = Object.keys(ex).sort(); + var options = objectMerge(currentOptions, { + message: + 'Non-Error exception captured with keys: ' + serializeKeysForMessage(exKeys), + fingerprint: [md5(exKeys)], + extra: currentOptions.extra || {} + }); + options.extra.__serialized__ = serializeException(ex); + + return options; + }, + + /* * Manually send a message to Sentry * * @param {string} msg A plain message to be captured in Sentry * @param {object} options A specific set of options for this message [optional] * @return {Raven} */ - captureMessage: function(msg, options) { - // config() automagically converts ignoreErrors from a list to a RegExp so we need to test for an - // early call; we'll error on the side of logging anything called before configuration since it's - // probably something you should see: - if ( - !!this._globalOptions.ignoreErrors.test && - this._globalOptions.ignoreErrors.test(msg) - ) { - return; - } - - options = options || {}; - - var data = objectMerge( - { - message: msg + '' // Make sure it's actually a string - }, - options - ); - - if (this._globalOptions.stacktrace || (options && options.stacktrace)) { - var ex; - // Generate a "synthetic" stack trace from this point. - // NOTE: If you are a Sentry user, and you are seeing this stack frame, it is NOT indicative - // of a bug with Raven.js. Sentry generates synthetic traces either by configuration, - // or if it catches a thrown object without a "stack" property. - try { - throw new Error(msg); - } catch (ex1) { - ex = ex1; - } - - // null exception name so `Error` isn't prefixed to msg - ex.name = null; - - options = objectMerge( - { - // fingerprint on msg, not stack trace (legacy behavior, could be - // revisited) - fingerprint: msg, - // since we know this is a synthetic trace, the top N-most frames - // MUST be from Raven.js, so mark them as in_app later by setting - // trimHeadFrames - trimHeadFrames: (options.trimHeadFrames || 0) + 1 - }, - options - ); - - var stack = TraceKit.computeStackTrace(ex); - var frames = this._prepareFrames(stack, options); - data.stacktrace = { - // Sentry expects frames oldest to newest - frames: frames.reverse() - }; - } - - // Fire away! - this._send(data); - - return this; - }, - - captureBreadcrumb: function(obj) { - var crumb = objectMerge( - { - timestamp: now() / 1000 - }, - obj - ); - - if (isFunction(this._globalOptions.breadcrumbCallback)) { - var result = this._globalOptions.breadcrumbCallback(crumb); - - if (isObject(result) && !isEmptyObject(result)) { - crumb = result; - } else if (result === false) { - return this; - } - } - - this._breadcrumbs.push(crumb); - if (this._breadcrumbs.length > this._globalOptions.maxBreadcrumbs) { - this._breadcrumbs.shift(); - } - return this; - }, - - addPlugin: function(plugin /*arg1, arg2, ... argN*/) { - var pluginArgs = [].slice.call(arguments, 1); - - this._plugins.push([plugin, pluginArgs]); - if (this._isRavenInstalled) { - this._drainPlugins(); - } - - return this; - }, - - /* + captureMessage: function(msg, options) { + // config() automagically converts ignoreErrors from a list to a RegExp so we need to test for an + // early call; we'll error on the side of logging anything called before configuration since it's + // probably something you should see: + if ( + !!this._globalOptions.ignoreErrors.test && + this._globalOptions.ignoreErrors.test(msg) + ) { + return; + } + + options = options || {}; + msg = msg + ''; // Make sure it's actually a string + + var data = objectMerge( + { + message: msg + }, + options + ); + + var ex; + // Generate a "synthetic" stack trace from this point. + // NOTE: If you are a Sentry user, and you are seeing this stack frame, it is NOT indicative + // of a bug with Raven.js. Sentry generates synthetic traces either by configuration, + // or if it catches a thrown object without a "stack" property. + try { + throw new Error(msg); + } catch (ex1) { + ex = ex1; + } + + // null exception name so `Error` isn't prefixed to msg + ex.name = null; + var stack = TraceKit.computeStackTrace(ex); + + // stack[0] is `throw new Error(msg)` call itself, we are interested in the frame that was just before that, stack[1] + var initialCall = isArray(stack.stack) && stack.stack[1]; + var fileurl = (initialCall && initialCall.url) || ''; + + if ( + !!this._globalOptions.ignoreUrls.test && + this._globalOptions.ignoreUrls.test(fileurl) + ) { + return; + } + + if ( + !!this._globalOptions.whitelistUrls.test && + !this._globalOptions.whitelistUrls.test(fileurl) + ) { + return; + } + + if (this._globalOptions.stacktrace || (options && options.stacktrace)) { + // fingerprint on msg, not stack trace (legacy behavior, could be revisited) + data.fingerprint = data.fingerprint == null ? msg : data.fingerprint; + + options = objectMerge( + { + trimHeadFrames: 0 + }, + options + ); + // Since we know this is a synthetic trace, the top frame (this function call) + // MUST be from Raven.js, so mark it for trimming + // We add to the trim counter so that callers can choose to trim extra frames, such + // as utility functions. + options.trimHeadFrames += 1; + + var frames = this._prepareFrames(stack, options); + data.stacktrace = { + // Sentry expects frames oldest to newest + frames: frames.reverse() + }; + } + + // Make sure that fingerprint is always wrapped in an array + if (data.fingerprint) { + data.fingerprint = isArray(data.fingerprint) + ? data.fingerprint + : [data.fingerprint]; + } + + // Fire away! + this._send(data); + + return this; + }, + + captureBreadcrumb: function(obj) { + var crumb = objectMerge( + { + timestamp: now() / 1000 + }, + obj + ); + + if (isFunction(this._globalOptions.breadcrumbCallback)) { + var result = this._globalOptions.breadcrumbCallback(crumb); + + if (isObject(result) && !isEmptyObject(result)) { + crumb = result; + } else if (result === false) { + return this; + } + } + + this._breadcrumbs.push(crumb); + if (this._breadcrumbs.length > this._globalOptions.maxBreadcrumbs) { + this._breadcrumbs.shift(); + } + return this; + }, + + addPlugin: function(plugin /*arg1, arg2, ... argN*/) { + var pluginArgs = [].slice.call(arguments, 1); + + this._plugins.push([plugin, pluginArgs]); + if (this._isRavenInstalled) { + this._drainPlugins(); + } + + return this; + }, + + /* * Set/clear a user to be sent along with the payload. * * @param {object} user An object representing user data [optional] * @return {Raven} */ - setUserContext: function(user) { - // Intentionally do not merge here since that's an unexpected behavior. - this._globalContext.user = user; - - return this; - }, - - /* + setUserContext: function(user) { + // Intentionally do not merge here since that's an unexpected behavior. + this._globalContext.user = user; + + return this; + }, + + /* * Merge extra attributes to be sent along with the payload. * * @param {object} extra An object representing extra data [optional] * @return {Raven} */ - setExtraContext: function(extra) { - this._mergeContext('extra', extra); - - return this; - }, - - /* + setExtraContext: function(extra) { + this._mergeContext('extra', extra); + + return this; + }, + + /* * Merge tags to be sent along with the payload. * * @param {object} tags An object representing tags [optional] * @return {Raven} */ - setTagsContext: function(tags) { - this._mergeContext('tags', tags); - - return this; - }, - - /* + setTagsContext: function(tags) { + this._mergeContext('tags', tags); + + return this; + }, + + /* * Clear all of the context. * * @return {Raven} */ - clearContext: function() { - this._globalContext = {}; - - return this; - }, - - /* + clearContext: function() { + this._globalContext = {}; + + return this; + }, + + /* * Get a copy of the current context. This cannot be mutated. * * @return {object} copy of context */ - getContext: function() { - // lol javascript - return JSON.parse(stringify(this._globalContext)); - }, - - /* + getContext: function() { + // lol javascript + return JSON.parse(stringify(this._globalContext)); + }, + + /* * Set environment of application * * @param {string} environment Typically something like 'production'. * @return {Raven} */ - setEnvironment: function(environment) { - this._globalOptions.environment = environment; - - return this; - }, - - /* + setEnvironment: function(environment) { + this._globalOptions.environment = environment; + + return this; + }, + + /* * Set release version of application * * @param {string} release Typically something like a git SHA to identify version * @return {Raven} */ - setRelease: function(release) { - this._globalOptions.release = release; - - return this; - }, - - /* + setRelease: function(release) { + this._globalOptions.release = release; + + return this; + }, + + /* * Set the dataCallback option * * @param {function} callback The callback to run which allows the * data blob to be mutated before sending * @return {Raven} */ - setDataCallback: function(callback) { - var original = this._globalOptions.dataCallback; - this._globalOptions.dataCallback = keepOriginalCallback( - original, - callback - ); - return this; - }, - - /* + setDataCallback: function(callback) { + var original = this._globalOptions.dataCallback; + this._globalOptions.dataCallback = keepOriginalCallback(original, callback); + return this; + }, + + /* * Set the breadcrumbCallback option * * @param {function} callback The callback to run which allows filtering * or mutating breadcrumbs * @return {Raven} */ - setBreadcrumbCallback: function(callback) { - var original = this._globalOptions.breadcrumbCallback; - this._globalOptions.breadcrumbCallback = keepOriginalCallback( - original, - callback - ); - return this; - }, - - /* + setBreadcrumbCallback: function(callback) { + var original = this._globalOptions.breadcrumbCallback; + this._globalOptions.breadcrumbCallback = keepOriginalCallback(original, callback); + return this; + }, + + /* * Set the shouldSendCallback option * * @param {function} callback The callback to run which allows * introspecting the blob before sending * @return {Raven} */ - setShouldSendCallback: function(callback) { - var original = this._globalOptions.shouldSendCallback; - this._globalOptions.shouldSendCallback = keepOriginalCallback( - original, - callback - ); - return this; - }, - - /** - * Override the default HTTP transport mechanism that transmits data - * to the Sentry server. - * - * @param {function} transport Function invoked instead of the default - * `makeRequest` handler. - * - * @return {Raven} - */ - setTransport: function(transport) { - this._globalOptions.transport = transport; - - return this; - }, - - /* + setShouldSendCallback: function(callback) { + var original = this._globalOptions.shouldSendCallback; + this._globalOptions.shouldSendCallback = keepOriginalCallback(original, callback); + return this; + }, + + /** + * Override the default HTTP transport mechanism that transmits data + * to the Sentry server. + * + * @param {function} transport Function invoked instead of the default + * `makeRequest` handler. + * + * @return {Raven} + */ + setTransport: function(transport) { + this._globalOptions.transport = transport; + + return this; + }, + + /* * Get the latest raw exception that was captured by Raven. * * @return {error} */ - lastException: function() { - return this._lastCapturedException; - }, - - /* + lastException: function() { + return this._lastCapturedException; + }, + + /* * Get the last event id * * @return {string} */ - lastEventId: function() { - return this._lastEventId; - }, - - /* + lastEventId: function() { + return this._lastEventId; + }, + + /* * Determine if Raven is setup and ready to go. * * @return {boolean} */ - isSetup: function() { - if (!this._hasJSON) return false; // needs JSON support - if (!this._globalServer) { - if (!this.ravenNotConfiguredError) { - this.ravenNotConfiguredError = true; - this._logDebug('error', 'Error: Raven has not been configured.'); - } - return false; - } - return true; - }, - - afterLoad: function() { - // TODO: remove window dependence? - - // Attempt to initialize Raven on load - var RavenConfig = _window.RavenConfig; - if (RavenConfig) { - this.config(RavenConfig.dsn, RavenConfig.config).install(); - } - }, - - showReportDialog: function(options) { - if ( - !_document // doesn't work without a document (React native) - ) - return; - - options = options || {}; - - var lastEventId = options.eventId || this.lastEventId(); - if (!lastEventId) { - throw new RavenConfigError('Missing eventId'); - } - - var dsn = options.dsn || this._dsn; - if (!dsn) { - throw new RavenConfigError('Missing DSN'); - } - - var encode = encodeURIComponent; - var qs = ''; - qs += '?eventId=' + encode(lastEventId); - qs += '&dsn=' + encode(dsn); - - var user = options.user || this._globalContext.user; - if (user) { - if (user.name) qs += '&name=' + encode(user.name); - if (user.email) qs += '&email=' + encode(user.email); + isSetup: function() { + if (!this._hasJSON) return false; // needs JSON support + if (!this._globalServer) { + if (!this.ravenNotConfiguredError) { + this.ravenNotConfiguredError = true; + this._logDebug('error', 'Error: Raven has not been configured.'); + } + return false; + } + return true; + }, + + afterLoad: function() { + // TODO: remove window dependence? + + // Attempt to initialize Raven on load + var RavenConfig = _window.RavenConfig; + if (RavenConfig) { + this.config(RavenConfig.dsn, RavenConfig.config).install(); + } + }, + + showReportDialog: function(options) { + if ( + !_document // doesn't work without a document (React native) + ) + return; + + options = options || {}; + + var lastEventId = options.eventId || this.lastEventId(); + if (!lastEventId) { + throw new RavenConfigError('Missing eventId'); + } + + var dsn = options.dsn || this._dsn; + if (!dsn) { + throw new RavenConfigError('Missing DSN'); + } + + var encode = encodeURIComponent; + var qs = ''; + qs += '?eventId=' + encode(lastEventId); + qs += '&dsn=' + encode(dsn); + + var user = options.user || this._globalContext.user; + if (user) { + if (user.name) qs += '&name=' + encode(user.name); + if (user.email) qs += '&email=' + encode(user.email); + } + + var globalServer = this._getGlobalServer(this._parseDSN(dsn)); + + var script = _document.createElement('script'); + script.async = true; + script.src = globalServer + '/api/embed/error-page/' + qs; + (_document.head || _document.body).appendChild(script); + }, + + /**** Private functions ****/ + _ignoreNextOnError: function() { + var self = this; + this._ignoreOnError += 1; + setTimeout(function() { + // onerror should trigger before setTimeout + self._ignoreOnError -= 1; + }); + }, + + _triggerEvent: function(eventType, options) { + // NOTE: `event` is a native browser thing, so let's avoid conflicting wiht it + var evt, key; + + if (!this._hasDocument) return; + + options = options || {}; + + eventType = 'raven' + eventType.substr(0, 1).toUpperCase() + eventType.substr(1); + + if (_document.createEvent) { + evt = _document.createEvent('HTMLEvents'); + evt.initEvent(eventType, true, true); + } else { + evt = _document.createEventObject(); + evt.eventType = eventType; + } + + for (key in options) + if (hasKey(options, key)) { + evt[key] = options[key]; + } + + if (_document.createEvent) { + // IE9 if standards + _document.dispatchEvent(evt); + } else { + // IE8 regardless of Quirks or Standards + // IE9 if quirks + try { + _document.fireEvent('on' + evt.eventType.toLowerCase(), evt); + } catch (e) { + // Do nothing + } + } + }, + + /** + * Wraps addEventListener to capture UI breadcrumbs + * @param evtName the event name (e.g. "click") + * @returns {Function} + * @private + */ + _breadcrumbEventHandler: function(evtName) { + var self = this; + return function(evt) { + // reset keypress timeout; e.g. triggering a 'click' after + // a 'keypress' will reset the keypress debounce so that a new + // set of keypresses can be recorded + self._keypressTimeout = null; + + // It's possible this handler might trigger multiple times for the same + // event (e.g. event propagation through node ancestors). Ignore if we've + // already captured the event. + if (self._lastCapturedEvent === evt) return; + + self._lastCapturedEvent = evt; + + // try/catch both: + // - accessing evt.target (see getsentry/raven-js#838, #768) + // - `htmlTreeAsString` because it's complex, and just accessing the DOM incorrectly + // can throw an exception in some circumstances. + var target; + try { + target = htmlTreeAsString(evt.target); + } catch (e) { + target = '<unknown>'; + } + + self.captureBreadcrumb({ + category: 'ui.' + evtName, // e.g. ui.click, ui.input + message: target + }); + }; + }, + + /** + * Wraps addEventListener to capture keypress UI events + * @returns {Function} + * @private + */ + _keypressEventHandler: function() { + var self = this, + debounceDuration = 1000; // milliseconds + + // TODO: if somehow user switches keypress target before + // debounce timeout is triggered, we will only capture + // a single breadcrumb from the FIRST target (acceptable?) + return function(evt) { + var target; + try { + target = evt.target; + } catch (e) { + // just accessing event properties can throw an exception in some rare circumstances + // see: https://github.com/getsentry/raven-js/issues/838 + return; + } + var tagName = target && target.tagName; + + // only consider keypress events on actual input elements + // this will disregard keypresses targeting body (e.g. tabbing + // through elements, hotkeys, etc) + if ( + !tagName || + (tagName !== 'INPUT' && tagName !== 'TEXTAREA' && !target.isContentEditable) + ) + return; + + // record first keypress in a series, but ignore subsequent + // keypresses until debounce clears + var timeout = self._keypressTimeout; + if (!timeout) { + self._breadcrumbEventHandler('input')(evt); + } + clearTimeout(timeout); + self._keypressTimeout = setTimeout(function() { + self._keypressTimeout = null; + }, debounceDuration); + }; + }, + + /** + * Captures a breadcrumb of type "navigation", normalizing input URLs + * @param to the originating URL + * @param from the target URL + * @private + */ + _captureUrlChange: function(from, to) { + var parsedLoc = parseUrl(this._location.href); + var parsedTo = parseUrl(to); + var parsedFrom = parseUrl(from); + + // because onpopstate only tells you the "new" (to) value of location.href, and + // not the previous (from) value, we need to track the value of the current URL + // state ourselves + this._lastHref = to; + + // Use only the path component of the URL if the URL matches the current + // document (almost all the time when using pushState) + if (parsedLoc.protocol === parsedTo.protocol && parsedLoc.host === parsedTo.host) + to = parsedTo.relative; + if (parsedLoc.protocol === parsedFrom.protocol && parsedLoc.host === parsedFrom.host) + from = parsedFrom.relative; + + this.captureBreadcrumb({ + category: 'navigation', + data: { + to: to, + from: from + } + }); + }, + + _patchFunctionToString: function() { + var self = this; + self._originalFunctionToString = Function.prototype.toString; + // eslint-disable-next-line no-extend-native + Function.prototype.toString = function() { + if (typeof this === 'function' && this.__raven__) { + return self._originalFunctionToString.apply(this.__orig__, arguments); + } + return self._originalFunctionToString.apply(this, arguments); + }; + }, + + _unpatchFunctionToString: function() { + if (this._originalFunctionToString) { + // eslint-disable-next-line no-extend-native + Function.prototype.toString = this._originalFunctionToString; + } + }, + + /** + * Wrap timer functions and event targets to catch errors and provide + * better metadata. + */ + _instrumentTryCatch: function() { + var self = this; + + var wrappedBuiltIns = self._wrappedBuiltIns; + + function wrapTimeFn(orig) { + return function(fn, t) { + // preserve arity + // Make a copy of the arguments to prevent deoptimization + // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + var originalCallback = args[0]; + if (isFunction(originalCallback)) { + args[0] = self.wrap(originalCallback); + } + + // IE < 9 doesn't support .call/.apply on setInterval/setTimeout, but it + // also supports only two arguments and doesn't care what this is, so we + // can just call the original function directly. + if (orig.apply) { + return orig.apply(this, args); + } else { + return orig(args[0], args[1]); + } + }; + } + + var autoBreadcrumbs = this._globalOptions.autoBreadcrumbs; + + function wrapEventTarget(global) { + var proto = _window[global] && _window[global].prototype; + if (proto && proto.hasOwnProperty && proto.hasOwnProperty('addEventListener')) { + fill( + proto, + 'addEventListener', + function(orig) { + return function(evtName, fn, capture, secure) { + // preserve arity + try { + if (fn && fn.handleEvent) { + fn.handleEvent = self.wrap(fn.handleEvent); } - - var globalServer = this._getGlobalServer(this._parseDSN(dsn)); - - var script = _document.createElement('script'); - script.async = true; - script.src = globalServer + '/api/embed/error-page/' + qs; - (_document.head || _document.body).appendChild(script); - }, - - /**** Private functions ****/ - _ignoreNextOnError: function() { - var self = this; - this._ignoreOnError += 1; - setTimeout(function() { - // onerror should trigger before setTimeout - self._ignoreOnError -= 1; - }); - }, - - _triggerEvent: function(eventType, options) { - // NOTE: `event` is a native browser thing, so let's avoid conflicting wiht it - var evt, key; - - if (!this._hasDocument) return; - - options = options || {}; - - eventType = - 'raven' + eventType.substr(0, 1).toUpperCase() + eventType.substr(1); - - if (_document.createEvent) { - evt = _document.createEvent('HTMLEvents'); - evt.initEvent(eventType, true, true); - } else { - evt = _document.createEventObject(); - evt.eventType = eventType; - } - - for (key in options) - if (hasKey(options, key)) { - evt[key] = options[key]; - } - - if (_document.createEvent) { - // IE9 if standards - _document.dispatchEvent(evt); - } else { - // IE8 regardless of Quirks or Standards - // IE9 if quirks + } catch (err) { + // can sometimes get 'Permission denied to access property "handle Event' + } + + // More breadcrumb DOM capture ... done here and not in `_instrumentBreadcrumbs` + // so that we don't have more than one wrapper function + var before, clickHandler, keypressHandler; + + if ( + autoBreadcrumbs && + autoBreadcrumbs.dom && + (global === 'EventTarget' || global === 'Node') + ) { + // NOTE: generating multiple handlers per addEventListener invocation, should + // revisit and verify we can just use one (almost certainly) + clickHandler = self._breadcrumbEventHandler('click'); + keypressHandler = self._keypressEventHandler(); + before = function(evt) { + // need to intercept every DOM event in `before` argument, in case that + // same wrapped method is re-used for different events (e.g. mousemove THEN click) + // see #724 + if (!evt) return; + + var eventType; try { - _document.fireEvent('on' + evt.eventType.toLowerCase(), evt); - } catch (e) { - // Do nothing - } - } - }, - - /** - * Wraps addEventListener to capture UI breadcrumbs - * @param evtName the event name (e.g. "click") - * @returns {Function} - * @private - */ - _breadcrumbEventHandler: function(evtName) { - var self = this; - return function(evt) { - // reset keypress timeout; e.g. triggering a 'click' after - // a 'keypress' will reset the keypress debounce so that a new - // set of keypresses can be recorded - self._keypressTimeout = null; - - // It's possible this handler might trigger multiple times for the same - // event (e.g. event propagation through node ancestors). Ignore if we've - // already captured the event. - if (self._lastCapturedEvent === evt) return; - - self._lastCapturedEvent = evt; - - // try/catch both: - // - accessing evt.target (see getsentry/raven-js#838, #768) - // - `htmlTreeAsString` because it's complex, and just accessing the DOM incorrectly - // can throw an exception in some circumstances. - var target; - try { - target = htmlTreeAsString(evt.target); - } catch (e) { - target = '<unknown>'; - } - - self.captureBreadcrumb({ - category: 'ui.' + evtName, // e.g. ui.click, ui.input - message: target - }); - }; - }, - - /** - * Wraps addEventListener to capture keypress UI events - * @returns {Function} - * @private - */ - _keypressEventHandler: function() { - var self = this, - debounceDuration = 1000; // milliseconds - - // TODO: if somehow user switches keypress target before - // debounce timeout is triggered, we will only capture - // a single breadcrumb from the FIRST target (acceptable?) - return function(evt) { - var target; - try { - target = evt.target; + eventType = evt.type; } catch (e) { // just accessing event properties can throw an exception in some rare circumstances // see: https://github.com/getsentry/raven-js/issues/838 return; } - var tagName = target && target.tagName; - - // only consider keypress events on actual input elements - // this will disregard keypresses targeting body (e.g. tabbing - // through elements, hotkeys, etc) - if ( - !tagName || - (tagName !== 'INPUT' && - tagName !== 'TEXTAREA' && - !target.isContentEditable) - ) - return; - - // record first keypress in a series, but ignore subsequent - // keypresses until debounce clears - var timeout = self._keypressTimeout; - if (!timeout) { - self._breadcrumbEventHandler('input')(evt); - } - clearTimeout(timeout); - self._keypressTimeout = setTimeout(function() { - self._keypressTimeout = null; - }, debounceDuration); + if (eventType === 'click') return clickHandler(evt); + else if (eventType === 'keypress') return keypressHandler(evt); }; - }, - - /** - * Captures a breadcrumb of type "navigation", normalizing input URLs - * @param to the originating URL - * @param from the target URL - * @private - */ - _captureUrlChange: function(from, to) { - var parsedLoc = parseUrl(this._location.href); - var parsedTo = parseUrl(to); - var parsedFrom = parseUrl(from); - - // because onpopstate only tells you the "new" (to) value of location.href, and - // not the previous (from) value, we need to track the value of the current URL - // state ourselves - this._lastHref = to; - - // Use only the path component of the URL if the URL matches the current - // document (almost all the time when using pushState) - if ( - parsedLoc.protocol === parsedTo.protocol && - parsedLoc.host === parsedTo.host - ) - to = parsedTo.relative; - if ( - parsedLoc.protocol === parsedFrom.protocol && - parsedLoc.host === parsedFrom.host - ) - from = parsedFrom.relative; - - this.captureBreadcrumb({ - category: 'navigation', - data: { - to: to, - from: from - } - }); - }, - - /** - * Wrap timer functions and event targets to catch errors and provide - * better metadata. - */ - _instrumentTryCatch: function() { - var self = this; - - var wrappedBuiltIns = self._wrappedBuiltIns; - - function wrapTimeFn(orig) { - return function(fn, t) { - // preserve arity - // Make a copy of the arguments to prevent deoptimization - // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments - var args = new Array(arguments.length); - for (var i = 0; i < args.length; ++i) { - args[i] = arguments[i]; - } - var originalCallback = args[0]; - if (isFunction(originalCallback)) { - args[0] = self.wrap(originalCallback); - } - - // IE < 9 doesn't support .call/.apply on setInterval/setTimeout, but it - // also supports only two arguments and doesn't care what this is, so we - // can just call the original function directly. - if (orig.apply) { - return orig.apply(this, args); - } else { - return orig(args[0], args[1]); - } - }; - } - - var autoBreadcrumbs = this._globalOptions.autoBreadcrumbs; - - function wrapEventTarget(global) { - var proto = _window[global] && _window[global].prototype; - if ( - proto && - proto.hasOwnProperty && - proto.hasOwnProperty('addEventListener') - ) { - fill( - proto, - 'addEventListener', - function(orig) { - return function(evtName, fn, capture, secure) { - // preserve arity - try { - if (fn && fn.handleEvent) { - fn.handleEvent = self.wrap(fn.handleEvent); - } - } catch (err) { - // can sometimes get 'Permission denied to access property "handle Event' - } - - // More breadcrumb DOM capture ... done here and not in `_instrumentBreadcrumbs` - // so that we don't have more than one wrapper function - var before, clickHandler, keypressHandler; - - if ( - autoBreadcrumbs && - autoBreadcrumbs.dom && - (global === 'EventTarget' || global === 'Node') - ) { - // NOTE: generating multiple handlers per addEventListener invocation, should - // revisit and verify we can just use one (almost certainly) - clickHandler = self._breadcrumbEventHandler('click'); - keypressHandler = self._keypressEventHandler(); - before = function(evt) { - // need to intercept every DOM event in `before` argument, in case that - // same wrapped method is re-used for different events (e.g. mousemove THEN click) - // see #724 - if (!evt) return; - - var eventType; - try { - eventType = evt.type; - } catch (e) { - // just accessing event properties can throw an exception in some rare circumstances - // see: https://github.com/getsentry/raven-js/issues/838 - return; - } - if (eventType === 'click') return clickHandler(evt); - else if (eventType === 'keypress') - return keypressHandler(evt); - }; - } - return orig.call( - this, - evtName, - self.wrap(fn, undefined, before), - capture, - secure - ); - }; - }, - wrappedBuiltIns - ); - fill( - proto, - 'removeEventListener', - function(orig) { - return function(evt, fn, capture, secure) { - try { - fn = fn && (fn.__raven_wrapper__ ? fn.__raven_wrapper__ : fn); - } catch (e) { - // ignore, accessing __raven_wrapper__ will throw in some Selenium environments - } - return orig.call(this, evt, fn, capture, secure); - }; - }, - wrappedBuiltIns - ); - } - } - - fill(_window, 'setTimeout', wrapTimeFn, wrappedBuiltIns); - fill(_window, 'setInterval', wrapTimeFn, wrappedBuiltIns); - if (_window.requestAnimationFrame) { - fill( - _window, - 'requestAnimationFrame', - function(orig) { - return function(cb) { - return orig(self.wrap(cb)); - }; - }, - wrappedBuiltIns - ); - } - - // event targets borrowed from bugsnag-js: - // https://github.com/bugsnag/bugsnag-js/blob/master/src/bugsnag.js#L666 - var eventTargets = [ - 'EventTarget', - 'Window', - 'Node', - 'ApplicationCache', - 'AudioTrackList', - 'ChannelMergerNode', - 'CryptoOperation', - 'EventSource', - 'FileReader', - 'HTMLUnknownElement', - 'IDBDatabase', - 'IDBRequest', - 'IDBTransaction', - 'KeyOperation', - 'MediaController', - 'MessagePort', - 'ModalWindow', - 'Notification', - 'SVGElementInstance', - 'Screen', - 'TextTrack', - 'TextTrackCue', - 'TextTrackList', - 'WebSocket', - 'WebSocketWorker', - 'Worker', - 'XMLHttpRequest', - 'XMLHttpRequestEventTarget', - 'XMLHttpRequestUpload' - ]; - for (var i = 0; i < eventTargets.length; i++) { - wrapEventTarget(eventTargets[i]); - } - }, - - /** - * Instrument browser built-ins w/ breadcrumb capturing - * - XMLHttpRequests - * - DOM interactions (click/typing) - * - window.location changes - * - console - * - * Can be disabled or individually configured via the `autoBreadcrumbs` config option - */ - _instrumentBreadcrumbs: function() { - var self = this; - var autoBreadcrumbs = this._globalOptions.autoBreadcrumbs; - - var wrappedBuiltIns = self._wrappedBuiltIns; - - function wrapProp(prop, xhr) { - if (prop in xhr && isFunction(xhr[prop])) { - fill(xhr, prop, function(orig) { - return self.wrap(orig); - }); // intentionally don't track filled methods on XHR instances - } + } + return orig.call( + this, + evtName, + self.wrap(fn, undefined, before), + capture, + secure + ); + }; + }, + wrappedBuiltIns + ); + fill( + proto, + 'removeEventListener', + function(orig) { + return function(evt, fn, capture, secure) { + try { + fn = fn && (fn.__raven_wrapper__ ? fn.__raven_wrapper__ : fn); + } catch (e) { + // ignore, accessing __raven_wrapper__ will throw in some Selenium environments + } + return orig.call(this, evt, fn, capture, secure); + }; + }, + wrappedBuiltIns + ); + } + } + + fill(_window, 'setTimeout', wrapTimeFn, wrappedBuiltIns); + fill(_window, 'setInterval', wrapTimeFn, wrappedBuiltIns); + if (_window.requestAnimationFrame) { + fill( + _window, + 'requestAnimationFrame', + function(orig) { + return function(cb) { + return orig(self.wrap(cb)); + }; + }, + wrappedBuiltIns + ); + } + + // event targets borrowed from bugsnag-js: + // https://github.com/bugsnag/bugsnag-js/blob/master/src/bugsnag.js#L666 + var eventTargets = [ + 'EventTarget', + 'Window', + 'Node', + 'ApplicationCache', + 'AudioTrackList', + 'ChannelMergerNode', + 'CryptoOperation', + 'EventSource', + 'FileReader', + 'HTMLUnknownElement', + 'IDBDatabase', + 'IDBRequest', + 'IDBTransaction', + 'KeyOperation', + 'MediaController', + 'MessagePort', + 'ModalWindow', + 'Notification', + 'SVGElementInstance', + 'Screen', + 'TextTrack', + 'TextTrackCue', + 'TextTrackList', + 'WebSocket', + 'WebSocketWorker', + 'Worker', + 'XMLHttpRequest', + 'XMLHttpRequestEventTarget', + 'XMLHttpRequestUpload' + ]; + for (var i = 0; i < eventTargets.length; i++) { + wrapEventTarget(eventTargets[i]); + } + }, + + /** + * Instrument browser built-ins w/ breadcrumb capturing + * - XMLHttpRequests + * - DOM interactions (click/typing) + * - window.location changes + * - console + * + * Can be disabled or individually configured via the `autoBreadcrumbs` config option + */ + _instrumentBreadcrumbs: function() { + var self = this; + var autoBreadcrumbs = this._globalOptions.autoBreadcrumbs; + + var wrappedBuiltIns = self._wrappedBuiltIns; + + function wrapProp(prop, xhr) { + if (prop in xhr && isFunction(xhr[prop])) { + fill(xhr, prop, function(orig) { + return self.wrap(orig); + }); // intentionally don't track filled methods on XHR instances + } + } + + if (autoBreadcrumbs.xhr && 'XMLHttpRequest' in _window) { + var xhrproto = _window.XMLHttpRequest && _window.XMLHttpRequest.prototype; + fill( + xhrproto, + 'open', + function(origOpen) { + return function(method, url) { + // preserve arity + + // if Sentry key appears in URL, don't capture + if (isString(url) && url.indexOf(self._globalKey) === -1) { + this.__raven_xhr = { + method: method, + url: url, + status_code: null + }; + } + + return origOpen.apply(this, arguments); + }; + }, + wrappedBuiltIns + ); + + fill( + xhrproto, + 'send', + function(origSend) { + return function() { + // preserve arity + var xhr = this; + + function onreadystatechangeHandler() { + if (xhr.__raven_xhr && xhr.readyState === 4) { + try { + // touching statusCode in some platforms throws + // an exception + xhr.__raven_xhr.status_code = xhr.status; + } catch (e) { + /* do nothing */ } - if (autoBreadcrumbs.xhr && 'XMLHttpRequest' in _window) { - var xhrproto = XMLHttpRequest.prototype; - fill( - xhrproto, - 'open', - function(origOpen) { - return function(method, url) { - // preserve arity - - // if Sentry key appears in URL, don't capture - if (isString(url) && url.indexOf(self._globalKey) === -1) { - this.__raven_xhr = { - method: method, - url: url, - status_code: null - }; - } - - return origOpen.apply(this, arguments); - }; - }, - wrappedBuiltIns - ); - - fill( - xhrproto, - 'send', - function(origSend) { - return function(data) { - // preserve arity - var xhr = this; - - function onreadystatechangeHandler() { - if (xhr.__raven_xhr && xhr.readyState === 4) { - try { - // touching statusCode in some platforms throws - // an exception - xhr.__raven_xhr.status_code = xhr.status; - } catch (e) { - /* do nothing */ - } - - self.captureBreadcrumb({ - type: 'http', - category: 'xhr', - data: xhr.__raven_xhr - }); - } - } - - var props = ['onload', 'onerror', 'onprogress']; - for (var j = 0; j < props.length; j++) { - wrapProp(props[j], xhr); - } - - if ( - 'onreadystatechange' in xhr && - isFunction(xhr.onreadystatechange) - ) { - fill( - xhr, - 'onreadystatechange', - function(orig) { - return self.wrap( - orig, - undefined, - onreadystatechangeHandler - ); - } /* intentionally don't track this instrumentation */ - ); - } else { - // if onreadystatechange wasn't actually set by the page on this xhr, we - // are free to set our own and capture the breadcrumb - xhr.onreadystatechange = onreadystatechangeHandler; - } - - return origSend.apply(this, arguments); - }; - }, - wrappedBuiltIns - ); - } - - if (autoBreadcrumbs.xhr && 'fetch' in _window) { - fill( - _window, - 'fetch', - function(origFetch) { - return function(fn, t) { - // preserve arity - // Make a copy of the arguments to prevent deoptimization - // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments - var args = new Array(arguments.length); - for (var i = 0; i < args.length; ++i) { - args[i] = arguments[i]; - } - - var fetchInput = args[0]; - var method = 'GET'; - var url; - - if (typeof fetchInput === 'string') { - url = fetchInput; - } else { - url = fetchInput.url; - if (fetchInput.method) { - method = fetchInput.method; - } - } - - if (args[1] && args[1].method) { - method = args[1].method; - } - - var fetchData = { - method: method, - url: url, - status_code: null - }; - - self.captureBreadcrumb({ - type: 'http', - category: 'fetch', - data: fetchData - }); - - return origFetch.apply(this, args).then(function(response) { - fetchData.status_code = response.status; - - return response; - }); - }; - }, - wrappedBuiltIns - ); - } - - // Capture breadcrumbs from any click that is unhandled / bubbled up all the way - // to the document. Do this before we instrument addEventListener. - if (autoBreadcrumbs.dom && this._hasDocument) { - if (_document.addEventListener) { - _document.addEventListener( - 'click', - self._breadcrumbEventHandler('click'), - false - ); - _document.addEventListener( - 'keypress', - self._keypressEventHandler(), - false - ); - } else { - // IE8 Compatibility - _document.attachEvent( - 'onclick', - self._breadcrumbEventHandler('click') - ); - _document.attachEvent('onkeypress', self._keypressEventHandler()); - } - } - - // record navigation (URL) changes - // NOTE: in Chrome App environment, touching history.pushState, *even inside - // a try/catch block*, will cause Chrome to output an error to console.error - // borrowed from: https://github.com/angular/angular.js/pull/13945/files - var chrome = _window.chrome; - var isChromePackagedApp = chrome && chrome.app && chrome.app.runtime; - var hasPushState = - !isChromePackagedApp && _window.history && history.pushState; - if (autoBreadcrumbs.location && hasPushState) { - // TODO: remove onpopstate handler on uninstall() - var oldOnPopState = _window.onpopstate; - _window.onpopstate = function() { - var currentHref = self._location.href; - self._captureUrlChange(self._lastHref, currentHref); - - if (oldOnPopState) { - return oldOnPopState.apply(this, arguments); - } - }; - - fill( - history, - 'pushState', - function(origPushState) { - // note history.pushState.length is 0; intentionally not declaring - // params to preserve 0 arity - return function(/* state, title, url */) { - var url = arguments.length > 2 ? arguments[2] : undefined; - - // url argument is optional - if (url) { - // coerce to string (this is what pushState does) - self._captureUrlChange(self._lastHref, url + ''); - } - - return origPushState.apply(this, arguments); - }; - }, - wrappedBuiltIns - ); - } - - if (autoBreadcrumbs.console && 'console' in _window && console.log) { - // console - var consoleMethodCallback = function(msg, data) { - self.captureBreadcrumb({ - message: msg, - level: data.level, - category: 'console' - }); - }; - - each(['debug', 'info', 'warn', 'error', 'log'], function(_, level) { - wrapConsoleMethod(console, level, consoleMethodCallback); - }); - } - }, - - _restoreBuiltIns: function() { - // restore any wrapped builtins - var builtin; - while (this._wrappedBuiltIns.length) { - builtin = this._wrappedBuiltIns.shift(); - - var obj = builtin[0], - name = builtin[1], - orig = builtin[2]; - - obj[name] = orig; - } - }, - - _drainPlugins: function() { - var self = this; - - // FIX ME TODO - each(this._plugins, function(_, plugin) { - var installer = plugin[0]; - var args = plugin[1]; - installer.apply(self, [self].concat(args)); - }); - }, - - _parseDSN: function(str) { - var m = dsnPattern.exec(str), - dsn = {}, - i = 7; - - try { - while (i--) dsn[dsnKeys[i]] = m[i] || ''; - } catch (e) { - throw new RavenConfigError('Invalid DSN: ' + str); - } - - if (dsn.pass && !this._globalOptions.allowSecretKey) { - throw new RavenConfigError( - 'Do not specify your secret key in the DSN. See: http://bit.ly/raven-secret-key' - ); - } - - return dsn; - }, - - _getGlobalServer: function(uri) { - // assemble the endpoint from the uri pieces - var globalServer = '//' + uri.host + (uri.port ? ':' + uri.port : ''); - - if (uri.protocol) { - globalServer = uri.protocol + ':' + globalServer; - } - return globalServer; - }, - - _handleOnErrorStackInfo: function() { - // if we are intentionally ignoring errors via onerror, bail out - if (!this._ignoreOnError) { - this._handleStackInfo.apply(this, arguments); - } - }, - - _handleStackInfo: function(stackInfo, options) { - var frames = this._prepareFrames(stackInfo, options); - - this._triggerEvent('handle', { - stackInfo: stackInfo, - options: options + self.captureBreadcrumb({ + type: 'http', + category: 'xhr', + data: xhr.__raven_xhr }); - - this._processException( - stackInfo.name, - stackInfo.message, - stackInfo.url, - stackInfo.lineno, - frames, - options - ); - }, - - _prepareFrames: function(stackInfo, options) { - var self = this; - var frames = []; - if (stackInfo.stack && stackInfo.stack.length) { - each(stackInfo.stack, function(i, stack) { - var frame = self._normalizeFrame(stack, stackInfo.url); - if (frame) { - frames.push(frame); - } - }); - - // e.g. frames captured via captureMessage throw - if (options && options.trimHeadFrames) { - for ( - var j = 0; - j < options.trimHeadFrames && j < frames.length; - j++ - ) { - frames[j].in_app = false; - } - } - } - frames = frames.slice(0, this._globalOptions.stackTraceLimit); - return frames; - }, - - _normalizeFrame: function(frame, stackInfoUrl) { - // normalize the frames data - var normalized = { - filename: frame.url, - lineno: frame.line, - colno: frame.column, - function: frame.func || '?' - }; - - // Case when we don't have any information about the error - // E.g. throwing a string or raw object, instead of an `Error` in Firefox - // Generating synthetic error doesn't add any value here - // - // We should probably somehow let a user know that they should fix their code - if (!frame.url) { - normalized.filename = stackInfoUrl; // fallback to whole stacks url from onerror handler - } - - normalized.in_app = !// determine if an exception came from outside of our app - // first we check the global includePaths list. - ( - (!!this._globalOptions.includePaths.test && - !this._globalOptions.includePaths.test(normalized.filename)) || - // Now we check for fun, if the function name is Raven or TraceKit - /(Raven|TraceKit)\./.test(normalized['function']) || - // finally, we do a last ditch effort and check for raven.min.js - /raven\.(min\.)?js$/.test(normalized.filename) - ); - - return normalized; - }, - - _processException: function( - type, - message, - fileurl, - lineno, - frames, - options - ) { - var testString = (type || '') + ': ' + (message || ''); - - if ( - !!this._globalOptions.ignoreErrors.test && - this._globalOptions.ignoreErrors.test(testString) - ) - return; - - var stacktrace; - - if (frames && frames.length) { - fileurl = frames[0].filename || fileurl; - // Sentry expects frames oldest to newest - // and JS sends them as newest to oldest - frames.reverse(); - stacktrace = {frames: frames}; - } else if (fileurl) { - stacktrace = { - frames: [ - { - filename: fileurl, - lineno: lineno, - in_app: true - } - ] - }; - } - - if ( - !!this._globalOptions.ignoreUrls.test && - this._globalOptions.ignoreUrls.test(fileurl) - ) - return; - if ( - !!this._globalOptions.whitelistUrls.test && - !this._globalOptions.whitelistUrls.test(fileurl) - ) - return; - - var data = objectMerge( - { - // sentry.interfaces.Exception - exception: { - values: [ - { - type: type, - value: message, - stacktrace: stacktrace - } - ] - }, - culprit: fileurl - }, - options - ); - - // Fire away! - this._send(data); - }, - - _trimPacket: function(data) { - // For now, we only want to truncate the two different messages - // but this could/should be expanded to just trim everything - var max = this._globalOptions.maxMessageLength; - if (data.message) { - data.message = truncate(data.message, max); - } - if (data.exception) { - var exception = data.exception.values[0]; - exception.value = truncate(exception.value, max); - } - - var request = data.request; - if (request) { - if (request.url) { - request.url = truncate(request.url, this._globalOptions.maxUrlLength); - } - if (request.Referer) { - request.Referer = truncate( - request.Referer, - this._globalOptions.maxUrlLength - ); - } - } - - if (data.breadcrumbs && data.breadcrumbs.values) - this._trimBreadcrumbs(data.breadcrumbs); - - return data; - }, - - /** - * Truncate breadcrumb values (right now just URLs) - */ - _trimBreadcrumbs: function(breadcrumbs) { - // known breadcrumb properties with urls - // TODO: also consider arbitrary prop values that start with (https?)?:// - var urlProps = ['to', 'from', 'url'], - urlProp, - crumb, - data; - - for (var i = 0; i < breadcrumbs.values.length; ++i) { - crumb = breadcrumbs.values[i]; - if ( - !crumb.hasOwnProperty('data') || - !isObject(crumb.data) || - objectFrozen(crumb.data) - ) - continue; - - data = objectMerge({}, crumb.data); - for (var j = 0; j < urlProps.length; ++j) { - urlProp = urlProps[j]; - if (data.hasOwnProperty(urlProp) && data[urlProp]) { - data[urlProp] = truncate( - data[urlProp], - this._globalOptions.maxUrlLength - ); - } - } - breadcrumbs.values[i].data = data; - } - }, - - _getHttpData: function() { - if (!this._hasNavigator && !this._hasDocument) return; - var httpData = {}; - - if (this._hasNavigator && _navigator.userAgent) { - httpData.headers = { - 'User-Agent': navigator.userAgent - }; - } - - if (this._hasDocument) { - if (_document.location && _document.location.href) { - httpData.url = _document.location.href; - } - if (_document.referrer) { - if (!httpData.headers) httpData.headers = {}; - httpData.headers.Referer = _document.referrer; - } - } - - return httpData; - }, - - _resetBackoff: function() { - this._backoffDuration = 0; - this._backoffStart = null; - }, - - _shouldBackoff: function() { - return ( - this._backoffDuration && - now() - this._backoffStart < this._backoffDuration - ); - }, - - /** - * Returns true if the in-process data payload matches the signature - * of the previously-sent data - * - * NOTE: This has to be done at this level because TraceKit can generate - * data from window.onerror WITHOUT an exception object (IE8, IE9, - * other old browsers). This can take the form of an "exception" - * data object with a single frame (derived from the onerror args). - */ - _isRepeatData: function(current) { - var last = this._lastData; - - if ( - !last || - current.message !== last.message || // defined for captureMessage - current.culprit !== last.culprit // defined for captureException/onerror - ) - return false; - - // Stacktrace interface (i.e. from captureMessage) - if (current.stacktrace || last.stacktrace) { - return isSameStacktrace(current.stacktrace, last.stacktrace); - } else if (current.exception || last.exception) { - // Exception interface (i.e. from captureException/onerror) - return isSameException(current.exception, last.exception); - } - - return true; - }, - - _setBackoffState: function(request) { - // If we are already in a backoff state, don't change anything - if (this._shouldBackoff()) { - return; - } - - var status = request.status; - - // 400 - project_id doesn't exist or some other fatal - // 401 - invalid/revoked dsn - // 429 - too many requests - if (!(status === 400 || status === 401 || status === 429)) return; - - var retry; - try { - // If Retry-After is not in Access-Control-Expose-Headers, most - // browsers will throw an exception trying to access it - retry = request.getResponseHeader('Retry-After'); - retry = parseInt(retry, 10) * 1000; // Retry-After is returned in seconds - } catch (e) { - /* eslint no-empty:0 */ - } - - this._backoffDuration = retry - ? // If Sentry server returned a Retry-After value, use it - retry - : // Otherwise, double the last backoff duration (starts at 1 sec) - this._backoffDuration * 2 || 1000; - - this._backoffStart = now(); - }, - - _send: function(data) { - var globalOptions = this._globalOptions; - - var baseData = { - project: this._globalProject, - logger: globalOptions.logger, - platform: 'javascript' - }, - httpData = this._getHttpData(); - - if (httpData) { - baseData.request = httpData; - } - - // HACK: delete `trimHeadFrames` to prevent from appearing in outbound payload - if (data.trimHeadFrames) delete data.trimHeadFrames; - - data = objectMerge(baseData, data); - - // Merge in the tags and extra separately since objectMerge doesn't handle a deep merge - data.tags = objectMerge( - objectMerge({}, this._globalContext.tags), - data.tags - ); - data.extra = objectMerge( - objectMerge({}, this._globalContext.extra), - data.extra - ); - - // Send along our own collected metadata with extra - data.extra['session:duration'] = now() - this._startTime; - - if (this._breadcrumbs && this._breadcrumbs.length > 0) { - // intentionally make shallow copy so that additions - // to breadcrumbs aren't accidentally sent in this request - data.breadcrumbs = { - values: [].slice.call(this._breadcrumbs, 0) - }; - } - - // If there are no tags/extra, strip the key from the payload alltogther. - if (isEmptyObject(data.tags)) delete data.tags; - - if (this._globalContext.user) { - // sentry.interfaces.User - data.user = this._globalContext.user; - } - - // Include the environment if it's defined in globalOptions - if (globalOptions.environment) - data.environment = globalOptions.environment; - - // Include the release if it's defined in globalOptions - if (globalOptions.release) data.release = globalOptions.release; - - // Include server_name if it's defined in globalOptions - if (globalOptions.serverName) data.server_name = globalOptions.serverName; - - if (isFunction(globalOptions.dataCallback)) { - data = globalOptions.dataCallback(data) || data; - } - - // Why?????????? - if (!data || isEmptyObject(data)) { - return; - } - - // Check if the request should be filtered or not - if ( - isFunction(globalOptions.shouldSendCallback) && - !globalOptions.shouldSendCallback(data) - ) { - return; - } - - // Backoff state: Sentry server previously responded w/ an error (e.g. 429 - too many requests), - // so drop requests until "cool-off" period has elapsed. - if (this._shouldBackoff()) { - this._logDebug('warn', 'Raven dropped error due to backoff: ', data); - return; - } - - if (typeof globalOptions.sampleRate === 'number') { - if (Math.random() < globalOptions.sampleRate) { - this._sendProcessedPayload(data); - } - } else { - this._sendProcessedPayload(data); - } - }, - - _getUuid: function() { - return uuid4(); - }, - - _sendProcessedPayload: function(data, callback) { - var self = this; - var globalOptions = this._globalOptions; - - if (!this.isSetup()) return; - - // Try and clean up the packet before sending by truncating long values - data = this._trimPacket(data); - - // ideally duplicate error testing should occur *before* dataCallback/shouldSendCallback, - // but this would require copying an un-truncated copy of the data packet, which can be - // arbitrarily deep (extra_data) -- could be worthwhile? will revisit - if (!this._globalOptions.allowDuplicates && this._isRepeatData(data)) { - this._logDebug('warn', 'Raven dropped repeat event: ', data); - return; - } - - // Send along an event_id if not explicitly passed. - // This event_id can be used to reference the error within Sentry itself. - // Set lastEventId after we know the error should actually be sent - this._lastEventId = data.event_id || (data.event_id = this._getUuid()); - - // Store outbound payload after trim - this._lastData = data; - - this._logDebug('debug', 'Raven about to send:', data); - - var auth = { - sentry_version: '7', - sentry_client: 'raven-js/' + this.VERSION, - sentry_key: this._globalKey - }; - - if (this._globalSecret) { - auth.sentry_secret = this._globalSecret; - } - - var exception = data.exception && data.exception.values[0]; - this.captureBreadcrumb({ - category: 'sentry', - message: exception - ? (exception.type ? exception.type + ': ' : '') + exception.value - : data.message, - event_id: data.event_id, - level: data.level || 'error' // presume error unless specified - }); - - var url = this._globalEndpoint; - (globalOptions.transport || this._makeRequest).call(this, { - url: url, - auth: auth, - data: data, - options: globalOptions, - onSuccess: function success() { - self._resetBackoff(); - - self._triggerEvent('success', { - data: data, - src: url - }); - callback && callback(); - }, - onError: function failure(error) { - self._logDebug('error', 'Raven transport failed to send: ', error); - - if (error.request) { - self._setBackoffState(error.request); - } - - self._triggerEvent('failure', { - data: data, - src: url - }); - error = - error || - new Error('Raven send failed (no additional details provided)'); - callback && callback(error); - } - }); - }, - - _makeRequest: function(opts) { - var request = _window.XMLHttpRequest && new _window.XMLHttpRequest(); - if (!request) return; - - // if browser doesn't support CORS (e.g. IE7), we are out of luck - var hasCORS = - 'withCredentials' in request || typeof XDomainRequest !== 'undefined'; - - if (!hasCORS) return; - - var url = opts.url; - - if ('withCredentials' in request) { - request.onreadystatechange = function() { - if (request.readyState !== 4) { - return; - } else if (request.status === 200) { - opts.onSuccess && opts.onSuccess(); - } else if (opts.onError) { - var err = new Error('Sentry error code: ' + request.status); - err.request = request; - opts.onError(err); - } - }; - } else { - request = new XDomainRequest(); - // xdomainrequest cannot go http -> https (or vice versa), - // so always use protocol relative - url = url.replace(/^https?:/, ''); - - // onreadystatechange not supported by XDomainRequest - if (opts.onSuccess) { - request.onload = opts.onSuccess; - } - if (opts.onError) { - request.onerror = function() { - var err = new Error('Sentry error code: XDomainRequest'); - err.request = request; - opts.onError(err); - }; - } - } - - // NOTE: auth is intentionally sent as part of query string (NOT as custom - // HTTP header) so as to avoid preflight CORS requests - request.open('POST', url + '?' + urlencode(opts.auth)); - request.send(stringify(opts.data)); - }, - - _logDebug: function(level) { - if (this._originalConsoleMethods[level] && this.debug) { - // In IE<10 console methods do not have their own 'apply' method - Function.prototype.apply.call( - this._originalConsoleMethods[level], - this._originalConsole, - [].slice.call(arguments, 1) - ); - } - }, - - _mergeContext: function(key, context) { - if (isUndefined(context)) { - delete this._globalContext[key]; - } else { - this._globalContext[key] = objectMerge( - this._globalContext[key] || {}, - context - ); - } - } - }; - - /*------------------------------------------------ - * utils - * - * conditionally exported for test via Raven.utils - ================================================= - */ - var objectPrototype = Object.prototype; - - function isUndefined(what) { - return what === void 0; - } - - function isFunction(what) { - return typeof what === 'function'; - } - - function isString(what) { - return objectPrototype.toString.call(what) === '[object String]'; - } - - function isEmptyObject(what) { - for (var _ in what) return false; // eslint-disable-line guard-for-in, no-unused-vars - return true; - } - - function each(obj, callback) { - var i, j; - - if (isUndefined(obj.length)) { - for (i in obj) { - if (hasKey(obj, i)) { - callback.call(null, i, obj[i]); - } - } - } else { - j = obj.length; - if (j) { - for (i = 0; i < j; i++) { - callback.call(null, i, obj[i]); - } - } } } - function objectMerge(obj1, obj2) { - if (!obj2) { - return obj1; + var props = ['onload', 'onerror', 'onprogress']; + for (var j = 0; j < props.length; j++) { + wrapProp(props[j], xhr); + } + + if ('onreadystatechange' in xhr && isFunction(xhr.onreadystatechange)) { + fill( + xhr, + 'onreadystatechange', + function(orig) { + return self.wrap(orig, undefined, onreadystatechangeHandler); + } /* intentionally don't track this instrumentation */ + ); + } else { + // if onreadystatechange wasn't actually set by the page on this xhr, we + // are free to set our own and capture the breadcrumb + xhr.onreadystatechange = onreadystatechangeHandler; + } + + return origSend.apply(this, arguments); + }; + }, + wrappedBuiltIns + ); + } + + if (autoBreadcrumbs.xhr && supportsFetch()) { + fill( + _window, + 'fetch', + function(origFetch) { + return function() { + // preserve arity + // Make a copy of the arguments to prevent deoptimization + // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + + var fetchInput = args[0]; + var method = 'GET'; + var url; + + if (typeof fetchInput === 'string') { + url = fetchInput; + } else if ('Request' in _window && fetchInput instanceof _window.Request) { + url = fetchInput.url; + if (fetchInput.method) { + method = fetchInput.method; } - each(obj2, function(key, value) { - obj1[key] = value; + } else { + url = '' + fetchInput; + } + + // if Sentry key appears in URL, don't capture, as it's our own request + if (url.indexOf(self._globalKey) !== -1) { + return origFetch.apply(this, args); + } + + if (args[1] && args[1].method) { + method = args[1].method; + } + + var fetchData = { + method: method, + url: url, + status_code: null + }; + + return origFetch.apply(this, args).then(function(response) { + fetchData.status_code = response.status; + + self.captureBreadcrumb({ + type: 'http', + category: 'fetch', + data: fetchData }); - return obj1; + + return response; + }); + }; + }, + wrappedBuiltIns + ); + } + + // Capture breadcrumbs from any click that is unhandled / bubbled up all the way + // to the document. Do this before we instrument addEventListener. + if (autoBreadcrumbs.dom && this._hasDocument) { + if (_document.addEventListener) { + _document.addEventListener('click', self._breadcrumbEventHandler('click'), false); + _document.addEventListener('keypress', self._keypressEventHandler(), false); + } else if(_document.attachEvent){ + // IE8 Compatibility + _document.attachEvent('onclick', self._breadcrumbEventHandler('click')); + _document.attachEvent('onkeypress', self._keypressEventHandler()); + } + } + + // record navigation (URL) changes + // NOTE: in Chrome App environment, touching history.pushState, *even inside + // a try/catch block*, will cause Chrome to output an error to console.error + // borrowed from: https://github.com/angular/angular.js/pull/13945/files + var chrome = _window.chrome; + var isChromePackagedApp = chrome && chrome.app && chrome.app.runtime; + var hasPushAndReplaceState = + !isChromePackagedApp && + _window.history && + history.pushState && + history.replaceState; + if (autoBreadcrumbs.location && hasPushAndReplaceState) { + // TODO: remove onpopstate handler on uninstall() + var oldOnPopState = _window.onpopstate; + _window.onpopstate = function() { + var currentHref = self._location.href; + self._captureUrlChange(self._lastHref, currentHref); + + if (oldOnPopState) { + return oldOnPopState.apply(this, arguments); + } + }; + + var historyReplacementFunction = function(origHistFunction) { + // note history.pushState.length is 0; intentionally not declaring + // params to preserve 0 arity + return function(/* state, title, url */) { + var url = arguments.length > 2 ? arguments[2] : undefined; + + // url argument is optional + if (url) { + // coerce to string (this is what pushState does) + self._captureUrlChange(self._lastHref, url + ''); + } + + return origHistFunction.apply(this, arguments); + }; + }; + + fill(history, 'pushState', historyReplacementFunction, wrappedBuiltIns); + fill(history, 'replaceState', historyReplacementFunction, wrappedBuiltIns); + } + + if (autoBreadcrumbs.console && 'console' in _window && console.log) { + // console + var consoleMethodCallback = function(msg, data) { + self.captureBreadcrumb({ + message: msg, + level: data.level, + category: 'console' + }); + }; + + each(['debug', 'info', 'warn', 'error', 'log'], function(_, level) { + wrapConsoleMethod(console, level, consoleMethodCallback); + }); + } + }, + + _restoreBuiltIns: function() { + // restore any wrapped builtins + var builtin; + while (this._wrappedBuiltIns.length) { + builtin = this._wrappedBuiltIns.shift(); + + var obj = builtin[0], + name = builtin[1], + orig = builtin[2]; + + obj[name] = orig; + } + }, + + _restoreConsole: function() { + // eslint-disable-next-line guard-for-in + for (var method in this._originalConsoleMethods) { + this._originalConsole[method] = this._originalConsoleMethods[method]; + } + }, + + _drainPlugins: function() { + var self = this; + + // FIX ME TODO + each(this._plugins, function(_, plugin) { + var installer = plugin[0]; + var args = plugin[1]; + installer.apply(self, [self].concat(args)); + }); + }, + + _parseDSN: function(str) { + var m = dsnPattern.exec(str), + dsn = {}, + i = 7; + + try { + while (i--) dsn[dsnKeys[i]] = m[i] || ''; + } catch (e) { + throw new RavenConfigError('Invalid DSN: ' + str); + } + + if (dsn.pass && !this._globalOptions.allowSecretKey) { + throw new RavenConfigError( + 'Do not specify your secret key in the DSN. See: http://bit.ly/raven-secret-key' + ); + } + + return dsn; + }, + + _getGlobalServer: function(uri) { + // assemble the endpoint from the uri pieces + var globalServer = '//' + uri.host + (uri.port ? ':' + uri.port : ''); + + if (uri.protocol) { + globalServer = uri.protocol + ':' + globalServer; + } + return globalServer; + }, + + _handleOnErrorStackInfo: function() { + // if we are intentionally ignoring errors via onerror, bail out + if (!this._ignoreOnError) { + this._handleStackInfo.apply(this, arguments); + } + }, + + _handleStackInfo: function(stackInfo, options) { + var frames = this._prepareFrames(stackInfo, options); + + this._triggerEvent('handle', { + stackInfo: stackInfo, + options: options + }); + + this._processException( + stackInfo.name, + stackInfo.message, + stackInfo.url, + stackInfo.lineno, + frames, + options + ); + }, + + _prepareFrames: function(stackInfo, options) { + var self = this; + var frames = []; + if (stackInfo.stack && stackInfo.stack.length) { + each(stackInfo.stack, function(i, stack) { + var frame = self._normalizeFrame(stack, stackInfo.url); + if (frame) { + frames.push(frame); + } + }); + + // e.g. frames captured via captureMessage throw + if (options && options.trimHeadFrames) { + for (var j = 0; j < options.trimHeadFrames && j < frames.length; j++) { + frames[j].in_app = false; + } + } + } + frames = frames.slice(0, this._globalOptions.stackTraceLimit); + return frames; + }, + + _normalizeFrame: function(frame, stackInfoUrl) { + // normalize the frames data + var normalized = { + filename: frame.url, + lineno: frame.line, + colno: frame.column, + function: frame.func || '?' + }; + + // Case when we don't have any information about the error + // E.g. throwing a string or raw object, instead of an `Error` in Firefox + // Generating synthetic error doesn't add any value here + // + // We should probably somehow let a user know that they should fix their code + if (!frame.url) { + normalized.filename = stackInfoUrl; // fallback to whole stacks url from onerror handler + } + + normalized.in_app = !// determine if an exception came from outside of our app + // first we check the global includePaths list. + ( + (!!this._globalOptions.includePaths.test && + !this._globalOptions.includePaths.test(normalized.filename)) || + // Now we check for fun, if the function name is Raven or TraceKit + /(Raven|TraceKit)\./.test(normalized['function']) || + // finally, we do a last ditch effort and check for raven.min.js + /raven\.(min\.)?js$/.test(normalized.filename) + ); + + return normalized; + }, + + _processException: function(type, message, fileurl, lineno, frames, options) { + var prefixedMessage = (type ? type + ': ' : '') + (message || ''); + if ( + !!this._globalOptions.ignoreErrors.test && + (this._globalOptions.ignoreErrors.test(message) || + this._globalOptions.ignoreErrors.test(prefixedMessage)) + ) { + return; + } + + var stacktrace; + + if (frames && frames.length) { + fileurl = frames[0].filename || fileurl; + // Sentry expects frames oldest to newest + // and JS sends them as newest to oldest + frames.reverse(); + stacktrace = {frames: frames}; + } else if (fileurl) { + stacktrace = { + frames: [ + { + filename: fileurl, + lineno: lineno, + in_app: true + } + ] + }; + } + + if ( + !!this._globalOptions.ignoreUrls.test && + this._globalOptions.ignoreUrls.test(fileurl) + ) { + return; + } + + if ( + !!this._globalOptions.whitelistUrls.test && + !this._globalOptions.whitelistUrls.test(fileurl) + ) { + return; + } + + var data = objectMerge( + { + // sentry.interfaces.Exception + exception: { + values: [ + { + type: type, + value: message, + stacktrace: stacktrace } - - /** + ] + }, + culprit: fileurl + }, + options + ); + + // Fire away! + this._send(data); + }, + + _trimPacket: function(data) { + // For now, we only want to truncate the two different messages + // but this could/should be expanded to just trim everything + var max = this._globalOptions.maxMessageLength; + if (data.message) { + data.message = truncate(data.message, max); + } + if (data.exception) { + var exception = data.exception.values[0]; + exception.value = truncate(exception.value, max); + } + + var request = data.request; + if (request) { + if (request.url) { + request.url = truncate(request.url, this._globalOptions.maxUrlLength); + } + if (request.Referer) { + request.Referer = truncate(request.Referer, this._globalOptions.maxUrlLength); + } + } + + if (data.breadcrumbs && data.breadcrumbs.values) + this._trimBreadcrumbs(data.breadcrumbs); + + return data; + }, + + /** + * Truncate breadcrumb values (right now just URLs) + */ + _trimBreadcrumbs: function(breadcrumbs) { + // known breadcrumb properties with urls + // TODO: also consider arbitrary prop values that start with (https?)?:// + var urlProps = ['to', 'from', 'url'], + urlProp, + crumb, + data; + + for (var i = 0; i < breadcrumbs.values.length; ++i) { + crumb = breadcrumbs.values[i]; + if ( + !crumb.hasOwnProperty('data') || + !isObject(crumb.data) || + objectFrozen(crumb.data) + ) + continue; + + data = objectMerge({}, crumb.data); + for (var j = 0; j < urlProps.length; ++j) { + urlProp = urlProps[j]; + if (data.hasOwnProperty(urlProp) && data[urlProp]) { + data[urlProp] = truncate(data[urlProp], this._globalOptions.maxUrlLength); + } + } + breadcrumbs.values[i].data = data; + } + }, + + _getHttpData: function() { + if (!this._hasNavigator && !this._hasDocument) return; + var httpData = {}; + + if (this._hasNavigator && _navigator.userAgent) { + httpData.headers = { + 'User-Agent': navigator.userAgent + }; + } + + // Check in `window` instead of `document`, as we may be in ServiceWorker environment + if (_window.location && _window.location.href) { + httpData.url = _window.location.href; + } + + if (this._hasDocument && _document.referrer) { + if (!httpData.headers) httpData.headers = {}; + httpData.headers.Referer = _document.referrer; + } + + return httpData; + }, + + _resetBackoff: function() { + this._backoffDuration = 0; + this._backoffStart = null; + }, + + _shouldBackoff: function() { + return this._backoffDuration && now() - this._backoffStart < this._backoffDuration; + }, + + /** + * Returns true if the in-process data payload matches the signature + * of the previously-sent data + * + * NOTE: This has to be done at this level because TraceKit can generate + * data from window.onerror WITHOUT an exception object (IE8, IE9, + * other old browsers). This can take the form of an "exception" + * data object with a single frame (derived from the onerror args). + */ + _isRepeatData: function(current) { + var last = this._lastData; + + if ( + !last || + current.message !== last.message || // defined for captureMessage + current.culprit !== last.culprit // defined for captureException/onerror + ) + return false; + + // Stacktrace interface (i.e. from captureMessage) + if (current.stacktrace || last.stacktrace) { + return isSameStacktrace(current.stacktrace, last.stacktrace); + } else if (current.exception || last.exception) { + // Exception interface (i.e. from captureException/onerror) + return isSameException(current.exception, last.exception); + } + + return true; + }, + + _setBackoffState: function(request) { + // If we are already in a backoff state, don't change anything + if (this._shouldBackoff()) { + return; + } + + var status = request.status; + + // 400 - project_id doesn't exist or some other fatal + // 401 - invalid/revoked dsn + // 429 - too many requests + if (!(status === 400 || status === 401 || status === 429)) return; + + var retry; + try { + // If Retry-After is not in Access-Control-Expose-Headers, most + // browsers will throw an exception trying to access it + if (supportsFetch()) { + retry = request.headers.get('Retry-After'); + } else { + retry = request.getResponseHeader('Retry-After'); + } + + // Retry-After is returned in seconds + retry = parseInt(retry, 10) * 1000; + } catch (e) { + /* eslint no-empty:0 */ + } + + this._backoffDuration = retry + ? // If Sentry server returned a Retry-After value, use it + retry + : // Otherwise, double the last backoff duration (starts at 1 sec) + this._backoffDuration * 2 || 1000; + + this._backoffStart = now(); + }, + + _send: function(data) { + var globalOptions = this._globalOptions; + + var baseData = { + project: this._globalProject, + logger: globalOptions.logger, + platform: 'javascript' + }, + httpData = this._getHttpData(); + + if (httpData) { + baseData.request = httpData; + } + + // HACK: delete `trimHeadFrames` to prevent from appearing in outbound payload + if (data.trimHeadFrames) delete data.trimHeadFrames; + + data = objectMerge(baseData, data); + + // Merge in the tags and extra separately since objectMerge doesn't handle a deep merge + data.tags = objectMerge(objectMerge({}, this._globalContext.tags), data.tags); + data.extra = objectMerge(objectMerge({}, this._globalContext.extra), data.extra); + + // Send along our own collected metadata with extra + data.extra['session:duration'] = now() - this._startTime; + + if (this._breadcrumbs && this._breadcrumbs.length > 0) { + // intentionally make shallow copy so that additions + // to breadcrumbs aren't accidentally sent in this request + data.breadcrumbs = { + values: [].slice.call(this._breadcrumbs, 0) + }; + } + + if (this._globalContext.user) { + // sentry.interfaces.User + data.user = this._globalContext.user; + } + + // Include the environment if it's defined in globalOptions + if (globalOptions.environment) data.environment = globalOptions.environment; + + // Include the release if it's defined in globalOptions + if (globalOptions.release) data.release = globalOptions.release; + + // Include server_name if it's defined in globalOptions + if (globalOptions.serverName) data.server_name = globalOptions.serverName; + + data = this._sanitizeData(data); + + // Cleanup empty properties before sending them to the server + Object.keys(data).forEach(function(key) { + if (data[key] == null || data[key] === '' || isEmptyObject(data[key])) { + delete data[key]; + } + }); + + if (isFunction(globalOptions.dataCallback)) { + data = globalOptions.dataCallback(data) || data; + } + + // Why?????????? + if (!data || isEmptyObject(data)) { + return; + } + + // Check if the request should be filtered or not + if ( + isFunction(globalOptions.shouldSendCallback) && + !globalOptions.shouldSendCallback(data) + ) { + return; + } + + // Backoff state: Sentry server previously responded w/ an error (e.g. 429 - too many requests), + // so drop requests until "cool-off" period has elapsed. + if (this._shouldBackoff()) { + this._logDebug('warn', 'Raven dropped error due to backoff: ', data); + return; + } + + if (typeof globalOptions.sampleRate === 'number') { + if (Math.random() < globalOptions.sampleRate) { + this._sendProcessedPayload(data); + } + } else { + this._sendProcessedPayload(data); + } + }, + + _sanitizeData: function(data) { + return sanitize(data, this._globalOptions.sanitizeKeys); + }, + + _getUuid: function() { + return uuid4(); + }, + + _sendProcessedPayload: function(data, callback) { + var self = this; + var globalOptions = this._globalOptions; + + if (!this.isSetup()) return; + + // Try and clean up the packet before sending by truncating long values + data = this._trimPacket(data); + + // ideally duplicate error testing should occur *before* dataCallback/shouldSendCallback, + // but this would require copying an un-truncated copy of the data packet, which can be + // arbitrarily deep (extra_data) -- could be worthwhile? will revisit + if (!this._globalOptions.allowDuplicates && this._isRepeatData(data)) { + this._logDebug('warn', 'Raven dropped repeat event: ', data); + return; + } + + // Send along an event_id if not explicitly passed. + // This event_id can be used to reference the error within Sentry itself. + // Set lastEventId after we know the error should actually be sent + this._lastEventId = data.event_id || (data.event_id = this._getUuid()); + + // Store outbound payload after trim + this._lastData = data; + + this._logDebug('debug', 'Raven about to send:', data); + + var auth = { + sentry_version: '7', + sentry_client: 'raven-js/' + this.VERSION, + sentry_key: this._globalKey + }; + + if (this._globalSecret) { + auth.sentry_secret = this._globalSecret; + } + + var exception = data.exception && data.exception.values[0]; + + // only capture 'sentry' breadcrumb is autoBreadcrumbs is truthy + if ( + this._globalOptions.autoBreadcrumbs && + this._globalOptions.autoBreadcrumbs.sentry + ) { + this.captureBreadcrumb({ + category: 'sentry', + message: exception + ? (exception.type ? exception.type + ': ' : '') + exception.value + : data.message, + event_id: data.event_id, + level: data.level || 'error' // presume error unless specified + }); + } + + var url = this._globalEndpoint; + (globalOptions.transport || this._makeRequest).call(this, { + url: url, + auth: auth, + data: data, + options: globalOptions, + onSuccess: function success() { + self._resetBackoff(); + + self._triggerEvent('success', { + data: data, + src: url + }); + callback && callback(); + }, + onError: function failure(error) { + self._logDebug('error', 'Raven transport failed to send: ', error); + + if (error.request) { + self._setBackoffState(error.request); + } + + self._triggerEvent('failure', { + data: data, + src: url + }); + error = error || new Error('Raven send failed (no additional details provided)'); + callback && callback(error); + } + }); + }, + + _makeRequest: function(opts) { + // Auth is intentionally sent as part of query string (NOT as custom HTTP header) to avoid preflight CORS requests + var url = opts.url + '?' + urlencode(opts.auth); + + var evaluatedHeaders = null; + var evaluatedFetchParameters = {}; + + if (opts.options.headers) { + evaluatedHeaders = this._evaluateHash(opts.options.headers); + } + + if (opts.options.fetchParameters) { + evaluatedFetchParameters = this._evaluateHash(opts.options.fetchParameters); + } + + if (supportsFetch()) { + evaluatedFetchParameters.body = stringify(opts.data); + + var defaultFetchOptions = objectMerge({}, this._fetchDefaults); + var fetchOptions = objectMerge(defaultFetchOptions, evaluatedFetchParameters); + + if (evaluatedHeaders) { + fetchOptions.headers = evaluatedHeaders; + } + + return _window + .fetch(url, fetchOptions) + .then(function(response) { + if (response.ok) { + opts.onSuccess && opts.onSuccess(); + } else { + var error = new Error('Sentry error code: ' + response.status); + // It's called request only to keep compatibility with XHR interface + // and not add more redundant checks in setBackoffState method + error.request = response; + opts.onError && opts.onError(error); + } + }) + ['catch'](function() { + opts.onError && + opts.onError(new Error('Sentry error code: network unavailable')); + }); + } + + var request = _window.XMLHttpRequest && new _window.XMLHttpRequest(); + if (!request) return; + + // if browser doesn't support CORS (e.g. IE7), we are out of luck + var hasCORS = 'withCredentials' in request || typeof XDomainRequest !== 'undefined'; + + if (!hasCORS) return; + + if ('withCredentials' in request) { + request.onreadystatechange = function() { + if (request.readyState !== 4) { + return; + } else if (request.status === 200) { + opts.onSuccess && opts.onSuccess(); + } else if (opts.onError) { + var err = new Error('Sentry error code: ' + request.status); + err.request = request; + opts.onError(err); + } + }; + } else { + request = new XDomainRequest(); + // xdomainrequest cannot go http -> https (or vice versa), + // so always use protocol relative + url = url.replace(/^https?:/, ''); + + // onreadystatechange not supported by XDomainRequest + if (opts.onSuccess) { + request.onload = opts.onSuccess; + } + if (opts.onError) { + request.onerror = function() { + var err = new Error('Sentry error code: XDomainRequest'); + err.request = request; + opts.onError(err); + }; + } + } + + request.open('POST', url); + + if (evaluatedHeaders) { + each(evaluatedHeaders, function(key, value) { + request.setRequestHeader(key, value); + }); + } + + request.send(stringify(opts.data)); + }, + + _evaluateHash: function(hash) { + var evaluated = {}; + + for (var key in hash) { + if (hash.hasOwnProperty(key)) { + var value = hash[key]; + evaluated[key] = typeof value === 'function' ? value() : value; + } + } + + return evaluated; + }, + + _logDebug: function(level) { + if (this._originalConsoleMethods[level] && this.debug) { + // In IE<10 console methods do not have their own 'apply' method + Function.prototype.apply.call( + this._originalConsoleMethods[level], + this._originalConsole, + [].slice.call(arguments, 1) + ); + } + }, + + _mergeContext: function(key, context) { + if (isUndefined(context)) { + delete this._globalContext[key]; + } else { + this._globalContext[key] = objectMerge(this._globalContext[key] || {}, context); + } + } +}; + +// Deprecations +Raven.prototype.setUser = Raven.prototype.setUserContext; +Raven.prototype.setReleaseContext = Raven.prototype.setRelease; + +module.exports = Raven; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"1":1,"2":2,"5":5,"6":6,"7":7,"8":8}],4:[function(_dereq_,module,exports){ +(function (global){ +/** + * Enforces a single instance of the Raven client, and the + * main entry point for Raven. If you are a consumer of the + * Raven library, you SHOULD load this file (vs raven.js). + **/ + +var RavenConstructor = _dereq_(3); + +// This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785) +var _window = + typeof window !== 'undefined' + ? window + : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; +var _Raven = _window.Raven; + +var Raven = new RavenConstructor(); + +/* + * Allow multiple versions of Raven to be installed. + * Strip Raven from the global context and returns the instance. + * + * @return {Raven} + */ +Raven.noConflict = function() { + _window.Raven = _Raven; + return Raven; +}; + +Raven.afterLoad(); + +module.exports = Raven; + +/** + * DISCLAIMER: + * + * Expose `Client` constructor for cases where user want to track multiple "sub-applications" in one larger app. + * It's not meant to be used by a wide audience, so pleaaase make sure that you know what you're doing before using it. + * Accidentally calling `install` multiple times, may result in an unexpected behavior that's very hard to debug. + * + * It's called `Client' to be in-line with Raven Node implementation. + * + * HOWTO: + * + * import Raven from 'raven-js'; + * + * const someAppReporter = new Raven.Client(); + * const someOtherAppReporter = new Raven.Client(); + * + * someAppReporter.config('__DSN__', { + * ...config goes here + * }); + * + * someOtherAppReporter.config('__OTHER_DSN__', { + * ...config goes here + * }); + * + * someAppReporter.captureMessage(...); + * someAppReporter.captureException(...); + * someAppReporter.captureBreadcrumb(...); + * + * someOtherAppReporter.captureMessage(...); + * someOtherAppReporter.captureException(...); + * someOtherAppReporter.captureBreadcrumb(...); + * + * It should "just work". + */ +module.exports.Client = RavenConstructor; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"3":3}],5:[function(_dereq_,module,exports){ +(function (global){ +var stringify = _dereq_(7); + +var _window = + typeof window !== 'undefined' + ? window + : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; + +function isObject(what) { + return typeof what === 'object' && what !== null; +} + +// Yanked from https://git.io/vS8DV re-used under CC0 +// with some tiny modifications +function isError(value) { + switch ({}.toString.call(value)) { + case '[object Error]': + return true; + case '[object Exception]': + return true; + case '[object DOMException]': + return true; + default: + return value instanceof Error; + } +} + +function isErrorEvent(value) { + return supportsErrorEvent() && {}.toString.call(value) === '[object ErrorEvent]'; +} + +function isUndefined(what) { + return what === void 0; +} + +function isFunction(what) { + return typeof what === 'function'; +} + +function isPlainObject(what) { + return Object.prototype.toString.call(what) === '[object Object]'; +} + +function isString(what) { + return Object.prototype.toString.call(what) === '[object String]'; +} + +function isArray(what) { + return Object.prototype.toString.call(what) === '[object Array]'; +} + +function isEmptyObject(what) { + if (!isPlainObject(what)) return false; + + for (var _ in what) { + if (what.hasOwnProperty(_)) { + return false; + } + } + return true; +} + +function supportsErrorEvent() { + try { + new ErrorEvent(''); // eslint-disable-line no-new + return true; + } catch (e) { + return false; + } +} + +function supportsFetch() { + if (!('fetch' in _window)) return false; + + try { + new Headers(); // eslint-disable-line no-new + new Request(''); // eslint-disable-line no-new + new Response(); // eslint-disable-line no-new + return true; + } catch (e) { + return false; + } +} + +// Despite all stars in the sky saying that Edge supports old draft syntax, aka 'never', 'always', 'origin' and 'default +// https://caniuse.com/#feat=referrer-policy +// It doesn't. And it throw exception instead of ignoring this parameter... +// REF: https://github.com/getsentry/raven-js/issues/1233 +function supportsReferrerPolicy() { + if (!supportsFetch()) return false; + + try { + // eslint-disable-next-line no-new + new Request('pickleRick', { + referrerPolicy: 'origin' + }); + return true; + } catch (e) { + return false; + } +} + +function supportsPromiseRejectionEvent() { + return typeof PromiseRejectionEvent === 'function'; +} + +function wrappedCallback(callback) { + function dataCallback(data, original) { + var normalizedData = callback(data) || data; + if (original) { + return original(normalizedData) || normalizedData; + } + return normalizedData; + } + + return dataCallback; +} + +function each(obj, callback) { + var i, j; + + if (isUndefined(obj.length)) { + for (i in obj) { + if (hasKey(obj, i)) { + callback.call(null, i, obj[i]); + } + } + } else { + j = obj.length; + if (j) { + for (i = 0; i < j; i++) { + callback.call(null, i, obj[i]); + } + } + } +} + +function objectMerge(obj1, obj2) { + if (!obj2) { + return obj1; + } + each(obj2, function(key, value) { + obj1[key] = value; + }); + return obj1; +} + +/** * This function is only used for react-native. * react-native freezes object that have already been sent over the * js bridge. We need this function in order to check if the object is frozen. * So it's ok that objectFrozen returns false if Object.isFrozen is not * supported because it's not relevant for other "platforms". See related issue: * https://github.com/getsentry/react-native-sentry/issues/57 */ - function objectFrozen(obj) { - if (!Object.isFrozen) { - return false; - } - return Object.isFrozen(obj); - } - - function truncate(str, max) { - return !max || str.length <= max ? str : str.substr(0, max) + '\u2026'; - } - - /** +function objectFrozen(obj) { + if (!Object.isFrozen) { + return false; + } + return Object.isFrozen(obj); +} + +function truncate(str, max) { + return !max || str.length <= max ? str : str.substr(0, max) + '\u2026'; +} + +/** * hasKey, a better form of hasOwnProperty * Example: hasKey(MainHostObject, property) === true/false * * @param {Object} host object to check property * @param {string} key to check */ - function hasKey(object, key) { - return objectPrototype.hasOwnProperty.call(object, key); - } - - function joinRegExp(patterns) { - // Combine an array of regular expressions and strings into one large regexp - // Be mad. - var sources = [], - i = 0, - len = patterns.length, - pattern; - - for (; i < len; i++) { - pattern = patterns[i]; - if (isString(pattern)) { - // If it's a string, we need to escape it - // Taken from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions - sources.push(pattern.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1')); - } else if (pattern && pattern.source) { - // If it's a regexp already, we want to extract the source - sources.push(pattern.source); - } - // Intentionally skip other cases - } - return new RegExp(sources.join('|'), 'i'); - } - - function urlencode(o) { - var pairs = []; - each(o, function(key, value) { - pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value)); - }); - return pairs.join('&'); - } - - // borrowed from https://tools.ietf.org/html/rfc3986#appendix-B - // intentionally using regex and not <a/> href parsing trick because React Native and other - // environments where DOM might not be available - function parseUrl(url) { - var match = url.match( - /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/ - ); - if (!match) return {}; - - // coerce to undefined values to empty string so we don't get 'undefined' - var query = match[6] || ''; - var fragment = match[8] || ''; - return { - protocol: match[2], - host: match[4], - path: match[5], - relative: match[5] + query + fragment // everything minus origin - }; - } - function uuid4() { - var crypto = _window.crypto || _window.msCrypto; - - if (!isUndefined(crypto) && crypto.getRandomValues) { - // Use window.crypto API if available - // eslint-disable-next-line no-undef - var arr = new Uint16Array(8); - crypto.getRandomValues(arr); - - // set 4 in byte 7 - arr[3] = (arr[3] & 0xfff) | 0x4000; - // set 2 most significant bits of byte 9 to '10' - arr[4] = (arr[4] & 0x3fff) | 0x8000; - - var pad = function(num) { - var v = num.toString(16); - while (v.length < 4) { - v = '0' + v; - } - return v; - }; - - return ( - pad(arr[0]) + - pad(arr[1]) + - pad(arr[2]) + - pad(arr[3]) + - pad(arr[4]) + - pad(arr[5]) + - pad(arr[6]) + - pad(arr[7]) - ); - } else { - // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523 - return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) { - var r = (Math.random() * 16) | 0, - v = c === 'x' ? r : (r & 0x3) | 0x8; - return v.toString(16); - }); - } - } - - /** +function hasKey(object, key) { + return Object.prototype.hasOwnProperty.call(object, key); +} + +function joinRegExp(patterns) { + // Combine an array of regular expressions and strings into one large regexp + // Be mad. + var sources = [], + i = 0, + len = patterns.length, + pattern; + + for (; i < len; i++) { + pattern = patterns[i]; + if (isString(pattern)) { + // If it's a string, we need to escape it + // Taken from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions + sources.push(pattern.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1')); + } else if (pattern && pattern.source) { + // If it's a regexp already, we want to extract the source + sources.push(pattern.source); + } + // Intentionally skip other cases + } + return new RegExp(sources.join('|'), 'i'); +} + +function urlencode(o) { + var pairs = []; + each(o, function(key, value) { + pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value)); + }); + return pairs.join('&'); +} + +// borrowed from https://tools.ietf.org/html/rfc3986#appendix-B +// intentionally using regex and not <a/> href parsing trick because React Native and other +// environments where DOM might not be available +function parseUrl(url) { + if (typeof url !== 'string') return {}; + var match = url.match(/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/); + + // coerce to undefined values to empty string so we don't get 'undefined' + var query = match[6] || ''; + var fragment = match[8] || ''; + return { + protocol: match[2], + host: match[4], + path: match[5], + relative: match[5] + query + fragment // everything minus origin + }; +} +function uuid4() { + var crypto = _window.crypto || _window.msCrypto; + + if (!isUndefined(crypto) && crypto.getRandomValues) { + // Use window.crypto API if available + // eslint-disable-next-line no-undef + var arr = new Uint16Array(8); + crypto.getRandomValues(arr); + + // set 4 in byte 7 + arr[3] = (arr[3] & 0xfff) | 0x4000; + // set 2 most significant bits of byte 9 to '10' + arr[4] = (arr[4] & 0x3fff) | 0x8000; + + var pad = function(num) { + var v = num.toString(16); + while (v.length < 4) { + v = '0' + v; + } + return v; + }; + + return ( + pad(arr[0]) + + pad(arr[1]) + + pad(arr[2]) + + pad(arr[3]) + + pad(arr[4]) + + pad(arr[5]) + + pad(arr[6]) + + pad(arr[7]) + ); + } else { + // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523 + return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = (Math.random() * 16) | 0, + v = c === 'x' ? r : (r & 0x3) | 0x8; + return v.toString(16); + }); + } +} + +/** * Given a child DOM element, returns a query-selector statement describing that * and its ancestors * e.g. [HTMLElement] => body > div > input#foo.btn[name=baz] * @param elem * @returns {string} */ - function htmlTreeAsString(elem) { - /* eslint no-extra-parens:0*/ - var MAX_TRAVERSE_HEIGHT = 5, - MAX_OUTPUT_LEN = 80, - out = [], - height = 0, - len = 0, - separator = ' > ', - sepLength = separator.length, - nextStr; - - while (elem && height++ < MAX_TRAVERSE_HEIGHT) { - nextStr = htmlElementAsString(elem); - // bail out if - // - nextStr is the 'html' element - // - the length of the string that would be created exceeds MAX_OUTPUT_LEN - // (ignore this limit if we are on the first iteration) - if ( - nextStr === 'html' || - (height > 1 && - len + out.length * sepLength + nextStr.length >= MAX_OUTPUT_LEN) - ) { - break; - } - - out.push(nextStr); - - len += nextStr.length; - elem = elem.parentNode; - } - - return out.reverse().join(separator); - } - - /** +function htmlTreeAsString(elem) { + /* eslint no-extra-parens:0*/ + var MAX_TRAVERSE_HEIGHT = 5, + MAX_OUTPUT_LEN = 80, + out = [], + height = 0, + len = 0, + separator = ' > ', + sepLength = separator.length, + nextStr; + + while (elem && height++ < MAX_TRAVERSE_HEIGHT) { + nextStr = htmlElementAsString(elem); + // bail out if + // - nextStr is the 'html' element + // - the length of the string that would be created exceeds MAX_OUTPUT_LEN + // (ignore this limit if we are on the first iteration) + if ( + nextStr === 'html' || + (height > 1 && len + out.length * sepLength + nextStr.length >= MAX_OUTPUT_LEN) + ) { + break; + } + + out.push(nextStr); + + len += nextStr.length; + elem = elem.parentNode; + } + + return out.reverse().join(separator); +} + +/** * Returns a simple, query-selector representation of a DOM element * e.g. [HTMLElement] => input#foo.btn[name=baz] * @param HTMLElement * @returns {string} */ - function htmlElementAsString(elem) { - var out = [], - className, - classes, - key, - attr, - i; - - if (!elem || !elem.tagName) { - return ''; - } - - out.push(elem.tagName.toLowerCase()); - if (elem.id) { - out.push('#' + elem.id); - } - - className = elem.className; - if (className && isString(className)) { - classes = className.split(/\s+/); - for (i = 0; i < classes.length; i++) { - out.push('.' + classes[i]); - } - } - var attrWhitelist = ['type', 'name', 'title', 'alt']; - for (i = 0; i < attrWhitelist.length; i++) { - key = attrWhitelist[i]; - attr = elem.getAttribute(key); - if (attr) { - out.push('[' + key + '="' + attr + '"]'); - } - } - return out.join(''); - } - - /** +function htmlElementAsString(elem) { + var out = [], + className, + classes, + key, + attr, + i; + + if (!elem || !elem.tagName) { + return ''; + } + + out.push(elem.tagName.toLowerCase()); + if (elem.id) { + out.push('#' + elem.id); + } + + className = elem.className; + if (className && isString(className)) { + classes = className.split(/\s+/); + for (i = 0; i < classes.length; i++) { + out.push('.' + classes[i]); + } + } + var attrWhitelist = ['type', 'name', 'title', 'alt']; + for (i = 0; i < attrWhitelist.length; i++) { + key = attrWhitelist[i]; + attr = elem.getAttribute(key); + if (attr) { + out.push('[' + key + '="' + attr + '"]'); + } + } + return out.join(''); +} + +/** * Returns true if either a OR b is truthy, but not both */ - function isOnlyOneTruthy(a, b) { - return !!(!!a ^ !!b); - } - - /** +function isOnlyOneTruthy(a, b) { + return !!(!!a ^ !!b); +} + +/** + * Returns true if both parameters are undefined + */ +function isBothUndefined(a, b) { + return isUndefined(a) && isUndefined(b); +} + +/** * Returns true if the two input exception interfaces have the same content */ - function isSameException(ex1, ex2) { - if (isOnlyOneTruthy(ex1, ex2)) return false; - - ex1 = ex1.values[0]; - ex2 = ex2.values[0]; - - if (ex1.type !== ex2.type || ex1.value !== ex2.value) return false; - - return isSameStacktrace(ex1.stacktrace, ex2.stacktrace); - } - - /** +function isSameException(ex1, ex2) { + if (isOnlyOneTruthy(ex1, ex2)) return false; + + ex1 = ex1.values[0]; + ex2 = ex2.values[0]; + + if (ex1.type !== ex2.type || ex1.value !== ex2.value) return false; + + // in case both stacktraces are undefined, we can't decide so default to false + if (isBothUndefined(ex1.stacktrace, ex2.stacktrace)) return false; + + return isSameStacktrace(ex1.stacktrace, ex2.stacktrace); +} + +/** * Returns true if the two input stack trace interfaces have the same content */ - function isSameStacktrace(stack1, stack2) { - if (isOnlyOneTruthy(stack1, stack2)) return false; - - var frames1 = stack1.frames; - var frames2 = stack2.frames; - - // Exit early if frame count differs - if (frames1.length !== frames2.length) return false; - - // Iterate through every frame; bail out if anything differs - var a, b; - for (var i = 0; i < frames1.length; i++) { - a = frames1[i]; - b = frames2[i]; - if ( - a.filename !== b.filename || - a.lineno !== b.lineno || - a.colno !== b.colno || - a['function'] !== b['function'] - ) - return false; - } - return true; - } - - /** +function isSameStacktrace(stack1, stack2) { + if (isOnlyOneTruthy(stack1, stack2)) return false; + + var frames1 = stack1.frames; + var frames2 = stack2.frames; + + // Exit early if frame count differs + if (frames1.length !== frames2.length) return false; + + // Iterate through every frame; bail out if anything differs + var a, b; + for (var i = 0; i < frames1.length; i++) { + a = frames1[i]; + b = frames2[i]; + if ( + a.filename !== b.filename || + a.lineno !== b.lineno || + a.colno !== b.colno || + a['function'] !== b['function'] + ) + return false; + } + return true; +} + +/** * Polyfill a method * @param obj object e.g. `document` * @param name method name present on object e.g. `addEventListener` * @param replacement replacement function * @param track {optional} record instrumentation to an array */ - function fill(obj, name, replacement, track) { - var orig = obj[name]; - obj[name] = replacement(orig); - if (track) { - track.push([obj, name, orig]); - } - } - - if (typeof __DEV__ !== 'undefined' && __DEV__) { - Raven.utils = { - isUndefined: isUndefined, - isFunction: isFunction, - isString: isString, - isObject: isObject, - isEmptyObject: isEmptyObject, - isError: isError, - each: each, - objectMerge: objectMerge, - truncate: truncate, - hasKey: hasKey, - joinRegExp: joinRegExp, - urlencode: urlencode, - uuid4: uuid4, - htmlTreeAsString: htmlTreeAsString, - htmlElementAsString: htmlElementAsString, - parseUrl: parseUrl, - fill: fill - }; - } - - // Deprecations - Raven.prototype.setUser = Raven.prototype.setUserContext; - Raven.prototype.setReleaseContext = Raven.prototype.setRelease; - - module.exports = Raven; - }.call( - this, - typeof global !== 'undefined' - ? global - : typeof self !== 'undefined' - ? self - : typeof window !== 'undefined' ? window : {} - )); - }, - {'1': 1, '2': 2, '5': 5, '6': 6, '7': 7} - ], - 4: [ - function(_dereq_, module, exports) { - (function(global) { - /** - * Enforces a single instance of the Raven client, and the - * main entry point for Raven. If you are a consumer of the - * Raven library, you SHOULD load this file (vs raven.js). - **/ - - var RavenConstructor = _dereq_(3); - - // This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785) - var _window = - typeof window !== 'undefined' - ? window - : typeof global !== 'undefined' - ? global - : typeof self !== 'undefined' ? self : {}; - var _Raven = _window.Raven; - - var Raven = new RavenConstructor(); - - /* - * Allow multiple versions of Raven to be installed. - * Strip Raven from the global context and returns the instance. - * - * @return {Raven} +function fill(obj, name, replacement, track) { + if (obj == null) return; + var orig = obj[name]; + obj[name] = replacement(orig); + obj[name].__raven__ = true; + obj[name].__orig__ = orig; + if (track) { + track.push([obj, name, orig]); + } +} + +/** + * Join values in array + * @param input array of values to be joined together + * @param delimiter string to be placed in-between values + * @returns {string} */ - Raven.noConflict = function() { - _window.Raven = _Raven; - return Raven; - }; - - Raven.afterLoad(); - - module.exports = Raven; - }.call( - this, - typeof global !== 'undefined' - ? global - : typeof self !== 'undefined' - ? self - : typeof window !== 'undefined' ? window : {} - )); - }, - {'3': 3} - ], - 5: [ - function(_dereq_, module, exports) { - function isObject(what) { - return typeof what === 'object' && what !== null; - } - - // Yanked from https://git.io/vS8DV re-used under CC0 - // with some tiny modifications - function isError(value) { - switch ({}.toString.call(value)) { - case '[object Error]': - return true; - case '[object Exception]': - return true; - case '[object DOMException]': - return true; - default: - return value instanceof Error; - } - } - - function wrappedCallback(callback) { - function dataCallback(data, original) { - var normalizedData = callback(data) || data; - if (original) { - return original(normalizedData) || normalizedData; - } - return normalizedData; - } - - return dataCallback; - } - - module.exports = { - isObject: isObject, - isError: isError, - wrappedCallback: wrappedCallback - }; - }, - {} - ], - 6: [ - function(_dereq_, module, exports) { - (function(global) { - var utils = _dereq_(5); - - /* +function safeJoin(input, delimiter) { + if (!isArray(input)) return ''; + + var output = []; + + for (var i = 0; i < input.length; i++) { + try { + output.push(String(input[i])); + } catch (e) { + output.push('[value cannot be serialized]'); + } + } + + return output.join(delimiter); +} + +// Default Node.js REPL depth +var MAX_SERIALIZE_EXCEPTION_DEPTH = 3; +// 50kB, as 100kB is max payload size, so half sounds reasonable +var MAX_SERIALIZE_EXCEPTION_SIZE = 50 * 1024; +var MAX_SERIALIZE_KEYS_LENGTH = 40; + +function utf8Length(value) { + return ~-encodeURI(value).split(/%..|./).length; +} + +function jsonSize(value) { + return utf8Length(JSON.stringify(value)); +} + +function serializeValue(value) { + var maxLength = 40; + + if (typeof value === 'string') { + return value.length <= maxLength ? value : value.substr(0, maxLength - 1) + '\u2026'; + } else if ( + typeof value === 'number' || + typeof value === 'boolean' || + typeof value === 'undefined' + ) { + return value; + } + + var type = Object.prototype.toString.call(value); + + // Node.js REPL notation + if (type === '[object Object]') return '[Object]'; + if (type === '[object Array]') return '[Array]'; + if (type === '[object Function]') + return value.name ? '[Function: ' + value.name + ']' : '[Function]'; + + return value; +} + +function serializeObject(value, depth) { + if (depth === 0) return serializeValue(value); + + if (isPlainObject(value)) { + return Object.keys(value).reduce(function(acc, key) { + acc[key] = serializeObject(value[key], depth - 1); + return acc; + }, {}); + } else if (Array.isArray(value)) { + return value.map(function(val) { + return serializeObject(val, depth - 1); + }); + } + + return serializeValue(value); +} + +function serializeException(ex, depth, maxSize) { + if (!isPlainObject(ex)) return ex; + + depth = typeof depth !== 'number' ? MAX_SERIALIZE_EXCEPTION_DEPTH : depth; + maxSize = typeof depth !== 'number' ? MAX_SERIALIZE_EXCEPTION_SIZE : maxSize; + + var serialized = serializeObject(ex, depth); + + if (jsonSize(stringify(serialized)) > maxSize) { + return serializeException(ex, depth - 1); + } + + return serialized; +} + +function serializeKeysForMessage(keys, maxLength) { + if (typeof keys === 'number' || typeof keys === 'string') return keys.toString(); + if (!Array.isArray(keys)) return ''; + + keys = keys.filter(function(key) { + return typeof key === 'string'; + }); + if (keys.length === 0) return '[object has no keys]'; + + maxLength = typeof maxLength !== 'number' ? MAX_SERIALIZE_KEYS_LENGTH : maxLength; + if (keys[0].length >= maxLength) return keys[0]; + + for (var usedKeys = keys.length; usedKeys > 0; usedKeys--) { + var serialized = keys.slice(0, usedKeys).join(', '); + if (serialized.length > maxLength) continue; + if (usedKeys === keys.length) return serialized; + return serialized + '\u2026'; + } + + return ''; +} + +function sanitize(input, sanitizeKeys) { + if (!isArray(sanitizeKeys) || (isArray(sanitizeKeys) && sanitizeKeys.length === 0)) + return input; + + var sanitizeRegExp = joinRegExp(sanitizeKeys); + var sanitizeMask = '********'; + var safeInput; + + try { + safeInput = JSON.parse(stringify(input)); + } catch (o_O) { + return input; + } + + function sanitizeWorker(workerInput) { + if (isArray(workerInput)) { + return workerInput.map(function(val) { + return sanitizeWorker(val); + }); + } + + if (isPlainObject(workerInput)) { + return Object.keys(workerInput).reduce(function(acc, k) { + if (sanitizeRegExp.test(k)) { + acc[k] = sanitizeMask; + } else { + acc[k] = sanitizeWorker(workerInput[k]); + } + return acc; + }, {}); + } + + return workerInput; + } + + return sanitizeWorker(safeInput); +} + +module.exports = { + isObject: isObject, + isError: isError, + isErrorEvent: isErrorEvent, + isUndefined: isUndefined, + isFunction: isFunction, + isPlainObject: isPlainObject, + isString: isString, + isArray: isArray, + isEmptyObject: isEmptyObject, + supportsErrorEvent: supportsErrorEvent, + supportsFetch: supportsFetch, + supportsReferrerPolicy: supportsReferrerPolicy, + supportsPromiseRejectionEvent: supportsPromiseRejectionEvent, + wrappedCallback: wrappedCallback, + each: each, + objectMerge: objectMerge, + truncate: truncate, + objectFrozen: objectFrozen, + hasKey: hasKey, + joinRegExp: joinRegExp, + urlencode: urlencode, + uuid4: uuid4, + htmlTreeAsString: htmlTreeAsString, + htmlElementAsString: htmlElementAsString, + isSameException: isSameException, + isSameStacktrace: isSameStacktrace, + parseUrl: parseUrl, + fill: fill, + safeJoin: safeJoin, + serializeException: serializeException, + serializeKeysForMessage: serializeKeysForMessage, + sanitize: sanitize +}; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"7":7}],6:[function(_dereq_,module,exports){ +(function (global){ +var utils = _dereq_(5); + +/* TraceKit - Cross brower stack traces This was originally forked from github.com/occ/TraceKit, but has since been largely re-written and is now maintained as part of raven-js. Tests for this are in test/vendor. MIT license */ - var TraceKit = { - collectWindowErrors: true, - debug: false - }; - - // This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785) - var _window = - typeof window !== 'undefined' - ? window - : typeof global !== 'undefined' - ? global - : typeof self !== 'undefined' ? self : {}; - - // global reference to slice - var _slice = [].slice; - var UNKNOWN_FUNCTION = '?'; - - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Error_types - var ERROR_TYPES_RE = /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/; - - function getLocationHref() { - if (typeof document === 'undefined' || document.location == null) return ''; - - return document.location.href; - } - - /** +var TraceKit = { + collectWindowErrors: true, + debug: false +}; + +// This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785) +var _window = + typeof window !== 'undefined' + ? window + : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; + +// global reference to slice +var _slice = [].slice; +var UNKNOWN_FUNCTION = '?'; + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Error_types +var ERROR_TYPES_RE = /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/; + +function getLocationHref() { + if (typeof document === 'undefined' || document.location == null) return ''; + + return document.location.href; +} + +/** * TraceKit.report: cross-browser processing of unhandled exceptions * * Syntax: * TraceKit.report.subscribe(function(stackInfo) { ... }) * TraceKit.report.unsubscribe(function(stackInfo) { ... }) * TraceKit.report(exception) * try { ...code... } catch(ex) { TraceKit.report(ex); } * @@ -2610,217 +2965,218 @@ * subscribed handlers. Please note that TraceKit.report will rethrow the * exception. This is REQUIRED in order to get a useful stack trace in IE. * If the exception does not reach the top of the browser, you will only * get a stack trace from the point where TraceKit.report was called. * * Handlers receive a stackInfo object as described in the * TraceKit.computeStackTrace docs. */ - TraceKit.report = (function reportModuleWrapper() { - var handlers = [], - lastArgs = null, - lastException = null, - lastExceptionStack = null; - - /** - * Add a crash handler. - * @param {Function} handler - */ - function subscribe(handler) { - installGlobalHandler(); - handlers.push(handler); - } - - /** - * Remove a crash handler. - * @param {Function} handler - */ - function unsubscribe(handler) { - for (var i = handlers.length - 1; i >= 0; --i) { - if (handlers[i] === handler) { - handlers.splice(i, 1); - } - } - } - - /** - * Remove all crash handlers. - */ - function unsubscribeAll() { - uninstallGlobalHandler(); - handlers = []; - } - - /** - * Dispatch stack information to all handlers. - * @param {Object.<string, *>} stack - */ - function notifyHandlers(stack, isWindowError) { - var exception = null; - if (isWindowError && !TraceKit.collectWindowErrors) { - return; - } - for (var i in handlers) { - if (handlers.hasOwnProperty(i)) { - try { - handlers[i].apply(null, [stack].concat(_slice.call(arguments, 2))); - } catch (inner) { - exception = inner; - } - } - } - - if (exception) { - throw exception; - } - } - - var _oldOnerrorHandler, _onErrorHandlerInstalled; - - /** - * Ensures all global unhandled exceptions are recorded. - * Supported by Gecko and IE. - * @param {string} message Error message. - * @param {string} url URL of script that generated the exception. - * @param {(number|string)} lineNo The line number at which the error - * occurred. - * @param {?(number|string)} colNo The column number at which the error - * occurred. - * @param {?Error} ex The actual Error object. - */ - function traceKitWindowOnError(message, url, lineNo, colNo, ex) { - var stack = null; - - if (lastExceptionStack) { - TraceKit.computeStackTrace.augmentStackTraceWithInitialElement( - lastExceptionStack, - url, - lineNo, - message - ); - processLastException(); - } else if (ex && utils.isError(ex)) { - // non-string `ex` arg; attempt to extract stack trace - - // New chrome and blink send along a real error object - // Let's just report that like a normal error. - // See: https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror - stack = TraceKit.computeStackTrace(ex); - notifyHandlers(stack, true); - } else { - var location = { - url: url, - line: lineNo, - column: colNo - }; - - var name = undefined; - var msg = message; // must be new var or will modify original `arguments` - var groups; - if ({}.toString.call(message) === '[object String]') { - var groups = message.match(ERROR_TYPES_RE); - if (groups) { - name = groups[1]; - msg = groups[2]; - } - } - - location.func = UNKNOWN_FUNCTION; - - stack = { - name: name, - message: msg, - url: getLocationHref(), - stack: [location] - }; - notifyHandlers(stack, true); - } - - if (_oldOnerrorHandler) { - return _oldOnerrorHandler.apply(this, arguments); - } - - return false; - } - - function installGlobalHandler() { - if (_onErrorHandlerInstalled) { - return; - } - _oldOnerrorHandler = _window.onerror; - _window.onerror = traceKitWindowOnError; - _onErrorHandlerInstalled = true; - } - - function uninstallGlobalHandler() { - if (!_onErrorHandlerInstalled) { - return; - } - _window.onerror = _oldOnerrorHandler; - _onErrorHandlerInstalled = false; - _oldOnerrorHandler = undefined; - } - - function processLastException() { - var _lastExceptionStack = lastExceptionStack, - _lastArgs = lastArgs; - lastArgs = null; - lastExceptionStack = null; - lastException = null; - notifyHandlers.apply( - null, - [_lastExceptionStack, false].concat(_lastArgs) - ); - } - - /** - * Reports an unhandled Error to TraceKit. - * @param {Error} ex - * @param {?boolean} rethrow If false, do not re-throw the exception. - * Only used for window.onerror to not cause an infinite loop of - * rethrowing. - */ - function report(ex, rethrow) { - var args = _slice.call(arguments, 1); - if (lastExceptionStack) { - if (lastException === ex) { - return; // already caught by an inner catch block, ignore - } else { - processLastException(); - } - } - - var stack = TraceKit.computeStackTrace(ex); - lastExceptionStack = stack; - lastException = ex; - lastArgs = args; - - // If the stack trace is incomplete, wait for 2 seconds for - // slow slow IE to see if onerror occurs or not before reporting - // this exception; otherwise, we will end up with an incomplete - // stack trace - setTimeout(function() { - if (lastException === ex) { - processLastException(); - } - }, stack.incomplete ? 2000 : 0); - - if (rethrow !== false) { - throw ex; // re-throw to propagate to the top level (and cause window.onerror) - } - } - - report.subscribe = subscribe; - report.unsubscribe = unsubscribe; - report.uninstall = unsubscribeAll; - return report; - })(); - - /** +TraceKit.report = (function reportModuleWrapper() { + var handlers = [], + lastArgs = null, + lastException = null, + lastExceptionStack = null; + + /** + * Add a crash handler. + * @param {Function} handler + */ + function subscribe(handler) { + installGlobalHandler(); + handlers.push(handler); + } + + /** + * Remove a crash handler. + * @param {Function} handler + */ + function unsubscribe(handler) { + for (var i = handlers.length - 1; i >= 0; --i) { + if (handlers[i] === handler) { + handlers.splice(i, 1); + } + } + } + + /** + * Remove all crash handlers. + */ + function unsubscribeAll() { + uninstallGlobalHandler(); + handlers = []; + } + + /** + * Dispatch stack information to all handlers. + * @param {Object.<string, *>} stack + */ + function notifyHandlers(stack, isWindowError) { + var exception = null; + if (isWindowError && !TraceKit.collectWindowErrors) { + return; + } + for (var i in handlers) { + if (handlers.hasOwnProperty(i)) { + try { + handlers[i].apply(null, [stack].concat(_slice.call(arguments, 2))); + } catch (inner) { + exception = inner; + } + } + } + + if (exception) { + throw exception; + } + } + + var _oldOnerrorHandler, _onErrorHandlerInstalled; + + /** + * Ensures all global unhandled exceptions are recorded. + * Supported by Gecko and IE. + * @param {string} msg Error message. + * @param {string} url URL of script that generated the exception. + * @param {(number|string)} lineNo The line number at which the error + * occurred. + * @param {?(number|string)} colNo The column number at which the error + * occurred. + * @param {?Error} ex The actual Error object. + */ + function traceKitWindowOnError(msg, url, lineNo, colNo, ex) { + var stack = null; + // If 'ex' is ErrorEvent, get real Error from inside + var exception = utils.isErrorEvent(ex) ? ex.error : ex; + // If 'msg' is ErrorEvent, get real message from inside + var message = utils.isErrorEvent(msg) ? msg.message : msg; + + if (lastExceptionStack) { + TraceKit.computeStackTrace.augmentStackTraceWithInitialElement( + lastExceptionStack, + url, + lineNo, + message + ); + processLastException(); + } else if (exception && utils.isError(exception)) { + // non-string `exception` arg; attempt to extract stack trace + + // New chrome and blink send along a real error object + // Let's just report that like a normal error. + // See: https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror + stack = TraceKit.computeStackTrace(exception); + notifyHandlers(stack, true); + } else { + var location = { + url: url, + line: lineNo, + column: colNo + }; + + var name = undefined; + var groups; + + if ({}.toString.call(message) === '[object String]') { + var groups = message.match(ERROR_TYPES_RE); + if (groups) { + name = groups[1]; + message = groups[2]; + } + } + + location.func = UNKNOWN_FUNCTION; + + stack = { + name: name, + message: message, + url: getLocationHref(), + stack: [location] + }; + notifyHandlers(stack, true); + } + + if (_oldOnerrorHandler) { + return _oldOnerrorHandler.apply(this, arguments); + } + + return false; + } + + function installGlobalHandler() { + if (_onErrorHandlerInstalled) { + return; + } + _oldOnerrorHandler = _window.onerror; + _window.onerror = traceKitWindowOnError; + _onErrorHandlerInstalled = true; + } + + function uninstallGlobalHandler() { + if (!_onErrorHandlerInstalled) { + return; + } + _window.onerror = _oldOnerrorHandler; + _onErrorHandlerInstalled = false; + _oldOnerrorHandler = undefined; + } + + function processLastException() { + var _lastExceptionStack = lastExceptionStack, + _lastArgs = lastArgs; + lastArgs = null; + lastExceptionStack = null; + lastException = null; + notifyHandlers.apply(null, [_lastExceptionStack, false].concat(_lastArgs)); + } + + /** + * Reports an unhandled Error to TraceKit. + * @param {Error} ex + * @param {?boolean} rethrow If false, do not re-throw the exception. + * Only used for window.onerror to not cause an infinite loop of + * rethrowing. + */ + function report(ex, rethrow) { + var args = _slice.call(arguments, 1); + if (lastExceptionStack) { + if (lastException === ex) { + return; // already caught by an inner catch block, ignore + } else { + processLastException(); + } + } + + var stack = TraceKit.computeStackTrace(ex); + lastExceptionStack = stack; + lastException = ex; + lastArgs = args; + + // If the stack trace is incomplete, wait for 2 seconds for + // slow slow IE to see if onerror occurs or not before reporting + // this exception; otherwise, we will end up with an incomplete + // stack trace + setTimeout(function() { + if (lastException === ex) { + processLastException(); + } + }, stack.incomplete ? 2000 : 0); + + if (rethrow !== false) { + throw ex; // re-throw to propagate to the top level (and cause window.onerror) + } + } + + report.subscribe = subscribe; + report.unsubscribe = unsubscribe; + report.uninstall = unsubscribeAll; + return report; +})(); + +/** * TraceKit.computeStackTrace: cross-browser stack traces in JavaScript * * Syntax: * s = TraceKit.computeStackTrace(exception) // consider using TraceKit.report instead (see below) * Returns: * s.name - exception name * s.message - exception message * s.stack[i].url - JavaScript or HTML file URL @@ -2861,414 +3217,658 @@ * * This is okay for tracing (because you are likely to be calling * computeStackTrace from the function you want to be the topmost element * of the stack trace anyway), but not okay for logging unhandled * exceptions (because your catch block will likely be far away from the * inner function that actually caused the exception). * */ - TraceKit.computeStackTrace = (function computeStackTraceWrapper() { - // Contents of Exception in various browsers. - // - // SAFARI: - // ex.message = Can't find variable: qq - // ex.line = 59 - // ex.sourceId = 580238192 - // ex.sourceURL = http://... - // ex.expressionBeginOffset = 96 - // ex.expressionCaretOffset = 98 - // ex.expressionEndOffset = 98 - // ex.name = ReferenceError - // - // FIREFOX: - // ex.message = qq is not defined - // ex.fileName = http://... - // ex.lineNumber = 59 - // ex.columnNumber = 69 - // ex.stack = ...stack trace... (see the example below) - // ex.name = ReferenceError - // - // CHROME: - // ex.message = qq is not defined - // ex.name = ReferenceError - // ex.type = not_defined - // ex.arguments = ['aa'] - // ex.stack = ...stack trace... - // - // INTERNET EXPLORER: - // ex.message = ... - // ex.name = ReferenceError - // - // OPERA: - // ex.message = ...message... (see the example below) - // ex.name = ReferenceError - // ex.opera#sourceloc = 11 (pretty much useless, duplicates the info in ex.message) - // ex.stacktrace = n/a; see 'opera:config#UserPrefs|Exceptions Have Stacktrace' - - /** - * Computes stack trace information from the stack property. - * Chrome and Gecko use this property. - * @param {Error} ex - * @return {?Object.<string, *>} Stack trace information. - */ - function computeStackTraceFromStackProp(ex) { - if (typeof ex.stack === 'undefined' || !ex.stack) return; - - var chrome = /^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|webpack|<anonymous>|\/).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i, - gecko = /^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|webpack|resource|\[native).*?|[^@]*bundle)(?::(\d+))?(?::(\d+))?\s*$/i, - winjs = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i, - // Used to additionally parse URL/line/column from eval frames - geckoEval = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i, - chromeEval = /\((\S*)(?::(\d+))(?::(\d+))\)/, - lines = ex.stack.split('\n'), - stack = [], - submatch, - parts, - element, - reference = /^(.*) is undefined$/.exec(ex.message); - - for (var i = 0, j = lines.length; i < j; ++i) { - if ((parts = chrome.exec(lines[i]))) { - var isNative = parts[2] && parts[2].indexOf('native') === 0; // start of line - var isEval = parts[2] && parts[2].indexOf('eval') === 0; // start of line - if (isEval && (submatch = chromeEval.exec(parts[2]))) { - // throw out eval line/column and use top-most line/column number - parts[2] = submatch[1]; // url - parts[3] = submatch[2]; // line - parts[4] = submatch[3]; // column - } - element = { - url: !isNative ? parts[2] : null, - func: parts[1] || UNKNOWN_FUNCTION, - args: isNative ? [parts[2]] : [], - line: parts[3] ? +parts[3] : null, - column: parts[4] ? +parts[4] : null - }; - } else if ((parts = winjs.exec(lines[i]))) { - element = { - url: parts[2], - func: parts[1] || UNKNOWN_FUNCTION, - args: [], - line: +parts[3], - column: parts[4] ? +parts[4] : null - }; - } else if ((parts = gecko.exec(lines[i]))) { - var isEval = parts[3] && parts[3].indexOf(' > eval') > -1; - if (isEval && (submatch = geckoEval.exec(parts[3]))) { - // throw out eval line/column and use top-most line number - parts[3] = submatch[1]; - parts[4] = submatch[2]; - parts[5] = null; // no column when eval - } else if ( - i === 0 && - !parts[5] && - typeof ex.columnNumber !== 'undefined' - ) { - // FireFox uses this awesome columnNumber property for its top frame - // Also note, Firefox's column number is 0-based and everything else expects 1-based, - // so adding 1 - // NOTE: this hack doesn't work if top-most frame is eval - stack[0].column = ex.columnNumber + 1; - } - element = { - url: parts[3], - func: parts[1] || UNKNOWN_FUNCTION, - args: parts[2] ? parts[2].split(',') : [], - line: parts[4] ? +parts[4] : null, - column: parts[5] ? +parts[5] : null - }; - } else { - continue; - } - - if (!element.func && element.line) { - element.func = UNKNOWN_FUNCTION; - } - - stack.push(element); - } - - if (!stack.length) { - return null; - } - - return { - name: ex.name, - message: ex.message, - url: getLocationHref(), - stack: stack - }; - } - - /** - * Adds information about the first frame to incomplete stack traces. - * Safari and IE require this to get complete data on the first frame. - * @param {Object.<string, *>} stackInfo Stack trace information from - * one of the compute* methods. - * @param {string} url The URL of the script that caused an error. - * @param {(number|string)} lineNo The line number of the script that - * caused an error. - * @param {string=} message The error generated by the browser, which - * hopefully contains the name of the object that caused the error. - * @return {boolean} Whether or not the stack information was - * augmented. - */ - function augmentStackTraceWithInitialElement( - stackInfo, - url, - lineNo, - message - ) { - var initial = { - url: url, - line: lineNo - }; - - if (initial.url && initial.line) { - stackInfo.incomplete = false; - - if (!initial.func) { - initial.func = UNKNOWN_FUNCTION; - } - - if (stackInfo.stack.length > 0) { - if (stackInfo.stack[0].url === initial.url) { - if (stackInfo.stack[0].line === initial.line) { - return false; // already in stack trace - } else if ( - !stackInfo.stack[0].line && - stackInfo.stack[0].func === initial.func - ) { - stackInfo.stack[0].line = initial.line; - return false; - } - } - } - - stackInfo.stack.unshift(initial); - stackInfo.partial = true; - return true; - } else { - stackInfo.incomplete = true; - } - - return false; - } - - /** - * Computes stack trace information by walking the arguments.caller - * chain at the time the exception occurred. This will cause earlier - * frames to be missed but is the only way to get any stack trace in - * Safari and IE. The top frame is restored by - * {@link augmentStackTraceWithInitialElement}. - * @param {Error} ex - * @return {?Object.<string, *>} Stack trace information. - */ - function computeStackTraceByWalkingCallerChain(ex, depth) { - var functionName = /function\s+([_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*)?\s*\(/i, - stack = [], - funcs = {}, - recursion = false, - parts, - item, - source; - - for ( - var curr = computeStackTraceByWalkingCallerChain.caller; - curr && !recursion; - curr = curr.caller - ) { - if (curr === computeStackTrace || curr === TraceKit.report) { - // console.log('skipping internal function'); - continue; - } - - item = { - url: null, - func: UNKNOWN_FUNCTION, - line: null, - column: null - }; - - if (curr.name) { - item.func = curr.name; - } else if ((parts = functionName.exec(curr.toString()))) { - item.func = parts[1]; - } - - if (typeof item.func === 'undefined') { - try { - item.func = parts.input.substring(0, parts.input.indexOf('{')); - } catch (e) {} - } - - if (funcs['' + curr]) { - recursion = true; - } else { - funcs['' + curr] = true; - } - - stack.push(item); - } - - if (depth) { - // console.log('depth is ' + depth); - // console.log('stack is ' + stack.length); - stack.splice(0, depth); - } - - var result = { - name: ex.name, - message: ex.message, - url: getLocationHref(), - stack: stack - }; - augmentStackTraceWithInitialElement( - result, - ex.sourceURL || ex.fileName, - ex.line || ex.lineNumber, - ex.message || ex.description - ); - return result; - } - - /** - * Computes a stack trace for an exception. - * @param {Error} ex - * @param {(string|number)=} depth - */ - function computeStackTrace(ex, depth) { - var stack = null; - depth = depth == null ? 0 : +depth; - - try { - stack = computeStackTraceFromStackProp(ex); - if (stack) { - return stack; - } - } catch (e) { - if (TraceKit.debug) { - throw e; - } - } - - try { - stack = computeStackTraceByWalkingCallerChain(ex, depth + 1); - if (stack) { - return stack; - } - } catch (e) { - if (TraceKit.debug) { - throw e; - } - } - return { - name: ex.name, - message: ex.message, - url: getLocationHref() - }; - } - - computeStackTrace.augmentStackTraceWithInitialElement = augmentStackTraceWithInitialElement; - computeStackTrace.computeStackTraceFromStackProp = computeStackTraceFromStackProp; - - return computeStackTrace; - })(); - - module.exports = TraceKit; - }.call( - this, - typeof global !== 'undefined' - ? global - : typeof self !== 'undefined' - ? self - : typeof window !== 'undefined' ? window : {} - )); - }, - {'5': 5} - ], - 7: [ - function(_dereq_, module, exports) { - /* +TraceKit.computeStackTrace = (function computeStackTraceWrapper() { + // Contents of Exception in various browsers. + // + // SAFARI: + // ex.message = Can't find variable: qq + // ex.line = 59 + // ex.sourceId = 580238192 + // ex.sourceURL = http://... + // ex.expressionBeginOffset = 96 + // ex.expressionCaretOffset = 98 + // ex.expressionEndOffset = 98 + // ex.name = ReferenceError + // + // FIREFOX: + // ex.message = qq is not defined + // ex.fileName = http://... + // ex.lineNumber = 59 + // ex.columnNumber = 69 + // ex.stack = ...stack trace... (see the example below) + // ex.name = ReferenceError + // + // CHROME: + // ex.message = qq is not defined + // ex.name = ReferenceError + // ex.type = not_defined + // ex.arguments = ['aa'] + // ex.stack = ...stack trace... + // + // INTERNET EXPLORER: + // ex.message = ... + // ex.name = ReferenceError + // + // OPERA: + // ex.message = ...message... (see the example below) + // ex.name = ReferenceError + // ex.opera#sourceloc = 11 (pretty much useless, duplicates the info in ex.message) + // ex.stacktrace = n/a; see 'opera:config#UserPrefs|Exceptions Have Stacktrace' + + /** + * Computes stack trace information from the stack property. + * Chrome and Gecko use this property. + * @param {Error} ex + * @return {?Object.<string, *>} Stack trace information. + */ + function computeStackTraceFromStackProp(ex) { + if (typeof ex.stack === 'undefined' || !ex.stack) return; + + var chrome = /^\s*at (?:(.*?) ?\()?((?:file|https?|blob|chrome-extension|native|eval|webpack|<anonymous>|[a-z]:|\/).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i; + var winjs = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx(?:-web)|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i; + // NOTE: blob urls are now supposed to always have an origin, therefore it's format + // which is `blob:http://url/path/with-some-uuid`, is matched by `blob.*?:\/` as well + var gecko = /^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|webpack|resource|moz-extension).*?:\/.*?|\[native code\]|[^@]*bundle)(?::(\d+))?(?::(\d+))?\s*$/i; + // Used to additionally parse URL/line/column from eval frames + var geckoEval = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i; + var chromeEval = /\((\S*)(?::(\d+))(?::(\d+))\)/; + var lines = ex.stack.split('\n'); + var stack = []; + var submatch; + var parts; + var element; + var reference = /^(.*) is undefined$/.exec(ex.message); + + for (var i = 0, j = lines.length; i < j; ++i) { + if ((parts = chrome.exec(lines[i]))) { + var isNative = parts[2] && parts[2].indexOf('native') === 0; // start of line + var isEval = parts[2] && parts[2].indexOf('eval') === 0; // start of line + if (isEval && (submatch = chromeEval.exec(parts[2]))) { + // throw out eval line/column and use top-most line/column number + parts[2] = submatch[1]; // url + parts[3] = submatch[2]; // line + parts[4] = submatch[3]; // column + } + element = { + url: !isNative ? parts[2] : null, + func: parts[1] || UNKNOWN_FUNCTION, + args: isNative ? [parts[2]] : [], + line: parts[3] ? +parts[3] : null, + column: parts[4] ? +parts[4] : null + }; + } else if ((parts = winjs.exec(lines[i]))) { + element = { + url: parts[2], + func: parts[1] || UNKNOWN_FUNCTION, + args: [], + line: +parts[3], + column: parts[4] ? +parts[4] : null + }; + } else if ((parts = gecko.exec(lines[i]))) { + var isEval = parts[3] && parts[3].indexOf(' > eval') > -1; + if (isEval && (submatch = geckoEval.exec(parts[3]))) { + // throw out eval line/column and use top-most line number + parts[3] = submatch[1]; + parts[4] = submatch[2]; + parts[5] = null; // no column when eval + } else if (i === 0 && !parts[5] && typeof ex.columnNumber !== 'undefined') { + // FireFox uses this awesome columnNumber property for its top frame + // Also note, Firefox's column number is 0-based and everything else expects 1-based, + // so adding 1 + // NOTE: this hack doesn't work if top-most frame is eval + stack[0].column = ex.columnNumber + 1; + } + element = { + url: parts[3], + func: parts[1] || UNKNOWN_FUNCTION, + args: parts[2] ? parts[2].split(',') : [], + line: parts[4] ? +parts[4] : null, + column: parts[5] ? +parts[5] : null + }; + } else { + continue; + } + + if (!element.func && element.line) { + element.func = UNKNOWN_FUNCTION; + } + + stack.push(element); + } + + if (!stack.length) { + return null; + } + + return { + name: ex.name, + message: ex.message, + url: getLocationHref(), + stack: stack + }; + } + + /** + * Adds information about the first frame to incomplete stack traces. + * Safari and IE require this to get complete data on the first frame. + * @param {Object.<string, *>} stackInfo Stack trace information from + * one of the compute* methods. + * @param {string} url The URL of the script that caused an error. + * @param {(number|string)} lineNo The line number of the script that + * caused an error. + * @param {string=} message The error generated by the browser, which + * hopefully contains the name of the object that caused the error. + * @return {boolean} Whether or not the stack information was + * augmented. + */ + function augmentStackTraceWithInitialElement(stackInfo, url, lineNo, message) { + var initial = { + url: url, + line: lineNo + }; + + if (initial.url && initial.line) { + stackInfo.incomplete = false; + + if (!initial.func) { + initial.func = UNKNOWN_FUNCTION; + } + + if (stackInfo.stack.length > 0) { + if (stackInfo.stack[0].url === initial.url) { + if (stackInfo.stack[0].line === initial.line) { + return false; // already in stack trace + } else if ( + !stackInfo.stack[0].line && + stackInfo.stack[0].func === initial.func + ) { + stackInfo.stack[0].line = initial.line; + return false; + } + } + } + + stackInfo.stack.unshift(initial); + stackInfo.partial = true; + return true; + } else { + stackInfo.incomplete = true; + } + + return false; + } + + /** + * Computes stack trace information by walking the arguments.caller + * chain at the time the exception occurred. This will cause earlier + * frames to be missed but is the only way to get any stack trace in + * Safari and IE. The top frame is restored by + * {@link augmentStackTraceWithInitialElement}. + * @param {Error} ex + * @return {?Object.<string, *>} Stack trace information. + */ + function computeStackTraceByWalkingCallerChain(ex, depth) { + var functionName = /function\s+([_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*)?\s*\(/i, + stack = [], + funcs = {}, + recursion = false, + parts, + item, + source; + + for ( + var curr = computeStackTraceByWalkingCallerChain.caller; + curr && !recursion; + curr = curr.caller + ) { + if (curr === computeStackTrace || curr === TraceKit.report) { + // console.log('skipping internal function'); + continue; + } + + item = { + url: null, + func: UNKNOWN_FUNCTION, + line: null, + column: null + }; + + if (curr.name) { + item.func = curr.name; + } else if ((parts = functionName.exec(curr.toString()))) { + item.func = parts[1]; + } + + if (typeof item.func === 'undefined') { + try { + item.func = parts.input.substring(0, parts.input.indexOf('{')); + } catch (e) {} + } + + if (funcs['' + curr]) { + recursion = true; + } else { + funcs['' + curr] = true; + } + + stack.push(item); + } + + if (depth) { + // console.log('depth is ' + depth); + // console.log('stack is ' + stack.length); + stack.splice(0, depth); + } + + var result = { + name: ex.name, + message: ex.message, + url: getLocationHref(), + stack: stack + }; + augmentStackTraceWithInitialElement( + result, + ex.sourceURL || ex.fileName, + ex.line || ex.lineNumber, + ex.message || ex.description + ); + return result; + } + + /** + * Computes a stack trace for an exception. + * @param {Error} ex + * @param {(string|number)=} depth + */ + function computeStackTrace(ex, depth) { + var stack = null; + depth = depth == null ? 0 : +depth; + + try { + stack = computeStackTraceFromStackProp(ex); + if (stack) { + return stack; + } + } catch (e) { + if (TraceKit.debug) { + throw e; + } + } + + try { + stack = computeStackTraceByWalkingCallerChain(ex, depth + 1); + if (stack) { + return stack; + } + } catch (e) { + if (TraceKit.debug) { + throw e; + } + } + return { + name: ex.name, + message: ex.message, + url: getLocationHref() + }; + } + + computeStackTrace.augmentStackTraceWithInitialElement = augmentStackTraceWithInitialElement; + computeStackTrace.computeStackTraceFromStackProp = computeStackTraceFromStackProp; + + return computeStackTrace; +})(); + +module.exports = TraceKit; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"5":5}],7:[function(_dereq_,module,exports){ +/* json-stringify-safe Like JSON.stringify, but doesn't throw on circular references. Originally forked from https://github.com/isaacs/json-stringify-safe version 5.0.1 on 3/8/2017 and modified to handle Errors serialization and IE8 compatibility. Tests for this are in test/vendor. ISC license: https://github.com/isaacs/json-stringify-safe/blob/master/LICENSE */ - exports = module.exports = stringify; - exports.getSerialize = serializer; - - function indexOf(haystack, needle) { - for (var i = 0; i < haystack.length; ++i) { - if (haystack[i] === needle) return i; - } - return -1; - } - - function stringify(obj, replacer, spaces, cycleReplacer) { - return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces); - } - - // https://github.com/ftlabs/js-abbreviate/blob/fa709e5f139e7770a71827b1893f22418097fbda/index.js#L95-L106 - function stringifyError(value) { - var err = { - // These properties are implemented as magical getters and don't show up in for in - stack: value.stack, - message: value.message, - name: value.name - }; - - for (var i in value) { - if (Object.prototype.hasOwnProperty.call(value, i)) { - err[i] = value[i]; - } - } - - return err; - } - - function serializer(replacer, cycleReplacer) { - var stack = []; - var keys = []; - - if (cycleReplacer == null) { - cycleReplacer = function(key, value) { - if (stack[0] === value) { - return '[Circular ~]'; - } - return ( - '[Circular ~.' + keys.slice(0, indexOf(stack, value)).join('.') + ']' - ); - }; - } - - return function(key, value) { - if (stack.length > 0) { - var thisPos = indexOf(stack, this); - ~thisPos ? stack.splice(thisPos + 1) : stack.push(this); - ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key); - - if (~indexOf(stack, value)) { - value = cycleReplacer.call(this, key, value); - } - } else { - stack.push(value); - } - - return replacer == null - ? value instanceof Error ? stringifyError(value) : value - : replacer.call(this, key, value); - }; - } - }, - {} - ] - }, - {}, - [4] - )(4); -}); +exports = module.exports = stringify; +exports.getSerialize = serializer; + +function indexOf(haystack, needle) { + for (var i = 0; i < haystack.length; ++i) { + if (haystack[i] === needle) return i; + } + return -1; +} + +function stringify(obj, replacer, spaces, cycleReplacer) { + return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces); +} + +// https://github.com/ftlabs/js-abbreviate/blob/fa709e5f139e7770a71827b1893f22418097fbda/index.js#L95-L106 +function stringifyError(value) { + var err = { + // These properties are implemented as magical getters and don't show up in for in + stack: value.stack, + message: value.message, + name: value.name + }; + + for (var i in value) { + if (Object.prototype.hasOwnProperty.call(value, i)) { + err[i] = value[i]; + } + } + + return err; +} + +function serializer(replacer, cycleReplacer) { + var stack = []; + var keys = []; + + if (cycleReplacer == null) { + cycleReplacer = function(key, value) { + if (stack[0] === value) { + return '[Circular ~]'; + } + return '[Circular ~.' + keys.slice(0, indexOf(stack, value)).join('.') + ']'; + }; + } + + return function(key, value) { + if (stack.length > 0) { + var thisPos = indexOf(stack, this); + ~thisPos ? stack.splice(thisPos + 1) : stack.push(this); + ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key); + + if (~indexOf(stack, value)) { + value = cycleReplacer.call(this, key, value); + } + } else { + stack.push(value); + } + + return replacer == null + ? value instanceof Error ? stringifyError(value) : value + : replacer.call(this, key, value); + }; +} + +},{}],8:[function(_dereq_,module,exports){ +/* + * JavaScript MD5 + * https://github.com/blueimp/JavaScript-MD5 + * + * Copyright 2011, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * https://opensource.org/licenses/MIT + * + * Based on + * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message + * Digest Algorithm, as defined in RFC 1321. + * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009 + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * Distributed under the BSD License + * See http://pajhome.org.uk/crypt/md5 for more info. + */ + +/* +* Add integers, wrapping at 2^32. This uses 16-bit operations internally +* to work around bugs in some JS interpreters. +*/ +function safeAdd(x, y) { + var lsw = (x & 0xffff) + (y & 0xffff); + var msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xffff); +} + +/* +* Bitwise rotate a 32-bit number to the left. +*/ +function bitRotateLeft(num, cnt) { + return (num << cnt) | (num >>> (32 - cnt)); +} + +/* +* These functions implement the four basic operations the algorithm uses. +*/ +function md5cmn(q, a, b, x, s, t) { + return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b); +} +function md5ff(a, b, c, d, x, s, t) { + return md5cmn((b & c) | (~b & d), a, b, x, s, t); +} +function md5gg(a, b, c, d, x, s, t) { + return md5cmn((b & d) | (c & ~d), a, b, x, s, t); +} +function md5hh(a, b, c, d, x, s, t) { + return md5cmn(b ^ c ^ d, a, b, x, s, t); +} +function md5ii(a, b, c, d, x, s, t) { + return md5cmn(c ^ (b | ~d), a, b, x, s, t); +} + +/* +* Calculate the MD5 of an array of little-endian words, and a bit length. +*/ +function binlMD5(x, len) { + /* append padding */ + x[len >> 5] |= 0x80 << (len % 32); + x[(((len + 64) >>> 9) << 4) + 14] = len; + + var i; + var olda; + var oldb; + var oldc; + var oldd; + var a = 1732584193; + var b = -271733879; + var c = -1732584194; + var d = 271733878; + + for (i = 0; i < x.length; i += 16) { + olda = a; + oldb = b; + oldc = c; + oldd = d; + + a = md5ff(a, b, c, d, x[i], 7, -680876936); + d = md5ff(d, a, b, c, x[i + 1], 12, -389564586); + c = md5ff(c, d, a, b, x[i + 2], 17, 606105819); + b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330); + a = md5ff(a, b, c, d, x[i + 4], 7, -176418897); + d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426); + c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341); + b = md5ff(b, c, d, a, x[i + 7], 22, -45705983); + a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416); + d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417); + c = md5ff(c, d, a, b, x[i + 10], 17, -42063); + b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162); + a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682); + d = md5ff(d, a, b, c, x[i + 13], 12, -40341101); + c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290); + b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329); + + a = md5gg(a, b, c, d, x[i + 1], 5, -165796510); + d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632); + c = md5gg(c, d, a, b, x[i + 11], 14, 643717713); + b = md5gg(b, c, d, a, x[i], 20, -373897302); + a = md5gg(a, b, c, d, x[i + 5], 5, -701558691); + d = md5gg(d, a, b, c, x[i + 10], 9, 38016083); + c = md5gg(c, d, a, b, x[i + 15], 14, -660478335); + b = md5gg(b, c, d, a, x[i + 4], 20, -405537848); + a = md5gg(a, b, c, d, x[i + 9], 5, 568446438); + d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690); + c = md5gg(c, d, a, b, x[i + 3], 14, -187363961); + b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501); + a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467); + d = md5gg(d, a, b, c, x[i + 2], 9, -51403784); + c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473); + b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734); + + a = md5hh(a, b, c, d, x[i + 5], 4, -378558); + d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463); + c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562); + b = md5hh(b, c, d, a, x[i + 14], 23, -35309556); + a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060); + d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353); + c = md5hh(c, d, a, b, x[i + 7], 16, -155497632); + b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640); + a = md5hh(a, b, c, d, x[i + 13], 4, 681279174); + d = md5hh(d, a, b, c, x[i], 11, -358537222); + c = md5hh(c, d, a, b, x[i + 3], 16, -722521979); + b = md5hh(b, c, d, a, x[i + 6], 23, 76029189); + a = md5hh(a, b, c, d, x[i + 9], 4, -640364487); + d = md5hh(d, a, b, c, x[i + 12], 11, -421815835); + c = md5hh(c, d, a, b, x[i + 15], 16, 530742520); + b = md5hh(b, c, d, a, x[i + 2], 23, -995338651); + + a = md5ii(a, b, c, d, x[i], 6, -198630844); + d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415); + c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905); + b = md5ii(b, c, d, a, x[i + 5], 21, -57434055); + a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571); + d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606); + c = md5ii(c, d, a, b, x[i + 10], 15, -1051523); + b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799); + a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359); + d = md5ii(d, a, b, c, x[i + 15], 10, -30611744); + c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380); + b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649); + a = md5ii(a, b, c, d, x[i + 4], 6, -145523070); + d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379); + c = md5ii(c, d, a, b, x[i + 2], 15, 718787259); + b = md5ii(b, c, d, a, x[i + 9], 21, -343485551); + + a = safeAdd(a, olda); + b = safeAdd(b, oldb); + c = safeAdd(c, oldc); + d = safeAdd(d, oldd); + } + return [a, b, c, d]; +} + +/* +* Convert an array of little-endian words to a string +*/ +function binl2rstr(input) { + var i; + var output = ''; + var length32 = input.length * 32; + for (i = 0; i < length32; i += 8) { + output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xff); + } + return output; +} + +/* +* Convert a raw string to an array of little-endian words +* Characters >255 have their high-byte silently ignored. +*/ +function rstr2binl(input) { + var i; + var output = []; + output[(input.length >> 2) - 1] = undefined; + for (i = 0; i < output.length; i += 1) { + output[i] = 0; + } + var length8 = input.length * 8; + for (i = 0; i < length8; i += 8) { + output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << (i % 32); + } + return output; +} + +/* +* Calculate the MD5 of a raw string +*/ +function rstrMD5(s) { + return binl2rstr(binlMD5(rstr2binl(s), s.length * 8)); +} + +/* +* Calculate the HMAC-MD5, of a key and some data (raw strings) +*/ +function rstrHMACMD5(key, data) { + var i; + var bkey = rstr2binl(key); + var ipad = []; + var opad = []; + var hash; + ipad[15] = opad[15] = undefined; + if (bkey.length > 16) { + bkey = binlMD5(bkey, key.length * 8); + } + for (i = 0; i < 16; i += 1) { + ipad[i] = bkey[i] ^ 0x36363636; + opad[i] = bkey[i] ^ 0x5c5c5c5c; + } + hash = binlMD5(ipad.concat(rstr2binl(data)), 512 + data.length * 8); + return binl2rstr(binlMD5(opad.concat(hash), 512 + 128)); +} + +/* +* Convert a raw string to a hex string +*/ +function rstr2hex(input) { + var hexTab = '0123456789abcdef'; + var output = ''; + var x; + var i; + for (i = 0; i < input.length; i += 1) { + x = input.charCodeAt(i); + output += hexTab.charAt((x >>> 4) & 0x0f) + hexTab.charAt(x & 0x0f); + } + return output; +} + +/* +* Encode a string as utf-8 +*/ +function str2rstrUTF8(input) { + return unescape(encodeURIComponent(input)); +} + +/* +* Take string arguments and return either raw or hex encoded strings +*/ +function rawMD5(s) { + return rstrMD5(str2rstrUTF8(s)); +} +function hexMD5(s) { + return rstr2hex(rawMD5(s)); +} +function rawHMACMD5(k, d) { + return rstrHMACMD5(str2rstrUTF8(k), str2rstrUTF8(d)); +} +function hexHMACMD5(k, d) { + return rstr2hex(rawHMACMD5(k, d)); +} + +function md5(string, key, raw) { + if (!key) { + if (!raw) { + return hexMD5(string); + } + return rawMD5(string); + } + if (!raw) { + return hexHMACMD5(key, string); + } + return rawHMACMD5(key, string); +} + +module.exports = md5; + +},{}]},{},[4])(4) +}); \ No newline at end of file
--- a/browser/extensions/screenshots/webextension/build/shot.js +++ b/browser/extensions/screenshots/webextension/build/shot.js @@ -1,40 +1,37 @@ this.shot = (function () {let exports={}; // Note: in this library we can't use any "system" dependencies because this can be used from multiple // environments +const isNode = typeof process !== "undefined" && Object.prototype.toString.call(process) === "[object process]"; +const URL = (isNode && require("url").URL) || window.URL; + /** Throws an error if the condition isn't true. Any extra arguments after the condition are used as console.error() arguments. */ function assert(condition, ...args) { if (condition) { return; } console.error("Failed assertion", ...args); throw new Error(`Failed assertion: ${args.join(" ")}`); } /** True if `url` is a valid URL */ function isUrl(url) { - // FIXME: this is rather naive, obviously - if ((/^about:.{1,8000}$/i).test(url)) { - return true; - } - if ((/^file:\/.{0,8000}$/i).test(url)) { - return true; - } - if ((/^data:.*$/i).test(url)) { + try { + const parsed = new URL(url); + + if (parsed.protocol === "view-source:") { + return isUrl(url.substr("view-source:".length)); + } + return true; - } - if ((/^chrome:.{0,8000}/i).test(url)) { - return true; + } catch (e) { + return false; } - if ((/^view-source:/i).test(url)) { - return isUrl(url.substr("view-source:".length)); - } - return (/^https?:\/\/[a-z0-9._-]{1,8000}[a-z0-9](:[0-9]{1,8000})?\/?/i).test(url); } function isValidClipImageUrl(url) { return isUrl(url) && !(url.indexOf(")") > -1); } function assertUrl(url) { if (!url) { @@ -43,17 +40,17 @@ function assertUrl(url) { if (!isUrl(url)) { const exc = new Error("Not a URL"); exc.scheme = url.split(":")[0]; throw exc; } } function isSecureWebUri(url) { - return (/^https?:\/\/[a-z0-9._-]{1,8000}[a-z0-9](:[0-9]{1,8000})?\/?/i).test(url); + return isUrl(url) && url.toLowerCase().startsWith("https"); } function assertOrigin(url) { assertUrl(url); if (url.search(/^https?:/i) !== -1) { const match = (/^https?:\/\/[^/:]{1,4000}\/?$/i).exec(url); if (!match) { throw new Error("Bad origin, might include path"); @@ -110,49 +107,16 @@ function jsonify(obj, required, optional for (const attr of optional) { if (obj[attr]) { result[attr] = obj[attr]; } } return result; } -/** Resolve url relative to base */ -function resolveUrl(base, url) { - // FIXME: totally ad hoc and probably incorrect, but we can't - // use any libraries in this file - if (url.search(/^https?:/) !== -1) { - // Absolute url - return url; - } - if (url.indexOf("//") === 0) { - // Protocol-relative URL - return (/^https?:/i).exec(base)[0] + url; - } - if (url.indexOf("/") === 0) { - // Domain-relative URL - return (/^https?:\/\/[a-z0-9._-]{1,4000}/i).exec(base)[0] + url; - } - // Otherwise, a full relative URL - while (url.indexOf("./") === 0) { - url = url.substr(2); - } - if (!base) { - // It's not an absolute URL, and we don't have a base URL, so we have - // to throw away the URL - return null; - } - let match = (/.*\//).exec(base)[0]; - if (match.search(/^https?:\/$/i) === 0) { - // Domain without path - match = match + "/"; - } - return match + url; -} - /** True if the two objects look alike. Null, undefined, and absent properties are all treated as equivalent. Traverses objects and arrays */ function deepEqual(a, b) { if ((a === null || a === undefined) && (b === null || b === undefined)) { return true; } if (typeof a !== "object" || typeof b !== "object") { return a === b; @@ -270,70 +234,70 @@ class AbstractShot { const ALL_ATTRS = ["clips"].concat(this.REGULAR_ATTRS); assert(checkObject(json, [], ALL_ATTRS), "Bad attr to new Shot():", Object.keys(json)); for (const attr in json) { if (attr === "clips") { continue; } if (typeof json[attr] === "object" && typeof this[attr] === "object" && this[attr] !== null) { let val = this[attr]; - if (val.asJson) { - val = val.asJson(); + if (val.toJSON) { + val = val.toJSON(); } if (!deepEqual(json[attr], val)) { this[attr] = json[attr]; } } else if (json[attr] !== this[attr] && (json[attr] || this[attr])) { this[attr] = json[attr]; } } if (json.clips) { for (const clipId in json.clips) { if (!json.clips[clipId]) { this.delClip(clipId); } else if (!this.getClip(clipId)) { this.setClip(clipId, json.clips[clipId]); - } else if (!deepEqual(this.getClip(clipId).asJson(), json.clips[clipId])) { + } else if (!deepEqual(this.getClip(clipId).toJSON(), json.clips[clipId])) { this.setClip(clipId, json.clips[clipId]); } } } } /** Returns a JSON version of this shot */ - asJson() { + toJSON() { const result = {}; for (const attr of this.REGULAR_ATTRS) { let val = this[attr]; - if (val && val.asJson) { - val = val.asJson(); + if (val && val.toJSON) { + val = val.toJSON(); } result[attr] = val; } result.clips = {}; for (const attr in this._clips) { - result.clips[attr] = this._clips[attr].asJson(); + result.clips[attr] = this._clips[attr].toJSON(); } return result; } /** A more minimal JSON representation for creating indexes of shots */ asRecallJson() { const result = {clips: {}}; for (const attr of this.RECALL_ATTRS) { let val = this[attr]; - if (val && val.asJson) { - val = val.asJson(); + if (val && val.toJSON) { + val = val.toJSON(); } result[attr] = val; } for (const name of this.clipNames()) { - result.clips[name] = this.getClip(name).asJson(); + result.clips[name] = this.getClip(name).toJSON(); } return result; } get backend() { return this._backend; } @@ -369,17 +333,18 @@ class AbstractShot { } get filename() { let filenameTitle = this.title; const date = new Date(this.createdDate); // eslint-disable-next-line no-control-regex filenameTitle = filenameTitle.replace(/[:\\<>/!@&?"*.|\x00-\x1F]/g, " "); filenameTitle = filenameTitle.replace(/\s{1,4000}/g, " "); - let clipFilename = `Screenshot-${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${filenameTitle}`; + const filenameDate = new Date(date.getTime() - date.getTimezoneOffset() * 60 * 1000).toISOString().substring(0, 10); + let clipFilename = `Screenshot_${filenameDate} ${filenameTitle}`; const clipFilenameBytesSize = clipFilename.length * 2; // JS STrings are UTF-16 if (clipFilenameBytesSize > 251) { // 255 bytes (Usual filesystems max) - 4 for the ".png" file extension string const excedingchars = (clipFilenameBytesSize - 246) / 2; // 251 - 5 for ellipsis "[...]" clipFilename = clipFilename.substring(0, clipFilename.length - excedingchars); clipFilename = clipFilename + "[...]"; } const clip = this.getClip(this.clipNames()[0]); let extension = ".png"; @@ -492,24 +457,18 @@ class AbstractShot { assert(val === null || typeof val === "number", "Bad createdDate:", val); this._createdDate = val; } get favicon() { return this._favicon; } set favicon(val) { - // We allow but ignore bad favicon URLs, as they seem somewhat common + // We set the favicon with tabs.Tab.faviConUrl, which is a full URL. val = val || null; - if (!isUrl(val)) { - val = null; - } - if (val) { - val = resolveUrl(this.url, val); - } this._favicon = val; } clipNames() { const names = Object.getOwnPropertyNames(this._clips); names.sort(function(a, b) { return a.sortOrder < b.sortOrder ? 1 : 0; }); @@ -653,17 +612,17 @@ class _Image { "Bad Image dimensions:", json.dimensions); this.dimensions = json.dimensions; assert(typeof json.title === "string" || !json.title, "Bad Image title:", json.title); this.title = json.title; assert(typeof json.alt === "string" || !json.alt, "Bad Image alt:", json.alt); this.alt = json.alt; } - asJson() { + toJSON() { return jsonify(this, ["url"], ["dimensions"]); } } AbstractShot.prototype.Image = _Image; /** Represents a clip, either a text or image clip */ class _Clip { @@ -684,17 +643,17 @@ class _Clip { } this.image = json.image; } toString() { return `[Shot Clip id=${this.id} sortOrder=${this.sortOrder} image ${this.image.dimensions.x}x${this.image.dimensions.y}]`; } - asJson() { + toJSON() { return jsonify(this, ["createdDate"], ["sortOrder", "image"]); } get id() { return this._id; } get createdDate() {
--- a/browser/extensions/screenshots/webextension/clipboard.js +++ b/browser/extensions/screenshots/webextension/clipboard.js @@ -9,16 +9,17 @@ this.clipboard = (function() { return new Promise((resolve, reject) => { const element = document.createElement("iframe"); element.src = browser.extension.getURL("blank.html"); // We can't actually hide the iframe while copying, but we can make // it close to invisible: element.style.opacity = "0"; element.style.width = "1px"; element.style.height = "1px"; + element.style.display = "block"; element.addEventListener("load", catcher.watchFunction(() => { try { const doc = element.contentDocument; assertIsBlankDocument(doc); const el = doc.createElement("textarea"); doc.body.appendChild(el); el.value = text; if (!text) {
--- a/browser/extensions/screenshots/webextension/icons/icon-v2.svg +++ b/browser/extensions/screenshots/webextension/icons/icon-v2.svg @@ -1,1 +1,1 @@ -<svg width="32" height="32" xmlns="http://www.w3.org/2000/svg" fill="context-fill" fill-opacity="context-fill-opacity"><path d="M8 2a4 4 0 0 0-4 4h4V2zm12 0h-4v4h4V2zm8 0v4h4a4 4 0 0 0-4-4zM14 2h-4v4h4V2zm12 0h-4v4h4V2zm2 10h4V8h-4v4zm0 12a4 4 0 0 0 4-4h-4v4zm0-6h4v-4h-4v4zm-.882-4.334a4 4 0 0 0-5.57-.984l-7.67 5.662-3.936-2.76c.031-.193.05-.388.058-.584a4.976 4.976 0 0 0-2-3.978V8H4v2.1a5 5 0 1 0 3.916 8.948l2.484 1.738-2.8 1.964a4.988 4.988 0 1 0 2.3 3.266l17.218-12.35zM5 17.5a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5zm0 12a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5zm10.8-4.858l6.034 4.6a4 4 0 0 0 5.57-.984L19.28 22.2l-3.48 2.442z"/></svg> \ No newline at end of file +<svg viewBox="0 0 32 32" width="32" height="32" xmlns="http://www.w3.org/2000/svg" fill="context-fill" fill-opacity="context-fill-opacity"><path d="M8 2a4 4 0 0 0-4 4h4V2zm12 0h-4v4h4V2zm8 0v4h4a4 4 0 0 0-4-4zM14 2h-4v4h4V2zm12 0h-4v4h4V2zm2 10h4V8h-4v4zm0 12a4 4 0 0 0 4-4h-4v4zm0-6h4v-4h-4v4zm-.882-4.334a4 4 0 0 0-5.57-.984l-7.67 5.662-3.936-2.76c.031-.193.05-.388.058-.584a4.976 4.976 0 0 0-2-3.978V8H4v2.1a5 5 0 1 0 3.916 8.948l2.484 1.738-2.8 1.964a4.988 4.988 0 1 0 2.3 3.266l17.218-12.35zM5 17.5a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5zm0 12a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5zm10.8-4.858l6.034 4.6a4 4 0 0 0 5.57-.984L19.28 22.2l-3.48 2.442z"/></svg>
index 541f5e01c200c595b9f122468d295c8bd5f6ca25..22bab1da8d2386f0f88e817649f1eea5db54ea40 GIT binary patch literal 27596 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
index f29292be45ce4cb28c03a08e5cd2309faf78932a..10e86053f25768091018c6d394a0d8430c6a2688 GIT binary patch literal 41136 zc${>)1yq#X_XawEgh;3~5(eGU4T^wtcT3mM-6HTwcZYy<cMT=dokI@YokMd66n|^o z|Gl$>Bk!rbpZ)AO^Pcar(xT|7gs1=j0A1|!CwTw>(HQ`MA3{NbJ)v2~=Y}<KcJiVh z0mTDEn*hKMGqFz}6rJIA=7eqOrre;1I!5X74D)NAIxIVFwQw?$SZ#zm#Q35{$%&0& z0xWHYFD1y8vV|i2MK-I|^Ls{a+}}zk6RGq!9safpOkS&y(UEw6xk@IGR`1CnY(weL zh!42`Flfu|{4CzjqH0Q55m12Mu?aLB`nm_dC0(Tr+c&U3xylRDk=T;Xei-ciCAs`| z`^2?5j<msZf2ZZj54kx$|F(1NR6ZN?VL-;DwAQt_=h;N_Dqjx>yiR%z%)iu8$996d z@8&eE7QPweGOgX0TWgGJaJUJ`+h*Q{zwZ*%ii15*^Ei+Y%e<aXY3Nt?XWm7;?|FTM zdn&N<bCZJFcIg;;%d6!1ZWsAsN<wp&%=|_F+cb|$b>>THc_(Is2QjHS#O{jTrZwEn z<ZTP|?>s#FLFJhvd32n>_VCxWd&D5*$}Ez!o9;od1#`o|C26B)51qm`>jqzI2KpX) zexN-(gJ=A|jd#nl|J%GifdBt(P(XnH+x)*)-p_j2_PO)_NApA*@P8X0y#L=uZ~^iE zHvgY1-#58iE4tA;B+1EZ4f)I(Fol4{`~bifnMOl*P>O+Z^V51#!%3OgwaA!u1D7|{ zFpVlzVJH^ZvB3dk!QGj7-EHgL!#4a8o*Z>$v6?z!J<NYlY58ffOFr)r9~^p=cjDIl ziX$?{Pm}$RKnswlmD>u<+*jt^=HSSTE@7AjdCQRif+;IA(4<<oVG=%>CYODA5`Ba} z8aF|iT&hzfD(aHlw>{<8fBl6nOq)!G-L5;a>&myY4(0E@w~Gdu+nMW98#mE^F!A2! z7vFL{+J2o+^LI#&T0&_<+V@w)lmhNA|B3`6;ZNIx%r~~V%l{p<Xj9%8mv_0>_H%#| z{*UD>_6)YpFd{n{kHr3pz~g4>N6IfPA`16MQtb%{Rgvs?H<T3l55Uf9qMfE8cs$)f zyRa!yqv!y@u05}`NN2an*evDx`HG_H0B2r)We=2uvNuRf?Fj%-JAE%Tw^YlVfFwp} z<c*x|kb6$PkzK}lfJ`K~1h#Ok`u&KuOatiaDW1=Er0j2?G%hE+1Z+O_3EU)<4l#-j zgu?RJVlK8lYkT&Q(h2rFcTTSTVl_!?4RP2oQ6ZUK#{1)SL;wI$aO7?yt8w%9lw=Jo z*9NiXw37x?X+{G3s1GGi+yAiGnn85gz`TZQ<|@kbNeD3E<jVA`RrB|zHqYa^?I#b4 z|4=IAnUuj0ky0gA5mRG*Q!+1ui_|^KL`}hzb@hdEYF5heOlX_66}lH<VEG;Z&_!kh zw6KyMx|S#K&H#UHsnw@YY*`kPJ!{j9Z}eT6a>R1L6iLr=om)8*v9;u)4wQsZAs%KZ z{IDoee#<4G*1G0eK=)~kfhE@__IeV&XHyFA+)Ia<;u$eu2_;y?Z!Qo1!LpOHVok>f zOC(g=(cK-kSa<$6$!3~tQT2n>Mus<EOWn*HpI#Uyn8H7B<u5Z6+v_M!is_rYM(m&q zcgi0Wl}ze@3A3s)z+%*fKU63>xDkcXKjA;^oP;fUo_=3bH{=AkXhSRqt3r+lya!Oc zacl1i6Ci%0_B{Q-RlQ~|yZ#g)<>(*kw47-lRgr#^P-L}XO^H$<Gn`6(D<(&l7saa5 zp(GFPwi)MjbW$YGn<4R>$MQh#URc>9ztR4~)>=BH823Y-?hQ`7-@s`-EOr*oIv%l_ znI}VU*ExzK+cMX@xOst1m?C0IIpf^I%=oO$-Pf;S4vH=OcL5Y=8U-}f-;OHKpcpJ! zIB=S@FHMQ1kn^<h>JO_dtPXagyJu>mV*LL2gY}Di?u*%)L^c2@`2&X<K1EQQ0q5tQ zN>MG22q!mzDelktG(|)E<ac*K<{jie`Uf4^(_RetCi`4`C?ESYcT%5#?oe(Q51N#4 zghp|XmT{(m+^%|ff5Ech94xZ5AMYN#_ir?C;a5wPa82n;eXB;qzV4jNJ1?faL$S<_ zRZ*4<av2@a`m_2o70M|H=Ke5s<f@;6!y5v7J41V(drIi%C2V-q+g+G*X*a~qYQmC~ z>M#IcO@JZVjn{+~*>{?h+rSGolT1=pI<m;&Qt~*AxGtLM-ueOnv^;@X#*mA*ht|rJ zVM^8gl;n=4cV*fZtBMq;Jxk`)90zjCnNo8Xh64x^0KC~bN!DW&vAeh2j|Tcz_FS=? zbd=_#k6(eBY~H*1tKj|qE9&>$V>9ZgLVAjQh&Zr_{03-ZpcfDFU{OmLyqMZVdjOb; zE9Gs;w~Pe3ex91hBwDc=G}3gs$QkqQl&a(XHxL2I4yC`wvuM;V2CZHgF`rJDjqHAl zyY7e7N1W7i#iiW*wbhjWji#F24TU_UEO1d1X>O&+yjxS}J{Q19ZjvRP(1&iDb;8}9 za=OyL-Cc^u?<Y;^vaDXtbRV+r_fR)Ae53MFj_g%$HxEbDYMr%D^S1e`?j<HhOX%yo z{Ksu3;#iJhem=tyxh)|kxAO8okP}$PGqR>QYOd|i7^d%9RT9SuVulH`+7jB$MuU4l z-|m+hOTpZ^=q)U^%uaRnsim4vyf5qvc#3q|@JcKvip6rz;BQ=3O_pGfWx?t(iez<~ zhyoI1AO1JD)w{b`#PZ@#Yyx?{BRCl`6HY{Kj$QkOO^+5;)t<ZwiIV&qvQwB{pwf0V z;~nzM0x0y>`dph1w)vfmk@W*)e+A_Dd^<nqC;|cIJOxz>$&qm=+veNE@J{uS!95I^ z?7Ye>sj<>(>-Zv%oh}T^g?fxRuIIc_NDqFj8wu^4w-{_QH;jO(fUB<4lZO!&**_;q z_#l9H3dm|s+;b<yz)<fpD#MUK-NYr{o||jDqUi7b=+D}W824pB37R?y(k5D|&n%Ua zZ|*Gi$dqvYq>${o>cx<x?!l&+*&85523?fj2a}=-g#B7+b}0OxSW+1y%+lQe3jeOU zK}r2FsQ)xdn=;3>iTKX~(_h>evEB>xq3PPhD_HK5JN&^mzCLq#<$wz7{rn)B?qMMt z`m%u&;~!KdXU=X{CO(|FCBXGz4tRPGl4$>pXp>WepU|$vBGd#AUye>_G{8MjN_W~3 zllg_Owox!OWORp^qVq3R<74y1&tN@mz}k;K9AyD`e@5q@iHfCkZ$O!a@XsR4dEFY3 z{}@Z5+$%ne{GgnvE6)+*uWCw|pcLj!?1cAy&_PI>1~|bG02o5wRRFs@g(Z6`tl@nl zr1v{1jcqRb2aQ**Z~&^n*9WNqKipWQLkreGL;&4=CamFY1ElvG>ym>`kv0H;a|v)N zS$n;2AqWROxTAZ3ii`7>%mxo|&Um&5&zb*Zx8b~Q<R#@dA0Iy(u!Gm!<3|fT7oavz z>_p^G_S~}W%)`nFosCY6JLQ`+ce2hnF{!2u-)XZ8oSKMC^q0%<Uq6n)TA5GRqsYYM z6NUIJISPzA<W~?&0^zr)$q3?JibVp^UuqMwQvC`q^afFN`Fel#Ch7|On(UqY^({)b z56GL><gN|{{wQO?BJv2sl`M2ae}I*Yy4Q@X@KYpNw`YuCtPq?3>mO1`+C}(I5^!k# za5E8PVo}kP#Ox^DZ@jC&X06h#-xf|av}mBprR7KK7I)ZwHTq?O4TvNYQ}05Rj}14d zM2iN5`>6EqfNI^O&;+<i4kHj=kfT=fh(+R><0Ohjou1EJd`<oScC6FK2~jxqIF=7? z+2$LWSmg9ec!+B6`4kI5Se8r>NDgHcrLJ|`)a@M~<6hIw(MwW}=Y&q=VsXKop=u?| zA;F=^CbC=^QKIWuoT9M($pe)OE}M-Uq|nw`<*85&78)zbFI?tdcpC72;rtZ&Q$n1Y z;l=Y(lAOro)bB!<(c<q@i1M>ON0M!RY}0GdN>RWLU+HRbGnHUT&1UO;EjE%xfdZdF z^@T4x@@p!J5DGlIhN-7d!R@YB-Ey2~z^l8J5&~}5WF=XA`{YSZ$meh}u@S^rpo*zK z8Yr&1aAC6t`*Wl{HBDSkVW)lKD3)A{QINR<@M2d$4?44-eYs-VLE`)^>Gq(6YOuco zl}5O5<rAMUOc6ZQdM!gl=7ajyATqJQ<yrv-=gjba$Ej8)xrD^E%?$OBL(JJ;N4BH$ z7r>i-ft5Oe>*LuYH?NM3Oc~8eVhC19Fau1jscM6T7&0-`ws5Lo)gYqy-6=$?-$t+8 z4u%blUsY$0%))crcF!}}uW;&R4jkwQ)GprLc{tc^W$YZJn*6jEgGs`CARbB9F4w2t zmxI_s{(kuR8bMnasS$OM8?=;o7qs4_Jn2!-BsO`vhgnJ?xADmrW<@j*Y~m}+B1ZM3 z!Dp@%Xj76x0iiWZYw~Nwje)x+D-oS);-Fe<Irx41$l<PMu7Z#II61G6Tu0U|5WDQy z#LCY!BpI0e*Tkw5z;ZnFS+!?_*~iId9}-lZ$(yqBKjEC5+g|_Fs7i)Bg?BBn+evO% zGrJr)H09(*%rRQ{PPy0zFT-KI#d^p*^Q!`^<uDAtubDVZ4x8THLMs+o8H-oM79{Z{ zBfYWt>UYFIS%nVWm|k@U7vk<vk`b@rNg1W8I5Yc45Ag2}WJuLMw*+ioByF+CpH!h6 z@>Wp!Ht&9UJ~pvQ3kJJZHyn?gRO@tj=|RWY`4p|ec3Yy6Zq3*-B-ortAC*WluF;_) zqXF_4&d0V-z2BnbhK0Uwc9YrWvb(7w^F=sG5l<lRr%O#T!*@A(8q4f7H@<Flo|<U7 zf4<i4-)GU6#RXePULX+Wky{nt{eMz1d`E{|E<DHd?4hh+vLP_d>Tx0AhFit=&u3ys z8Goq(u|}kM&nnMgy+yZp4X;;Jc1<Q6+ZM&bRN?GN)bxep+r1`aF0C*iF2CY|TFx*) zAx|)5ex-{Uoe`UK&SkMGaI(yMs!a{&oy}W0#O>;GU3~m?)qD`ddHog7=Bx$IY<ZbZ zTJm7UvPNF!qw@+`ADuSm6;sV;Q8?PfK~$L9WQ@)*u<Nm(!0A1Y#N#T!vzY(x&+=^d z0uHsFStp>KduIwxUr6IB(8(QTfUY#!Ue`OyX?EC^A>Kw+I<bgh3=gq4&zQ@c@=;LG z(s&dLOd;7)3NGs<I?4XBtX&9P8-m4u0Q}<OtCtP;blQ@6b8ja|fAd00qVJ{)`|lMx zG6D@{z|PFmOy5EY>|ae!bE79bPEzm+{a{l5@e`B{5hCmp=@wxX^XkkUadA1G|3Wd_ zCWm?$m1w<<e#PR#Uv=mW;(vnEdPV%r{H*1(gHRL6vj;6X?zMCsl9l*S>Y-h(44@no zY#l-BDRsj}qS5@3&=OU_q|?llsw{Ehcev<Sn3DJ|IWdcuL&ilfH8<h80fAm>)vY9c zWZajL;TI!~SgfMGWO7~kIEb{=D5~!s$?86=&e5YMIQRq+Z(I|dkXmB;J&Ie$8(q`Y zmpf@DGCawtei`1cy?Z;~1Wx9E1?e9A784;6=vyemLQCcpGu)y(TzWg#MXQ7h-PPOM zqUChk4(Zc!XrD%~YE}RI$ZIB)O%X2LD@j(mG1ZaJRm5g0`s+-==pWztiKEGNDPv_* z^Xz$qr;vie%8*GY^2gOmO682t4~2@1_qXSN{ld9&Ig#LxyDU!8{Gq!{Nq7$qLJ5qv zP@AHcNHbS4C?5cB9SvTKle!5-*}OcRt5%*oam4PJ!(T#zOg9eXfY-~-YrX>KmMeN= zN(<<|0N4U~q<){bSRz*>Cj2vWXNV4%`Ja5SUK9utx>SqL-tk)9u03A&^zEHLsCJy) z_30U7<=J!l4=CeY@TT!KY&yZcaqvKGaxK-=pz)VG^G|(oa${kZ+#$(KQyLhsy(+mT zxzawS_{y3EMhZ*aan*Htji&bO9{2A^FigG{qY1y6>5;9vvA{ysKBJ+DQm}|MaQ^Rj zg)0T^6CB^`f8<;sMtL)te@cKZ;P{^u)|2GMRo8(ioB|Zko5vWChm6h!$X`_6ZRqL6 zVecKQ*#B8(mwdc}n1Ut(XZ}yzqqjLMj0{)pn|QoI{o%nOHs>q?5zkgG+NbZS`X|P^ zq>dJub>U`3k>0*E0)j%d#YO#{H_V&j4XneT8Nftq|F^3tdSJ#64FtVXVM;>9Z*Z7i zr+9W}XGxoWeh61}P@bj;KjR%kF&Amd<*eubVxxa;k|DtyO#qxm+!5#6V#)Q}P>+#L zL2O&OPj5N}ZZm85XPRO=&hdN2?YI-Dg#S^fs*mM~J$>&p*9@wmY3!D=up5-Un@}s= z#X#fIi2Eeq)^w%8dHIx7c$oTi4goE-AKW{e&jR<deu4e4%f%WO3aby1`lu9be!FT~ zel_U;ZQwd62frL-ELE;*Y~VWkPTF>2$x&fu>iNFdiTp9llv{-O;9Z*dsvuflUJNeY z2wct)UBrjICC2~ptmxT-X!G^6+I6?4tfbnRSR*`rFBmXlM_*yTsl{GSyroCI3@exa zj#UHc$fGUMTZ#~!vfJlFla?xv2C3^^o@sF2{90#n8pp9OU2EZy3k`