diff --git a/.npmignore b/.npmignore index 32ff635b..34fa1755 100644 --- a/.npmignore +++ b/.npmignore @@ -2,8 +2,9 @@ demo .github .vscode *.code-workspace +build.json credentials.json -bundles.json +deploy.json yarn.lock package-lock.json *.mp4 diff --git a/build.json b/build.json new file mode 100644 index 00000000..462649e3 --- /dev/null +++ b/build.json @@ -0,0 +1,46 @@ +{ + "js": { + "plyr.js": { + "src": "./src/js/plyr.js", + "dist": "./dist/", + "formats": ["es", "umd"], + "namespace": "Plyr" + }, + "plyr.polyfilled.js": { + "src": "./src/js/plyr.polyfilled.js", + "dist": "./dist/", + "formats": ["es", "umd"], + "namespace": "Plyr" + }, + "demo.js": { + "src": "./demo/src/js/demo.js", + "dist": "./demo/dist/", + "formats": ["iife"], + "namespace": "Demo" + } + }, + "css": { + "plyr.css": { + "src": "./src/sass/plyr.scss", + "dist": "./dist/" + }, + "demo.css": { + "src": "./demo/src/sass/bundles/demo.scss", + "dist": "./demo/dist/" + }, + "error.css": { + "src": "./demo/src/sass/bundles/error.scss", + "dist": "./demo/dist/" + } + }, + "sprite": { + "plyr.svg": { + "src": "./src/sprite/*.svg", + "dist": "./dist" + }, + "demo.svg": { + "src": "./src/sprite/*.svg", + "dist": "./demo/dist" + } + } +} diff --git a/bundles.json b/bundles.json deleted file mode 100644 index 9c7733cc..00000000 --- a/bundles.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "plyr": { - "sass": { - "plyr.css": "src/sass/plyr.scss" - }, - "js": { - "plyr.js": "src/js/plyr.js", - "plyr.polyfilled.js": "src/js/plyr.polyfilled.js" - } - }, - "demo": { - "sass": { - "demo.css": "demo/src/sass/bundles/demo.scss", - "error.css": "demo/src/sass/bundles/error.scss" - }, - "js": { - "demo.js": "demo/src/js/demo.js" - } - } -} diff --git a/changelog.md b/changelog.md index c382a814..b604a29e 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,28 @@ +## v3.5.2 + +- Fixed issue where the preview thumbnail was present while scrubbing + +## v3.5.1 + +- Fixed build issues with babel and browserslist + +## v3.5.0 + +- Preview seek/scrubbing thumbnails (thanks @jamesoflol) +- Fixes for proxy listeners (thanks @gurupras) +- Fix for buffer progress transition on WebKit (thanks @samuelgozi) +- Fix for error when mime type not specified (fixes #1274) +- Support YouTube noCookie (thanks Omar Khatib) +- Add Angular plugin reference (thanks @smnbbrv) +- Use `Math.trunc` instead of `parseInt` (thanks @taion) +- Many fixes for fullscreen in embedded players with non 16:9 screens or videos +- Added 'force' fallback option for fullscreen +- [RangeTouch](https://rangetouch.com) is now bundled with Plyr as a dependency to fix the scrubber on touch devices + +### v3.4.8 + +- Calling customized controls function with proper arguments (thanks @a60814billy) + ### v3.4.7 - Fix for Vimeo fullscreen with non native aspect ratios (fixes #854) @@ -48,7 +73,7 @@ - Add support for YouTube's hl param (thanks @renaudleo) - Fix for captions positioning when no controls (thanks @friday and @mjfwebb) - Fix #1108: Make sure youtube.onReady doesn't run twice (thanks @friday) -- Fix for WebKit redraw loop on the `` elements +- Fix for WebKit repaint loop on the `` elements ### v3.3.22 diff --git a/demo/dist/demo.css b/demo/dist/demo.css index 26340f61..32511c5d 100644 --- a/demo/dist/demo.css +++ b/demo/dist/demo.css @@ -1 +1 @@ -@font-face{font-display:swap;font-family:Gordita;font-style:normal;font-weight:300;src:url(https://cdn.plyr.io/static/fonts/gordita-light.woff2) format("woff2"),url(https://cdn.plyr.io/static/fonts/gordita-light.woff) format("woff")}@font-face{font-display:swap;font-family:Gordita;font-style:normal;font-weight:400;src:url(https://cdn.plyr.io/static/fonts/gordita-regular.woff2) format("woff2"),url(https://cdn.plyr.io/static/fonts/gordita-regular.woff) format("woff")}@font-face{font-display:swap;font-family:Gordita;font-style:normal;font-weight:500;src:url(https://cdn.plyr.io/static/fonts/gordita-medium.woff2) format("woff2"),url(https://cdn.plyr.io/static/fonts/gordita-medium.woff) format("woff")}@font-face{font-display:swap;font-family:Gordita;font-style:normal;font-weight:600;src:url(https://cdn.plyr.io/static/fonts/gordita-bold.woff2) format("woff2"),url(https://cdn.plyr.io/static/fonts/gordita-bold.woff) format("woff")}@font-face{font-display:swap;font-family:Gordita;font-style:normal;font-weight:900;src:url(https://cdn.plyr.io/static/fonts/gordita-black.woff2) format("woff2"),url(https://cdn.plyr.io/static/fonts/gordita-black.woff) format("woff")}@keyframes fadein{0%{opacity:0}100%{opacity:1}}/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a,button.faux-link{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:border-box}body,html{display:flex;width:100%}html{background:linear-gradient(to left top,#4dc1ff,#0074b3);background-attachment:fixed;height:100%}body{align-items:center;display:flex;flex-direction:column;min-height:100%}.grid{flex:1;overflow:auto}main{margin:auto;padding-bottom:1px;text-align:center}aside{align-items:center;background:#fff;color:#55646b;display:flex;flex-shrink:0;justify-content:center;padding:15px;position:relative;text-align:center;text-shadow:none;width:100%}aside .icon{fill:#4baaf4;margin-right:10px}aside p{margin:0}aside a,aside button.faux-link{color:#4baaf4}aside a.tab-focus,aside button.tab-focus.faux-link{box-shadow:0 0 0 3px rgba(75,170,244,.35);outline:0}.grid{margin:0 auto;padding:20px}@media only screen and (min-width:768px){.grid{align-items:center;display:flex;max-width:1260px;width:100%}.grid>*{flex:1}}html{font-size:100%}body{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-size:15px;font-size:.9375rem;color:#fff;font-family:Gordita,Avenir,"Helvetica Neue",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-weight:500;line-height:1.75;text-shadow:0 1px 1px rgba(0,0,0,.15)}button,input,select,textarea{font:inherit}p,small{margin:0 0 20px}small{font-size:13px;font-size:.8125rem;display:block}h1{font-size:64px;font-size:4rem;font-weight:600;letter-spacing:-.025em;line-height:1.2;margin:0 0 20px}.button,.button__count{align-items:center;background:#fff;border:0;border-radius:4px;box-shadow:0 1px 1px rgba(0,0,0,.1);color:#55646b;display:inline-flex;padding:15px;position:relative;text-shadow:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle}.button{font-weight:600;padding-left:20px;padding-right:20px;transition:all .2s ease}.button:focus,.button:hover{color:#343f4a}.button:focus::after,.button:hover::after{display:none}.button:hover{box-shadow:0 2px 2px rgba(0,0,0,.1);transform:translateY(-1px)}.button:focus{outline:0}.button.tab-focus{box-shadow:0 0 0 3px rgba(255,255,255,.35);outline:0}.button:active{transform:translateY(1px)}.button--with-count{display:inline-flex}.button--with-count .button .icon{flex-shrink:0}.button__count{animation:fadein .2s ease;margin-left:10px}.button__count::before{border:5px solid transparent;border-left-width:0;border-right-color:#fff;content:'';height:0;position:absolute;right:100%;top:50%;transform:translateY(-50%);width:0}header{padding-bottom:20px;text-align:center}header .call-to-action{margin-top:30px}@media only screen and (min-width:768px){header{margin-right:60px;max-width:360px;padding-bottom:40px;text-align:left}}.icon{fill:currentColor;height:16px;vertical-align:-3px;width:16px}a svg,button svg,button.faux-link svg,label svg{pointer-events:none}.btn .icon,a .icon,button.faux-link .icon{margin-right:6px}button.faux-link{background:0 0;border:0;border-radius:0;cursor:pointer;font:inherit;line-height:1.75;margin:0;padding:0;position:relative;text-align:inherit;text-shadow:inherit;-moz-user-select:text;vertical-align:baseline;width:auto}a,button.faux-link{border-bottom:1px dotted currentColor;color:#fff;font-weight:600;position:relative;text-decoration:none;transition:all .2s ease}a::after,button.faux-link::after{background:currentColor;content:'';height:1px;left:50%;position:absolute;top:100%;transform:translateX(-50%);transition:width .2s ease;width:0}a:focus,a:hover,button.faux-link:focus,button.faux-link:hover{border-bottom-color:transparent;outline:0}a:focus::after,a:hover::after,button.faux-link:focus::after,button.faux-link:hover::after{width:100%}a.tab-focus,button.tab-focus.faux-link{box-shadow:0 0 0 3px rgba(255,255,255,.35);outline:0}a.no-border::after,button.no-border.faux-link::after{display:none}li,ul{list-style:none;margin:0;padding:0}audio,img,video{max-width:100%;vertical-align:middle}nav{display:flex;justify-content:center;margin-bottom:20px}video{max-width:100%;vertical-align:middle}.plyr{border-radius:4px;box-shadow:0 2px 5px rgba(0,0,0,.2);margin:20px auto}.plyr.plyr--audio{max-width:480px}.plyr__video-wrapper::after{border:1px solid rgba(0,0,0,.15);border-radius:inherit;bottom:0;content:'';left:0;pointer-events:none;position:absolute;right:0;top:0;z-index:3}.plyr__cite{display:none;margin-top:20px}.plyr__cite .icon{margin-right:4px}.plyr--audio~ul .plyr__cite--audio,.plyr--video:not(.plyr--youtube):not(.plyr--vimeo)~ul .plyr__cite--video,.plyr--vimeo~ul .plyr__cite--vimeo,.plyr--youtube~ul .plyr__cite--youtube{display:block}:root{--plyr-color-main:#1aafff;--plyr-color-gunmetal:#2f343d;--plyr-color-fiord:#4f5b5f;--plyr-color-lynch:#6b7d85;--plyr-color-heather:#b7c5cd}:root{--plyr-tab-focus-default-color:#1aafff}:root{--plyr-captions-background:rgba(0, 0, 0, 0.8);--plyr-captions-text-color:#fff}:root{--plyr-control-icon-size:18px;--plyr-control-spacing:10px;--plyr-control-padding:7px;--plyr-control-radius:3px;--plyr-video-controls-bg:#000;--plyr-video-control-color:#fff;--plyr-video-control-color-hover:#fff;--plyr-video-control-bg-hover:var(--plyr-color-main);--plyr-audio-controls-bg:#fff;--plyr-audio-control-color:var(--plyr-color-fiord);--plyr-audio-control-color-hover:#fff;--plyr-audio-control-bg-hover:var(--plyr-color-main)}@keyframes plyr-progress{to{background-position:25px 0}}@keyframes plyr-popup{0%{opacity:.5;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}@keyframes plyr-fade-in{from{opacity:0}to{opacity:1}}.plyr{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;direction:ltr;font-family:inherit;font-variant-numeric:tabular-nums;font-weight:500;line-height:1.7;max-width:100%;min-width:200px;position:relative;text-shadow:none;transition:box-shadow .3s ease}.plyr audio,.plyr video{border-radius:inherit;height:auto;vertical-align:middle;width:100%}.plyr button{font:inherit;line-height:inherit;width:auto}.plyr:focus{outline:0}.plyr--full-ui{box-sizing:border-box}.plyr--full-ui *,.plyr--full-ui ::after,.plyr--full-ui ::before{box-sizing:inherit}.plyr--full-ui a,.plyr--full-ui button,.plyr--full-ui button.faux-link,.plyr--full-ui input,.plyr--full-ui label{touch-action:manipulation}.plyr__badge{background:#4f5b5f;border-radius:2px;color:#fff;font-size:9px;line-height:1;padding:3px 4px}.plyr--full-ui ::-webkit-media-text-track-container{display:none}.plyr__captions{animation:plyr-fade-in .3s ease;bottom:0;color:#fff;color:var(--plyr-captions-text-color);display:none;font-size:12px;left:0;padding:10px;position:absolute;text-align:center;transition:transform .4s ease-in-out;width:100%}.plyr__captions .plyr__caption{background:rgba(0,0,0,.8);background:var(--plyr-captions-background);border-radius:2px;-webkit-box-decoration-break:clone;box-decoration-break:clone;line-height:185%;padding:.2em .5em;white-space:pre-wrap}.plyr__captions .plyr__caption div{display:inline}.plyr__captions span:empty{display:none}@media (min-width:480px){.plyr__captions{font-size:13px;padding:20px}}@media (min-width:768px){.plyr__captions{font-size:18px}}.plyr--captions-active .plyr__captions{display:block}.plyr:not(.plyr--hide-controls) .plyr__controls:not(:empty)~.plyr__captions{transform:translateY(-40px)}.plyr__control{background:0 0;border:0;border-radius:3px;border-radius:var(--plyr-control-radius);color:inherit;cursor:pointer;flex-shrink:0;overflow:visible;padding:7px;padding:var(--plyr-control-padding);position:relative;transition:all .3s ease}.plyr__control svg{display:block;fill:currentColor;height:18px;height:var(--plyr-control-icon-size);pointer-events:none;width:18px;width:var(--plyr-control-icon-size)}.plyr__control:focus{outline:0}.plyr__control.plyr__tab-focus{outline-color:#1aafff;outline-color:var(--plyr-color-main);outline-offset:2px;outline-style:dotted;outline-width:3px}a.plyr__control,button.plyr__control.faux-link{text-decoration:none}a.plyr__control::after,a.plyr__control::before,button.plyr__control.faux-link::after,button.plyr__control.faux-link::before{display:none}.plyr__control.plyr__control--pressed .icon--not-pressed,.plyr__control.plyr__control--pressed .label--not-pressed,.plyr__control:not(.plyr__control--pressed) .icon--pressed,.plyr__control:not(.plyr__control--pressed) .label--pressed{display:none}.plyr--audio .plyr__control.plyr__tab-focus,.plyr--audio .plyr__control:hover,.plyr--audio .plyr__control[aria-expanded=true]{background:#1aafff;background:var(--plyr-color-main);color:#fff}.plyr--video .plyr__control svg{filter:drop-shadow(0 1px 1px rgba(0, 0, 0, .15))}.plyr--video .plyr__control.plyr__tab-focus,.plyr--video .plyr__control:hover,.plyr--video .plyr__control[aria-expanded=true]{background:#1aafff;background:var(--plyr-audio-control-bg-hover);color:#fff;color:var(--plyr-audio-control-color-hover)}.plyr__control--overlaid{background:#1aafff;background:var(--plyr-video-control-bg-hover);border:0;border-radius:100%;box-shadow:0 1px 1px rgba(0,0,0,.15);color:#fff;color:var(--plyr-video-control-color);display:none;left:50%;padding:15px;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:2}.plyr__control--overlaid svg{left:2px;position:relative}.plyr__control--overlaid:focus,.plyr__control--overlaid:hover{background:#1aafff;background:var(--plyr-video-control-bg-hover)}.plyr--playing .plyr__control--overlaid{opacity:0;visibility:hidden}.plyr--full-ui.plyr--video .plyr__control--overlaid{display:block}.plyr--full-ui ::-webkit-media-controls{display:none}.plyr__controls{align-items:center;display:flex;justify-content:flex-end;text-align:center}.plyr__controls .plyr__menu,.plyr__controls .plyr__progress,.plyr__controls .plyr__time,.plyr__controls .plyr__volume,.plyr__controls>.plyr__control{margin-left:5px}.plyr__controls .plyr__menu+.plyr__control,.plyr__controls .plyr__progress+.plyr__control,.plyr__controls>.plyr__control+.plyr__control,.plyr__controls>.plyr__control+.plyr__menu{margin-left:2px}.plyr__controls>.plyr__control:first-child,.plyr__controls>.plyr__control:first-child+[data-plyr=pause]{margin-left:0;margin-right:auto}.plyr__controls:empty{display:none}@media (min-width:480px){.plyr__controls .plyr__menu,.plyr__controls .plyr__progress,.plyr__controls .plyr__time,.plyr__controls .plyr__volume,.plyr__controls>.plyr__control{margin-left:10px}}.plyr--audio .plyr__controls{background:#fff;border-radius:inherit;color:#4f5b5f;color:var(--plyr-color-fiord);padding:10px}.plyr--video .plyr__controls{background:linear-gradient(rgba(0,0,0,0),rgba(0,0,0,.7));border-bottom-left-radius:inherit;border-bottom-right-radius:inherit;bottom:0;color:#fff;left:0;padding:20px 5px 5px;position:absolute;right:0;transition:opacity .4s ease-in-out,transform .4s ease-in-out;z-index:3}@media (min-width:480px){.plyr--video .plyr__controls{padding:35px 10px 10px}}.plyr--video.plyr--hide-controls .plyr__controls{opacity:0;pointer-events:none;transform:translateY(100%)}.plyr [data-plyr=airplay],.plyr [data-plyr=captions],.plyr [data-plyr=fullscreen],.plyr [data-plyr=pip]{display:none}.plyr--airplay-supported [data-plyr=airplay],.plyr--captions-enabled [data-plyr=captions],.plyr--fullscreen-enabled [data-plyr=fullscreen],.plyr--pip-supported [data-plyr=pip]{display:inline-block}.plyr__video-embed{height:0;padding-bottom:56.25%;position:relative}.plyr__video-embed iframe{border:0;height:100%;left:0;position:absolute;top:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:100%}.plyr--full-ui .plyr__video-embed>.plyr__video-embed__container{padding-bottom:240%;position:relative;transform:translateY(-38.28125%)}.plyr__menu{display:flex;position:relative}.plyr__menu .plyr__control svg{transition:transform .3s ease}.plyr__menu .plyr__control[aria-expanded=true] svg{transform:rotate(90deg)}.plyr__menu .plyr__control[aria-expanded=true] .plyr__tooltip{display:none}.plyr__menu__container{animation:plyr-popup .2s ease;background:rgba(255,255,255,.9);border-radius:4px;bottom:100%;box-shadow:0 1px 2px rgba(0,0,0,.15);color:#4f5b5f;font-size:13px;margin-bottom:10px;position:absolute;right:-3px;text-align:left;white-space:nowrap;z-index:3}.plyr__menu__container>div{overflow:hidden;transition:height .35s cubic-bezier(.4,0,.2,1),width .35s cubic-bezier(.4,0,.2,1)}.plyr__menu__container::after{border:4px solid transparent;border-top-color:rgba(255,255,255,.9);content:'';height:0;position:absolute;right:15px;top:100%;width:0}.plyr__menu__container [role=menu]{padding:7px}.plyr__menu__container [role=menuitem],.plyr__menu__container [role=menuitemradio]{margin-top:2px}.plyr__menu__container [role=menuitem]:first-child,.plyr__menu__container [role=menuitemradio]:first-child{margin-top:0}.plyr__menu__container .plyr__control{align-items:center;color:#4f5b5f;display:flex;font-size:13px;padding:4px 11px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:100%}.plyr__menu__container .plyr__control>span{align-items:inherit;display:flex;width:100%}.plyr__menu__container .plyr__control::after{border:4px solid transparent;content:'';position:absolute;top:50%;transform:translateY(-50%)}.plyr__menu__container .plyr__control--forward{padding-right:28px}.plyr__menu__container .plyr__control--forward::after{border-left-color:rgba(79,91,95,.8);right:5px}.plyr__menu__container .plyr__control--forward.plyr__tab-focus::after,.plyr__menu__container .plyr__control--forward:hover::after{border-left-color:currentColor}.plyr__menu__container .plyr__control--back{font-weight:500;margin:7px;margin-bottom:3px;padding-left:28px;position:relative;width:calc(100% - 14px)}.plyr__menu__container .plyr__control--back::after{border-right-color:rgba(79,91,95,.8);left:7px}.plyr__menu__container .plyr__control--back::before{background:#b7c5cd;box-shadow:0 1px 0 #fff;content:'';height:1px;left:0;margin-top:4px;overflow:hidden;position:absolute;right:0;top:100%}.plyr__menu__container .plyr__control--back.plyr__tab-focus::after,.plyr__menu__container .plyr__control--back:hover::after{border-right-color:currentColor}.plyr__menu__container .plyr__control[role=menuitemradio]{padding-left:7px}.plyr__menu__container .plyr__control[role=menuitemradio]::after,.plyr__menu__container .plyr__control[role=menuitemradio]::before{border-radius:100%}.plyr__menu__container .plyr__control[role=menuitemradio]::before{background:rgba(0,0,0,.1);content:'';display:block;flex-shrink:0;height:16px;margin-right:10px;transition:all .3s ease;width:16px}.plyr__menu__container .plyr__control[role=menuitemradio]::after{background:#fff;border:0;height:6px;left:12px;opacity:0;top:50%;transform:translateY(-50%) scale(0);transition:transform .3s ease,opacity .3s ease;width:6px}.plyr__menu__container .plyr__control[role=menuitemradio][aria-checked=true]::before{background:#1aafff}.plyr__menu__container .plyr__control[role=menuitemradio][aria-checked=true]::after{opacity:1;transform:translateY(-50%) scale(1)}.plyr__menu__container .plyr__control[role=menuitemradio].plyr__tab-focus::before,.plyr__menu__container .plyr__control[role=menuitemradio]:hover::before{background:rgba(0,0,0,.1)}.plyr__menu__container .plyr__menu__value{align-items:center;display:flex;margin-left:auto;margin-right:-5px;overflow:hidden;padding-left:25px;pointer-events:none}.plyr--full-ui input[type=range]{-webkit-appearance:none;background:0 0;border:0;border-radius:28px;color:#1aafff;color:var(--plyr-color-main);display:block;height:20px;margin:0;padding:0;transition:box-shadow .3s ease;width:100%}.plyr--full-ui input[type=range]::-webkit-slider-runnable-track{background:0 0;border:0;border-radius:2px;height:4px;transition:box-shadow .3s ease;-webkit-user-select:none;user-select:none;background-image:linear-gradient(to right,currentColor 0,transparent 0);background-image:linear-gradient(to right,currentColor var(--value,0),transparent var(--value,0))}.plyr--full-ui input[type=range]::-webkit-slider-thumb{background:#fff;border:0;border-radius:100%;box-shadow:0 1px 1px rgba(0,0,0,.15),0 0 0 1px rgba(47,52,61,.2);height:14px;position:relative;transition:all .2s ease;width:14px;-webkit-appearance:none;margin-top:-5px}.plyr--full-ui input[type=range]::-moz-range-track{background:0 0;border:0;border-radius:2px;height:4px;transition:box-shadow .3s ease;-moz-user-select:none;user-select:none}.plyr--full-ui input[type=range]::-moz-range-thumb{background:#fff;border:0;border-radius:100%;box-shadow:0 1px 1px rgba(0,0,0,.15),0 0 0 1px rgba(47,52,61,.2);height:14px;position:relative;transition:all .2s ease;width:14px}.plyr--full-ui input[type=range]::-moz-range-progress{background:currentColor;border-radius:2px;height:4px}.plyr--full-ui input[type=range]::-ms-track{background:0 0;border:0;border-radius:2px;height:4px;transition:box-shadow .3s ease;-ms-user-select:none;user-select:none;color:transparent}.plyr--full-ui input[type=range]::-ms-fill-upper{background:0 0;border:0;border-radius:2px;height:4px;transition:box-shadow .3s ease;-ms-user-select:none;user-select:none}.plyr--full-ui input[type=range]::-ms-fill-lower{background:0 0;border:0;border-radius:2px;height:4px;transition:box-shadow .3s ease;-ms-user-select:none;user-select:none;background:currentColor}.plyr--full-ui input[type=range]::-ms-thumb{background:#fff;border:0;border-radius:100%;box-shadow:0 1px 1px rgba(0,0,0,.15),0 0 0 1px rgba(47,52,61,.2);height:14px;position:relative;transition:all .2s ease;width:14px;margin-top:0}.plyr--full-ui input[type=range]::-ms-tooltip{display:none}.plyr--full-ui input[type=range]:focus{outline:0}.plyr--full-ui input[type=range]::-moz-focus-outer{border:0}.plyr--full-ui input[type=range].plyr__tab-focus::-webkit-slider-runnable-track{outline-color:#1aafff;outline-color:var(--plyr-color-main);outline-offset:2px;outline-style:dotted;outline-width:3px}.plyr--full-ui input[type=range].plyr__tab-focus::-moz-range-track{outline-color:#1aafff;outline-color:var(--plyr-color-main);outline-offset:2px;outline-style:dotted;outline-width:3px}.plyr--full-ui input[type=range].plyr__tab-focus::-ms-track{outline-color:#1aafff;outline-color:var(--plyr-color-main);outline-offset:2px;outline-style:dotted;outline-width:3px}.plyr--full-ui.plyr--video input[type=range]::-webkit-slider-runnable-track{background-color:rgba(255,255,255,.25)}.plyr--full-ui.plyr--video input[type=range]::-moz-range-track{background-color:rgba(255,255,255,.25)}.plyr--full-ui.plyr--video input[type=range]::-ms-track{background-color:rgba(255,255,255,.25)}.plyr--full-ui.plyr--video input[type=range]:active::-webkit-slider-thumb{box-shadow:0 1px 1px rgba(0,0,0,.15),0 0 0 1px rgba(47,52,61,.2),0 0 0 3px rgba(255,255,255,.5)}.plyr--full-ui.plyr--video input[type=range]:active::-moz-range-thumb{box-shadow:0 1px 1px rgba(0,0,0,.15),0 0 0 1px rgba(47,52,61,.2),0 0 0 3px rgba(255,255,255,.5)}.plyr--full-ui.plyr--video input[type=range]:active::-ms-thumb{box-shadow:0 1px 1px rgba(0,0,0,.15),0 0 0 1px rgba(47,52,61,.2),0 0 0 3px rgba(255,255,255,.5)}.plyr--full-ui.plyr--audio input[type=range]::-webkit-slider-runnable-track{background-color:rgba(183,197,205,.66)}.plyr--full-ui.plyr--audio input[type=range]::-moz-range-track{background-color:rgba(183,197,205,.66)}.plyr--full-ui.plyr--audio input[type=range]::-ms-track{background-color:rgba(183,197,205,.66)}.plyr--full-ui.plyr--audio input[type=range]:active::-webkit-slider-thumb{box-shadow:0 1px 1px rgba(0,0,0,.15),0 0 0 1px rgba(47,52,61,.2),0 0 0 3px rgba(0,0,0,.1)}.plyr--full-ui.plyr--audio input[type=range]:active::-moz-range-thumb{box-shadow:0 1px 1px rgba(0,0,0,.15),0 0 0 1px rgba(47,52,61,.2),0 0 0 3px rgba(0,0,0,.1)}.plyr--full-ui.plyr--audio input[type=range]:active::-ms-thumb{box-shadow:0 1px 1px rgba(0,0,0,.15),0 0 0 1px rgba(47,52,61,.2),0 0 0 3px rgba(0,0,0,.1)}.plyr__poster{background-color:#000;background-position:50% 50%;background-repeat:no-repeat;background-size:contain;height:100%;left:0;opacity:0;position:absolute;top:0;transition:opacity .2s ease;width:100%;z-index:1}.plyr--stopped.plyr__poster-enabled .plyr__poster{opacity:1}.plyr__time{font-size:11px}.plyr__time+.plyr__time::before{content:'\2044';margin-right:10px}@media (max-width:767px){.plyr__time+.plyr__time{display:none}}.plyr--video .plyr__time{text-shadow:0 1px 1px rgba(0,0,0,.15)}.plyr__tooltip{background:rgba(255,255,255,.9);border-radius:3px;bottom:100%;box-shadow:0 1px 2px rgba(0,0,0,.15);color:#4f5b5f;font-size:12px;font-weight:500;left:50%;line-height:1.3;margin-bottom:10px;opacity:0;padding:5px 7.5px;pointer-events:none;position:absolute;transform:translate(-50%,10px) scale(.8);transform-origin:50% 100%;transition:transform .2s .1s ease,opacity .2s .1s ease;white-space:nowrap;z-index:2}.plyr__tooltip::before{border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid rgba(255,255,255,.9);bottom:-4px;content:'';height:0;left:50%;position:absolute;transform:translateX(-50%);width:0;z-index:2}.plyr .plyr__control.plyr__tab-focus .plyr__tooltip,.plyr .plyr__control:hover .plyr__tooltip,.plyr__tooltip--visible{opacity:1;transform:translate(-50%,0) scale(1)}.plyr .plyr__control:hover .plyr__tooltip{z-index:3}.plyr__controls>.plyr__control:first-child .plyr__tooltip,.plyr__controls>.plyr__control:first-child+.plyr__control .plyr__tooltip{left:0;transform:translate(0,10px) scale(.8);transform-origin:0 100%}.plyr__controls>.plyr__control:first-child .plyr__tooltip::before,.plyr__controls>.plyr__control:first-child+.plyr__control .plyr__tooltip::before{left:16px}.plyr__controls>.plyr__control:last-child .plyr__tooltip{left:auto;right:0;transform:translate(0,10px) scale(.8);transform-origin:100% 100%}.plyr__controls>.plyr__control:last-child .plyr__tooltip::before{left:auto;right:16px;transform:translateX(50%)}.plyr__controls>.plyr__control:first-child .plyr__tooltip--visible,.plyr__controls>.plyr__control:first-child+.plyr__control .plyr__tooltip--visible,.plyr__controls>.plyr__control:first-child+.plyr__control.plyr__tab-focus .plyr__tooltip,.plyr__controls>.plyr__control:first-child+.plyr__control:hover .plyr__tooltip,.plyr__controls>.plyr__control:first-child.plyr__tab-focus .plyr__tooltip,.plyr__controls>.plyr__control:first-child:hover .plyr__tooltip,.plyr__controls>.plyr__control:last-child .plyr__tooltip--visible,.plyr__controls>.plyr__control:last-child.plyr__tab-focus .plyr__tooltip,.plyr__controls>.plyr__control:last-child:hover .plyr__tooltip{transform:translate(0,0) scale(1)}.plyr--video{background:#000;overflow:hidden}.plyr--video.plyr--menu-open{overflow:visible}.plyr__video-wrapper{background:#000;border-radius:inherit;overflow:hidden;position:relative;z-index:0}.plyr__progress{flex:1;left:7px;margin-right:14px;position:relative}.plyr__progress input[type=range],.plyr__progress__buffer{margin-left:-7px;margin-right:-7px;width:calc(100% + 14px)}.plyr__progress input[type=range]{position:relative;z-index:2}.plyr__progress .plyr__tooltip{font-size:11px;left:0}.plyr__progress__buffer{-webkit-appearance:none;background:0 0;border:0;border-radius:100px;height:4px;left:0;margin-top:-2px;padding:0;position:absolute;top:50%}.plyr__progress__buffer::-webkit-progress-bar{background:0 0;transition:width .2s ease}.plyr__progress__buffer::-webkit-progress-value{background:currentColor;border-radius:100px;min-width:4px}.plyr__progress__buffer::-moz-progress-bar{background:currentColor;border-radius:100px;min-width:4px;transition:width .2s ease}.plyr__progress__buffer::-ms-fill{border-radius:100px;transition:width .2s ease}.plyr--video .plyr__progress__buffer{box-shadow:0 1px 1px rgba(0,0,0,.15);color:rgba(255,255,255,.25)}.plyr--audio .plyr__progress__buffer{color:rgba(183,197,205,.66)}.plyr--loading .plyr__progress__buffer{animation:plyr-progress 1s linear infinite;background-image:linear-gradient(-45deg,rgba(47,52,61,.6) 25%,transparent 25%,transparent 50%,rgba(47,52,61,.6) 50%,rgba(47,52,61,.6) 75%,transparent 75%,transparent);background-repeat:repeat-x;background-size:25px 25px;color:transparent}.plyr--video.plyr--loading .plyr__progress__buffer{background-color:rgba(255,255,255,.25)}.plyr--audio.plyr--loading .plyr__progress__buffer{background-color:rgba(183,197,205,.66)}.plyr__volume{align-items:center;display:flex;flex:1;position:relative}.plyr__volume input[type=range]{margin-left:5px;position:relative;z-index:2}@media (min-width:480px){.plyr__volume{max-width:90px}}@media (min-width:768px){.plyr__volume{max-width:110px}}.plyr--is-ios .plyr__volume{display:none!important}.plyr--is-ios.plyr--vimeo [data-plyr=mute]{display:none!important}.plyr:-webkit-full-screen{background:#000;border-radius:0!important;height:100%;margin:0;width:100%}.plyr:-moz-full-screen{background:#000;border-radius:0!important;height:100%;margin:0;width:100%}.plyr:-ms-fullscreen{background:#000;border-radius:0!important;height:100%;margin:0;width:100%}.plyr:fullscreen{background:#000;border-radius:0!important;height:100%;margin:0;width:100%}.plyr:-webkit-full-screen video{height:100%}.plyr:-moz-full-screen video{height:100%}.plyr:-ms-fullscreen video{height:100%}.plyr:fullscreen video{height:100%}.plyr:-webkit-full-screen .plyr__video-wrapper{height:100%;width:100%}.plyr:-moz-full-screen .plyr__video-wrapper{height:100%;width:100%}.plyr:-ms-fullscreen .plyr__video-wrapper{height:100%;width:100%}.plyr:fullscreen .plyr__video-wrapper{height:100%;width:100%}.plyr:-webkit-full-screen .plyr__video-embed{overflow:visible}.plyr:-moz-full-screen .plyr__video-embed{overflow:visible}.plyr:-ms-fullscreen .plyr__video-embed{overflow:visible}.plyr:fullscreen .plyr__video-embed{overflow:visible}.plyr:-webkit-full-screen.plyr--vimeo .plyr__video-wrapper{height:0;top:50%;transform:translateY(-50%)}.plyr:-moz-full-screen.plyr--vimeo .plyr__video-wrapper{height:0;top:50%;transform:translateY(-50%)}.plyr:-ms-fullscreen.plyr--vimeo .plyr__video-wrapper{height:0;top:50%;transform:translateY(-50%)}.plyr:fullscreen.plyr--vimeo .plyr__video-wrapper{height:0;top:50%;transform:translateY(-50%)}.plyr:-webkit-full-screen .plyr__control .icon--exit-fullscreen{display:block}.plyr:-moz-full-screen .plyr__control .icon--exit-fullscreen{display:block}.plyr:-ms-fullscreen .plyr__control .icon--exit-fullscreen{display:block}.plyr:fullscreen .plyr__control .icon--exit-fullscreen{display:block}.plyr:-webkit-full-screen .plyr__control .icon--exit-fullscreen+svg{display:none}.plyr:-moz-full-screen .plyr__control .icon--exit-fullscreen+svg{display:none}.plyr:-ms-fullscreen .plyr__control .icon--exit-fullscreen+svg{display:none}.plyr:fullscreen .plyr__control .icon--exit-fullscreen+svg{display:none}.plyr:-webkit-full-screen.plyr--hide-controls{cursor:none}.plyr:-moz-full-screen.plyr--hide-controls{cursor:none}.plyr:-ms-fullscreen.plyr--hide-controls{cursor:none}.plyr:fullscreen.plyr--hide-controls{cursor:none}@media (min-width:1024px){.plyr:-webkit-full-screen .plyr__captions{font-size:21px}.plyr:-moz-full-screen .plyr__captions{font-size:21px}.plyr:-ms-fullscreen .plyr__captions{font-size:21px}.plyr:fullscreen .plyr__captions{font-size:21px}}.plyr:-webkit-full-screen{background:#000;border-radius:0!important;height:100%;margin:0;width:100%}.plyr:-webkit-full-screen video{height:100%}.plyr:-webkit-full-screen .plyr__video-wrapper{height:100%;width:100%}.plyr:-webkit-full-screen .plyr__video-embed{overflow:visible}.plyr:-webkit-full-screen.plyr--vimeo .plyr__video-wrapper{height:0;top:50%;transform:translateY(-50%)}.plyr:-webkit-full-screen .plyr__control .icon--exit-fullscreen{display:block}.plyr:-webkit-full-screen .plyr__control .icon--exit-fullscreen+svg{display:none}.plyr:-webkit-full-screen.plyr--hide-controls{cursor:none}@media (min-width:1024px){.plyr:-webkit-full-screen .plyr__captions{font-size:21px}}.plyr:-moz-full-screen{background:#000;border-radius:0!important;height:100%;margin:0;width:100%}.plyr:-moz-full-screen video{height:100%}.plyr:-moz-full-screen .plyr__video-wrapper{height:100%;width:100%}.plyr:-moz-full-screen .plyr__video-embed{overflow:visible}.plyr:-moz-full-screen.plyr--vimeo .plyr__video-wrapper{height:0;top:50%;transform:translateY(-50%)}.plyr:-moz-full-screen .plyr__control .icon--exit-fullscreen{display:block}.plyr:-moz-full-screen .plyr__control .icon--exit-fullscreen+svg{display:none}.plyr:-moz-full-screen.plyr--hide-controls{cursor:none}@media (min-width:1024px){.plyr:-moz-full-screen .plyr__captions{font-size:21px}}.plyr:-ms-fullscreen{background:#000;border-radius:0!important;height:100%;margin:0;width:100%}.plyr:-ms-fullscreen video{height:100%}.plyr:-ms-fullscreen .plyr__video-wrapper{height:100%;width:100%}.plyr:-ms-fullscreen .plyr__video-embed{overflow:visible}.plyr:-ms-fullscreen.plyr--vimeo .plyr__video-wrapper{height:0;top:50%;transform:translateY(-50%)}.plyr:-ms-fullscreen .plyr__control .icon--exit-fullscreen{display:block}.plyr:-ms-fullscreen .plyr__control .icon--exit-fullscreen+svg{display:none}.plyr:-ms-fullscreen.plyr--hide-controls{cursor:none}@media (min-width:1024px){.plyr:-ms-fullscreen .plyr__captions{font-size:21px}}.plyr--fullscreen-fallback{background:#000;border-radius:0!important;height:100%;margin:0;width:100%;bottom:0;left:0;position:fixed;right:0;top:0;z-index:10000000}.plyr--fullscreen-fallback video{height:100%}.plyr--fullscreen-fallback .plyr__video-wrapper{height:100%;width:100%}.plyr--fullscreen-fallback .plyr__video-embed{overflow:visible}.plyr--fullscreen-fallback.plyr--vimeo .plyr__video-wrapper{height:0;top:50%;transform:translateY(-50%)}.plyr--fullscreen-fallback .plyr__control .icon--exit-fullscreen{display:block}.plyr--fullscreen-fallback .plyr__control .icon--exit-fullscreen+svg{display:none}.plyr--fullscreen-fallback.plyr--hide-controls{cursor:none}@media (min-width:1024px){.plyr--fullscreen-fallback .plyr__captions{font-size:21px}}.plyr__ads{border-radius:inherit;bottom:0;cursor:pointer;left:0;overflow:hidden;position:absolute;right:0;top:0;z-index:-1}.plyr__ads>div,.plyr__ads>div iframe{height:100%;position:absolute;width:100%}.plyr__ads::after{background:rgba(47,52,61,.8);border-radius:2px;bottom:10px;color:#fff;content:attr(data-badge-text);font-size:11px;padding:2px 6px;pointer-events:none;position:absolute;right:10px;z-index:3}.plyr__ads::after:empty{display:none}.plyr__cues{background:currentColor;display:block;height:4px;left:0;margin:-2px 0 0;opacity:.8;position:absolute;top:50%;width:3px;z-index:3}.plyr--no-transition{transition:none!important}.plyr__sr-only{clip:rect(1px,1px,1px,1px);overflow:hidden;border:0!important;height:1px!important;padding:0!important;position:absolute!important;width:1px!important}.plyr [hidden]{display:none!important}.no-border{border:0}[hidden]{display:none}.sr-only{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;opacity:.001;overflow:hidden;padding:0;position:absolute;width:1px} \ No newline at end of file +@font-face{font-display:swap;font-family:Gordita;font-style:normal;font-weight:300;src:url(https://cdn.plyr.io/static/fonts/gordita-light.woff2) format("woff2"),url(https://cdn.plyr.io/static/fonts/gordita-light.woff) format("woff")}@font-face{font-display:swap;font-family:Gordita;font-style:normal;font-weight:400;src:url(https://cdn.plyr.io/static/fonts/gordita-regular.woff2) format("woff2"),url(https://cdn.plyr.io/static/fonts/gordita-regular.woff) format("woff")}@font-face{font-display:swap;font-family:Gordita;font-style:normal;font-weight:500;src:url(https://cdn.plyr.io/static/fonts/gordita-medium.woff2) format("woff2"),url(https://cdn.plyr.io/static/fonts/gordita-medium.woff) format("woff")}@font-face{font-display:swap;font-family:Gordita;font-style:normal;font-weight:600;src:url(https://cdn.plyr.io/static/fonts/gordita-bold.woff2) format("woff2"),url(https://cdn.plyr.io/static/fonts/gordita-bold.woff) format("woff")}@font-face{font-display:swap;font-family:Gordita;font-style:normal;font-weight:900;src:url(https://cdn.plyr.io/static/fonts/gordita-black.woff2) format("woff2"),url(https://cdn.plyr.io/static/fonts/gordita-black.woff) format("woff")}@keyframes fadein{0%{opacity:0}100%{opacity:1}}/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a,button.faux-link{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:border-box}body,html{display:flex;width:100%}html{background:linear-gradient(to left top,#4dc1ff,#0074b3);background-attachment:fixed;height:100%}body{align-items:center;display:flex;flex-direction:column;min-height:100%}.grid{flex:1;overflow:auto}main{margin:auto;padding-bottom:1px;text-align:center}aside{align-items:center;background:#fff;color:#55646b;display:flex;flex-shrink:0;justify-content:center;padding:15px;position:relative;text-align:center;text-shadow:none;width:100%}aside .icon{fill:#4baaf4;margin-right:10px}aside p{margin:0}aside a,aside button.faux-link{color:#4baaf4}aside a.tab-focus,aside button.tab-focus.faux-link{box-shadow:0 0 0 3px rgba(75,170,244,.35);outline:0}.grid{margin:0 auto;padding:20px}@media only screen and (min-width:768px){.grid{align-items:center;display:flex;max-width:1260px;width:100%}.grid>*{flex:1}}html{font-size:100%}body{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-size:15px;font-size:.9375rem;color:#fff;font-family:Gordita,Avenir,"Helvetica Neue",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-weight:500;line-height:1.75;text-shadow:0 1px 1px rgba(0,0,0,.15)}button,input,select,textarea{font:inherit}p,small{margin:0 0 20px}small{font-size:13px;font-size:.8125rem;display:block}h1{font-size:64px;font-size:4rem;font-weight:600;letter-spacing:-.025em;line-height:1.2;margin:0 0 20px}.button,.button__count{align-items:center;background:#fff;border:0;border-radius:4px;box-shadow:0 1px 1px rgba(0,0,0,.1);color:#55646b;display:inline-flex;padding:15px;position:relative;text-shadow:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle}.button{font-weight:600;padding-left:20px;padding-right:20px;transition:all .2s ease}.button:focus,.button:hover{color:#343f4a}.button:focus::after,.button:hover::after{display:none}.button:hover{box-shadow:0 2px 2px rgba(0,0,0,.1);transform:translateY(-1px)}.button:focus{outline:0}.button.tab-focus{box-shadow:0 0 0 3px rgba(255,255,255,.35);outline:0}.button:active{transform:translateY(1px)}.button--with-count{display:inline-flex}.button--with-count .button .icon{flex-shrink:0}.button__count{animation:fadein .2s ease;margin-left:10px}.button__count::before{border:5px solid transparent;border-left-width:0;border-right-color:#fff;content:'';height:0;position:absolute;right:100%;top:50%;transform:translateY(-50%);width:0}header{padding-bottom:20px;text-align:center}header .call-to-action{margin-top:30px}@media only screen and (min-width:768px){header{margin-right:60px;max-width:360px;padding-bottom:40px;text-align:left}}.icon{fill:currentColor;height:16px;vertical-align:-3px;width:16px}a svg,button svg,button.faux-link svg,label svg{pointer-events:none}.btn .icon,a .icon,button.faux-link .icon{margin-right:6px}button.faux-link{background:0 0;border:0;border-radius:0;cursor:pointer;font:inherit;line-height:1.75;margin:0;padding:0;position:relative;text-align:inherit;text-shadow:inherit;-moz-user-select:text;vertical-align:baseline;width:auto}a,button.faux-link{border-bottom:1px dotted currentColor;color:#fff;font-weight:600;position:relative;text-decoration:none;transition:all .2s ease}a::after,button.faux-link::after{background:currentColor;content:'';height:1px;left:50%;position:absolute;top:100%;transform:translateX(-50%);transition:width .2s ease;width:0}a:focus,a:hover,button.faux-link:focus,button.faux-link:hover{border-bottom-color:transparent;outline:0}a:focus::after,a:hover::after,button.faux-link:focus::after,button.faux-link:hover::after{width:100%}a.tab-focus,button.tab-focus.faux-link{box-shadow:0 0 0 3px rgba(255,255,255,.35);outline:0}a.no-border::after,button.no-border.faux-link::after{display:none}li,ul{list-style:none;margin:0;padding:0}audio,img,video{max-width:100%;vertical-align:middle}nav{display:flex;justify-content:center;margin-bottom:20px}video{max-width:100%;vertical-align:middle}.plyr{border-radius:4px;box-shadow:0 2px 5px rgba(0,0,0,.2);margin:20px auto}.plyr.plyr--audio{max-width:480px}.plyr__video-wrapper::after{border:1px solid rgba(0,0,0,.15);border-radius:inherit;bottom:0;content:'';left:0;pointer-events:none;position:absolute;right:0;top:0;z-index:3}.plyr__cite{display:none;margin-top:20px}.plyr__cite .icon{margin-right:4px}.plyr--audio~ul .plyr__cite--audio,.plyr--video:not(.plyr--youtube):not(.plyr--vimeo)~ul .plyr__cite--video,.plyr--vimeo~ul .plyr__cite--vimeo,.plyr--youtube~ul .plyr__cite--youtube{display:block}:root{--plyr-color-main:#1aafff;--plyr-color-gunmetal:#2f343d;--plyr-color-fiord:#4f5b5f;--plyr-color-lynch:#6b7d85;--plyr-color-heather:#b7c5cd}:root{--plyr-tab-focus-default-color:#1aafff}:root{--plyr-captions-background:rgba(0, 0, 0, 0.8);--plyr-captions-text-color:#fff}:root{--plyr-control-icon-size:18px;--plyr-control-spacing:10px;--plyr-control-padding:7px;--plyr-control-radius:3px;--plyr-video-controls-bg:#000;--plyr-video-control-color:#fff;--plyr-video-control-color-hover:#fff;--plyr-video-control-bg-hover:var(--plyr-color-main);--plyr-audio-controls-bg:#fff;--plyr-audio-control-color:var(--plyr-color-fiord);--plyr-audio-control-color-hover:#fff;--plyr-audio-control-bg-hover:var(--plyr-color-main)}@keyframes plyr-progress{to{background-position:25px 0}}@keyframes plyr-popup{0%{opacity:.5;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}@keyframes plyr-fade-in{from{opacity:0}to{opacity:1}}.plyr{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;direction:ltr;font-family:inherit;font-variant-numeric:tabular-nums;font-weight:500;line-height:1.7;max-width:100%;min-width:200px;position:relative;text-shadow:none;transition:box-shadow .3s ease}.plyr audio,.plyr video{border-radius:inherit;height:auto;vertical-align:middle;width:100%}.plyr button{font:inherit;line-height:inherit;width:auto}.plyr:focus{outline:0}.plyr--full-ui{box-sizing:border-box}.plyr--full-ui *,.plyr--full-ui ::after,.plyr--full-ui ::before{box-sizing:inherit}.plyr--full-ui a,.plyr--full-ui button,.plyr--full-ui button.faux-link,.plyr--full-ui input,.plyr--full-ui label{touch-action:manipulation}.plyr__badge{background:#4f5b5f;border-radius:2px;color:#fff;font-size:9px;line-height:1;padding:3px 4px}.plyr--full-ui ::-webkit-media-text-track-container{display:none}.plyr__captions{animation:plyr-fade-in .3s ease;bottom:0;color:#fff;color:var(--plyr-captions-text-color);display:none;font-size:12px;left:0;padding:10px;position:absolute;text-align:center;transition:transform .4s ease-in-out;width:100%}.plyr__captions .plyr__caption{background:rgba(0,0,0,.8);background:var(--plyr-captions-background);border-radius:2px;-webkit-box-decoration-break:clone;box-decoration-break:clone;line-height:185%;padding:.2em .5em;white-space:pre-wrap}.plyr__captions .plyr__caption div{display:inline}.plyr__captions span:empty{display:none}@media (min-width:480px){.plyr__captions{font-size:13px;padding:20px}}@media (min-width:768px){.plyr__captions{font-size:18px}}.plyr--captions-active .plyr__captions{display:block}.plyr:not(.plyr--hide-controls) .plyr__controls:not(:empty)~.plyr__captions{transform:translateY(-40px)}.plyr__control{background:0 0;border:0;border-radius:3px;border-radius:var(--plyr-control-radius);color:inherit;cursor:pointer;flex-shrink:0;overflow:visible;padding:7px;padding:var(--plyr-control-padding);position:relative;transition:all .3s ease}.plyr__control svg{display:block;fill:currentColor;height:18px;height:var(--plyr-control-icon-size);pointer-events:none;width:18px;width:var(--plyr-control-icon-size)}.plyr__control:focus{outline:0}.plyr__control.plyr__tab-focus{outline-color:#1aafff;outline-color:var(--plyr-color-main);outline-offset:2px;outline-style:dotted;outline-width:3px}a.plyr__control,button.plyr__control.faux-link{text-decoration:none}a.plyr__control::after,a.plyr__control::before,button.plyr__control.faux-link::after,button.plyr__control.faux-link::before{display:none}.plyr__control.plyr__control--pressed .icon--not-pressed,.plyr__control.plyr__control--pressed .label--not-pressed,.plyr__control:not(.plyr__control--pressed) .icon--pressed,.plyr__control:not(.plyr__control--pressed) .label--pressed{display:none}.plyr--audio .plyr__control.plyr__tab-focus,.plyr--audio .plyr__control:hover,.plyr--audio .plyr__control[aria-expanded=true]{background:#1aafff;background:var(--plyr-color-main);color:#fff}.plyr--video .plyr__control svg{filter:drop-shadow(0 1px 1px rgba(0, 0, 0, .15))}.plyr--video .plyr__control.plyr__tab-focus,.plyr--video .plyr__control:hover,.plyr--video .plyr__control[aria-expanded=true]{background:#1aafff;background:var(--plyr-video-control-bg-hover);color:#fff;color:var(--plyr-video-control-color-hover)}.plyr__control--overlaid{background:#1aafff;background:var(--plyr-video-control-bg-hover);border:0;border-radius:100%;box-shadow:0 1px 1px rgba(0,0,0,.15);color:#fff;color:var(--plyr-video-control-color);display:none;left:50%;padding:15px;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:2}.plyr__control--overlaid svg{left:2px;position:relative}.plyr__control--overlaid:focus,.plyr__control--overlaid:hover{background:#1aafff;background:var(--plyr-video-control-bg-hover)}.plyr--playing .plyr__control--overlaid{opacity:0;visibility:hidden}.plyr--full-ui.plyr--video .plyr__control--overlaid{display:block}.plyr--full-ui ::-webkit-media-controls{display:none}.plyr__controls{align-items:center;display:flex;justify-content:flex-end;text-align:center}.plyr__controls .plyr__menu,.plyr__controls .plyr__progress,.plyr__controls .plyr__time,.plyr__controls .plyr__volume,.plyr__controls>.plyr__control{margin-left:5px}.plyr__controls .plyr__menu+.plyr__control,.plyr__controls .plyr__progress+.plyr__control,.plyr__controls>.plyr__control+.plyr__control,.plyr__controls>.plyr__control+.plyr__menu{margin-left:2px}.plyr__controls>.plyr__control:first-child,.plyr__controls>.plyr__control:first-child+[data-plyr=pause]{margin-left:0;margin-right:auto}.plyr__controls:empty{display:none}@media (min-width:480px){.plyr__controls .plyr__menu,.plyr__controls .plyr__progress,.plyr__controls .plyr__time,.plyr__controls .plyr__volume,.plyr__controls>.plyr__control{margin-left:10px}}.plyr--audio .plyr__controls{background:#fff;border-radius:inherit;color:#4f5b5f;color:var(--plyr-color-fiord);padding:10px}.plyr--video .plyr__controls{background:linear-gradient(rgba(0,0,0,0),rgba(0,0,0,.7));border-bottom-left-radius:inherit;border-bottom-right-radius:inherit;bottom:0;color:#fff;left:0;padding:20px 5px 5px;position:absolute;right:0;transition:opacity .4s ease-in-out,transform .4s ease-in-out;z-index:3}@media (min-width:480px){.plyr--video .plyr__controls{padding:35px 10px 10px}}.plyr--video.plyr--hide-controls .plyr__controls{opacity:0;pointer-events:none;transform:translateY(100%)}.plyr [data-plyr=airplay],.plyr [data-plyr=captions],.plyr [data-plyr=fullscreen],.plyr [data-plyr=pip]{display:none}.plyr--airplay-supported [data-plyr=airplay],.plyr--captions-enabled [data-plyr=captions],.plyr--fullscreen-enabled [data-plyr=fullscreen],.plyr--pip-supported [data-plyr=pip]{display:inline-block}.plyr__video-embed{height:0;padding-bottom:56.25%;position:relative}.plyr__video-embed iframe{border:0;height:100%;left:0;position:absolute;top:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:100%}.plyr--full-ui .plyr__video-embed>.plyr__video-embed__container{padding-bottom:240%;position:relative;transform:translateY(-38.28125%)}.plyr__menu{display:flex;position:relative}.plyr__menu .plyr__control svg{transition:transform .3s ease}.plyr__menu .plyr__control[aria-expanded=true] svg{transform:rotate(90deg)}.plyr__menu .plyr__control[aria-expanded=true] .plyr__tooltip{display:none}.plyr__menu__container{animation:plyr-popup .2s ease;background:rgba(255,255,255,.9);border-radius:4px;bottom:100%;box-shadow:0 1px 2px rgba(0,0,0,.15);color:#4f5b5f;font-size:13px;margin-bottom:10px;position:absolute;right:-3px;text-align:left;white-space:nowrap;z-index:3}.plyr__menu__container>div{overflow:hidden;transition:height .35s cubic-bezier(.4,0,.2,1),width .35s cubic-bezier(.4,0,.2,1)}.plyr__menu__container::after{border:4px solid transparent;border-top-color:rgba(255,255,255,.9);content:'';height:0;position:absolute;right:15px;top:100%;width:0}.plyr__menu__container [role=menu]{padding:7px}.plyr__menu__container [role=menuitem],.plyr__menu__container [role=menuitemradio]{margin-top:2px}.plyr__menu__container [role=menuitem]:first-child,.plyr__menu__container [role=menuitemradio]:first-child{margin-top:0}.plyr__menu__container .plyr__control{align-items:center;color:#4f5b5f;display:flex;font-size:13px;padding:4px 11px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:100%}.plyr__menu__container .plyr__control>span{align-items:inherit;display:flex;width:100%}.plyr__menu__container .plyr__control::after{border:4px solid transparent;content:'';position:absolute;top:50%;transform:translateY(-50%)}.plyr__menu__container .plyr__control--forward{padding-right:28px}.plyr__menu__container .plyr__control--forward::after{border-left-color:rgba(79,91,95,.8);right:5px}.plyr__menu__container .plyr__control--forward.plyr__tab-focus::after,.plyr__menu__container .plyr__control--forward:hover::after{border-left-color:currentColor}.plyr__menu__container .plyr__control--back{font-weight:500;margin:7px;margin-bottom:3px;padding-left:28px;position:relative;width:calc(100% - 14px)}.plyr__menu__container .plyr__control--back::after{border-right-color:rgba(79,91,95,.8);left:7px}.plyr__menu__container .plyr__control--back::before{background:#b7c5cd;box-shadow:0 1px 0 #fff;content:'';height:1px;left:0;margin-top:4px;overflow:hidden;position:absolute;right:0;top:100%}.plyr__menu__container .plyr__control--back.plyr__tab-focus::after,.plyr__menu__container .plyr__control--back:hover::after{border-right-color:currentColor}.plyr__menu__container .plyr__control[role=menuitemradio]{padding-left:7px}.plyr__menu__container .plyr__control[role=menuitemradio]::after,.plyr__menu__container .plyr__control[role=menuitemradio]::before{border-radius:100%}.plyr__menu__container .plyr__control[role=menuitemradio]::before{background:rgba(0,0,0,.1);content:'';display:block;flex-shrink:0;height:16px;margin-right:10px;transition:all .3s ease;width:16px}.plyr__menu__container .plyr__control[role=menuitemradio]::after{background:#fff;border:0;height:6px;left:12px;opacity:0;top:50%;transform:translateY(-50%) scale(0);transition:transform .3s ease,opacity .3s ease;width:6px}.plyr__menu__container .plyr__control[role=menuitemradio][aria-checked=true]::before{background:#1aafff}.plyr__menu__container .plyr__control[role=menuitemradio][aria-checked=true]::after{opacity:1;transform:translateY(-50%) scale(1)}.plyr__menu__container .plyr__control[role=menuitemradio].plyr__tab-focus::before,.plyr__menu__container .plyr__control[role=menuitemradio]:hover::before{background:rgba(0,0,0,.1)}.plyr__menu__container .plyr__menu__value{align-items:center;display:flex;margin-left:auto;margin-right:-5px;overflow:hidden;padding-left:25px;pointer-events:none}.plyr--full-ui input[type=range]{-webkit-appearance:none;background:0 0;border:0;border-radius:26px;color:#1aafff;color:var(--plyr-color-main);display:block;height:19px;margin:0;padding:0;transition:box-shadow .3s ease;width:100%}.plyr--full-ui input[type=range]::-webkit-slider-runnable-track{background:0 0;border:0;border-radius:2.5px;height:5px;transition:box-shadow .3s ease;-webkit-user-select:none;user-select:none;background-image:linear-gradient(to right,currentColor 0,transparent 0);background-image:linear-gradient(to right,currentColor var(--value,0),transparent var(--value,0))}.plyr--full-ui input[type=range]::-webkit-slider-thumb{background:#fff;border:0;border-radius:100%;box-shadow:0 1px 1px rgba(0,0,0,.15),0 0 0 1px rgba(47,52,61,.2);height:13px;position:relative;transition:all .2s ease;width:13px;-webkit-appearance:none;margin-top:-4px}.plyr--full-ui input[type=range]::-moz-range-track{background:0 0;border:0;border-radius:2.5px;height:5px;transition:box-shadow .3s ease;-moz-user-select:none;user-select:none}.plyr--full-ui input[type=range]::-moz-range-thumb{background:#fff;border:0;border-radius:100%;box-shadow:0 1px 1px rgba(0,0,0,.15),0 0 0 1px rgba(47,52,61,.2);height:13px;position:relative;transition:all .2s ease;width:13px}.plyr--full-ui input[type=range]::-moz-range-progress{background:currentColor;border-radius:2.5px;height:5px}.plyr--full-ui input[type=range]::-ms-track{background:0 0;border:0;border-radius:2.5px;height:5px;transition:box-shadow .3s ease;-ms-user-select:none;user-select:none;color:transparent}.plyr--full-ui input[type=range]::-ms-fill-upper{background:0 0;border:0;border-radius:2.5px;height:5px;transition:box-shadow .3s ease;-ms-user-select:none;user-select:none}.plyr--full-ui input[type=range]::-ms-fill-lower{background:0 0;border:0;border-radius:2.5px;height:5px;transition:box-shadow .3s ease;-ms-user-select:none;user-select:none;background:currentColor}.plyr--full-ui input[type=range]::-ms-thumb{background:#fff;border:0;border-radius:100%;box-shadow:0 1px 1px rgba(0,0,0,.15),0 0 0 1px rgba(47,52,61,.2);height:13px;position:relative;transition:all .2s ease;width:13px;margin-top:0}.plyr--full-ui input[type=range]::-ms-tooltip{display:none}.plyr--full-ui input[type=range]:focus{outline:0}.plyr--full-ui input[type=range]::-moz-focus-outer{border:0}.plyr--full-ui input[type=range].plyr__tab-focus::-webkit-slider-runnable-track{outline-color:#1aafff;outline-color:var(--plyr-color-main);outline-offset:2px;outline-style:dotted;outline-width:3px}.plyr--full-ui input[type=range].plyr__tab-focus::-moz-range-track{outline-color:#1aafff;outline-color:var(--plyr-color-main);outline-offset:2px;outline-style:dotted;outline-width:3px}.plyr--full-ui input[type=range].plyr__tab-focus::-ms-track{outline-color:#1aafff;outline-color:var(--plyr-color-main);outline-offset:2px;outline-style:dotted;outline-width:3px}.plyr--full-ui.plyr--video input[type=range]::-webkit-slider-runnable-track{background-color:rgba(255,255,255,.25)}.plyr--full-ui.plyr--video input[type=range]::-moz-range-track{background-color:rgba(255,255,255,.25)}.plyr--full-ui.plyr--video input[type=range]::-ms-track{background-color:rgba(255,255,255,.25)}.plyr--full-ui.plyr--video input[type=range]:active::-webkit-slider-thumb{box-shadow:0 1px 1px rgba(0,0,0,.15),0 0 0 1px rgba(47,52,61,.2),0 0 0 3px rgba(255,255,255,.5)}.plyr--full-ui.plyr--video input[type=range]:active::-moz-range-thumb{box-shadow:0 1px 1px rgba(0,0,0,.15),0 0 0 1px rgba(47,52,61,.2),0 0 0 3px rgba(255,255,255,.5)}.plyr--full-ui.plyr--video input[type=range]:active::-ms-thumb{box-shadow:0 1px 1px rgba(0,0,0,.15),0 0 0 1px rgba(47,52,61,.2),0 0 0 3px rgba(255,255,255,.5)}.plyr--full-ui.plyr--audio input[type=range]::-webkit-slider-runnable-track{background-color:rgba(183,197,205,.66)}.plyr--full-ui.plyr--audio input[type=range]::-moz-range-track{background-color:rgba(183,197,205,.66)}.plyr--full-ui.plyr--audio input[type=range]::-ms-track{background-color:rgba(183,197,205,.66)}.plyr--full-ui.plyr--audio input[type=range]:active::-webkit-slider-thumb{box-shadow:0 1px 1px rgba(0,0,0,.15),0 0 0 1px rgba(47,52,61,.2),0 0 0 3px rgba(0,0,0,.1)}.plyr--full-ui.plyr--audio input[type=range]:active::-moz-range-thumb{box-shadow:0 1px 1px rgba(0,0,0,.15),0 0 0 1px rgba(47,52,61,.2),0 0 0 3px rgba(0,0,0,.1)}.plyr--full-ui.plyr--audio input[type=range]:active::-ms-thumb{box-shadow:0 1px 1px rgba(0,0,0,.15),0 0 0 1px rgba(47,52,61,.2),0 0 0 3px rgba(0,0,0,.1)}.plyr__poster{background-color:#000;background-position:50% 50%;background-repeat:no-repeat;background-size:contain;height:100%;left:0;opacity:0;position:absolute;top:0;transition:opacity .2s ease;width:100%;z-index:1}.plyr--stopped.plyr__poster-enabled .plyr__poster{opacity:1}.plyr__time{font-size:11px}.plyr__time+.plyr__time::before{content:'\2044';margin-right:10px}@media (max-width:767px){.plyr__time+.plyr__time{display:none}}.plyr--video .plyr__time{text-shadow:0 1px 1px rgba(0,0,0,.15)}.plyr__tooltip{background:rgba(255,255,255,.9);border-radius:3px;bottom:100%;box-shadow:0 1px 2px rgba(0,0,0,.15);color:#4f5b5f;font-size:12px;font-weight:500;left:50%;line-height:1.3;margin-bottom:10px;opacity:0;padding:5px 7.5px;pointer-events:none;position:absolute;transform:translate(-50%,10px) scale(.8);transform-origin:50% 100%;transition:transform .2s .1s ease,opacity .2s .1s ease;white-space:nowrap;z-index:2}.plyr__tooltip::before{border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid rgba(255,255,255,.9);bottom:-4px;content:'';height:0;left:50%;position:absolute;transform:translateX(-50%);width:0;z-index:2}.plyr .plyr__control.plyr__tab-focus .plyr__tooltip,.plyr .plyr__control:hover .plyr__tooltip,.plyr__tooltip--visible{opacity:1;transform:translate(-50%,0) scale(1)}.plyr .plyr__control:hover .plyr__tooltip{z-index:3}.plyr__controls>.plyr__control:first-child .plyr__tooltip,.plyr__controls>.plyr__control:first-child+.plyr__control .plyr__tooltip{left:0;transform:translate(0,10px) scale(.8);transform-origin:0 100%}.plyr__controls>.plyr__control:first-child .plyr__tooltip::before,.plyr__controls>.plyr__control:first-child+.plyr__control .plyr__tooltip::before{left:16px}.plyr__controls>.plyr__control:last-child .plyr__tooltip{left:auto;right:0;transform:translate(0,10px) scale(.8);transform-origin:100% 100%}.plyr__controls>.plyr__control:last-child .plyr__tooltip::before{left:auto;right:16px;transform:translateX(50%)}.plyr__controls>.plyr__control:first-child .plyr__tooltip--visible,.plyr__controls>.plyr__control:first-child+.plyr__control .plyr__tooltip--visible,.plyr__controls>.plyr__control:first-child+.plyr__control.plyr__tab-focus .plyr__tooltip,.plyr__controls>.plyr__control:first-child+.plyr__control:hover .plyr__tooltip,.plyr__controls>.plyr__control:first-child.plyr__tab-focus .plyr__tooltip,.plyr__controls>.plyr__control:first-child:hover .plyr__tooltip,.plyr__controls>.plyr__control:last-child .plyr__tooltip--visible,.plyr__controls>.plyr__control:last-child.plyr__tab-focus .plyr__tooltip,.plyr__controls>.plyr__control:last-child:hover .plyr__tooltip{transform:translate(0,0) scale(1)}.plyr--video{background:#000;overflow:hidden}.plyr--video.plyr--menu-open{overflow:visible}.plyr__video-wrapper{background:#000;border-radius:inherit;overflow:hidden;position:relative;z-index:0}.plyr__progress{flex:1;left:6.5px;margin-right:13px;position:relative}.plyr__progress input[type=range],.plyr__progress__buffer{margin-left:-6.5px;margin-right:-6.5px;width:calc(100% + 13px)}.plyr__progress input[type=range]{position:relative;z-index:2}.plyr__progress .plyr__tooltip{font-size:11px;left:0}.plyr__progress__buffer{-webkit-appearance:none;background:0 0;border:0;border-radius:100px;height:5px;left:0;margin-top:-2.5px;padding:0;position:absolute;top:50%}.plyr__progress__buffer::-webkit-progress-bar{background:0 0}.plyr__progress__buffer::-webkit-progress-value{background:currentColor;border-radius:100px;min-width:5px;transition:width .2s ease}.plyr__progress__buffer::-moz-progress-bar{background:currentColor;border-radius:100px;min-width:5px;transition:width .2s ease}.plyr__progress__buffer::-ms-fill{border-radius:100px;transition:width .2s ease}.plyr--video .plyr__progress__buffer{box-shadow:0 1px 1px rgba(0,0,0,.15);color:rgba(255,255,255,.25)}.plyr--audio .plyr__progress__buffer{color:rgba(183,197,205,.66)}.plyr--loading .plyr__progress__buffer{animation:plyr-progress 1s linear infinite;background-image:linear-gradient(-45deg,rgba(47,52,61,.6) 25%,transparent 25%,transparent 50%,rgba(47,52,61,.6) 50%,rgba(47,52,61,.6) 75%,transparent 75%,transparent);background-repeat:repeat-x;background-size:25px 25px;color:transparent}.plyr--video.plyr--loading .plyr__progress__buffer{background-color:rgba(255,255,255,.25)}.plyr--audio.plyr--loading .plyr__progress__buffer{background-color:rgba(183,197,205,.66)}.plyr__volume{align-items:center;display:flex;flex:1;position:relative}.plyr__volume input[type=range]{margin-left:5px;position:relative;z-index:2}@media (min-width:480px){.plyr__volume{max-width:90px}}@media (min-width:768px){.plyr__volume{max-width:110px}}.plyr--is-ios .plyr__volume{display:none!important}.plyr--is-ios.plyr--vimeo [data-plyr=mute]{display:none!important}.plyr:-webkit-full-screen{background:#000;border-radius:0!important;height:100%;margin:0;width:100%}.plyr:-ms-fullscreen{background:#000;border-radius:0!important;height:100%;margin:0;width:100%}.plyr:fullscreen{background:#000;border-radius:0!important;height:100%;margin:0;width:100%}.plyr:-webkit-full-screen video{height:100%}.plyr:-ms-fullscreen video{height:100%}.plyr:fullscreen video{height:100%}.plyr:-webkit-full-screen .plyr__video-wrapper{height:100%;width:100%}.plyr:-ms-fullscreen .plyr__video-wrapper{height:100%;width:100%}.plyr:fullscreen .plyr__video-wrapper{height:100%;width:100%}.plyr:-webkit-full-screen.plyr--vimeo .plyr__video-wrapper{height:0;top:50%;transform:translateY(-50%)}.plyr:-ms-fullscreen.plyr--vimeo .plyr__video-wrapper{height:0;top:50%;transform:translateY(-50%)}.plyr:fullscreen.plyr--vimeo .plyr__video-wrapper{height:0;top:50%;transform:translateY(-50%)}.plyr:-webkit-full-screen .plyr__control .icon--exit-fullscreen{display:block}.plyr:-ms-fullscreen .plyr__control .icon--exit-fullscreen{display:block}.plyr:fullscreen .plyr__control .icon--exit-fullscreen{display:block}.plyr:-webkit-full-screen .plyr__control .icon--exit-fullscreen+svg{display:none}.plyr:-ms-fullscreen .plyr__control .icon--exit-fullscreen+svg{display:none}.plyr:fullscreen .plyr__control .icon--exit-fullscreen+svg{display:none}.plyr:-webkit-full-screen.plyr--hide-controls{cursor:none}.plyr:-ms-fullscreen.plyr--hide-controls{cursor:none}.plyr:fullscreen.plyr--hide-controls{cursor:none}@media (min-width:1024px){.plyr:-webkit-full-screen .plyr__captions{font-size:21px}.plyr:-ms-fullscreen .plyr__captions{font-size:21px}.plyr:fullscreen .plyr__captions{font-size:21px}}.plyr:-webkit-full-screen{background:#000;border-radius:0!important;height:100%;margin:0;width:100%}.plyr:-webkit-full-screen video{height:100%}.plyr:-webkit-full-screen .plyr__video-wrapper{height:100%;width:100%}.plyr:-webkit-full-screen.plyr--vimeo .plyr__video-wrapper{height:0;top:50%;transform:translateY(-50%)}.plyr:-webkit-full-screen .plyr__control .icon--exit-fullscreen{display:block}.plyr:-webkit-full-screen .plyr__control .icon--exit-fullscreen+svg{display:none}.plyr:-webkit-full-screen.plyr--hide-controls{cursor:none}@media (min-width:1024px){.plyr:-webkit-full-screen .plyr__captions{font-size:21px}}.plyr:-moz-full-screen{background:#000;border-radius:0!important;height:100%;margin:0;width:100%}.plyr:-moz-full-screen video{height:100%}.plyr:-moz-full-screen .plyr__video-wrapper{height:100%;width:100%}.plyr:-moz-full-screen.plyr--vimeo .plyr__video-wrapper{height:0;top:50%;transform:translateY(-50%)}.plyr:-moz-full-screen .plyr__control .icon--exit-fullscreen{display:block}.plyr:-moz-full-screen .plyr__control .icon--exit-fullscreen+svg{display:none}.plyr:-moz-full-screen.plyr--hide-controls{cursor:none}@media (min-width:1024px){.plyr:-moz-full-screen .plyr__captions{font-size:21px}}.plyr:-ms-fullscreen{background:#000;border-radius:0!important;height:100%;margin:0;width:100%}.plyr:-ms-fullscreen video{height:100%}.plyr:-ms-fullscreen .plyr__video-wrapper{height:100%;width:100%}.plyr:-ms-fullscreen.plyr--vimeo .plyr__video-wrapper{height:0;top:50%;transform:translateY(-50%)}.plyr:-ms-fullscreen .plyr__control .icon--exit-fullscreen{display:block}.plyr:-ms-fullscreen .plyr__control .icon--exit-fullscreen+svg{display:none}.plyr:-ms-fullscreen.plyr--hide-controls{cursor:none}@media (min-width:1024px){.plyr:-ms-fullscreen .plyr__captions{font-size:21px}}.plyr--fullscreen-fallback{background:#000;border-radius:0!important;height:100%;margin:0;width:100%;bottom:0;left:0;position:fixed;right:0;top:0;z-index:10000000}.plyr--fullscreen-fallback video{height:100%}.plyr--fullscreen-fallback .plyr__video-wrapper{height:100%;width:100%}.plyr--fullscreen-fallback.plyr--vimeo .plyr__video-wrapper{height:0;top:50%;transform:translateY(-50%)}.plyr--fullscreen-fallback .plyr__control .icon--exit-fullscreen{display:block}.plyr--fullscreen-fallback .plyr__control .icon--exit-fullscreen+svg{display:none}.plyr--fullscreen-fallback.plyr--hide-controls{cursor:none}@media (min-width:1024px){.plyr--fullscreen-fallback .plyr__captions{font-size:21px}}.plyr__ads{border-radius:inherit;bottom:0;cursor:pointer;left:0;overflow:hidden;position:absolute;right:0;top:0;z-index:-1}.plyr__ads>div,.plyr__ads>div iframe{height:100%;position:absolute;width:100%}.plyr__ads::after{background:rgba(47,52,61,.8);border-radius:2px;bottom:10px;color:#fff;content:attr(data-badge-text);font-size:11px;padding:2px 6px;pointer-events:none;position:absolute;right:10px;z-index:3}.plyr__ads::after:empty{display:none}.plyr__cues{background:currentColor;display:block;height:5px;left:0;margin:-2.5px 0 0;opacity:.8;position:absolute;top:50%;width:3px;z-index:3}.plyr__preview-thumb{background-color:rgba(255,255,255,.9);border-radius:3px;bottom:100%;box-shadow:0 1px 2px rgba(0,0,0,.15);margin-bottom:10px;opacity:0;padding:3px;pointer-events:none;position:absolute;transform:translate(0,10px) scale(.8);transform-origin:50% 100%;transition:transform .2s .1s ease,opacity .2s .1s ease;z-index:2}.plyr__preview-thumb--is-shown{opacity:1;transform:translate(0,0) scale(1)}.plyr__preview-thumb::before{border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid rgba(255,255,255,.9);bottom:-4px;content:'';height:0;left:50%;position:absolute;transform:translateX(-50%);width:0;z-index:2}.plyr__preview-thumb__image-container{background:#b7c5cd;border-radius:2px;overflow:hidden;position:relative;z-index:0}.plyr__preview-thumb__image-container img{height:100%;left:0;max-height:none;max-width:none;position:absolute;top:0;width:100%}.plyr__preview-thumb__time-container{bottom:6px;left:0;position:absolute;right:0;white-space:nowrap;z-index:3}.plyr__preview-thumb__time-container span{background-color:rgba(0,0,0,.55);border-radius:2px;color:#fff;font-size:11px;padding:3px 6px}.plyr__preview-scrubbing{bottom:0;filter:blur(1px);height:100%;left:0;margin:auto;opacity:0;overflow:hidden;position:absolute;right:0;top:0;transition:opacity .3s ease;width:100%;z-index:1}.plyr__preview-scrubbing--is-shown{opacity:1}.plyr__preview-scrubbing img{height:100%;left:0;max-height:none;max-width:none;-o-object-fit:contain;object-fit:contain;position:absolute;top:0;width:100%}.plyr--no-transition{transition:none!important}.plyr__sr-only{clip:rect(1px,1px,1px,1px);overflow:hidden;border:0!important;height:1px!important;padding:0!important;position:absolute!important;width:1px!important}.plyr [hidden]{display:none!important}.no-border{border:0}[hidden]{display:none}.sr-only{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;opacity:.001;overflow:hidden;padding:0;position:absolute;width:1px} \ No newline at end of file diff --git a/demo/dist/demo.js b/demo/dist/demo.js index e3d4e6c2..30103056 100644 --- a/demo/dist/demo.js +++ b/demo/dist/demo.js @@ -7,107 +7,211 @@ typeof navigator === "object" && (function () { return module = { exports: {} }, fn(module, module.exports), module.exports; } - var stringify_1 = createCommonjsModule(function (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('.') + ']'; + function _typeof(obj) { + if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { + _typeof = function (obj) { + return typeof obj; + }; + } else { + _typeof = function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } - 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); + return _typeof(obj); + } - if (~indexOf(stack, value)) { - value = cycleReplacer.call(this, key, value); - } - } else { - stack.push(value); + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; + } + + function _defineProperty(obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + + return obj; + } + + function _slicedToArray(arr, i) { + return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); + } + + function _toConsumableArray(arr) { + return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); + } + + function _arrayWithoutHoles(arr) { + if (Array.isArray(arr)) { + for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; + + return arr2; + } + } + + function _arrayWithHoles(arr) { + if (Array.isArray(arr)) return arr; + } + + function _iterableToArray(iter) { + if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); + } + + function _iterableToArrayLimit(arr, i) { + var _arr = []; + var _n = true; + var _d = false; + var _e = undefined; + + try { + for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { + _arr.push(_s.value); + + if (i && _arr.length === i) break; + } + } catch (err) { + _d = true; + _e = err; + } finally { + try { + if (!_n && _i["return"] != null) _i["return"](); + } finally { + if (_d) throw _e; + } + } + + return _arr; + } + + function _nonIterableSpread() { + throw new TypeError("Invalid attempt to spread non-iterable instance"); + } + + function _nonIterableRest() { + throw new TypeError("Invalid attempt to destructure non-iterable instance"); + } + + var stringify_1 = createCommonjsModule(function (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 replacer == null - ? value instanceof Error ? stringifyError(value) : value - : replacer.call(this, key, value); - }; - } + 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 cycleReplacer(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); + }; + } }); var stringify_2 = stringify_1.getSerialize; - var _window = - typeof window !== 'undefined' - ? window - : typeof commonjsGlobal !== 'undefined' - ? commonjsGlobal - : typeof self !== 'undefined' - ? self - : {}; + var _window = typeof window !== 'undefined' ? window : typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : typeof self !== 'undefined' ? self : {}; function isObject(what) { - return typeof what === 'object' && what !== null; - } - - // Yanked from https://git.io/vS8DV re-used under CC0 + return _typeof(what) === 'object' && what !== null; + } // Yanked from https://git.io/vS8DV re-used under CC0 // with some tiny modifications + + function isError(value) { switch (Object.prototype.toString.call(value)) { case '[object Error]': return true; + case '[object Exception]': return true; + case '[object DOMException]': return true; + default: return value instanceof Error; } @@ -153,12 +257,14 @@ typeof navigator === "object" && (function () { return false; } } + return true; } function supportsErrorEvent() { try { new ErrorEvent(''); // eslint-disable-line no-new + return true; } catch (e) { return false; @@ -168,6 +274,7 @@ typeof navigator === "object" && (function () { function supportsDOMError() { try { new DOMError(''); // eslint-disable-line no-new + return true; } catch (e) { return false; @@ -177,6 +284,7 @@ typeof navigator === "object" && (function () { function supportsDOMException() { try { new DOMException(''); // eslint-disable-line no-new + return true; } catch (e) { return false; @@ -188,18 +296,21 @@ typeof navigator === "object" && (function () { 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 + } // 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; @@ -221,9 +332,11 @@ typeof navigator === "object" && (function () { function wrappedCallback(callback) { function dataCallback(data, original) { var normalizedData = callback(data) || data; + if (original) { return original(normalizedData) || normalizedData; } + return normalizedData; } @@ -241,6 +354,7 @@ typeof navigator === "object" && (function () { } } else { j = obj.length; + if (j) { for (i = 0; i < j; i++) { callback.call(null, i, obj[i]); @@ -253,12 +367,12 @@ typeof navigator === "object" && (function () { if (!obj2) { return obj1; } - each(obj2, function(key, value) { + + 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 @@ -267,10 +381,13 @@ typeof navigator === "object" && (function () { * 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); } @@ -278,12 +395,13 @@ typeof navigator === "object" && (function () { if (typeof max !== 'number') { throw new Error('2nd argument to `truncate` function should be a number'); } + if (typeof str !== 'string' || max === 0) { return str; } - return str.length <= max ? str : str.substr(0, max) + '\u2026'; - } + return str.length <= max ? str : str.substr(0, max) + "\u2026"; + } /** * hasKey, a better form of hasOwnProperty * Example: hasKey(MainHostObject, property) === true/false @@ -291,6 +409,8 @@ typeof navigator === "object" && (function () { * @param {Object} host object to check property * @param {string} key to check */ + + function hasKey(object, key) { return Object.prototype.hasOwnProperty.call(object, key); } @@ -299,12 +419,13 @@ typeof navigator === "object" && (function () { // Combine an array of regular expressions and strings into one large regexp // Be mad. var sources = [], - i = 0, - len = patterns.length, - pattern; + 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 @@ -312,28 +433,28 @@ typeof navigator === "object" && (function () { } 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 + } // Intentionally skip other cases + } + return new RegExp(sources.join('|'), 'i'); } function urlencode(o) { var pairs = []; - each(o, function(key, value) { + each(o, function (key, value) { pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value)); }); return pairs.join('&'); - } - - // borrowed from https://tools.ietf.org/html/rfc3986#appendix-B + } // borrowed from https://tools.ietf.org/html/rfc3986#appendix-B // intentionally using regex and not 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(/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/); + var match = url.match(/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/); // coerce to undefined values to empty string so we don't get 'undefined' - // coerce to undefined values to empty string so we don't get 'undefined' var query = match[6] || ''; var fragment = match[8] || ''; return { @@ -341,8 +462,10 @@ typeof navigator === "object" && (function () { host: match[4], path: match[5], relative: match[5] + query + fragment // everything minus origin + }; } + function uuid4() { var crypto = _window.crypto || _window.msCrypto; @@ -350,41 +473,32 @@ typeof navigator === "object" && (function () { // Use window.crypto API if available // eslint-disable-next-line no-undef var arr = new Uint16Array(8); - crypto.getRandomValues(arr); + crypto.getRandomValues(arr); // set 4 in byte 7 - // 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; + arr[3] = arr[3] & 0xfff | 0x4000; // set 2 most significant bits of byte 9 to '10' - var pad = function(num) { + arr[4] = arr[4] & 0x3fff | 0x8000; + + var pad = function pad(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]) - ); + 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 '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 @@ -392,142 +506,139 @@ typeof navigator === "object" && (function () { * @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; + 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 = 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) - ) { + + 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; + 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); } - /** * 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; // in case both stacktraces are undefined, we can't decide so default to false - 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; + var frames2 = stack2.frames; // Exit early if stacktrace is malformed - // Exit early if stacktrace is malformed - if (frames1 === undefined || frames2 === undefined) return false; + if (frames1 === undefined || frames2 === undefined) return false; // Exit early if frame count differs - // Exit early if frame count differs - if (frames1.length !== frames2.length) return false; + if (frames1.length !== frames2.length) return false; // Iterate through every frame; bail out if anything differs - // 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; + 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` @@ -535,26 +646,29 @@ typeof navigator === "object" && (function () { * @param replacement replacement function * @param track {optional} record instrumentation to an array */ + + 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} */ + + function safeJoin(input, delimiter) { if (!isArray(input)) return ''; - var output = []; for (var i = 0; i < input.length; i++) { @@ -566,11 +680,11 @@ typeof navigator === "object" && (function () { } 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 - // 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; @@ -586,22 +700,15 @@ typeof navigator === "object" && (function () { if (typeof value === 'string') { var maxLength = 40; return truncate(value, maxLength); - } else if ( - typeof value === 'number' || - typeof value === 'boolean' || - typeof value === 'undefined' - ) { + } else if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'undefined') { return value; } - var type = Object.prototype.toString.call(value); + var type = Object.prototype.toString.call(value); // Node.js REPL notation - // 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]'; - + if (type === '[object Function]') return value.name ? '[Function: ' + value.name + ']' : '[Function]'; return value; } @@ -609,12 +716,12 @@ typeof navigator === "object" && (function () { if (depth === 0) return serializeValue(value); if (isPlainObject(value)) { - return Object.keys(value).reduce(function(acc, key) { + 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 value.map(function (val) { return serializeObject(val, depth - 1); }); } @@ -624,10 +731,8 @@ typeof navigator === "object" && (function () { 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_1(serialized)) > maxSize) { @@ -640,12 +745,10 @@ typeof navigator === "object" && (function () { function serializeKeysForMessage(keys, maxLength) { if (typeof keys === 'number' || typeof keys === 'string') return keys.toString(); if (!Array.isArray(keys)) return ''; - - keys = keys.filter(function(key) { + 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]; @@ -653,16 +756,14 @@ typeof navigator === "object" && (function () { var serialized = keys.slice(0, usedKeys).join(', '); if (serialized.length > maxLength) continue; if (usedKeys === keys.length) return serialized; - return serialized + '\u2026'; + return serialized + "\u2026"; } return ''; } function sanitize(input, sanitizeKeys) { - if (!isArray(sanitizeKeys) || (isArray(sanitizeKeys) && sanitizeKeys.length === 0)) - return input; - + if (!isArray(sanitizeKeys) || isArray(sanitizeKeys) && sanitizeKeys.length === 0) return input; var sanitizeRegExp = joinRegExp(sanitizeKeys); var sanitizeMask = '********'; var safeInput; @@ -675,18 +776,19 @@ typeof navigator === "object" && (function () { function sanitizeWorker(workerInput) { if (isArray(workerInput)) { - return workerInput.map(function(val) { + return workerInput.map(function (val) { return sanitizeWorker(val); }); } if (isPlainObject(workerInput)) { - return Object.keys(workerInput).reduce(function(acc, k) { + return Object.keys(workerInput).reduce(function (acc, k) { if (sanitizeRegExp.test(k)) { acc[k] = sanitizeMask; } else { acc[k] = sanitizeWorker(workerInput[k]); } + return acc; }, {}); } @@ -749,19 +851,14 @@ typeof navigator === "object" && (function () { 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$1 = typeof window !== 'undefined' ? window : typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : typeof self !== 'undefined' ? self : {}; // global reference to slice - // This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785) - var _window$1 = - typeof window !== 'undefined' - ? window - : typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : typeof self !== 'undefined' ? self : {}; - // global reference to slice var _slice = [].slice; - var UNKNOWN_FUNCTION = '?'; + var UNKNOWN_FUNCTION = '?'; // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Error_types - // 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() { @@ -770,21 +867,14 @@ typeof navigator === "object" && (function () { } function getLocationOrigin() { - if (typeof document === 'undefined' || document.location == null) return ''; + if (typeof document === 'undefined' || document.location == null) return ''; // Oh dear IE10... - // Oh dear IE10... if (!document.location.origin) { - return ( - document.location.protocol + - '//' + - document.location.hostname + - (document.location.port ? ':' + document.location.port : '') - ); + return document.location.protocol + '//' + document.location.hostname + (document.location.port ? ':' + document.location.port : ''); } return document.location.origin; } - /** * TraceKit.report: cross-browser processing of unhandled exceptions * @@ -824,25 +914,28 @@ typeof navigator === "object" && (function () { * Handlers receive a stackInfo object as described in the * TraceKit.computeStackTrace docs. */ - TraceKit.report = (function reportModuleWrapper() { - var handlers = [], - lastArgs = null, - lastException = null, - lastExceptionStack = null; + + 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) { @@ -850,24 +943,28 @@ typeof navigator === "object" && (function () { } } } - /** * Remove all crash handlers. */ + + function unsubscribeAll() { uninstallGlobalHandler(); handlers = []; } - /** * Dispatch stack information to all handlers. * @param {Object.} stack */ + + function notifyHandlers(stack, isWindowError) { var exception = null; + if (isWindowError && !TraceKit.collectWindowErrors) { return; } + for (var i in handlers) { if (handlers.hasOwnProperty(i)) { try { @@ -884,7 +981,6 @@ typeof navigator === "object" && (function () { } var _oldOnerrorHandler, _onErrorHandlerInstalled; - /** * Ensures all global unhandled exceptions are recorded. * Supported by Gecko and IE. @@ -896,24 +992,20 @@ typeof navigator === "object" && (function () { * 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 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 - ); + 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 @@ -925,12 +1017,12 @@ typeof navigator === "object" && (function () { 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]; @@ -938,7 +1030,6 @@ typeof navigator === "object" && (function () { } location.func = UNKNOWN_FUNCTION; - stack = { name: name, message: message, @@ -959,6 +1050,7 @@ typeof navigator === "object" && (function () { if (_onErrorHandlerInstalled) { return; } + _oldOnerrorHandler = _window$1.onerror; _window$1.onerror = traceKitWindowOnError; _onErrorHandlerInstalled = true; @@ -968,6 +1060,7 @@ typeof navigator === "object" && (function () { if (!_onErrorHandlerInstalled) { return; } + _window$1.onerror = _oldOnerrorHandler; _onErrorHandlerInstalled = false; _oldOnerrorHandler = undefined; @@ -975,13 +1068,12 @@ typeof navigator === "object" && (function () { function processLastException() { var _lastExceptionStack = lastExceptionStack, - _lastArgs = lastArgs; + _lastArgs = lastArgs; lastArgs = null; lastExceptionStack = null; lastException = null; notifyHandlers.apply(null, [_lastExceptionStack, false].concat(_lastArgs)); } - /** * Reports an unhandled Error to TraceKit. * @param {Error} ex @@ -989,8 +1081,11 @@ typeof navigator === "object" && (function () { * 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 @@ -1002,13 +1097,12 @@ typeof navigator === "object" && (function () { var stack = TraceKit.computeStackTrace(ex); lastExceptionStack = stack; lastException = ex; - lastArgs = args; - - // If the stack trace is incomplete, wait for 2 seconds for + 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() { + + setTimeout(function () { if (lastException === ex) { processLastException(); } @@ -1023,8 +1117,7 @@ typeof navigator === "object" && (function () { report.unsubscribe = unsubscribe; report.uninstall = unsubscribeAll; return report; - })(); - + }(); /** * TraceKit.computeStackTrace: cross-browser stack traces in JavaScript * @@ -1076,7 +1169,9 @@ typeof navigator === "object" && (function () { * inner function that actually caused the exception). * */ - TraceKit.computeStackTrace = (function computeStackTraceWrapper() { + + + TraceKit.computeStackTrace = function computeStackTraceWrapper() { // Contents of Exception in various browsers. // // SAFARI: @@ -1122,13 +1217,12 @@ typeof navigator === "object" && (function () { */ function computeStackTraceFromStackProp(ex) { if (typeof ex.stack === 'undefined' || !ex.stack) return; - var chrome = /^\s*at (?:(.*?) ?\()?((?:file|https?|blob|chrome-extension|native|eval|webpack||[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 + 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 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'); @@ -1139,15 +1233,20 @@ typeof navigator === "object" && (function () { var reference = /^(.*) is undefined$/.exec(ex.message); for (var i = 0, j = lines.length; i < j; ++i) { - if ((parts = chrome.exec(lines[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, @@ -1155,7 +1254,7 @@ typeof navigator === "object" && (function () { line: parts[3] ? +parts[3] : null, column: parts[4] ? +parts[4] : null }; - } else if ((parts = winjs.exec(lines[i]))) { + } else if (parts = winjs.exec(lines[i])) { element = { url: parts[2], func: parts[1] || UNKNOWN_FUNCTION, @@ -1163,8 +1262,9 @@ typeof navigator === "object" && (function () { line: +parts[3], column: parts[4] ? +parts[4] : null }; - } else if ((parts = gecko.exec(lines[i]))) { + } 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]; @@ -1177,6 +1277,7 @@ typeof navigator === "object" && (function () { // 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, @@ -1200,31 +1301,26 @@ typeof navigator === "object" && (function () { // that much of an issue. var xhr = new XMLHttpRequest(); xhr.open('GET', element.url, false); - xhr.send(null); + xhr.send(null); // If we failed to download the source, skip this patch - // If we failed to download the source, skip this patch if (xhr.status === 200) { - var source = xhr.responseText || ''; - - // We trim the source down to the last 300 characters as sourceMappingURL is always at the end of the file. + var source = xhr.responseText || ''; // We trim the source down to the last 300 characters as sourceMappingURL is always at the end of the file. // Why 300? To be in line with: https://github.com/getsentry/sentry/blob/4af29e8f2350e20c28a6933354e4f42437b4ba42/src/sentry/lang/javascript/processor.py#L164-L175 - source = source.slice(-300); - // Now we dig out the source map URL - var sourceMaps = source.match(/\/\/# sourceMappingURL=(.*)$/); + source = source.slice(-300); // Now we dig out the source map URL + + var sourceMaps = source.match(/\/\/# sourceMappingURL=(.*)$/); // If we don't find a source map comment or we find more than one, continue on to the next element. - // If we don't find a source map comment or we find more than one, continue on to the next element. if (sourceMaps) { - var sourceMapAddress = sourceMaps[1]; - - // Now we check to see if it's a relative URL. + var sourceMapAddress = sourceMaps[1]; // Now we check to see if it's a relative URL. // If it is, convert it to an absolute one. + if (sourceMapAddress.charAt(0) === '~') { sourceMapAddress = getLocationOrigin() + sourceMapAddress.slice(1); - } - - // Now we strip the '.map' off of the end of the URL and update the + } // Now we strip the '.map' off of the end of the URL and update the // element so that Sentry can match the map to the blob. + + element.url = sourceMapAddress.slice(0, -4); } } @@ -1244,7 +1340,6 @@ typeof navigator === "object" && (function () { 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. @@ -1258,6 +1353,8 @@ typeof navigator === "object" && (function () { * @return {boolean} Whether or not the stack information was * augmented. */ + + function augmentStackTraceWithInitialElement(stackInfo, url, lineNo, message) { var initial = { url: url, @@ -1275,10 +1372,7 @@ typeof navigator === "object" && (function () { 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 - ) { + } else if (!stackInfo.stack[0].line && stackInfo.stack[0].func === initial.func) { stackInfo.stack[0].line = initial.line; return false; } @@ -1294,7 +1388,6 @@ typeof navigator === "object" && (function () { return false; } - /** * Computes stack trace information by walking the arguments.caller * chain at the time the exception occurred. This will cause earlier @@ -1304,19 +1397,17 @@ typeof navigator === "object" && (function () { * @param {Error} ex * @return {?Object.} 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; + stack = [], + funcs = {}, + recursion = false, + parts, + item; - for ( - var curr = computeStackTraceByWalkingCallerChain.caller; - curr && !recursion; - curr = curr.caller - ) { + for (var curr = computeStackTraceByWalkingCallerChain.caller; curr && !recursion; curr = curr.caller) { if (curr === computeStackTrace || curr === TraceKit.report) { // console.log('skipping internal function'); continue; @@ -1331,7 +1422,7 @@ typeof navigator === "object" && (function () { if (curr.name) { item.func = curr.name; - } else if ((parts = functionName.exec(curr.toString()))) { + } else if (parts = functionName.exec(curr.toString())) { item.func = parts[1]; } @@ -1362,26 +1453,23 @@ typeof navigator === "object" && (function () { url: getLocationHref(), stack: stack }; - augmentStackTraceWithInitialElement( - result, - ex.sourceURL || ex.fileName, - ex.line || ex.lineNumber, - ex.message || ex.description - ); + 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; } @@ -1393,6 +1481,7 @@ typeof navigator === "object" && (function () { try { stack = computeStackTraceByWalkingCallerChain(ex, depth + 1); + if (stack) { return stack; } @@ -1401,6 +1490,7 @@ typeof navigator === "object" && (function () { throw e; } } + return { name: ex.name, message: ex.message, @@ -1410,9 +1500,8 @@ typeof navigator === "object" && (function () { computeStackTrace.augmentStackTraceWithInitialElement = augmentStackTraceWithInitialElement; computeStackTrace.computeStackTraceFromStackProp = computeStackTraceFromStackProp; - return computeStackTrace; - })(); + }(); var tracekit = TraceKit; @@ -1442,43 +1531,49 @@ typeof navigator === "object" && (function () { function safeAdd(x, y) { var lsw = (x & 0xffff) + (y & 0xffff); var msw = (x >> 16) + (y >> 16) + (lsw >> 16); - return (msw << 16) | (lsw & 0xffff); + return msw << 16 | lsw & 0xffff; } - /* * Bitwise rotate a 32-bit number to the left. */ - function bitRotateLeft(num, cnt) { - return (num << cnt) | (num >>> (32 - cnt)); - } + + 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); + 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); + 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; - + x[len >> 5] |= 0x80 << len % 32; + x[(len + 64 >>> 9 << 4) + 14] = len; var i; var olda; var oldb; @@ -1494,7 +1589,6 @@ typeof navigator === "object" && (function () { 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); @@ -1511,7 +1605,6 @@ typeof navigator === "object" && (function () { 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); @@ -1528,7 +1621,6 @@ typeof navigator === "object" && (function () { 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); @@ -1545,7 +1637,6 @@ typeof navigator === "object" && (function () { 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); @@ -1562,56 +1653,66 @@ typeof navigator === "object" && (function () { 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); + 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); + 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); @@ -1619,51 +1720,62 @@ typeof navigator === "object" && (function () { 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); + 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)); } @@ -1673,11 +1785,14 @@ typeof navigator === "object" && (function () { if (!raw) { return hexMD5(string); } + return rawMD5(string); } + if (!raw) { return hexHMACMD5(key, string); } + return rawHMACMD5(key, string); } @@ -1687,12 +1802,12 @@ typeof navigator === "object" && (function () { this.name = 'RavenConfigError'; this.message = message; } + RavenConfigError.prototype = new Error(); RavenConfigError.prototype.constructor = RavenConfigError; - var configError = RavenConfigError; - var wrapMethod = function(console, level, callback) { + var wrapMethod = function wrapMethod(console, level, callback) { var originalConsoleLevel = console[level]; var originalConsole = console; @@ -1702,25 +1817,29 @@ typeof navigator === "object" && (function () { var sentryLevel = level === 'warn' ? 'warning' : level; - console[level] = function() { + console[level] = function () { var args = [].slice.call(arguments); - var msg = utils.safeJoin(args, ' '); - var data = {level: sentryLevel, logger: 'console', extra: {arguments: 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'); + msg = 'Assertion failed: ' + (utils.safeJoin(args.slice(1), ' ') || 'console.assert'); data.extra.arguments = args.slice(1); callback && callback(msg, data); } } else { callback && callback(msg, data); - } + } // this fails for some browsers. :( + - // 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 @@ -1735,12 +1854,6 @@ typeof navigator === "object" && (function () { /*global XDomainRequest:false */ - - - - - - var isErrorEvent$1 = utils.isErrorEvent; var isDOMError$1 = utils.isDOMError; var isDOMException$1 = utils.isDOMException; @@ -1770,38 +1883,32 @@ typeof navigator === "object" && (function () { var serializeKeysForMessage$1 = utils.serializeKeysForMessage; var serializeException$1 = utils.serializeException; var sanitize$1 = utils.sanitize; - var wrapConsoleMethod = console$1.wrapMethod; - var dsnKeys = 'source protocol user pass host port path'.split(' '), - dsnPattern = /^(?:(\w+):)?\/\/(?:(\w+)(:\w+)?@)?([\w\.-]+)(?::(\d+))?(\/.*)/; + 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$2 = typeof window !== 'undefined' ? window : typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : typeof self !== 'undefined' ? self : {}; - // This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785) - var _window$2 = - typeof window !== 'undefined' - ? window - : typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : typeof self !== 'undefined' ? self : {}; var _document = _window$2.document; var _navigator = _window$2.navigator; function keepOriginalCallback(original, callback) { - return isFunction$1(callback) - ? function(data) { - return callback(data, original); - } - : callback; - } - - // First, check for JSON support + return isFunction$1(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._hasJSON = !!((typeof JSON === "undefined" ? "undefined" : _typeof(JSON)) === 'object' && JSON.stringify); // Raven can run in contexts where there's no document (react-native) + this._hasDocument = !isUndefined$1(_document); this._hasNavigator = !isUndefined$1(_navigator); this._lastCapturedException = null; @@ -1841,9 +1948,9 @@ typeof navigator === "object" && (function () { }; this._ignoreOnError = 0; this._isRavenInstalled = false; - this._originalErrorStackTraceLimit = Error.stackTraceLimit; - // capture references to window.console *and* all its methods first + 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$2.console || {}; this._originalConsoleMethods = {}; this._plugins = []; @@ -1854,30 +1961,30 @@ typeof navigator === "object" && (function () { this._keypressTimeout; this._location = _window$2.location; this._lastHref = this._location && this._location.href; - this._resetBackoff(); - // eslint-disable-next-line guard-for-in + 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.27.0', - debug: false, - - TraceKit: tracekit, // alias to TraceKit + TraceKit: tracekit, + // alias to TraceKit /* * Configure Raven with a DSN and extra options @@ -1886,20 +1993,20 @@ typeof navigator === "object" && (function () { * @param {object} options Set of global options [optional] * @return {Raven} */ - config: function(dsn, options) { + config: function config(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 - var globalOptions = self._globalOptions; - - // merge in options if (options) { - each$1(options, function(key, value) { + each$1(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; @@ -1909,26 +2016,17 @@ typeof navigator === "object" && (function () { }); } - self.setDSN(dsn); - - // "Script error." is hard coded into browsers for errors that it can't read. + 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.push(/^Script error\.?$/); + globalOptions.ignoreErrors.push(/^Javascript error: Script error\.? on line 0$/); // join regexp rules into one big rule + globalOptions.ignoreErrors = joinRegExp$1(globalOptions.ignoreErrors); - globalOptions.ignoreUrls = globalOptions.ignoreUrls.length - ? joinRegExp$1(globalOptions.ignoreUrls) - : false; - globalOptions.whitelistUrls = globalOptions.whitelistUrls.length - ? joinRegExp$1(globalOptions.whitelistUrls) - : false; + globalOptions.ignoreUrls = globalOptions.ignoreUrls.length ? joinRegExp$1(globalOptions.ignoreUrls) : false; + globalOptions.whitelistUrls = globalOptions.whitelistUrls.length ? joinRegExp$1(globalOptions.whitelistUrls) : false; globalOptions.includePaths = joinRegExp$1(globalOptions.includePaths); - globalOptions.maxBreadcrumbs = Math.max( - 0, - Math.min(globalOptions.maxBreadcrumbs || 100, 100) - ); // default and hard limit is 100 + globalOptions.maxBreadcrumbs = Math.max(0, Math.min(globalOptions.maxBreadcrumbs || 100, 100)); // default and hard limit is 100 var autoBreadcrumbDefaults = { xhr: true, @@ -1937,30 +2035,29 @@ typeof navigator === "object" && (function () { location: true, sentry: true }; - var autoBreadcrumbs = globalOptions.autoBreadcrumbs; + if ({}.toString.call(autoBreadcrumbs) === '[object Object]') { autoBreadcrumbs = objectMerge$1(autoBreadcrumbDefaults, autoBreadcrumbs); } else if (autoBreadcrumbs !== false) { autoBreadcrumbs = autoBreadcrumbDefaults; } - globalOptions.autoBreadcrumbs = autoBreadcrumbs; + globalOptions.autoBreadcrumbs = autoBreadcrumbs; var instrumentDefaults = { tryCatch: true }; - var instrument = globalOptions.instrument; + if ({}.toString.call(instrument) === '[object Object]') { instrument = objectMerge$1(instrumentDefaults, instrument); } else if (instrument !== false) { instrument = instrumentDefaults; } + globalOptions.instrument = instrument; + tracekit.collectWindowErrors = !!globalOptions.collectWindowErrors; // return for chaining - tracekit.collectWindowErrors = !!globalOptions.collectWindowErrors; - - // return for chaining return self; }, @@ -1972,10 +2069,11 @@ typeof navigator === "object" && (function () { * * @return {Raven} */ - install: function() { + install: function install() { var self = this; + if (self.isSetup() && !self._isRavenInstalled) { - tracekit.report.subscribe(function() { + tracekit.report.subscribe(function () { self._handleOnErrorStackInfo.apply(self, arguments); }); @@ -1989,9 +2087,8 @@ typeof navigator === "object" && (function () { self._instrumentTryCatch(); } - if (self._globalOptions.autoBreadcrumbs) self._instrumentBreadcrumbs(); + if (self._globalOptions.autoBreadcrumbs) self._instrumentBreadcrumbs(); // Install all of the plugins - // Install all of the plugins self._drainPlugins(); self._isRavenInstalled = true; @@ -2006,24 +2103,20 @@ typeof navigator === "object" && (function () { * * @param {string} dsn The public Sentry DSN */ - setDSN: function(dsn) { + setDSN: function setDSN(dsn) { var self = this, - uri = self._parseDSN(dsn), - lastSlash = uri.path.lastIndexOf('/'), - path = uri.path.substr(1, lastSlash); + 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 + self._globalEndpoint = self._globalServer + '/' + path + 'api/' + self._globalProject + '/store/'; // Reset backoff state since we may be pointing at a // new project/server + this._resetBackoff(); }, @@ -2035,7 +2128,7 @@ typeof navigator === "object" && (function () { * @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) { + context: function context(options, func, args) { if (isFunction$1(options)) { args = func || []; func = options; @@ -2053,33 +2146,33 @@ typeof navigator === "object" && (function () { * @param {function} _before 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 + wrap: function wrap(options, func, _before) { + var self = this; // 1 argument has been passed, and it's not a function // so just return it + if (isUndefined$1(func) && !isFunction$1(options)) { return options; - } + } // options is optional + - // options is optional if (isFunction$1(options)) { func = options; options = undefined; - } - - // At this point, we've passed along 2 arguments, and the second one + } // 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$1(func)) { return func; - } + } // We don't wanna wrap it twice! + - // 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 this has already been wrapped in the past, return that if (func.__raven_wrapper__) { return func.__raven_wrapper__; } @@ -2092,16 +2185,18 @@ typeof navigator === "object" && (function () { function wrapped() { var args = [], - i = arguments.length, - deep = !options || (options && options.deep !== false); + i = arguments.length, + deep = !options || options && options.deep !== false; if (_before && isFunction$1(_before)) { _before.apply(this, arguments); - } - - // Recursively wrap all of a function's arguments that are + } // Recursively wrap all of a function's arguments that are // functions themselves. - while (i--) args[i] = deep ? self.wrap(options, arguments[i]) : arguments[i]; + + + while (i--) { + args[i] = deep ? self.wrap(options, arguments[i]) : arguments[i]; + } try { // Attempt to invoke user-land function @@ -2111,25 +2206,25 @@ typeof navigator === "object" && (function () { return func.apply(this, args); } catch (e) { self._ignoreNextOnError(); + self.captureException(e, options); throw e; } - } + } // copy over properties of the old function + - // copy over properties of the old function for (var property in func) { if (hasKey$1(func, property)) { wrapped[property] = func[property]; } } - wrapped.prototype = func.prototype; - func.__raven_wrapper__ = wrapped; - // Signal that this function has been wrapped/filled already + 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; }, @@ -2138,17 +2233,19 @@ typeof navigator === "object" && (function () { * * @return {Raven} */ - uninstall: function() { + uninstall: function uninstall() { tracekit.report.uninstall(); this._detachPromiseRejectionHandler(); + this._unpatchFunctionToString(); + this._restoreBuiltIns(); + this._restoreConsole(); Error.stackTraceLimit = this._originalErrorStackTraceLimit; this._isRavenInstalled = false; - return this; }, @@ -2160,8 +2257,9 @@ typeof navigator === "object" && (function () { * reason: the value with which the Promise was rejected * @return void */ - _promiseRejectionHandler: function(event) { + _promiseRejectionHandler: function _promiseRejectionHandler(event) { this._logDebug('debug', 'Raven caught unhandled promise rejection:', event); + this.captureException(event.reason, { mechanism: { type: 'onunhandledrejection', @@ -2175,10 +2273,9 @@ typeof navigator === "object" && (function () { * * @return {raven} */ - _attachPromiseRejectionHandler: function() { + _attachPromiseRejectionHandler: function _attachPromiseRejectionHandler() { this._promiseRejectionHandler = this._promiseRejectionHandler.bind(this); - _window$2.addEventListener && - _window$2.addEventListener('unhandledrejection', this._promiseRejectionHandler); + _window$2.addEventListener && _window$2.addEventListener('unhandledrejection', this._promiseRejectionHandler); return this; }, @@ -2187,9 +2284,8 @@ typeof navigator === "object" && (function () { * * @return {raven} */ - _detachPromiseRejectionHandler: function() { - _window$2.removeEventListener && - _window$2.removeEventListener('unhandledrejection', this._promiseRejectionHandler); + _detachPromiseRejectionHandler: function _detachPromiseRejectionHandler() { + _window$2.removeEventListener && _window$2.removeEventListener('unhandledrejection', this._promiseRejectionHandler); return this; }, @@ -2200,8 +2296,10 @@ typeof navigator === "object" && (function () { * @param {object} options A specific set of options for this error [optional] * @return {Raven} */ - captureException: function(ex, options) { - options = objectMerge$1({trimHeadFrames: 0}, options ? options : {}); + captureException: function captureException(ex, options) { + options = objectMerge$1({ + trimHeadFrames: 0 + }, options ? options : {}); if (isErrorEvent$1(ex) && ex.error) { // If it is an ErrorEvent with `error` property, extract it to get actual Error @@ -2213,16 +2311,12 @@ typeof navigator === "object" && (function () { // https://developer.mozilla.org/en-US/docs/Web/API/DOMException var name = ex.name || (isDOMError$1(ex) ? 'DOMError' : 'DOMException'); var message = ex.message ? name + ': ' + ex.message : name; - - return this.captureMessage( - message, - objectMerge$1(options, { - // neither DOMError or DOMException provide stack trace and we most likely wont get it this way as well - // but it's barely any overhead so we may at least try - stacktrace: true, - trimHeadFrames: options.trimHeadFrames + 1 - }) - ); + return this.captureMessage(message, objectMerge$1(options, { + // neither DOMError or DOMException provide stack trace and we most likely wont get it this way as well + // but it's barely any overhead so we may at least try + stacktrace: true, + trimHeadFrames: options.trimHeadFrames + 1 + })); } else if (isError$1(ex)) { // we have a real Error object ex = ex; @@ -2239,25 +2333,23 @@ typeof navigator === "object" && (function () { // 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$1(options, { - stacktrace: true, // if we fall back to captureMessage, default to attempting a new trace - trimHeadFrames: options.trimHeadFrames + 1 - }) - ); - } + return this.captureMessage(ex, objectMerge$1(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 - // Store the raw exception object for potential debugging and introspection - this._lastCapturedException = ex; - // TraceKit.report will re-raise any exception passed to it, + 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) { @@ -2267,17 +2359,14 @@ typeof navigator === "object" && (function () { return this; }, - - _getCaptureExceptionOptionsFromPlainObject: function(currentOptions, ex) { + _getCaptureExceptionOptionsFromPlainObject: function _getCaptureExceptionOptionsFromPlainObject(currentOptions, ex) { var exKeys = Object.keys(ex).sort(); var options = objectMerge$1(currentOptions, { - message: - 'Non-Error exception captured with keys: ' + serializeKeysForMessage$1(exKeys), + message: 'Non-Error exception captured with keys: ' + serializeKeysForMessage$1(exKeys), fingerprint: [md5_1(exKeys)], extra: currentOptions.extra || {} }); options.extra.__serialized__ = serializeException$1(ex); - return options; }, @@ -2288,113 +2377,89 @@ typeof navigator === "object" && (function () { * @param {object} options A specific set of options for this message [optional] * @return {Raven} */ - captureMessage: function(msg, options) { + captureMessage: function captureMessage(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) - ) { + 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$1( - { - message: msg - }, - options - ); - - var ex; - // Generate a "synthetic" stack trace from this point. + var data = objectMerge$1({ + 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 + - // null exception name so `Error` isn't prefixed to msg ex.name = null; - var stack = tracekit.computeStackTrace(ex); + 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] - // 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$1(stack.stack) && stack.stack[1]; - - // if stack[1] is `Raven.captureException`, it means that someone passed a string to it and we redirected that call + var initialCall = isArray$1(stack.stack) && stack.stack[1]; // if stack[1] is `Raven.captureException`, it means that someone passed a string to it and we redirected that call // to be handled by `captureMessage`, thus `initialCall` is the 3rd one, not 2nd // initialCall => captureException(string) => captureMessage(string) + if (initialCall && initialCall.func === 'Raven.captureException') { initialCall = stack.stack[2]; } - var fileurl = (initialCall && initialCall.url) || ''; + var fileurl = initialCall && initialCall.url || ''; - if ( - !!this._globalOptions.ignoreUrls.test && - this._globalOptions.ignoreUrls.test(fileurl) - ) { + if (!!this._globalOptions.ignoreUrls.test && this._globalOptions.ignoreUrls.test(fileurl)) { return; } - if ( - !!this._globalOptions.whitelistUrls.test && - !this._globalOptions.whitelistUrls.test(fileurl) - ) { + if (!!this._globalOptions.whitelistUrls.test && !this._globalOptions.whitelistUrls.test(fileurl)) { return; - } - - // Always attempt to get stacktrace if message is empty. + } // Always attempt to get stacktrace if message is empty. // It's the only way to provide any helpful information to the user. + + if (this._globalOptions.stacktrace || options.stacktrace || data.message === '') { // fingerprint on msg, not stack trace (legacy behavior, could be revisited) data.fingerprint = data.fingerprint == null ? msg : data.fingerprint; - - options = objectMerge$1( - { - trimHeadFrames: 0 - }, - options - ); - // Since we know this is a synthetic trace, the top frame (this function call) + options = objectMerge$1({ + 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 + - // Make sure that fingerprint is always wrapped in an array if (data.fingerprint) { - data.fingerprint = isArray$1(data.fingerprint) - ? data.fingerprint - : [data.fingerprint]; - } + data.fingerprint = isArray$1(data.fingerprint) ? data.fingerprint : [data.fingerprint]; + } // Fire away! + - // Fire away! this._send(data); return this; }, - - captureBreadcrumb: function(obj) { - var crumb = objectMerge$1( - { - timestamp: now() / 1000 - }, - obj - ); + captureBreadcrumb: function captureBreadcrumb(obj) { + var crumb = objectMerge$1({ + timestamp: now() / 1000 + }, obj); if (isFunction$1(this._globalOptions.breadcrumbCallback)) { var result = this._globalOptions.breadcrumbCallback(crumb); @@ -2407,16 +2472,20 @@ typeof navigator === "object" && (function () { } this._breadcrumbs.push(crumb); + if (this._breadcrumbs.length > this._globalOptions.maxBreadcrumbs) { this._breadcrumbs.shift(); } + return this; }, - - addPlugin: function(plugin /*arg1, arg2, ... argN*/) { + addPlugin: function addPlugin(plugin + /*arg1, arg2, ... argN*/ + ) { var pluginArgs = [].slice.call(arguments, 1); this._plugins.push([plugin, pluginArgs]); + if (this._isRavenInstalled) { this._drainPlugins(); } @@ -2430,10 +2499,9 @@ typeof navigator === "object" && (function () { * @param {object} user An object representing user data [optional] * @return {Raven} */ - setUserContext: function(user) { + setUserContext: function setUserContext(user) { // Intentionally do not merge here since that's an unexpected behavior. this._globalContext.user = user; - return this; }, @@ -2443,7 +2511,7 @@ typeof navigator === "object" && (function () { * @param {object} extra An object representing extra data [optional] * @return {Raven} */ - setExtraContext: function(extra) { + setExtraContext: function setExtraContext(extra) { this._mergeContext('extra', extra); return this; @@ -2455,7 +2523,7 @@ typeof navigator === "object" && (function () { * @param {object} tags An object representing tags [optional] * @return {Raven} */ - setTagsContext: function(tags) { + setTagsContext: function setTagsContext(tags) { this._mergeContext('tags', tags); return this; @@ -2466,9 +2534,8 @@ typeof navigator === "object" && (function () { * * @return {Raven} */ - clearContext: function() { + clearContext: function clearContext() { this._globalContext = {}; - return this; }, @@ -2477,7 +2544,7 @@ typeof navigator === "object" && (function () { * * @return {object} copy of context */ - getContext: function() { + getContext: function getContext() { // lol javascript return JSON.parse(stringify_1(this._globalContext)); }, @@ -2488,9 +2555,8 @@ typeof navigator === "object" && (function () { * @param {string} environment Typically something like 'production'. * @return {Raven} */ - setEnvironment: function(environment) { + setEnvironment: function setEnvironment(environment) { this._globalOptions.environment = environment; - return this; }, @@ -2500,9 +2566,8 @@ typeof navigator === "object" && (function () { * @param {string} release Typically something like a git SHA to identify version * @return {Raven} */ - setRelease: function(release) { + setRelease: function setRelease(release) { this._globalOptions.release = release; - return this; }, @@ -2513,7 +2578,7 @@ typeof navigator === "object" && (function () { * data blob to be mutated before sending * @return {Raven} */ - setDataCallback: function(callback) { + setDataCallback: function setDataCallback(callback) { var original = this._globalOptions.dataCallback; this._globalOptions.dataCallback = keepOriginalCallback(original, callback); return this; @@ -2526,7 +2591,7 @@ typeof navigator === "object" && (function () { * or mutating breadcrumbs * @return {Raven} */ - setBreadcrumbCallback: function(callback) { + setBreadcrumbCallback: function setBreadcrumbCallback(callback) { var original = this._globalOptions.breadcrumbCallback; this._globalOptions.breadcrumbCallback = keepOriginalCallback(original, callback); return this; @@ -2539,7 +2604,7 @@ typeof navigator === "object" && (function () { * introspecting the blob before sending * @return {Raven} */ - setShouldSendCallback: function(callback) { + setShouldSendCallback: function setShouldSendCallback(callback) { var original = this._globalOptions.shouldSendCallback; this._globalOptions.shouldSendCallback = keepOriginalCallback(original, callback); return this; @@ -2554,9 +2619,8 @@ typeof navigator === "object" && (function () { * * @return {Raven} */ - setTransport: function(transport) { + setTransport: function setTransport(transport) { this._globalOptions.transport = transport; - return this; }, @@ -2565,7 +2629,7 @@ typeof navigator === "object" && (function () { * * @return {error} */ - lastException: function() { + lastException: function lastException() { return this._lastCapturedException; }, @@ -2574,7 +2638,7 @@ typeof navigator === "object" && (function () { * * @return {string} */ - lastEventId: function() { + lastEventId: function lastEventId() { return this._lastEventId; }, @@ -2583,42 +2647,38 @@ typeof navigator === "object" && (function () { * * @return {boolean} */ - isSetup: function() { + isSetup: function isSetup() { 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() { + afterLoad: function afterLoad() { // TODO: remove window dependence? - // Attempt to initialize Raven on load var RavenConfig = _window$2.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 = objectMerge$1( - { - eventId: this.lastEventId(), - dsn: this._dsn, - user: this._globalContext.user || {} - }, - options - ); + showReportDialog: function showReportDialog(options) { + if (!_document // doesn't work without a document (React native) + ) return; + options = objectMerge$1({ + eventId: this.lastEventId(), + dsn: this._dsn, + user: this._globalContext.user || {} + }, options); if (!options.eventId) { throw new configError('Missing eventId'); @@ -2640,32 +2700,31 @@ typeof navigator === "object" && (function () { encodedOptions.push(encode(key) + '=' + encode(options[key])); } } + var globalServer = this._getGlobalServer(this._parseDSN(options.dsn)); var script = _document.createElement('script'); + script.async = true; script.src = globalServer + '/api/embed/error-page/?' + encodedOptions.join('&'); + (_document.head || _document.body).appendChild(script); }, /**** Private functions ****/ - _ignoreNextOnError: function() { + _ignoreNextOnError: function _ignoreNextOnError() { var self = this; this._ignoreOnError += 1; - setTimeout(function() { + setTimeout(function () { // onerror should trigger before setTimeout self._ignoreOnError -= 1; }); }, - - _triggerEvent: function(eventType, options) { + _triggerEvent: function _triggerEvent(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) { @@ -2676,10 +2735,11 @@ typeof navigator === "object" && (function () { evt.eventType = eventType; } - for (key in options) + for (key in options) { if (hasKey$1(options, key)) { evt[key] = options[key]; } + } if (_document.createEvent) { // IE9 if standards @@ -2689,8 +2749,7 @@ typeof navigator === "object" && (function () { // IE9 if quirks try { _document.fireEvent('on' + evt.eventType.toLowerCase(), evt); - } catch (e) { - // Do nothing + } catch (e) {// Do nothing } } }, @@ -2701,26 +2760,24 @@ typeof navigator === "object" && (function () { * @returns {Function} * @private */ - _breadcrumbEventHandler: function(evtName) { + _breadcrumbEventHandler: function _breadcrumbEventHandler(evtName) { var self = this; - return function(evt) { + 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 + 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: + 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$1(evt.target); } catch (e) { @@ -2728,7 +2785,8 @@ typeof navigator === "object" && (function () { } self.captureBreadcrumb({ - category: 'ui.' + evtName, // e.g. ui.click, ui.input + category: 'ui.' + evtName, + // e.g. ui.click, ui.input message: target }); }; @@ -2739,15 +2797,16 @@ typeof navigator === "object" && (function () { * @returns {Function} * @private */ - _keypressEventHandler: function() { + _keypressEventHandler: function _keypressEventHandler() { var self = this, - debounceDuration = 1000; // milliseconds - + 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) { + + return function (evt) { var target; + try { target = evt.target; } catch (e) { @@ -2755,25 +2814,22 @@ typeof navigator === "object" && (function () { // see: https://github.com/getsentry/raven-js/issues/838 return; } - var tagName = target && target.tagName; - // only consider keypress events on actual input elements + 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 + 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 = setTimeout(function () { self._keypressTimeout = null; }, debounceDuration); }; @@ -2785,23 +2841,18 @@ typeof navigator === "object" && (function () { * @param from the target URL * @private */ - _captureUrlChange: function(from, to) { + _captureUrlChange: function _captureUrlChange(from, to) { var parsedLoc = parseUrl$1(this._location.href); var parsedTo = parseUrl$1(to); - var parsedFrom = parseUrl$1(from); - - // because onpopstate only tells you the "new" (to) value of location.href, and + var parsedFrom = parseUrl$1(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 + 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; + 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: { @@ -2810,20 +2861,19 @@ typeof navigator === "object" && (function () { } }); }, - - _patchFunctionToString: function() { + _patchFunctionToString: function _patchFunctionToString() { var self = this; - self._originalFunctionToString = Function.prototype.toString; - // eslint-disable-next-line no-extend-native - Function.prototype.toString = function() { + 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() { + _unpatchFunctionToString: function _unpatchFunctionToString() { if (this._originalFunctionToString) { // eslint-disable-next-line no-extend-native Function.prototype.toString = this._originalFunctionToString; @@ -2834,36 +2884,37 @@ typeof navigator === "object" && (function () { * Wrap timer functions and event targets to catch errors and provide * better metadata. */ - _instrumentTryCatch: function() { + _instrumentTryCatch: function _instrumentTryCatch() { var self = this; - var wrappedBuiltIns = self._wrappedBuiltIns; function wrapTimeFn(orig) { - return function(fn, t) { + 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$1(originalCallback)) { - args[0] = self.wrap( - { - mechanism: { - type: 'instrument', - data: {function: orig.name || ''} - } - }, - originalCallback - ); - } - // IE < 9 doesn't support .call/.apply on setInterval/setTimeout, but it + var originalCallback = args[0]; + + if (isFunction$1(originalCallback)) { + args[0] = self.wrap({ + mechanism: { + type: 'instrument', + data: { + function: orig.name || '' + } + } + }, 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 { @@ -2876,167 +2927,104 @@ typeof navigator === "object" && (function () { function wrapEventTarget(global) { var proto = _window$2[global] && _window$2[global].prototype; + if (proto && proto.hasOwnProperty && proto.hasOwnProperty('addEventListener')) { - fill$1( - proto, - 'addEventListener', - function(orig) { - return function(evtName, fn, capture, secure) { - // preserve arity - try { - if (fn && fn.handleEvent) { - fn.handleEvent = self.wrap( - { - mechanism: { - type: 'instrument', - data: { - target: global, - function: 'handleEvent', - handler: (fn && fn.name) || '' - } - } - }, - 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( - { - mechanism: { - type: 'instrument', - data: { - target: global, - function: 'addEventListener', - handler: (fn && fn.name) || '' - } + fill$1(proto, 'addEventListener', function (orig) { + return function (evtName, fn, capture, secure) { + // preserve arity + try { + if (fn && fn.handleEvent) { + fn.handleEvent = self.wrap({ + mechanism: { + type: 'instrument', + data: { + target: global, + function: 'handleEvent', + handler: fn && fn.name || '' } - }, - fn, - before - ), - capture, - secure - ); - }; - }, - wrappedBuiltIns - ); - fill$1( - 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 + } + }, fn.handleEvent); } - return orig.call(this, evt, fn, capture, secure); - }; - }, - wrappedBuiltIns - ); + } 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 before(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({ + mechanism: { + type: 'instrument', + data: { + target: global, + function: 'addEventListener', + handler: fn && fn.name || '' + } + } + }, fn, before), capture, secure); + }; + }, wrappedBuiltIns); + fill$1(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$1(_window$2, 'setTimeout', wrapTimeFn, wrappedBuiltIns); fill$1(_window$2, 'setInterval', wrapTimeFn, wrappedBuiltIns); - if (_window$2.requestAnimationFrame) { - fill$1( - _window$2, - 'requestAnimationFrame', - function(orig) { - return function(cb) { - return orig( - self.wrap( - { - mechanism: { - type: 'instrument', - data: { - function: 'requestAnimationFrame', - handler: (orig && orig.name) || '' - } - } - }, - cb - ) - ); - }; - }, - wrappedBuiltIns - ); - } - // event targets borrowed from bugsnag-js: + if (_window$2.requestAnimationFrame) { + fill$1(_window$2, 'requestAnimationFrame', function (orig) { + return function (cb) { + return orig(self.wrap({ + mechanism: { + type: 'instrument', + data: { + function: 'requestAnimationFrame', + handler: orig && orig.name || '' + } + } + }, 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' - ]; + + + 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]); } @@ -3051,219 +3039,192 @@ typeof navigator === "object" && (function () { * * Can be disabled or individually configured via the `autoBreadcrumbs` config option */ - _instrumentBreadcrumbs: function() { + _instrumentBreadcrumbs: function _instrumentBreadcrumbs() { var self = this; var autoBreadcrumbs = this._globalOptions.autoBreadcrumbs; - var wrappedBuiltIns = self._wrappedBuiltIns; function wrapProp(prop, xhr) { if (prop in xhr && isFunction$1(xhr[prop])) { - fill$1(xhr, prop, function(orig) { - return self.wrap( - { - mechanism: { - type: 'instrument', - data: {function: prop, handler: (orig && orig.name) || ''} + fill$1(xhr, prop, function (orig) { + return self.wrap({ + mechanism: { + type: 'instrument', + data: { + function: prop, + handler: orig && orig.name || '' } - }, - orig - ); + } + }, orig); }); // intentionally don't track filled methods on XHR instances } } if (autoBreadcrumbs.xhr && 'XMLHttpRequest' in _window$2) { var xhrproto = _window$2.XMLHttpRequest && _window$2.XMLHttpRequest.prototype; - fill$1( - xhrproto, - 'open', - function(origOpen) { - return function(method, url) { - // preserve arity - - // if Sentry key appears in URL, don't capture - if (isString$1(url) && url.indexOf(self._globalKey) === -1) { - this.__raven_xhr = { - method: method, - url: url, - status_code: null - }; - } - - return origOpen.apply(this, arguments); - }; - }, - wrappedBuiltIns - ); - - fill$1( - 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 */ - } - - 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$1(xhr.onreadystatechange)) { - fill$1( - xhr, - 'onreadystatechange', - function(orig) { - return self.wrap( - { - mechanism: { - type: 'instrument', - data: { - function: 'onreadystatechange', - handler: (orig && orig.name) || '' - } - } - }, - orig, - 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$1()) { - fill$1( - _window$2, - '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$2 && fetchInput instanceof _window$2.Request) { - url = fetchInput.url; - if (fetchInput.method) { - method = fetchInput.method; - } - } 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 = { + fill$1(xhrproto, 'open', function (origOpen) { + return function (method, url) { + // preserve arity + // if Sentry key appears in URL, don't capture + if (isString$1(url) && url.indexOf(self._globalKey) === -1) { + this.__raven_xhr = { method: method, url: url, status_code: null }; + } - return origFetch - .apply(this, args) - .then(function(response) { - fetchData.status_code = response.status; + return origOpen.apply(this, arguments); + }; + }, wrappedBuiltIns); + fill$1(xhrproto, 'send', function (origSend) { + return function () { + // preserve arity + var xhr = this; - self.captureBreadcrumb({ - type: 'http', - category: 'fetch', - data: fetchData - }); + 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 */ + } - return response; - }) - ['catch'](function(err) { - // if there is an error performing the request - self.captureBreadcrumb({ - type: 'http', - category: 'fetch', - data: fetchData, - level: 'error' - }); - - throw err; + self.captureBreadcrumb({ + type: 'http', + category: 'xhr', + data: xhr.__raven_xhr }); - }; - }, - wrappedBuiltIns - ); + } + } + + var props = ['onload', 'onerror', 'onprogress']; + + for (var j = 0; j < props.length; j++) { + wrapProp(props[j], xhr); + } + + if ('onreadystatechange' in xhr && isFunction$1(xhr.onreadystatechange)) { + fill$1(xhr, 'onreadystatechange', function (orig) { + return self.wrap({ + mechanism: { + type: 'instrument', + data: { + function: 'onreadystatechange', + handler: orig && orig.name || '' + } + } + }, orig, 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); } - // Capture breadcrumbs from any click that is unhandled / bubbled up all the way + if (autoBreadcrumbs.xhr && supportsFetch$1()) { + fill$1(_window$2, '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$2 && fetchInput instanceof _window$2.Request) { + url = fetchInput.url; + + if (fetchInput.method) { + method = fetchInput.method; + } + } 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 response; + })['catch'](function (err) { + // if there is an error performing the request + self.captureBreadcrumb({ + type: 'http', + category: 'fetch', + data: fetchData, + level: 'error' + }); + throw err; + }); + }; + }, 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 + } // 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$2.chrome; var isChromePackagedApp = chrome && chrome.app && chrome.app.runtime; - var hasPushAndReplaceState = - !isChromePackagedApp && - _window$2.history && - _window$2.history.pushState && - _window$2.history.replaceState; + var hasPushAndReplaceState = !isChromePackagedApp && _window$2.history && _window$2.history.pushState && _window$2.history.replaceState; + if (autoBreadcrumbs.location && hasPushAndReplaceState) { // TODO: remove onpopstate handler on uninstall() var oldOnPopState = _window$2.onpopstate; - _window$2.onpopstate = function() { + + _window$2.onpopstate = function () { var currentHref = self._location.href; + self._captureUrlChange(self._lastHref, currentHref); if (oldOnPopState) { @@ -3271,13 +3232,14 @@ typeof navigator === "object" && (function () { } }; - var historyReplacementFunction = function(origHistFunction) { + var historyReplacementFunction = function historyReplacementFunction(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; + return function () + /* state, title, url */ + { + var url = arguments.length > 2 ? arguments[2] : undefined; // url argument is optional - // url argument is optional if (url) { // coerce to string (this is what pushState does) self._captureUrlChange(self._lastHref, url + ''); @@ -3293,7 +3255,7 @@ typeof navigator === "object" && (function () { if (autoBreadcrumbs.console && 'console' in _window$2 && console.log) { // console - var consoleMethodCallback = function(msg, data) { + var consoleMethodCallback = function consoleMethodCallback(msg, data) { self.captureBreadcrumb({ message: msg, level: data.level, @@ -3301,88 +3263,79 @@ typeof navigator === "object" && (function () { }); }; - each$1(['debug', 'info', 'warn', 'error', 'log'], function(_, level) { + each$1(['debug', 'info', 'warn', 'error', 'log'], function (_, level) { wrapConsoleMethod(console, level, consoleMethodCallback); }); } }, - - _restoreBuiltIns: function() { + _restoreBuiltIns: function _restoreBuiltIns() { // restore any wrapped builtins var builtin; + while (this._wrappedBuiltIns.length) { builtin = this._wrappedBuiltIns.shift(); - var obj = builtin[0], - name = builtin[1], - orig = builtin[2]; - + name = builtin[1], + orig = builtin[2]; obj[name] = orig; } }, - - _restoreConsole: function() { + _restoreConsole: function _restoreConsole() { // eslint-disable-next-line guard-for-in for (var method in this._originalConsoleMethods) { this._originalConsole[method] = this._originalConsoleMethods[method]; } }, + _drainPlugins: function _drainPlugins() { + var self = this; // FIX ME TODO - _drainPlugins: function() { - var self = this; - - // FIX ME TODO - each$1(this._plugins, function(_, plugin) { + each$1(this._plugins, function (_, plugin) { var installer = plugin[0]; var args = plugin[1]; installer.apply(self, [self].concat(args)); }); }, - - _parseDSN: function(str) { + _parseDSN: function _parseDSN(str) { var m = dsnPattern.exec(str), - dsn = {}, - i = 7; + dsn = {}, + i = 7; try { - while (i--) dsn[dsnKeys[i]] = m[i] || ''; + while (i--) { + dsn[dsnKeys[i]] = m[i] || ''; + } } catch (e) { throw new configError('Invalid DSN: ' + str); } if (dsn.pass && !this._globalOptions.allowSecretKey) { - throw new configError( - 'Do not specify your secret key in the DSN. See: http://bit.ly/raven-secret-key' - ); + throw new configError('Do not specify your secret key in the DSN. See: http://bit.ly/raven-secret-key'); } return dsn; }, - - _getGlobalServer: function(uri) { + _getGlobalServer: function _getGlobalServer(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(stackInfo, options) { + _handleOnErrorStackInfo: function _handleOnErrorStackInfo(stackInfo, options) { options = options || {}; options.mechanism = options.mechanism || { type: 'onerror', handled: false - }; + }; // if we are intentionally ignoring errors via onerror, bail out - // if we are intentionally ignoring errors via onerror, bail out if (!this._ignoreOnError) { this._handleStackInfo(stackInfo, options); } }, - - _handleStackInfo: function(stackInfo, options) { + _handleStackInfo: function _handleStackInfo(stackInfo, options) { var frames = this._prepareFrames(stackInfo, options); this._triggerEvent('handle', { @@ -3390,216 +3343,180 @@ typeof navigator === "object" && (function () { options: options }); - this._processException( - stackInfo.name, - stackInfo.message, - stackInfo.url, - stackInfo.lineno, - frames, - options - ); + this._processException(stackInfo.name, stackInfo.message, stackInfo.url, stackInfo.lineno, frames, options); }, - - _prepareFrames: function(stackInfo, options) { + _prepareFrames: function _prepareFrames(stackInfo, options) { var self = this; var frames = []; + if (stackInfo.stack && stackInfo.stack.length) { - each$1(stackInfo.stack, function(i, stack) { + each$1(stackInfo.stack, function (i, stack) { var frame = self._normalizeFrame(stack, stackInfo.url); + if (frame) { frames.push(frame); } - }); + }); // e.g. frames captured via captureMessage throw - // 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) { + _normalizeFrame: function _normalizeFrame(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 + }; // 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 + 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) - ); - + !!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) { + _processException: function _processException(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)) - ) { + + 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 + fileurl = frames[0].filename || fileurl; // Sentry expects frames oldest to newest // and JS sends them as newest to oldest + frames.reverse(); - stacktrace = {frames: frames}; + stacktrace = { + frames: frames + }; } else if (fileurl) { stacktrace = { - frames: [ - { - filename: fileurl, - lineno: lineno, - in_app: true - } - ] + frames: [{ + filename: fileurl, + lineno: lineno, + in_app: true + }] }; } - if ( - !!this._globalOptions.ignoreUrls.test && - this._globalOptions.ignoreUrls.test(fileurl) - ) { + if (!!this._globalOptions.ignoreUrls.test && this._globalOptions.ignoreUrls.test(fileurl)) { return; } - if ( - !!this._globalOptions.whitelistUrls.test && - !this._globalOptions.whitelistUrls.test(fileurl) - ) { + if (!!this._globalOptions.whitelistUrls.test && !this._globalOptions.whitelistUrls.test(fileurl)) { return; } - var data = objectMerge$1( - { - // sentry.interfaces.Exception - exception: { - values: [ - { - type: type, - value: message, - stacktrace: stacktrace - } - ] - }, - transaction: fileurl + var data = objectMerge$1({ + // sentry.interfaces.Exception + exception: { + values: [{ + type: type, + value: message, + stacktrace: stacktrace + }] }, - options - ); - + transaction: fileurl + }, options); var ex = data.exception.values[0]; + if (ex.type == null && ex.value === '') { ex.value = 'Unrecoverable error caught'; - } - - // Move mechanism from options to exception interface + } // Move mechanism from options to exception interface // We do this, as requiring user to pass `{exception:{mechanism:{ ... }}}` would be // too much + + if (!data.exception.mechanism && data.mechanism) { data.exception.mechanism = data.mechanism; delete data.mechanism; } - data.exception.mechanism = objectMerge$1( - { - type: 'generic', - handled: true - }, - data.exception.mechanism || {} - ); + data.exception.mechanism = objectMerge$1({ + type: 'generic', + handled: true + }, data.exception.mechanism || {}); // Fire away! - // Fire away! this._send(data); }, - - _trimPacket: function(data) { + _trimPacket: function _trimPacket(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$1(data.message, max); } + if (data.exception) { var exception = data.exception.values[0]; exception.value = truncate$1(exception.value, max); } var request = data.request; + if (request) { if (request.url) { request.url = truncate$1(request.url, this._globalOptions.maxUrlLength); } + if (request.Referer) { request.Referer = truncate$1(request.Referer, this._globalOptions.maxUrlLength); } } - if (data.breadcrumbs && data.breadcrumbs.values) - this._trimBreadcrumbs(data.breadcrumbs); - + if (data.breadcrumbs && data.breadcrumbs.values) this._trimBreadcrumbs(data.breadcrumbs); return data; }, /** * Truncate breadcrumb values (right now just URLs) */ - _trimBreadcrumbs: function(breadcrumbs) { + _trimBreadcrumbs: function _trimBreadcrumbs(breadcrumbs) { // known breadcrumb properties with urls // TODO: also consider arbitrary prop values that start with (https?)?:// var urlProps = ['to', 'from', 'url'], - urlProp, - crumb, - data; + urlProp, + crumb, + data; for (var i = 0; i < breadcrumbs.values.length; ++i) { crumb = breadcrumbs.values[i]; - if ( - !crumb.hasOwnProperty('data') || - !isObject$1(crumb.data) || - objectFrozen$1(crumb.data) - ) - continue; - + if (!crumb.hasOwnProperty('data') || !isObject$1(crumb.data) || objectFrozen$1(crumb.data)) continue; data = objectMerge$1({}, crumb.data); + for (var j = 0; j < urlProps.length; ++j) { urlProp = urlProps[j]; + if (data.hasOwnProperty(urlProp) && data[urlProp]) { data[urlProp] = truncate$1(data[urlProp], this._globalOptions.maxUrlLength); } } + breadcrumbs.values[i].data = data; } }, - - _getHttpData: function() { + _getHttpData: function _getHttpData() { if (!this._hasNavigator && !this._hasDocument) return; var httpData = {}; @@ -3607,9 +3524,9 @@ typeof navigator === "object" && (function () { httpData.headers = { 'User-Agent': _navigator.userAgent }; - } + } // Check in `window` instead of `document`, as we may be in ServiceWorker environment + - // Check in `window` instead of `document`, as we may be in ServiceWorker environment if (_window$2.location && _window$2.location.href) { httpData.url = _window$2.location.href; } @@ -3621,13 +3538,11 @@ typeof navigator === "object" && (function () { return httpData; }, - - _resetBackoff: function() { + _resetBackoff: function _resetBackoff() { this._backoffDuration = 0; this._backoffStart = null; }, - - _shouldBackoff: function() { + _shouldBackoff: function _shouldBackoff() { return this._backoffDuration && now() - this._backoffStart < this._backoffDuration; }, @@ -3640,17 +3555,12 @@ typeof navigator === "object" && (function () { * 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) { + _isRepeatData: function _isRepeatData(current) { var last = this._lastData; + if (!last || current.message !== last.message || // defined for captureMessage + current.transaction !== last.transaction // defined for captureException/onerror + ) return false; // Stacktrace interface (i.e. from captureMessage) - if ( - !last || - current.message !== last.message || // defined for captureMessage - current.transaction !== last.transaction // defined for captureException/onerror - ) - return false; - - // Stacktrace interface (i.e. from captureMessage) if (current.stacktrace || last.stacktrace) { return isSameStacktrace$1(current.stacktrace, last.stacktrace); } else if (current.exception || last.exception) { @@ -3660,21 +3570,19 @@ typeof navigator === "object" && (function () { return true; }, - - _setBackoffState: function(request) { + _setBackoffState: function _setBackoffState(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 + 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; + 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 @@ -3682,47 +3590,40 @@ typeof navigator === "object" && (function () { retry = request.headers.get('Retry-After'); } else { retry = request.getResponseHeader('Retry-After'); - } + } // Retry-After is returned in seconds + - // 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._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) { + _send: function _send(data) { var globalOptions = this._globalOptions; var baseData = { - project: this._globalProject, - logger: globalOptions.logger, - platform: 'javascript' - }, - httpData = this._getHttpData(); + 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 + - // HACK: delete `trimHeadFrames` to prevent from appearing in outbound payload if (data.trimHeadFrames) delete data.trimHeadFrames; + data = objectMerge$1(baseData, data); // Merge in the tags and extra separately since objectMerge doesn't handle a deep merge - data = objectMerge$1(baseData, data); - - // Merge in the tags and extra separately since objectMerge doesn't handle a deep merge data.tags = objectMerge$1(objectMerge$1({}, this._globalContext.tags), data.tags); - data.extra = objectMerge$1(objectMerge$1({}, this._globalContext.extra), data.extra); + data.extra = objectMerge$1(objectMerge$1({}, this._globalContext.extra), data.extra); // Send along our own collected metadata with extra - // Send along our own collected metadata with extra data.extra['session:duration'] = now() - this._startTime; if (this._breadcrumbs && this._breadcrumbs.length > 0) { @@ -3736,21 +3637,17 @@ typeof navigator === "object" && (function () { if (this._globalContext.user) { // sentry.interfaces.User data.user = this._globalContext.user; - } + } // Include the environment if it's defined in globalOptions - // 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; + 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 - // 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 - data = this._sanitizeData(data); - - // Cleanup empty properties before sending them to the server - Object.keys(data).forEach(function(key) { + Object.keys(data).forEach(function (key) { if (data[key] == null || data[key] === '' || isEmptyObject$1(data[key])) { delete data[key]; } @@ -3758,25 +3655,23 @@ typeof navigator === "object" && (function () { if (isFunction$1(globalOptions.dataCallback)) { data = globalOptions.dataCallback(data) || data; - } + } // Why?????????? + - // Why?????????? if (!data || isEmptyObject$1(data)) { return; - } + } // Check if the request should be filtered or not - // Check if the request should be filtered or not - if ( - isFunction$1(globalOptions.shouldSendCallback) && - !globalOptions.shouldSendCallback(data) - ) { + + if (isFunction$1(globalOptions.shouldSendCallback) && !globalOptions.shouldSendCallback(data)) { return; - } - - // Backoff state: Sentry server previously responded w/ an error (e.g. 429 - too many requests), + } // 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; } @@ -3788,38 +3683,32 @@ typeof navigator === "object" && (function () { this._sendProcessedPayload(data); } }, - - _sanitizeData: function(data) { + _sanitizeData: function _sanitizeData(data) { return sanitize$1(data, this._globalOptions.sanitizeKeys); }, - - _getUuid: function() { + _getUuid: function _getUuid() { return uuid4$1(); }, - - _sendProcessedPayload: function(data, callback) { + _sendProcessedPayload: function _sendProcessedPayload(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 - 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, + 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. + 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._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); @@ -3834,24 +3723,20 @@ typeof navigator === "object" && (function () { auth.sentry_secret = this._globalSecret; } - var exception = data.exception && data.exception.values[0]; + var exception = data.exception && data.exception.values[0]; // only capture 'sentry' breadcrumb is autoBreadcrumbs is truthy - // only capture 'sentry' breadcrumb is autoBreadcrumbs is truthy - if ( - this._globalOptions.autoBreadcrumbs && - this._globalOptions.autoBreadcrumbs.sentry - ) { + if (this._globalOptions.autoBreadcrumbs && this._globalOptions.autoBreadcrumbs.sentry) { this.captureBreadcrumb({ category: 'sentry', - message: exception - ? (exception.type ? exception.type + ': ' : '') + exception.value - : data.message, + 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, @@ -3864,6 +3749,7 @@ typeof navigator === "object" && (function () { data: data, src: url }); + callback && callback(); }, onError: function failure(error) { @@ -3877,16 +3763,15 @@ typeof navigator === "object" && (function () { data: data, src: url }); + error = error || new Error('Raven send failed (no additional details provided)'); callback && callback(error); } }); }, - - _makeRequest: function(opts) { + _makeRequest: function _makeRequest(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$1(opts.auth); - var evaluatedHeaders = null; var evaluatedFetchParameters = {}; @@ -3900,7 +3785,6 @@ typeof navigator === "object" && (function () { if (supportsFetch$1()) { evaluatedFetchParameters.body = stringify_1(opts.data); - var defaultFetchOptions = objectMerge$1({}, this._fetchDefaults); var fetchOptions = objectMerge$1(defaultFetchOptions, evaluatedFetchParameters); @@ -3908,35 +3792,29 @@ typeof navigator === "object" && (function () { fetchOptions.headers = evaluatedHeaders; } - return _window$2 - .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')); - }); + return _window$2.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$2.XMLHttpRequest && new _window$2.XMLHttpRequest(); - if (!request) return; + if (!request) return; // if browser doesn't support CORS (e.g. IE7), we are out of luck - // 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() { + request.onreadystatechange = function () { if (request.readyState !== 4) { return; } else if (request.status === 200) { @@ -3948,17 +3826,17 @@ typeof navigator === "object" && (function () { } }; } else { - request = new XDomainRequest(); - // xdomainrequest cannot go http -> https (or vice versa), + 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 + url = url.replace(/^https?:/, ''); // onreadystatechange not supported by XDomainRequest + if (opts.onSuccess) { request.onload = opts.onSuccess; } + if (opts.onError) { - request.onerror = function() { + request.onerror = function () { var err = new Error('Sentry error code: XDomainRequest'); err.request = request; opts.onError(err); @@ -3969,15 +3847,14 @@ typeof navigator === "object" && (function () { request.open('POST', url); if (evaluatedHeaders) { - each$1(evaluatedHeaders, function(key, value) { + each$1(evaluatedHeaders, function (key, value) { request.setRequestHeader(key, value); }); } request.send(stringify_1(opts.data)); }, - - _evaluateHash: function(hash) { + _evaluateHash: function _evaluateHash(hash) { var evaluated = {}; for (var key in hash) { @@ -3989,35 +3866,24 @@ typeof navigator === "object" && (function () { return evaluated; }, - - _logDebug: function(level) { + _logDebug: function _logDebug(level) { // We allow `Raven.debug` and `Raven.config(DSN, { debug: true })` to not make backward incompatible API change - if ( - this._originalConsoleMethods[level] && - (this.debug || this._globalOptions.debug) - ) { + if (this._originalConsoleMethods[level] && (this.debug || this._globalOptions.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) - ); + Function.prototype.apply.call(this._originalConsoleMethods[level], this._originalConsole, [].slice.call(arguments, 1)); } }, - - _mergeContext: function(key, context) { + _mergeContext: function _mergeContext(key, context) { if (isUndefined$1(context)) { delete this._globalContext[key]; } else { this._globalContext[key] = objectMerge$1(this._globalContext[key] || {}, context); } } - }; + }; // Deprecations - // Deprecations Raven.prototype.setUser = Raven.prototype.setUserContext; Raven.prototype.setReleaseContext = Raven.prototype.setRelease; - var raven = Raven; /** @@ -4025,33 +3891,26 @@ typeof navigator === "object" && (function () { * main entry point for Raven. If you are a consumer of the * Raven library, you SHOULD load this file (vs raven.js). **/ - - - // This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785) - var _window$3 = - typeof window !== 'undefined' - ? window - : typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : typeof self !== 'undefined' ? self : {}; + + var _window$3 = typeof window !== 'undefined' ? window : typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : typeof self !== 'undefined' ? self : {}; + var _Raven = _window$3.Raven; - var Raven$1 = new raven(); - /* * Allow multiple versions of Raven to be installed. * Strip Raven from the global context and returns the instance. * * @return {Raven} */ - Raven$1.noConflict = function() { + + Raven$1.noConflict = function () { _window$3.Raven = _Raven; return Raven$1; }; Raven$1.afterLoad(); - var singleton = Raven$1; - /** * DISCLAIMER: * @@ -4086,9 +3945,8826 @@ typeof navigator === "object" && (function () { * * It should "just work". */ + var Client = raven; singleton.Client = Client; + var defaults = { + addCSS: true, + // Add CSS to the element to improve usability (required here or in your CSS!) + thumbWidth: 15, + // The width of the thumb handle + watch: true // Watch for new elements that match a string target + + }; + + // Element matches a selector + function matches(element, selector) { + + function match() { + return Array.from(document.querySelectorAll(selector)).includes(this); + } + + var matches = match; + return matches.call(element, selector); + } + + // Trigger event + function trigger(element, type) { + if (!element || !type) { + return; + } // Create and dispatch the event + + + var event = new Event(type); // Dispatch the event + + element.dispatchEvent(event); + } + + // ========================================================================== + // Type checking utils + // ========================================================================== + var getConstructor = function getConstructor(input) { + return input !== null && typeof input !== 'undefined' ? input.constructor : null; + }; + + var instanceOf = function instanceOf(input, constructor) { + return Boolean(input && constructor && input instanceof constructor); + }; + + var isNullOrUndefined = function isNullOrUndefined(input) { + return input === null || typeof input === 'undefined'; + }; + + var isObject$2 = function isObject(input) { + return getConstructor(input) === Object; + }; + + var isNumber = function isNumber(input) { + return getConstructor(input) === Number && !Number.isNaN(input); + }; + + var isString$2 = function isString(input) { + return getConstructor(input) === String; + }; + + var isBoolean = function isBoolean(input) { + return getConstructor(input) === Boolean; + }; + + var isFunction$2 = function isFunction(input) { + return getConstructor(input) === Function; + }; + + var isArray$2 = function isArray(input) { + return Array.isArray(input); + }; + + var isNodeList = function isNodeList(input) { + return instanceOf(input, NodeList); + }; + + var isElement = function isElement(input) { + return instanceOf(input, Element); + }; + + var isEvent = function isEvent(input) { + return instanceOf(input, Event); + }; + + var isEmpty = function isEmpty(input) { + return isNullOrUndefined(input) || (isString$2(input) || isArray$2(input) || isNodeList(input)) && !input.length || isObject$2(input) && !Object.keys(input).length; + }; + + var is = { + nullOrUndefined: isNullOrUndefined, + object: isObject$2, + number: isNumber, + string: isString$2, + boolean: isBoolean, + function: isFunction$2, + array: isArray$2, + nodeList: isNodeList, + element: isElement, + event: isEvent, + empty: isEmpty + }; + + // Get the number of decimal places + function getDecimalPlaces(value) { + var match = "".concat(value).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/); + + if (!match) { + return 0; + } + + return Math.max(0, // Number of digits right of decimal point. + (match[1] ? match[1].length : 0) - ( // Adjust for scientific notation. + match[2] ? +match[2] : 0)); + } // Round to the nearest step + + function round(number, step) { + if (step < 1) { + var places = getDecimalPlaces(step); + return parseFloat(number.toFixed(places)); + } + + return Math.round(number / step) * step; + } + + var RangeTouch = + /*#__PURE__*/ + function () { + /** + * Setup a new instance + * @param {String|Element} target + * @param {Object} options + */ + function RangeTouch(target, options) { + _classCallCheck(this, RangeTouch); + + if (is.element(target)) { + // An Element is passed, use it directly + this.element = target; + } else if (is.string(target)) { + // A CSS Selector is passed, fetch it from the DOM + this.element = document.querySelector(target); + } + + if (!is.element(this.element) || !is.empty(this.element.rangeTouch)) { + return; + } + + this.config = Object.assign({}, defaults, options); + this.init(); + } + + _createClass(RangeTouch, [{ + key: "init", + value: function init() { + // Bail if not a touch enabled device + if (!RangeTouch.enabled) { + return; + } // Add useful CSS + + + if (this.config.addCSS) { + // TODO: Restore original values on destroy + this.element.style.userSelect = 'none'; + this.element.style.webKitUserSelect = 'none'; + this.element.style.touchAction = 'manipulation'; + } + + this.listeners(true); + this.element.rangeTouch = this; + } + }, { + key: "destroy", + value: function destroy() { + // Bail if not a touch enabled device + if (!RangeTouch.enabled) { + return; + } + + this.listeners(false); + this.element.rangeTouch = null; + } + }, { + key: "listeners", + value: function listeners(toggle) { + var _this = this; + + var method = toggle ? 'addEventListener' : 'removeEventListener'; // Listen for events + + ['touchstart', 'touchmove', 'touchend'].forEach(function (type) { + _this.element[method](type, function (event) { + return _this.set(event); + }, false); + }); + } + /** + * Get the value based on touch position + * @param {Event} event + */ + + }, { + key: "get", + value: function get(event) { + if (!RangeTouch.enabled || !is.event(event)) { + return null; + } + + var input = event.target; + var touch = event.changedTouches[0]; + var min = parseFloat(input.getAttribute('min')) || 0; + var max = parseFloat(input.getAttribute('max')) || 100; + var step = parseFloat(input.getAttribute('step')) || 1; + var delta = max - min; // Calculate percentage + + var percent; + var clientRect = input.getBoundingClientRect(); + var thumbWidth = 100 / clientRect.width * (this.config.thumbWidth / 2) / 100; // Determine left percentage + + percent = 100 / clientRect.width * (touch.clientX - clientRect.left); // Don't allow outside bounds + + if (percent < 0) { + percent = 0; + } else if (percent > 100) { + percent = 100; + } // Factor in the thumb offset + + + if (percent < 50) { + percent -= (100 - percent * 2) * thumbWidth; + } else if (percent > 50) { + percent += (percent - 50) * 2 * thumbWidth; + } // Find the closest step to the mouse position + + + return min + round(delta * (percent / 100), step); + } + /** + * Update range value based on position + * @param {Event} event + */ + + }, { + key: "set", + value: function set(event) { + if (!RangeTouch.enabled || !is.event(event) || event.target.disabled) { + return; + } // Prevent text highlight on iOS + + + event.preventDefault(); // Set value + + event.target.value = this.get(event); // Trigger event + + trigger(event.target, event.type === 'touchend' ? 'change' : 'input'); + } + }], [{ + key: "setup", + + /** + * Setup multiple instances + * @param {String|Element|NodeList|Array} target + * @param {Object} options + */ + value: function setup(target) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + var targets = null; + + if (is.empty(target) || is.string(target)) { + targets = Array.from(document.querySelectorAll(is.string(target) ? target : 'input[type="range"]')); + } else if (is.element(target)) { + targets = [target]; + } else if (is.nodeList(target)) { + targets = Array.from(target); + } else if (is.array(target)) { + targets = target.filter(is.element); + } + + if (is.empty(targets)) { + return null; + } + + var config = Object.assign({}, defaults, options); + + if (is.string(target) && config.watch) { + // Create an observer instance + var observer = new MutationObserver(function (mutations) { + Array.from(mutations).forEach(function (mutation) { + Array.from(mutation.addedNodes).forEach(function (node) { + if (!is.element(node) || !matches(node, target)) { + return; + } // eslint-disable-next-line no-unused-vars + + + var range = new RangeTouch(node, config); + }); + }); + }); // Pass in the target node, as well as the observer options + + observer.observe(document.body, { + childList: true, + subtree: true + }); + } + + return targets.map(function (t) { + return new RangeTouch(t, options); + }); + } + }, { + key: "enabled", + get: function get() { + return 'ontouchstart' in document.documentElement; + } + }]); + + return RangeTouch; + }(); + + // ========================================================================== + // Type checking utils + // ========================================================================== + var getConstructor$1 = function getConstructor(input) { + return input !== null && typeof input !== 'undefined' ? input.constructor : null; + }; + + var instanceOf$1 = function instanceOf(input, constructor) { + return Boolean(input && constructor && input instanceof constructor); + }; + + var isNullOrUndefined$1 = function isNullOrUndefined(input) { + return input === null || typeof input === 'undefined'; + }; + + var isObject$3 = function isObject(input) { + return getConstructor$1(input) === Object; + }; + + var isNumber$1 = function isNumber(input) { + return getConstructor$1(input) === Number && !Number.isNaN(input); + }; + + var isString$3 = function isString(input) { + return getConstructor$1(input) === String; + }; + + var isBoolean$1 = function isBoolean(input) { + return getConstructor$1(input) === Boolean; + }; + + var isFunction$3 = function isFunction(input) { + return getConstructor$1(input) === Function; + }; + + var isArray$3 = function isArray(input) { + return Array.isArray(input); + }; + + var isWeakMap = function isWeakMap(input) { + return instanceOf$1(input, WeakMap); + }; + + var isNodeList$1 = function isNodeList(input) { + return instanceOf$1(input, NodeList); + }; + + var isElement$1 = function isElement(input) { + return instanceOf$1(input, Element); + }; + + var isTextNode = function isTextNode(input) { + return getConstructor$1(input) === Text; + }; + + var isEvent$1 = function isEvent(input) { + return instanceOf$1(input, Event); + }; + + var isKeyboardEvent = function isKeyboardEvent(input) { + return instanceOf$1(input, KeyboardEvent); + }; + + var isCue = function isCue(input) { + return instanceOf$1(input, window.TextTrackCue) || instanceOf$1(input, window.VTTCue); + }; + + var isTrack = function isTrack(input) { + return instanceOf$1(input, TextTrack) || !isNullOrUndefined$1(input) && isString$3(input.kind); + }; + + var isPromise = function isPromise(input) { + return instanceOf$1(input, Promise); + }; + + var isEmpty$1 = function isEmpty(input) { + return isNullOrUndefined$1(input) || (isString$3(input) || isArray$3(input) || isNodeList$1(input)) && !input.length || isObject$3(input) && !Object.keys(input).length; + }; + + var isUrl = function isUrl(input) { + // Accept a URL object + if (instanceOf$1(input, window.URL)) { + return true; + } // Must be string from here + + + if (!isString$3(input)) { + return false; + } // Add the protocol if required + + + var string = input; + + if (!input.startsWith('http://') || !input.startsWith('https://')) { + string = "http://".concat(input); + } + + try { + return !isEmpty$1(new URL(string).hostname); + } catch (e) { + return false; + } + }; + + var is$1 = { + nullOrUndefined: isNullOrUndefined$1, + object: isObject$3, + number: isNumber$1, + string: isString$3, + boolean: isBoolean$1, + function: isFunction$3, + array: isArray$3, + weakMap: isWeakMap, + nodeList: isNodeList$1, + element: isElement$1, + textNode: isTextNode, + event: isEvent$1, + keyboardEvent: isKeyboardEvent, + cue: isCue, + track: isTrack, + promise: isPromise, + url: isUrl, + empty: isEmpty$1 + }; + + // ========================================================================== + // https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md + // https://www.youtube.com/watch?v=NPM6172J22g + + var supportsPassiveListeners = function () { + // Test via a getter in the options object to see if the passive property is accessed + var supported = false; + + try { + var options = Object.defineProperty({}, 'passive', { + get: function get() { + supported = true; + return null; + } + }); + window.addEventListener('test', null, options); + window.removeEventListener('test', null, options); + } catch (e) {// Do nothing + } + + return supported; + }(); // Toggle event listener + + + function toggleListener(element, event, callback) { + var _this = this; + + var toggle = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; + var passive = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true; + var capture = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false; + + // Bail if no element, event, or callback + if (!element || !('addEventListener' in element) || is$1.empty(event) || !is$1.function(callback)) { + return; + } // Allow multiple events + + + var events = event.split(' '); // Build options + // Default to just the capture boolean for browsers with no passive listener support + + var options = capture; // If passive events listeners are supported + + if (supportsPassiveListeners) { + options = { + // Whether the listener can be passive (i.e. default never prevented) + passive: passive, + // Whether the listener is a capturing listener or not + capture: capture + }; + } // If a single node is passed, bind the event listener + + + events.forEach(function (type) { + if (_this && _this.eventListeners && toggle) { + // Cache event listener + _this.eventListeners.push({ + element: element, + type: type, + callback: callback, + options: options + }); + } + + element[toggle ? 'addEventListener' : 'removeEventListener'](type, callback, options); + }); + } // Bind event handler + + function on(element) { + var events = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; + var callback = arguments.length > 2 ? arguments[2] : undefined; + var passive = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; + var capture = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; + toggleListener.call(this, element, events, callback, true, passive, capture); + } // Unbind event handler + + function off(element) { + var events = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; + var callback = arguments.length > 2 ? arguments[2] : undefined; + var passive = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; + var capture = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; + toggleListener.call(this, element, events, callback, false, passive, capture); + } // Bind once-only event handler + + function once(element) { + var _this2 = this; + + var events = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; + var callback = arguments.length > 2 ? arguments[2] : undefined; + var passive = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; + var capture = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; + + var onceCallback = function onceCallback() { + off(element, events, onceCallback, passive, capture); + + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + callback.apply(_this2, args); + }; + + toggleListener.call(this, element, events, onceCallback, true, passive, capture); + } // Trigger event + + function triggerEvent(element) { + var type = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; + var bubbles = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + var detail = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; + + // Bail if no element + if (!is$1.element(element) || is$1.empty(type)) { + return; + } // Create and dispatch the event + + + var event = new CustomEvent(type, { + bubbles: bubbles, + detail: Object.assign({}, detail, { + plyr: this + }) + }); // Dispatch the event + + element.dispatchEvent(event); + } // Unbind all cached event listeners + + function unbindListeners() { + if (this && this.eventListeners) { + this.eventListeners.forEach(function (item) { + var element = item.element, + type = item.type, + callback = item.callback, + options = item.options; + element.removeEventListener(type, callback, options); + }); + this.eventListeners = []; + } + } // Run method when / if player is ready + + function ready() { + var _this3 = this; + + return new Promise(function (resolve) { + return _this3.ready ? setTimeout(resolve, 0) : on.call(_this3, _this3.elements.container, 'ready', resolve); + }).then(function () {}); + } + + function wrap(elements, wrapper) { + // Convert `elements` to an array, if necessary. + var targets = elements.length ? elements : [elements]; // Loops backwards to prevent having to clone the wrapper on the + // first element (see `child` below). + + Array.from(targets).reverse().forEach(function (element, index) { + var child = index > 0 ? wrapper.cloneNode(true) : wrapper; // Cache the current parent and sibling. + + var parent = element.parentNode; + var sibling = element.nextSibling; // Wrap the element (is automatically removed from its current + // parent). + + child.appendChild(element); // If the element had a sibling, insert the wrapper before + // the sibling to maintain the HTML structure; otherwise, just + // append it to the parent. + + if (sibling) { + parent.insertBefore(child, sibling); + } else { + parent.appendChild(child); + } + }); + } // Set attributes + + function setAttributes(element, attributes) { + if (!is$1.element(element) || is$1.empty(attributes)) { + return; + } // Assume null and undefined attributes should be left out, + // Setting them would otherwise convert them to "null" and "undefined" + + + Object.entries(attributes).filter(function (_ref) { + var _ref2 = _slicedToArray(_ref, 2), + value = _ref2[1]; + + return !is$1.nullOrUndefined(value); + }).forEach(function (_ref3) { + var _ref4 = _slicedToArray(_ref3, 2), + key = _ref4[0], + value = _ref4[1]; + + return element.setAttribute(key, value); + }); + } // Create a DocumentFragment + + function createElement(type, attributes, text) { + // Create a new + var element = document.createElement(type); // Set all passed attributes + + if (is$1.object(attributes)) { + setAttributes(element, attributes); + } // Add text node + + + if (is$1.string(text)) { + element.innerText = text; + } // Return built element + + + return element; + } // Inaert an element after another + + function insertAfter(element, target) { + if (!is$1.element(element) || !is$1.element(target)) { + return; + } + + target.parentNode.insertBefore(element, target.nextSibling); + } // Insert a DocumentFragment + + function insertElement(type, parent, attributes, text) { + if (!is$1.element(parent)) { + return; + } + + parent.appendChild(createElement(type, attributes, text)); + } // Remove element(s) + + function removeElement(element) { + if (is$1.nodeList(element) || is$1.array(element)) { + Array.from(element).forEach(removeElement); + return; + } + + if (!is$1.element(element) || !is$1.element(element.parentNode)) { + return; + } + + element.parentNode.removeChild(element); + } // Remove all child elements + + function emptyElement(element) { + if (!is$1.element(element)) { + return; + } + + var length = element.childNodes.length; + + while (length > 0) { + element.removeChild(element.lastChild); + length -= 1; + } + } // Replace element + + function replaceElement(newChild, oldChild) { + if (!is$1.element(oldChild) || !is$1.element(oldChild.parentNode) || !is$1.element(newChild)) { + return null; + } + + oldChild.parentNode.replaceChild(newChild, oldChild); + return newChild; + } // Get an attribute object from a string selector + + function getAttributesFromSelector(sel, existingAttributes) { + // For example: + // '.test' to { class: 'test' } + // '#test' to { id: 'test' } + // '[data-test="test"]' to { 'data-test': 'test' } + if (!is$1.string(sel) || is$1.empty(sel)) { + return {}; + } + + var attributes = {}; + var existing = existingAttributes; + sel.split(',').forEach(function (s) { + // Remove whitespace + var selector = s.trim(); + var className = selector.replace('.', ''); + var stripped = selector.replace(/[[\]]/g, ''); // Get the parts and value + + var parts = stripped.split('='); + var key = parts[0]; + var value = parts.length > 1 ? parts[1].replace(/["']/g, '') : ''; // Get the first character + + var start = selector.charAt(0); + + switch (start) { + case '.': + // Add to existing classname + if (is$1.object(existing) && is$1.string(existing.class)) { + existing.class += " ".concat(className); + } + + attributes.class = className; + break; + + case '#': + // ID selector + attributes.id = selector.replace('#', ''); + break; + + case '[': + // Attribute selector + attributes[key] = value; + break; + + default: + break; + } + }); + return attributes; + } // Toggle hidden + + function toggleHidden(element, hidden) { + if (!is$1.element(element)) { + return; + } + + var hide = hidden; + + if (!is$1.boolean(hide)) { + hide = !element.hidden; + } + + if (hide) { + element.setAttribute('hidden', ''); + } else { + element.removeAttribute('hidden'); + } + } // Mirror Element.classList.toggle, with IE compatibility for "force" argument + + function toggleClass(element, className, force) { + if (is$1.nodeList(element)) { + return Array.from(element).map(function (e) { + return toggleClass(e, className, force); + }); + } + + if (is$1.element(element)) { + var method = 'toggle'; + + if (typeof force !== 'undefined') { + method = force ? 'add' : 'remove'; + } + + element.classList[method](className); + return element.classList.contains(className); + } + + return false; + } // Has class name + + function hasClass(element, className) { + return is$1.element(element) && element.classList.contains(className); + } // Element matches selector + + function matches$1(element, selector) { + + function match() { + return Array.from(document.querySelectorAll(selector)).includes(this); + } + + var matches = match; + return matches.call(element, selector); + } // Find all elements + + function getElements(selector) { + return this.elements.container.querySelectorAll(selector); + } // Find a single element + + function getElement(selector) { + return this.elements.container.querySelector(selector); + } // Trap focus inside container + + function trapFocus() { + var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; + var toggle = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + + if (!is$1.element(element)) { + return; + } + + var focusable = getElements.call(this, 'button:not(:disabled), input:not(:disabled), [tabindex]'); + var first = focusable[0]; + var last = focusable[focusable.length - 1]; + + var trap = function trap(event) { + // Bail if not tab key or not fullscreen + if (event.key !== 'Tab' || event.keyCode !== 9) { + return; + } // Get the current focused element + + + var focused = document.activeElement; + + if (focused === last && !event.shiftKey) { + // Move focus to first element that can be tabbed if Shift isn't used + first.focus(); + event.preventDefault(); + } else if (focused === first && event.shiftKey) { + // Move focus to last element that can be tabbed if Shift is used + last.focus(); + event.preventDefault(); + } + }; + + toggleListener.call(this, this.elements.container, 'keydown', trap, toggle, false); + } // Set focus and tab focus class + + function setFocus() { + var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; + var tabFocus = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + + if (!is$1.element(element)) { + return; + } // Set regular focus + + + element.focus({ + preventScroll: true + }); // If we want to mimic keyboard focus via tab + + if (tabFocus) { + toggleClass(element, this.config.classNames.tabFocus); + } + } + + // ========================================================================== + var transitionEndEvent = function () { + var element = document.createElement('span'); + var events = { + WebkitTransition: 'webkitTransitionEnd', + MozTransition: 'transitionend', + OTransition: 'oTransitionEnd otransitionend', + transition: 'transitionend' + }; + var type = Object.keys(events).find(function (event) { + return element.style[event] !== undefined; + }); + return is$1.string(type) ? events[type] : false; + }(); // Force repaint of element + + function repaint(element) { + setTimeout(function () { + try { + toggleHidden(element, true); + element.offsetHeight; // eslint-disable-line + + toggleHidden(element, false); + } catch (e) {// Do nothing + } + }, 0); + } + + // ========================================================================== + // Browser sniffing + // Unfortunately, due to mixed support, UA sniffing is required + // ========================================================================== + var browser = { + isIE: + /* @cc_on!@ */ + !!document.documentMode, + isEdge: window.navigator.userAgent.includes('Edge'), + isWebkit: 'WebkitAppearance' in document.documentElement.style && !/Edge/.test(navigator.userAgent), + isIPhone: /(iPhone|iPod)/gi.test(navigator.platform), + isIos: /(iPad|iPhone|iPod)/gi.test(navigator.platform) + }; + + var defaultCodecs = { + 'audio/ogg': 'vorbis', + 'audio/wav': '1', + 'video/webm': 'vp8, vorbis', + 'video/mp4': 'avc1.42E01E, mp4a.40.2', + 'video/ogg': 'theora' + }; // Check for feature support + + var support = { + // Basic support + audio: 'canPlayType' in document.createElement('audio'), + video: 'canPlayType' in document.createElement('video'), + // Check for support + // Basic functionality vs full UI + check: function check(type, provider, playsinline) { + var canPlayInline = browser.isIPhone && playsinline && support.playsinline; + var api = support[type] || provider !== 'html5'; + var ui = api && support.rangeInput && (type !== 'video' || !browser.isIPhone || canPlayInline); + return { + api: api, + ui: ui + }; + }, + // Picture-in-picture support + // Safari & Chrome only currently + pip: function () { + if (browser.isIPhone) { + return false; + } // Safari + // https://developer.apple.com/documentation/webkitjs/adding_picture_in_picture_to_your_safari_media_controls + + + if (is$1.function(createElement('video').webkitSetPresentationMode)) { + return true; + } // Chrome + // https://developers.google.com/web/updates/2018/10/watch-video-using-picture-in-picture + + + if (document.pictureInPictureEnabled && !createElement('video').disablePictureInPicture) { + return true; + } + + return false; + }(), + // Airplay support + // Safari only currently + airplay: is$1.function(window.WebKitPlaybackTargetAvailabilityEvent), + // Inline playback support + // https://webkit.org/blog/6784/new-video-policies-for-ios/ + playsinline: 'playsInline' in document.createElement('video'), + // Check for mime type support against a player instance + // Credits: http://diveintohtml5.info/everything.html + // Related: http://www.leanbackplayer.com/test/h5mt.html + mime: function mime(input) { + if (is$1.empty(input)) { + return false; + } + + var _input$split = input.split('/'), + _input$split2 = _slicedToArray(_input$split, 1), + mediaType = _input$split2[0]; + + var type = input; // Verify we're using HTML5 and there's no media type mismatch + + if (!this.isHTML5 || mediaType !== this.type) { + return false; + } // Add codec if required + + + if (Object.keys(defaultCodecs).includes(type)) { + type += "; codecs=\"".concat(defaultCodecs[input], "\""); + } + + try { + return Boolean(type && this.media.canPlayType(type).replace(/no/, '')); + } catch (e) { + return false; + } + }, + // Check for textTracks support + textTracks: 'textTracks' in document.createElement('video'), + // Sliders + rangeInput: function () { + var range = document.createElement('input'); + range.type = 'range'; + return range.type === 'range'; + }(), + // Touch + // NOTE: Remember a device can be mouse + touch enabled so we check on first touch event + touch: 'ontouchstart' in document.documentElement, + // Detect transitions support + transitions: transitionEndEvent !== false, + // Reduced motion iOS & MacOS setting + // https://webkit.org/blog/7551/responsive-design-for-motion/ + reducedMotion: 'matchMedia' in window && window.matchMedia('(prefers-reduced-motion)').matches + }; + + // ========================================================================== + var html5 = { + getSources: function getSources() { + var _this = this; + + if (!this.isHTML5) { + return []; + } + + var sources = Array.from(this.media.querySelectorAll('source')); // Filter out unsupported sources (if type is specified) + + return sources.filter(function (source) { + var type = source.getAttribute('type'); + + if (is$1.empty(type)) { + return true; + } + + return support.mime.call(_this, type); + }); + }, + // Get quality levels + getQualityOptions: function getQualityOptions() { + // Get sizes from elements + return html5.getSources.call(this).map(function (source) { + return Number(source.getAttribute('size')); + }).filter(Boolean); + }, + extend: function extend() { + if (!this.isHTML5) { + return; + } + + var player = this; // Quality + + Object.defineProperty(player.media, 'quality', { + get: function get() { + // Get sources + var sources = html5.getSources.call(player); + var source = sources.find(function (source) { + return source.getAttribute('src') === player.source; + }); // Return size, if match is found + + return source && Number(source.getAttribute('size')); + }, + set: function set(input) { + // Get sources + var sources = html5.getSources.call(player); // Get first match for requested size + + var source = sources.find(function (source) { + return Number(source.getAttribute('size')) === input; + }); // No matching source found + + if (!source) { + return; + } // Get current state + + + var _player$media = player.media, + currentTime = _player$media.currentTime, + paused = _player$media.paused, + preload = _player$media.preload, + readyState = _player$media.readyState; // Set new source + + player.media.src = source.getAttribute('src'); // Prevent loading if preload="none" and the current source isn't loaded (#1044) + + if (preload !== 'none' || readyState) { + // Restore time + player.once('loadedmetadata', function () { + player.currentTime = currentTime; // Resume playing + + if (!paused) { + player.play(); + } + }); // Load new source + + player.media.load(); + } // Trigger change event + + + triggerEvent.call(player, player.media, 'qualitychange', false, { + quality: input + }); + } + }); + }, + // Cancel current network requests + // See https://github.com/sampotts/plyr/issues/174 + cancelRequests: function cancelRequests() { + if (!this.isHTML5) { + return; + } // Remove child sources + + + removeElement(html5.getSources.call(this)); // Set blank video src attribute + // This is to prevent a MEDIA_ERR_SRC_NOT_SUPPORTED error + // Info: http://stackoverflow.com/questions/32231579/how-to-properly-dispose-of-an-html5-video-and-close-socket-or-connection + + this.media.setAttribute('src', this.config.blankVideo); // Load the new empty source + // This will cancel existing requests + // See https://github.com/sampotts/plyr/issues/174 + + this.media.load(); // Debugging + + this.debug.log('Cancelled network requests'); + } + }; + + // ========================================================================== + + function dedupe(array) { + if (!is$1.array(array)) { + return array; + } + + return array.filter(function (item, index) { + return array.indexOf(item) === index; + }); + } // Get the closest value in an array + + function closest(array, value) { + if (!is$1.array(array) || !array.length) { + return null; + } + + return array.reduce(function (prev, curr) { + return Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev; + }); + } + + function cloneDeep(object) { + return JSON.parse(JSON.stringify(object)); + } // Get a nested value in an object + + function getDeep(object, path) { + return path.split('.').reduce(function (obj, key) { + return obj && obj[key]; + }, object); + } // Deep extend destination object with N more objects + + function extend() { + var target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + + for (var _len = arguments.length, sources = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + sources[_key - 1] = arguments[_key]; + } + + if (!sources.length) { + return target; + } + + var source = sources.shift(); + + if (!is$1.object(source)) { + return target; + } + + Object.keys(source).forEach(function (key) { + if (is$1.object(source[key])) { + if (!Object.keys(target).includes(key)) { + Object.assign(target, _defineProperty({}, key, {})); + } + + extend(target[key], source[key]); + } else { + Object.assign(target, _defineProperty({}, key, source[key])); + } + }); + return extend.apply(void 0, [target].concat(sources)); + } + + // ========================================================================== + + function generateId(prefix) { + return "".concat(prefix, "-").concat(Math.floor(Math.random() * 10000)); + } // Format string + + function format(input) { + for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; + } + + if (is$1.empty(input)) { + return input; + } + + return input.toString().replace(/{(\d+)}/g, function (match, i) { + return args[i].toString(); + }); + } // Get percentage + + function getPercentage(current, max) { + if (current === 0 || max === 0 || Number.isNaN(current) || Number.isNaN(max)) { + return 0; + } + + return (current / max * 100).toFixed(2); + } // Replace all occurances of a string in a string + + function replaceAll() { + var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; + var find = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; + var replace = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ''; + return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString()); + } // Convert to title case + + function toTitleCase() { + var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; + return input.toString().replace(/\w\S*/g, function (text) { + return text.charAt(0).toUpperCase() + text.substr(1).toLowerCase(); + }); + } // Convert string to pascalCase + + function toPascalCase() { + var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; + var string = input.toString(); // Convert kebab case + + string = replaceAll(string, '-', ' '); // Convert snake case + + string = replaceAll(string, '_', ' '); // Convert to title case + + string = toTitleCase(string); // Convert to pascal case + + return replaceAll(string, ' ', ''); + } // Convert string to pascalCase + + function toCamelCase() { + var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; + var string = input.toString(); // Convert to pascal case + + string = toPascalCase(string); // Convert first character to lowercase + + return string.charAt(0).toLowerCase() + string.slice(1); + } // Remove HTML from a string + + function stripHTML(source) { + var fragment = document.createDocumentFragment(); + var element = document.createElement('div'); + fragment.appendChild(element); + element.innerHTML = source; + return fragment.firstChild.innerText; + } // Like outerHTML, but also works for DocumentFragment + + function getHTML(element) { + var wrapper = document.createElement('div'); + wrapper.appendChild(element); + return wrapper.innerHTML; + } + + var resources = { + pip: 'PIP', + airplay: 'AirPlay', + html5: 'HTML5', + vimeo: 'Vimeo', + youtube: 'YouTube' + }; + var i18n = { + get: function get() { + var key = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; + var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + if (is$1.empty(key) || is$1.empty(config)) { + return ''; + } + + var string = getDeep(config.i18n, key); + + if (is$1.empty(string)) { + if (Object.keys(resources).includes(key)) { + return resources[key]; + } + + return ''; + } + + var replace = { + '{seektime}': config.seekTime, + '{title}': config.title + }; + Object.entries(replace).forEach(function (_ref) { + var _ref2 = _slicedToArray(_ref, 2), + key = _ref2[0], + value = _ref2[1]; + + string = replaceAll(string, key, value); + }); + return string; + } + }; + + var Storage = + /*#__PURE__*/ + function () { + function Storage(player) { + _classCallCheck(this, Storage); + + this.enabled = player.config.storage.enabled; + this.key = player.config.storage.key; + } // Check for actual support (see if we can use it) + + + _createClass(Storage, [{ + key: "get", + value: function get(key) { + if (!Storage.supported || !this.enabled) { + return null; + } + + var store = window.localStorage.getItem(this.key); + + if (is$1.empty(store)) { + return null; + } + + var json = JSON.parse(store); + return is$1.string(key) && key.length ? json[key] : json; + } + }, { + key: "set", + value: function set(object) { + // Bail if we don't have localStorage support or it's disabled + if (!Storage.supported || !this.enabled) { + return; + } // Can only store objectst + + + if (!is$1.object(object)) { + return; + } // Get current storage + + + var storage = this.get(); // Default to empty object + + if (is$1.empty(storage)) { + storage = {}; + } // Update the working copy of the values + + + extend(storage, object); // Update storage + + window.localStorage.setItem(this.key, JSON.stringify(storage)); + } + }], [{ + key: "supported", + get: function get() { + try { + if (!('localStorage' in window)) { + return false; + } + + var test = '___test'; // Try to use it (it might be disabled, e.g. user is in private mode) + // see: https://github.com/sampotts/plyr/issues/131 + + window.localStorage.setItem(test, test); + window.localStorage.removeItem(test); + return true; + } catch (e) { + return false; + } + } + }]); + + return Storage; + }(); + + // ========================================================================== + // Fetch wrapper + // Using XHR to avoid issues with older browsers + // ========================================================================== + function fetch(url) { + var responseType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'text'; + return new Promise(function (resolve, reject) { + try { + var request = new XMLHttpRequest(); // Check for CORS support + + if (!('withCredentials' in request)) { + return; + } + + request.addEventListener('load', function () { + if (responseType === 'text') { + try { + resolve(JSON.parse(request.responseText)); + } catch (e) { + resolve(request.responseText); + } + } else { + resolve(request.response); + } + }); + request.addEventListener('error', function () { + throw new Error(request.status); + }); + request.open('GET', url, true); // Set the required response type + + request.responseType = responseType; + request.send(); + } catch (e) { + reject(e); + } + }); + } + + // ========================================================================== + + function loadSprite(url, id) { + if (!is$1.string(url)) { + return; + } + + var prefix = 'cache'; + var hasId = is$1.string(id); + var isCached = false; + + var exists = function exists() { + return document.getElementById(id) !== null; + }; + + var update = function update(container, data) { + container.innerHTML = data; // Check again incase of race condition + + if (hasId && exists()) { + return; + } // Inject the SVG to the body + + + document.body.insertAdjacentElement('afterbegin', container); + }; // Only load once if ID set + + + if (!hasId || !exists()) { + var useStorage = Storage.supported; // Create container + + var container = document.createElement('div'); + container.setAttribute('hidden', ''); + + if (hasId) { + container.setAttribute('id', id); + } // Check in cache + + + if (useStorage) { + var cached = window.localStorage.getItem("".concat(prefix, "-").concat(id)); + isCached = cached !== null; + + if (isCached) { + var data = JSON.parse(cached); + update(container, data.content); + } + } // Get the sprite + + + fetch(url).then(function (result) { + if (is$1.empty(result)) { + return; + } + + if (useStorage) { + window.localStorage.setItem("".concat(prefix, "-").concat(id), JSON.stringify({ + content: result + })); + } + + update(container, result); + }).catch(function () {}); + } + } + + // ========================================================================== + + var getHours = function getHours(value) { + return Math.trunc(value / 60 / 60 % 60, 10); + }; + var getMinutes = function getMinutes(value) { + return Math.trunc(value / 60 % 60, 10); + }; + var getSeconds = function getSeconds(value) { + return Math.trunc(value % 60, 10); + }; // Format time to UI friendly string + + function formatTime() { + var time = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; + var displayHours = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + var inverted = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + + // Bail if the value isn't a number + if (!is$1.number(time)) { + return formatTime(null, displayHours, inverted); + } // Format time component to add leading zero + + + var format = function format(value) { + return "0".concat(value).slice(-2); + }; // Breakdown to hours, mins, secs + + + var hours = getHours(time); + var mins = getMinutes(time); + var secs = getSeconds(time); // Do we need to display hours? + + if (displayHours || hours > 0) { + hours = "".concat(hours, ":"); + } else { + hours = ''; + } // Render + + + return "".concat(inverted && time > 0 ? '-' : '').concat(hours).concat(format(mins), ":").concat(format(secs)); + } + + var controls = { + // Get icon URL + getIconUrl: function getIconUrl() { + var url = new URL(this.config.iconUrl, window.location); + var cors = url.host !== window.location.host || browser.isIE && !window.svg4everybody; + return { + url: this.config.iconUrl, + cors: cors + }; + }, + // Find the UI controls + findElements: function findElements() { + try { + this.elements.controls = getElement.call(this, this.config.selectors.controls.wrapper); // Buttons + + this.elements.buttons = { + play: getElements.call(this, this.config.selectors.buttons.play), + pause: getElement.call(this, this.config.selectors.buttons.pause), + restart: getElement.call(this, this.config.selectors.buttons.restart), + rewind: getElement.call(this, this.config.selectors.buttons.rewind), + fastForward: getElement.call(this, this.config.selectors.buttons.fastForward), + mute: getElement.call(this, this.config.selectors.buttons.mute), + pip: getElement.call(this, this.config.selectors.buttons.pip), + airplay: getElement.call(this, this.config.selectors.buttons.airplay), + settings: getElement.call(this, this.config.selectors.buttons.settings), + captions: getElement.call(this, this.config.selectors.buttons.captions), + fullscreen: getElement.call(this, this.config.selectors.buttons.fullscreen) + }; // Progress + + this.elements.progress = getElement.call(this, this.config.selectors.progress); // Inputs + + this.elements.inputs = { + seek: getElement.call(this, this.config.selectors.inputs.seek), + volume: getElement.call(this, this.config.selectors.inputs.volume) + }; // Display + + this.elements.display = { + buffer: getElement.call(this, this.config.selectors.display.buffer), + currentTime: getElement.call(this, this.config.selectors.display.currentTime), + duration: getElement.call(this, this.config.selectors.display.duration) + }; // Seek tooltip + + if (is$1.element(this.elements.progress)) { + this.elements.display.seekTooltip = this.elements.progress.querySelector(".".concat(this.config.classNames.tooltip)); + } + + return true; + } catch (error) { + // Log it + this.debug.warn('It looks like there is a problem with your custom controls HTML', error); // Restore native video controls + + this.toggleNativeControls(true); + return false; + } + }, + // Create icon + createIcon: function createIcon(type, attributes) { + var namespace = 'http://www.w3.org/2000/svg'; + var iconUrl = controls.getIconUrl.call(this); + var iconPath = "".concat(!iconUrl.cors ? iconUrl.url : '', "#").concat(this.config.iconPrefix); // Create + + var icon = document.createElementNS(namespace, 'svg'); + setAttributes(icon, extend(attributes, { + role: 'presentation', + focusable: 'false' + })); // Create the to reference sprite + + var use = document.createElementNS(namespace, 'use'); + var path = "".concat(iconPath, "-").concat(type); // Set `href` attributes + // https://github.com/sampotts/plyr/issues/460 + // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xlink:href + + if ('href' in use) { + use.setAttributeNS('http://www.w3.org/1999/xlink', 'href', path); + } // Always set the older attribute even though it's "deprecated" (it'll be around for ages) + + + use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', path); // Add to + + icon.appendChild(use); + return icon; + }, + // Create hidden text label + createLabel: function createLabel(key) { + var attr = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + var text = i18n.get(key, this.config); + var attributes = Object.assign({}, attr, { + class: [attr.class, this.config.classNames.hidden].filter(Boolean).join(' ') + }); + return createElement('span', attributes, text); + }, + // Create a badge + createBadge: function createBadge(text) { + if (is$1.empty(text)) { + return null; + } + + var badge = createElement('span', { + class: this.config.classNames.menu.value + }); + badge.appendChild(createElement('span', { + class: this.config.classNames.menu.badge + }, text)); + return badge; + }, + // Create a
to hide the standard controls and UI + + if (this.isVimeo && this.supported.ui) { + var height = 240; + var offset = (height - padding) / (height / 50); + this.media.style.transform = "translateY(-".concat(offset, "%)"); + } + + return { + padding: padding, + ratio: ratio + }; + } + + var Listeners = + /*#__PURE__*/ + function () { + function Listeners(player) { + _classCallCheck(this, Listeners); + + this.player = player; + this.lastKey = null; + this.focusTimer = null; + this.lastKeyDown = null; + this.handleKey = this.handleKey.bind(this); + this.toggleMenu = this.toggleMenu.bind(this); + this.setTabFocus = this.setTabFocus.bind(this); + this.firstTouch = this.firstTouch.bind(this); + } // Handle key presses + + + _createClass(Listeners, [{ + key: "handleKey", + value: function handleKey(event) { + var player = this.player; + var elements = player.elements; + var code = event.keyCode ? event.keyCode : event.which; + var pressed = event.type === 'keydown'; + var repeat = pressed && code === this.lastKey; // Bail if a modifier key is set + + if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) { + return; + } // If the event is bubbled from the media element + // Firefox doesn't get the keycode for whatever reason + + + if (!is$1.number(code)) { + return; + } // Seek by the number keys + + + var seekByKey = function seekByKey() { + // Divide the max duration into 10th's and times by the number value + player.currentTime = player.duration / 10 * (code - 48); + }; // Handle the key on keydown + // Reset on keyup + + + if (pressed) { + // Check focused element + // and if the focused element is not editable (e.g. text input) + // and any that accept key input http://webaim.org/techniques/keyboard/ + var focused = document.activeElement; + + if (is$1.element(focused)) { + var editable = player.config.selectors.editable; + var seek = elements.inputs.seek; + + if (focused !== seek && matches$1(focused, editable)) { + return; + } + + if (event.which === 32 && matches$1(focused, 'button, [role^="menuitem"]')) { + return; + } + } // Which keycodes should we prevent default + + + var preventDefault = [32, 37, 38, 39, 40, 48, 49, 50, 51, 52, 53, 54, 56, 57, 67, 70, 73, 75, 76, 77, 79]; // If the code is found prevent default (e.g. prevent scrolling for arrows) + + if (preventDefault.includes(code)) { + event.preventDefault(); + event.stopPropagation(); + } + + switch (code) { + case 48: + case 49: + case 50: + case 51: + case 52: + case 53: + case 54: + case 55: + case 56: + case 57: + // 0-9 + if (!repeat) { + seekByKey(); + } + + break; + + case 32: + case 75: + // Space and K key + if (!repeat) { + player.togglePlay(); + } + + break; + + case 38: + // Arrow up + player.increaseVolume(0.1); + break; + + case 40: + // Arrow down + player.decreaseVolume(0.1); + break; + + case 77: + // M key + if (!repeat) { + player.muted = !player.muted; + } + + break; + + case 39: + // Arrow forward + player.forward(); + break; + + case 37: + // Arrow back + player.rewind(); + break; + + case 70: + // F key + player.fullscreen.toggle(); + break; + + case 67: + // C key + if (!repeat) { + player.toggleCaptions(); + } + + break; + + case 76: + // L key + player.loop = !player.loop; + break; + + /* case 73: + this.setLoop('start'); + break; + case 76: + this.setLoop(); + break; + case 79: + this.setLoop('end'); + break; */ + + default: + break; + } // Escape is handle natively when in full screen + // So we only need to worry about non native + + + if (code === 27 && !player.fullscreen.usingNative && player.fullscreen.active) { + player.fullscreen.toggle(); + } // Store last code for next cycle + + + this.lastKey = code; + } else { + this.lastKey = null; + } + } // Toggle menu + + }, { + key: "toggleMenu", + value: function toggleMenu(event) { + controls.toggleMenu.call(this.player, event); + } // Device is touch enabled + + }, { + key: "firstTouch", + value: function firstTouch() { + var player = this.player; + var elements = player.elements; + player.touch = true; // Add touch class + + toggleClass(elements.container, player.config.classNames.isTouch, true); + } + }, { + key: "setTabFocus", + value: function setTabFocus(event) { + var player = this.player; + var elements = player.elements; + clearTimeout(this.focusTimer); // Ignore any key other than tab + + if (event.type === 'keydown' && event.which !== 9) { + return; + } // Store reference to event timeStamp + + + if (event.type === 'keydown') { + this.lastKeyDown = event.timeStamp; + } // Remove current classes + + + var removeCurrent = function removeCurrent() { + var className = player.config.classNames.tabFocus; + var current = getElements.call(player, ".".concat(className)); + toggleClass(current, className, false); + }; // Determine if a key was pressed to trigger this event + + + var wasKeyDown = event.timeStamp - this.lastKeyDown <= 20; // Ignore focus events if a key was pressed prior + + if (event.type === 'focus' && !wasKeyDown) { + return; + } // Remove all current + + + removeCurrent(); // Delay the adding of classname until the focus has changed + // This event fires before the focusin event + + this.focusTimer = setTimeout(function () { + var focused = document.activeElement; // Ignore if current focus element isn't inside the player + + if (!elements.container.contains(focused)) { + return; + } + + toggleClass(document.activeElement, player.config.classNames.tabFocus, true); + }, 10); + } // Global window & document listeners + + }, { + key: "global", + value: function global() { + var toggle = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; + var player = this.player; // Keyboard shortcuts + + if (player.config.keyboard.global) { + toggleListener.call(player, window, 'keydown keyup', this.handleKey, toggle, false); + } // Click anywhere closes menu + + + toggleListener.call(player, document.body, 'click', this.toggleMenu, toggle); // Detect touch by events + + once.call(player, document.body, 'touchstart', this.firstTouch); // Tab focus detection + + toggleListener.call(player, document.body, 'keydown focus blur', this.setTabFocus, toggle, false, true); + } // Container listeners + + }, { + key: "container", + value: function container() { + var player = this.player; + var config = player.config, + elements = player.elements, + timers = player.timers; // Keyboard shortcuts + + if (!config.keyboard.global && config.keyboard.focused) { + on.call(player, elements.container, 'keydown keyup', this.handleKey, false); + } // Toggle controls on mouse events and entering fullscreen + + + on.call(player, elements.container, 'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen', function (event) { + var controls = elements.controls; // Remove button states for fullscreen + + if (controls && event.type === 'enterfullscreen') { + controls.pressed = false; + controls.hover = false; + } // Show, then hide after a timeout unless another control event occurs + + + var show = ['touchstart', 'touchmove', 'mousemove'].includes(event.type); + var delay = 0; + + if (show) { + ui.toggleControls.call(player, true); // Use longer timeout for touch devices + + delay = player.touch ? 3000 : 2000; + } // Clear timer + + + clearTimeout(timers.controls); // Set new timer to prevent flicker when seeking + + timers.controls = setTimeout(function () { + return ui.toggleControls.call(player, false); + }, delay); + }); // Force edge to repaint on exit fullscreen + // TODO: Fix weird bug where Edge doesn't re-draw when exiting fullscreen + + /* if (browser.isEdge) { + on.call(player, elements.container, 'exitfullscreen', () => { + setTimeout(() => repaint(elements.container), 100); + }); + } */ + // Set a gutter for Vimeo + + var setGutter = function setGutter(ratio, padding, toggle) { + if (!player.isVimeo) { + return; + } + + var target = player.elements.wrapper.firstChild; + + var _ratio$split$map = ratio.split(':').map(Number), + _ratio$split$map2 = _slicedToArray(_ratio$split$map, 2), + height = _ratio$split$map2[1]; + + var _player$embed$ratio$s = player.embed.ratio.split(':').map(Number), + _player$embed$ratio$s2 = _slicedToArray(_player$embed$ratio$s, 2), + videoWidth = _player$embed$ratio$s2[0], + videoHeight = _player$embed$ratio$s2[1]; + + target.style.maxWidth = toggle ? "".concat(height / videoHeight * videoWidth, "px") : null; + target.style.margin = toggle ? '0 auto' : null; + }; // Resize on fullscreen change + + + var setPlayerSize = function setPlayerSize(measure) { + // If we don't need to measure the viewport + if (!measure) { + return setAspectRatio.call(player); + } + + var rect = elements.container.getBoundingClientRect(); + var width = rect.width, + height = rect.height; + return setAspectRatio.call(player, "".concat(width, ":").concat(height)); + }; + + var resized = function resized() { + window.clearTimeout(timers.resized); + timers.resized = window.setTimeout(setPlayerSize, 50); + }; + + on.call(player, elements.container, 'enterfullscreen exitfullscreen', function (event) { + var _player$fullscreen = player.fullscreen, + target = _player$fullscreen.target, + usingNative = _player$fullscreen.usingNative; // Ignore for iOS native + + if (!player.isEmbed || target !== elements.container) { + return; + } + + var isEnter = event.type === 'enterfullscreen'; // Set the player size when entering fullscreen to viewport size + + var _setPlayerSize = setPlayerSize(isEnter), + padding = _setPlayerSize.padding, + ratio = _setPlayerSize.ratio; // Set Vimeo gutter + + + setGutter(ratio, padding, isEnter); // If not using native fullscreen, we need to check for resizes of viewport + + if (!usingNative) { + if (isEnter) { + on.call(player, window, 'resize', resized); + } else { + off.call(player, window, 'resize', resized); + } + } + }); + } // Listen for media events + + }, { + key: "media", + value: function media() { + var _this = this; + + var player = this.player; + var elements = player.elements; // Time change on media + + on.call(player, player.media, 'timeupdate seeking seeked', function (event) { + return controls.timeUpdate.call(player, event); + }); // Display duration + + on.call(player, player.media, 'durationchange loadeddata loadedmetadata', function (event) { + return controls.durationUpdate.call(player, event); + }); // Check for audio tracks on load + // We can't use `loadedmetadata` as it doesn't seem to have audio tracks at that point + + on.call(player, player.media, 'canplay loadeddata', function () { + toggleHidden(elements.volume, !player.hasAudio); + toggleHidden(elements.buttons.mute, !player.hasAudio); + }); // Handle the media finishing + + on.call(player, player.media, 'ended', function () { + // Show poster on end + if (player.isHTML5 && player.isVideo && player.config.resetOnEnd) { + // Restart + player.restart(); + } + }); // Check for buffer progress + + on.call(player, player.media, 'progress playing seeking seeked', function (event) { + return controls.updateProgress.call(player, event); + }); // Handle volume changes + + on.call(player, player.media, 'volumechange', function (event) { + return controls.updateVolume.call(player, event); + }); // Handle play/pause + + on.call(player, player.media, 'playing play pause ended emptied timeupdate', function (event) { + return ui.checkPlaying.call(player, event); + }); // Loading state + + on.call(player, player.media, 'waiting canplay seeked playing', function (event) { + return ui.checkLoading.call(player, event); + }); // Click video + + if (player.supported.ui && player.config.clickToPlay && !player.isAudio) { + // Re-fetch the wrapper + var wrapper = getElement.call(player, ".".concat(player.config.classNames.video)); // Bail if there's no wrapper (this should never happen) + + if (!is$1.element(wrapper)) { + return; + } // On click play, pause or restart + + + on.call(player, elements.container, 'click', function (event) { + var targets = [elements.container, wrapper]; // Ignore if click if not container or in video wrapper + + if (!targets.includes(event.target) && !wrapper.contains(event.target)) { + return; + } // Touch devices will just show controls (if hidden) + + + if (player.touch && player.config.hideControls) { + return; + } + + if (player.ended) { + _this.proxy(event, player.restart, 'restart'); + + _this.proxy(event, player.play, 'play'); + } else { + _this.proxy(event, player.togglePlay, 'play'); + } + }); + } // Disable right click + + + if (player.supported.ui && player.config.disableContextMenu) { + on.call(player, elements.wrapper, 'contextmenu', function (event) { + event.preventDefault(); + }, false); + } // Volume change + + + on.call(player, player.media, 'volumechange', function () { + // Save to storage + player.storage.set({ + volume: player.volume, + muted: player.muted + }); + }); // Speed change + + on.call(player, player.media, 'ratechange', function () { + // Update UI + controls.updateSetting.call(player, 'speed'); // Save to storage + + + player.storage.set({ + speed: player.speed + }); + }); // Quality change + + on.call(player, player.media, 'qualitychange', function (event) { + // Update UI + controls.updateSetting.call(player, 'quality', null, event.detail.quality); + }); // Update download link when ready and if quality changes + + on.call(player, player.media, 'ready qualitychange', function () { + controls.setDownloadLink.call(player); + }); // Proxy events to container + // Bubble up key events for Edge + + var proxyEvents = player.config.events.concat(['keyup', 'keydown']).join(' '); + on.call(player, player.media, proxyEvents, function (event) { + var _event$detail = event.detail, + detail = _event$detail === void 0 ? {} : _event$detail; // Get error details from media + + if (event.type === 'error') { + detail = player.media.error; + } + + triggerEvent.call(player, elements.container, event.type, true, detail); + }); + } // Run default and custom handlers + + }, { + key: "proxy", + value: function proxy(event, defaultHandler, customHandlerKey) { + var player = this.player; + var customHandler = player.config.listeners[customHandlerKey]; + var hasCustomHandler = is$1.function(customHandler); + var returned = true; // Execute custom handler + + if (hasCustomHandler) { + returned = customHandler.call(player, event); + } // Only call default handler if not prevented in custom handler + + + if (returned && is$1.function(defaultHandler)) { + defaultHandler.call(player, event); + } + } // Trigger custom and default handlers + + }, { + key: "bind", + value: function bind(element, type, defaultHandler, customHandlerKey) { + var _this2 = this; + + var passive = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true; + var player = this.player; + var customHandler = player.config.listeners[customHandlerKey]; + var hasCustomHandler = is$1.function(customHandler); + on.call(player, element, type, function (event) { + return _this2.proxy(event, defaultHandler, customHandlerKey); + }, passive && !hasCustomHandler); + } // Listen for control events + + }, { + key: "controls", + value: function controls$1() { + var _this3 = this; + + var player = this.player; + var elements = player.elements; // IE doesn't support input event, so we fallback to change + + var inputEvent = browser.isIE ? 'change' : 'input'; // Play/pause toggle + + if (elements.buttons.play) { + Array.from(elements.buttons.play).forEach(function (button) { + _this3.bind(button, 'click', player.togglePlay, 'play'); + }); + } // Pause + + + this.bind(elements.buttons.restart, 'click', player.restart, 'restart'); // Rewind + + this.bind(elements.buttons.rewind, 'click', player.rewind, 'rewind'); // Rewind + + this.bind(elements.buttons.fastForward, 'click', player.forward, 'fastForward'); // Mute toggle + + this.bind(elements.buttons.mute, 'click', function () { + player.muted = !player.muted; + }, 'mute'); // Captions toggle + + this.bind(elements.buttons.captions, 'click', function () { + return player.toggleCaptions(); + }); // Download + + this.bind(elements.buttons.download, 'click', function () { + triggerEvent.call(player, player.media, 'download'); + }, 'download'); // Fullscreen toggle + + this.bind(elements.buttons.fullscreen, 'click', function () { + player.fullscreen.toggle(); + }, 'fullscreen'); // Picture-in-Picture + + this.bind(elements.buttons.pip, 'click', function () { + player.pip = 'toggle'; + }, 'pip'); // Airplay + + this.bind(elements.buttons.airplay, 'click', player.airplay, 'airplay'); // Settings menu - click toggle + + this.bind(elements.buttons.settings, 'click', function (event) { + // Prevent the document click listener closing the menu + event.stopPropagation(); + + controls.toggleMenu.call(player, event); + }); // Settings menu - keyboard toggle + // We have to bind to keyup otherwise Firefox triggers a click when a keydown event handler shifts focus + // https://bugzilla.mozilla.org/show_bug.cgi?id=1220143 + + this.bind(elements.buttons.settings, 'keyup', function (event) { + var code = event.which; // We only care about space and return + + if (![13, 32].includes(code)) { + return; + } // Because return triggers a click anyway, all we need to do is set focus + + + if (code === 13) { + controls.focusFirstMenuItem.call(player, null, true); + + return; + } // Prevent scroll + + + event.preventDefault(); // Prevent playing video (Firefox) + + event.stopPropagation(); // Toggle menu + + controls.toggleMenu.call(player, event); + }, null, false // Can't be passive as we're preventing default + ); // Escape closes menu + + this.bind(elements.settings.menu, 'keydown', function (event) { + if (event.which === 27) { + controls.toggleMenu.call(player, event); + } + }); // Set range input alternative "value", which matches the tooltip time (#954) + + this.bind(elements.inputs.seek, 'mousedown mousemove', function (event) { + var rect = elements.progress.getBoundingClientRect(); + var percent = 100 / rect.width * (event.pageX - rect.left); + event.currentTarget.setAttribute('seek-value', percent); + }); // Pause while seeking + + this.bind(elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', function (event) { + var seek = event.currentTarget; + var code = event.keyCode ? event.keyCode : event.which; + var attribute = 'play-on-seeked'; + + if (is$1.keyboardEvent(event) && code !== 39 && code !== 37) { + return; + } // Record seek time so we can prevent hiding controls for a few seconds after seek + + + player.lastSeekTime = Date.now(); // Was playing before? + + var play = seek.hasAttribute(attribute); // Done seeking + + var done = ['mouseup', 'touchend', 'keyup'].includes(event.type); // If we're done seeking and it was playing, resume playback + + if (play && done) { + seek.removeAttribute(attribute); + player.play(); + } else if (!done && player.playing) { + seek.setAttribute(attribute, ''); + player.pause(); + } + }); // Fix range inputs on iOS + // Super weird iOS bug where after you interact with an , + // it takes over further interactions on the page. This is a hack + + if (browser.isIos) { + var inputs = getElements.call(player, 'input[type="range"]'); + Array.from(inputs).forEach(function (input) { + return _this3.bind(input, inputEvent, function (event) { + return repaint(event.target); + }); + }); + } // Seek + + + this.bind(elements.inputs.seek, inputEvent, function (event) { + var seek = event.currentTarget; // If it exists, use seek-value instead of "value" for consistency with tooltip time (#954) + + var seekTo = seek.getAttribute('seek-value'); + + if (is$1.empty(seekTo)) { + seekTo = seek.value; + } + + seek.removeAttribute('seek-value'); + player.currentTime = seekTo / seek.max * player.duration; + }, 'seek'); // Seek tooltip + + this.bind(elements.progress, 'mouseenter mouseleave mousemove', function (event) { + return controls.updateSeekTooltip.call(player, event); + }); // Preview thumbnails plugin + // TODO: Really need to work on some sort of plug-in wide event bus or pub-sub for this + + this.bind(elements.progress, 'mousemove touchmove', function (event) { + var previewThumbnails = player.previewThumbnails; + + if (previewThumbnails && previewThumbnails.loaded) { + previewThumbnails.startMove(event); + } + }); // Hide thumbnail preview - on mouse click, mouse leave, and video play/seek. All four are required, e.g., for buffering + + this.bind(elements.progress, 'mouseleave click', function () { + var previewThumbnails = player.previewThumbnails; + + if (previewThumbnails && previewThumbnails.loaded) { + previewThumbnails.endMove(false, true); + } + }); // Show scrubbing preview + + this.bind(elements.progress, 'mousedown touchstart', function (event) { + var previewThumbnails = player.previewThumbnails; + + if (previewThumbnails && previewThumbnails.loaded) { + previewThumbnails.startScrubbing(event); + } + }); + this.bind(elements.progress, 'mouseup touchend', function (event) { + var previewThumbnails = player.previewThumbnails; + + if (previewThumbnails && previewThumbnails.loaded) { + previewThumbnails.endScrubbing(event); + } + }); // Polyfill for lower fill in for webkit + + if (browser.isWebkit) { + Array.from(getElements.call(player, 'input[type="range"]')).forEach(function (element) { + _this3.bind(element, 'input', function (event) { + return controls.updateRangeFill.call(player, event.target); + }); + }); + } // Current time invert + // Only if one time element is used for both currentTime and duration + + + if (player.config.toggleInvert && !is$1.element(elements.display.duration)) { + this.bind(elements.display.currentTime, 'click', function () { + // Do nothing if we're at the start + if (player.currentTime === 0) { + return; + } + + player.config.invertTime = !player.config.invertTime; + + controls.timeUpdate.call(player); + }); + } // Volume + + + this.bind(elements.inputs.volume, inputEvent, function (event) { + player.volume = event.target.value; + }, 'volume'); // Update controls.hover state (used for ui.toggleControls to avoid hiding when interacting) + + this.bind(elements.controls, 'mouseenter mouseleave', function (event) { + elements.controls.hover = !player.touch && event.type === 'mouseenter'; + }); // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting) + + this.bind(elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) { + elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type); + }); // Show controls when they receive focus (e.g., when using keyboard tab key) + + this.bind(elements.controls, 'focusin', function () { + var config = player.config, + elements = player.elements, + timers = player.timers; // Skip transition to prevent focus from scrolling the parent element + + toggleClass(elements.controls, config.classNames.noTransition, true); // Toggle + + ui.toggleControls.call(player, true); // Restore transition + + setTimeout(function () { + toggleClass(elements.controls, config.classNames.noTransition, false); + }, 0); // Delay a little more for mouse users + + var delay = _this3.touch ? 3000 : 4000; // Clear timer + + clearTimeout(timers.controls); // Hide again after delay + + timers.controls = setTimeout(function () { + return ui.toggleControls.call(player, false); + }, delay); + }); // Mouse wheel for volume + + this.bind(elements.inputs.volume, 'wheel', function (event) { + // Detect "natural" scroll - suppored on OS X Safari only + // Other browsers on OS X will be inverted until support improves + var inverted = event.webkitDirectionInvertedFromDevice; // Get delta from event. Invert if `inverted` is true + + var _map = [event.deltaX, -event.deltaY].map(function (value) { + return inverted ? -value : value; + }), + _map2 = _slicedToArray(_map, 2), + x = _map2[0], + y = _map2[1]; // Using the biggest delta, normalize to 1 or -1 (or 0 if no delta) + + + var direction = Math.sign(Math.abs(x) > Math.abs(y) ? x : y); // Change the volume by 2% + + player.increaseVolume(direction / 50); // Don't break page scrolling at max and min + + var volume = player.media.volume; + + if (direction === 1 && volume < 1 || direction === -1 && volume > 0) { + event.preventDefault(); + } + }, 'volume', false); + } + }]); + + return Listeners; + }(); + + var loadjs_umd = createCommonjsModule(function (module, exports) { + (function (root, factory) { + { + module.exports = factory(); + } + })(commonjsGlobal, function () { + /** + * Global dependencies. + * @global {Object} document - DOM + */ + var devnull = function devnull() {}, + bundleIdCache = {}, + bundleResultCache = {}, + bundleCallbackQueue = {}; + /** + * Subscribe to bundle load event. + * @param {string[]} bundleIds - Bundle ids + * @param {Function} callbackFn - The callback function + */ + + + function subscribe(bundleIds, callbackFn) { + // listify + bundleIds = bundleIds.push ? bundleIds : [bundleIds]; + var depsNotFound = [], + i = bundleIds.length, + numWaiting = i, + fn, + bundleId, + r, + q; // define callback function + + fn = function fn(bundleId, pathsNotFound) { + if (pathsNotFound.length) depsNotFound.push(bundleId); + numWaiting--; + if (!numWaiting) callbackFn(depsNotFound); + }; // register callback + + + while (i--) { + bundleId = bundleIds[i]; // execute callback if in result cache + + r = bundleResultCache[bundleId]; + + if (r) { + fn(bundleId, r); + continue; + } // add to callback queue + + + q = bundleCallbackQueue[bundleId] = bundleCallbackQueue[bundleId] || []; + q.push(fn); + } + } + /** + * Publish bundle load event. + * @param {string} bundleId - Bundle id + * @param {string[]} pathsNotFound - List of files not found + */ + + + function publish(bundleId, pathsNotFound) { + // exit if id isn't defined + if (!bundleId) return; + var q = bundleCallbackQueue[bundleId]; // cache result + + bundleResultCache[bundleId] = pathsNotFound; // exit if queue is empty + + if (!q) return; // empty callback queue + + while (q.length) { + q[0](bundleId, pathsNotFound); + q.splice(0, 1); + } + } + /** + * Execute callbacks. + * @param {Object or Function} args - The callback args + * @param {string[]} depsNotFound - List of dependencies not found + */ + + + function executeCallbacks(args, depsNotFound) { + // accept function as argument + if (args.call) args = { + success: args + }; // success and error callbacks + + if (depsNotFound.length) (args.error || devnull)(depsNotFound);else (args.success || devnull)(args); + } + /** + * Load individual file. + * @param {string} path - The file path + * @param {Function} callbackFn - The callback function + */ + + + function loadFile(path, callbackFn, args, numTries) { + var doc = document, + async = args.async, + maxTries = (args.numRetries || 0) + 1, + beforeCallbackFn = args.before || devnull, + pathStripped = path.replace(/^(css|img)!/, ''), + isLegacyIECss, + e; + numTries = numTries || 0; + + if (/(^css!|\.css$)/.test(path)) { + // css + e = doc.createElement('link'); + e.rel = 'stylesheet'; + e.href = pathStripped; // tag IE9+ + + isLegacyIECss = 'hideFocus' in e; // use preload in IE Edge (to detect load errors) + + if (isLegacyIECss && e.relList) { + isLegacyIECss = 0; + e.rel = 'preload'; + e.as = 'style'; + } + } else if (/(^img!|\.(png|gif|jpg|svg)$)/.test(path)) { + // image + e = doc.createElement('img'); + e.src = pathStripped; + } else { + // javascript + e = doc.createElement('script'); + e.src = path; + e.async = async === undefined ? true : async; + } + + e.onload = e.onerror = e.onbeforeload = function (ev) { + var result = ev.type[0]; // treat empty stylesheets as failures to get around lack of onerror + // support in IE9-11 + + if (isLegacyIECss) { + try { + if (!e.sheet.cssText.length) result = 'e'; + } catch (x) { + // sheets objects created from load errors don't allow access to + // `cssText` (unless error is Code:18 SecurityError) + if (x.code != 18) result = 'e'; + } + } // handle retries in case of load failure + + + if (result == 'e') { + // increment counter + numTries += 1; // exit function and try again + + if (numTries < maxTries) { + return loadFile(path, callbackFn, args, numTries); + } + } else if (e.rel == 'preload' && e.as == 'style') { + // activate preloaded stylesheets + return e.rel = 'stylesheet'; // jshint ignore:line + } // execute callback + + + callbackFn(path, result, ev.defaultPrevented); + }; // add to document (unless callback returns `false`) + + + if (beforeCallbackFn(path, e) !== false) doc.head.appendChild(e); + } + /** + * Load multiple files. + * @param {string[]} paths - The file paths + * @param {Function} callbackFn - The callback function + */ + + + function loadFiles(paths, callbackFn, args) { + // listify paths + paths = paths.push ? paths : [paths]; + var numWaiting = paths.length, + x = numWaiting, + pathsNotFound = [], + fn, + i; // define callback function + + fn = function fn(path, result, defaultPrevented) { + // handle error + if (result == 'e') pathsNotFound.push(path); // handle beforeload event. If defaultPrevented then that means the load + // will be blocked (ex. Ghostery/ABP on Safari) + + if (result == 'b') { + if (defaultPrevented) pathsNotFound.push(path);else return; + } + + numWaiting--; + if (!numWaiting) callbackFn(pathsNotFound); + }; // load scripts + + + for (i = 0; i < x; i++) { + loadFile(paths[i], fn, args); + } + } + /** + * Initiate script load and register bundle. + * @param {(string|string[])} paths - The file paths + * @param {(string|Function)} [arg1] - The bundleId or success callback + * @param {Function} [arg2] - The success or error callback + * @param {Function} [arg3] - The error callback + */ + + + function loadjs(paths, arg1, arg2) { + var bundleId, args; // bundleId (if string) + + if (arg1 && arg1.trim) bundleId = arg1; // args (default is {}) + + args = (bundleId ? arg2 : arg1) || {}; // throw error if bundle is already defined + + if (bundleId) { + if (bundleId in bundleIdCache) { + throw "LoadJS"; + } else { + bundleIdCache[bundleId] = true; + } + } + + function loadFn(resolve, reject) { + loadFiles(paths, function (pathsNotFound) { + // execute callbacks + executeCallbacks(args, pathsNotFound); // resolve Promise + + if (resolve) { + executeCallbacks({ + success: resolve, + error: reject + }, pathsNotFound); + } // publish bundle load event + + + publish(bundleId, pathsNotFound); + }, args); + } + + if (args.returnPromise) return new Promise(loadFn);else loadFn(); + } + /** + * Execute callbacks when dependencies have been satisfied. + * @param {(string|string[])} deps - List of bundle ids + * @param {Object} args - success/error arguments + */ + + + loadjs.ready = function ready(deps, args) { + // subscribe to bundle load event + subscribe(deps, function (depsNotFound) { + // execute callbacks + executeCallbacks(args, depsNotFound); + }); + return loadjs; + }; + /** + * Manually satisfy bundle dependencies. + * @param {string} bundleId - The bundle id + */ + + + loadjs.done = function done(bundleId) { + publish(bundleId, []); + }; + /** + * Reset loadjs dependencies statuses + */ + + + loadjs.reset = function reset() { + bundleIdCache = {}; + bundleResultCache = {}; + bundleCallbackQueue = {}; + }; + /** + * Determine if bundle has already been defined + * @param String} bundleId - The bundle id + */ + + + loadjs.isDefined = function isDefined(bundleId) { + return bundleId in bundleIdCache; + }; // export + + + return loadjs; + }); + }); + + // ========================================================================== + function loadScript(url) { + return new Promise(function (resolve, reject) { + loadjs_umd(url, { + success: resolve, + error: reject + }); + }); + } + + function parseId(url) { + if (is$1.empty(url)) { + return null; + } + + if (is$1.number(Number(url))) { + return url; + } + + var regex = /^.*(vimeo.com\/|video\/)(\d+).*/; + return url.match(regex) ? RegExp.$2 : url; + } // Set playback state and trigger change (only on actual change) + + + function assurePlaybackState(play) { + if (play && !this.embed.hasPlayed) { + this.embed.hasPlayed = true; + } + + if (this.media.paused === play) { + this.media.paused = !play; + triggerEvent.call(this, this.media, play ? 'play' : 'pause'); + } + } + + var vimeo = { + setup: function setup() { + var _this = this; + + // Add embed class for responsive + toggleClass(this.elements.wrapper, this.config.classNames.embed, true); // Set intial ratio + + setAspectRatio.call(this); // Load the API if not already + + if (!is$1.object(window.Vimeo)) { + loadScript(this.config.urls.vimeo.sdk).then(function () { + vimeo.ready.call(_this); + }).catch(function (error) { + _this.debug.warn('Vimeo API failed to load', error); + }); + } else { + vimeo.ready.call(this); + } + }, + // API Ready + ready: function ready() { + var _this2 = this; + + var player = this; + var config = player.config.vimeo; // Get Vimeo params for the iframe + + var params = buildUrlParams(extend({}, { + loop: player.config.loop.active, + autoplay: player.autoplay, + muted: player.muted, + gesture: 'media', + playsinline: !this.config.fullscreen.iosNative + }, config)); // Get the source URL or ID + + var source = player.media.getAttribute('src'); // Get from
if needed + + if (is$1.empty(source)) { + source = player.media.getAttribute(player.config.attributes.embed.id); + } + + var id = parseId(source); // Build an iframe + + var iframe = createElement('iframe'); + var src = format(player.config.urls.vimeo.iframe, id, params); + iframe.setAttribute('src', src); + iframe.setAttribute('allowfullscreen', ''); + iframe.setAttribute('allowtransparency', ''); + iframe.setAttribute('allow', 'autoplay'); // Get poster, if already set + + var poster = player.poster; // Inject the package + + var wrapper = createElement('div', { + poster: poster, + class: player.config.classNames.embedContainer + }); + wrapper.appendChild(iframe); + player.media = replaceElement(wrapper, player.media); // Get poster image + + fetch(format(player.config.urls.vimeo.api, id), 'json').then(function (response) { + if (is$1.empty(response)) { + return; + } // Get the URL for thumbnail + + + var url = new URL(response[0].thumbnail_large); // Get original image + + url.pathname = "".concat(url.pathname.split('_')[0], ".jpg"); // Set and show poster + + ui.setPoster.call(player, url.href).catch(function () {}); + }); // Setup instance + // https://github.com/vimeo/player.js + + player.embed = new window.Vimeo.Player(iframe, { + autopause: player.config.autopause, + muted: player.muted + }); + player.media.paused = true; + player.media.currentTime = 0; // Disable native text track rendering + + if (player.supported.ui) { + player.embed.disableTextTrack(); + } // Create a faux HTML5 API using the Vimeo API + + + player.media.play = function () { + assurePlaybackState.call(player, true); + return player.embed.play(); + }; + + player.media.pause = function () { + assurePlaybackState.call(player, false); + return player.embed.pause(); + }; + + player.media.stop = function () { + player.pause(); + player.currentTime = 0; + }; // Seeking + + + var currentTime = player.media.currentTime; + Object.defineProperty(player.media, 'currentTime', { + get: function get() { + return currentTime; + }, + set: function set(time) { + // Vimeo will automatically play on seek if the video hasn't been played before + // Get current paused state and volume etc + var embed = player.embed, + media = player.media, + paused = player.paused, + volume = player.volume; + var restorePause = paused && !embed.hasPlayed; // Set seeking state and trigger event + + media.seeking = true; + triggerEvent.call(player, media, 'seeking'); // If paused, mute until seek is complete + + Promise.resolve(restorePause && embed.setVolume(0)) // Seek + .then(function () { + return embed.setCurrentTime(time); + }) // Restore paused + .then(function () { + return restorePause && embed.pause(); + }) // Restore volume + .then(function () { + return restorePause && embed.setVolume(volume); + }).catch(function () {// Do nothing + }); + } + }); // Playback speed + + var speed = player.config.speed.selected; + Object.defineProperty(player.media, 'playbackRate', { + get: function get() { + return speed; + }, + set: function set(input) { + player.embed.setPlaybackRate(input).then(function () { + speed = input; + triggerEvent.call(player, player.media, 'ratechange'); + }).catch(function (error) { + // Hide menu item (and menu if empty) + if (error.name === 'Error') { + controls.setSpeedMenu.call(player, []); + } + }); + } + }); // Volume + + var volume = player.config.volume; + Object.defineProperty(player.media, 'volume', { + get: function get() { + return volume; + }, + set: function set(input) { + player.embed.setVolume(input).then(function () { + volume = input; + triggerEvent.call(player, player.media, 'volumechange'); + }); + } + }); // Muted + + var muted = player.config.muted; + Object.defineProperty(player.media, 'muted', { + get: function get() { + return muted; + }, + set: function set(input) { + var toggle = is$1.boolean(input) ? input : false; + player.embed.setVolume(toggle ? 0 : player.config.volume).then(function () { + muted = toggle; + triggerEvent.call(player, player.media, 'volumechange'); + }); + } + }); // Loop + + var loop = player.config.loop; + Object.defineProperty(player.media, 'loop', { + get: function get() { + return loop; + }, + set: function set(input) { + var toggle = is$1.boolean(input) ? input : player.config.loop.active; + player.embed.setLoop(toggle).then(function () { + loop = toggle; + }); + } + }); // Source + + var currentSrc; + player.embed.getVideoUrl().then(function (value) { + currentSrc = value; + controls.setDownloadLink.call(player); + }).catch(function (error) { + _this2.debug.warn(error); + }); + Object.defineProperty(player.media, 'currentSrc', { + get: function get() { + return currentSrc; + } + }); // Ended + + Object.defineProperty(player.media, 'ended', { + get: function get() { + return player.currentTime === player.duration; + } + }); // Set aspect ratio based on video size + + Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then(function (dimensions) { + var _dimensions = _slicedToArray(dimensions, 2), + width = _dimensions[0], + height = _dimensions[1]; + + player.embed.ratio = "".concat(width, ":").concat(height); + setAspectRatio.call(_this2, player.embed.ratio); + }); // Set autopause + + player.embed.setAutopause(player.config.autopause).then(function (state) { + player.config.autopause = state; + }); // Get title + + player.embed.getVideoTitle().then(function (title) { + player.config.title = title; + ui.setTitle.call(_this2); + }); // Get current time + + player.embed.getCurrentTime().then(function (value) { + currentTime = value; + triggerEvent.call(player, player.media, 'timeupdate'); + }); // Get duration + + player.embed.getDuration().then(function (value) { + player.media.duration = value; + triggerEvent.call(player, player.media, 'durationchange'); + }); // Get captions + + player.embed.getTextTracks().then(function (tracks) { + player.media.textTracks = tracks; + captions.setup.call(player); + }); + player.embed.on('cuechange', function (_ref) { + var _ref$cues = _ref.cues, + cues = _ref$cues === void 0 ? [] : _ref$cues; + var strippedCues = cues.map(function (cue) { + return stripHTML(cue.text); + }); + captions.updateCues.call(player, strippedCues); + }); + player.embed.on('loaded', function () { + // Assure state and events are updated on autoplay + player.embed.getPaused().then(function (paused) { + assurePlaybackState.call(player, !paused); + + if (!paused) { + triggerEvent.call(player, player.media, 'playing'); + } + }); + + if (is$1.element(player.embed.element) && player.supported.ui) { + var frame = player.embed.element; // Fix keyboard focus issues + // https://github.com/sampotts/plyr/issues/317 + + frame.setAttribute('tabindex', -1); + } + }); + player.embed.on('play', function () { + assurePlaybackState.call(player, true); + triggerEvent.call(player, player.media, 'playing'); + }); + player.embed.on('pause', function () { + assurePlaybackState.call(player, false); + }); + player.embed.on('timeupdate', function (data) { + player.media.seeking = false; + currentTime = data.seconds; + triggerEvent.call(player, player.media, 'timeupdate'); + }); + player.embed.on('progress', function (data) { + player.media.buffered = data.percent; + triggerEvent.call(player, player.media, 'progress'); // Check all loaded + + if (parseInt(data.percent, 10) === 1) { + triggerEvent.call(player, player.media, 'canplaythrough'); + } // Get duration as if we do it before load, it gives an incorrect value + // https://github.com/sampotts/plyr/issues/891 + + + player.embed.getDuration().then(function (value) { + if (value !== player.media.duration) { + player.media.duration = value; + triggerEvent.call(player, player.media, 'durationchange'); + } + }); + }); + player.embed.on('seeked', function () { + player.media.seeking = false; + triggerEvent.call(player, player.media, 'seeked'); + }); + player.embed.on('ended', function () { + player.media.paused = true; + triggerEvent.call(player, player.media, 'ended'); + }); + player.embed.on('error', function (detail) { + player.media.error = detail; + triggerEvent.call(player, player.media, 'error'); + }); // Rebuild UI + + setTimeout(function () { + return ui.build.call(player); + }, 0); + } + }; + + // ========================================================================== + + function parseId$1(url) { + if (is$1.empty(url)) { + return null; + } + + var regex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/; + return url.match(regex) ? RegExp.$2 : url; + } // Set playback state and trigger change (only on actual change) + + + function assurePlaybackState$1(play) { + if (play && !this.embed.hasPlayed) { + this.embed.hasPlayed = true; + } + + if (this.media.paused === play) { + this.media.paused = !play; + triggerEvent.call(this, this.media, play ? 'play' : 'pause'); + } + } + + var youtube = { + setup: function setup() { + var _this = this; + + // Add embed class for responsive + toggleClass(this.elements.wrapper, this.config.classNames.embed, true); // Set aspect ratio + + setAspectRatio.call(this); // Setup API + + if (is$1.object(window.YT) && is$1.function(window.YT.Player)) { + youtube.ready.call(this); + } else { + // Load the API + loadScript(this.config.urls.youtube.sdk).catch(function (error) { + _this.debug.warn('YouTube API failed to load', error); + }); // Setup callback for the API + // YouTube has it's own system of course... + + window.onYouTubeReadyCallbacks = window.onYouTubeReadyCallbacks || []; // Add to queue + + window.onYouTubeReadyCallbacks.push(function () { + youtube.ready.call(_this); + }); // Set callback to process queue + + window.onYouTubeIframeAPIReady = function () { + window.onYouTubeReadyCallbacks.forEach(function (callback) { + callback(); + }); + }; + } + }, + // Get the media title + getTitle: function getTitle(videoId) { + var _this2 = this; + + // Try via undocumented API method first + // This method disappears now and then though... + // https://github.com/sampotts/plyr/issues/709 + if (is$1.function(this.embed.getVideoData)) { + var _this$embed$getVideoD = this.embed.getVideoData(), + title = _this$embed$getVideoD.title; + + if (is$1.empty(title)) { + this.config.title = title; + ui.setTitle.call(this); + return; + } + } // Or via Google API + + + var key = this.config.keys.google; + + if (is$1.string(key) && !is$1.empty(key)) { + var url = format(this.config.urls.youtube.api, videoId, key); + fetch(url).then(function (result) { + if (is$1.object(result)) { + _this2.config.title = result.items[0].snippet.title; + ui.setTitle.call(_this2); + } + }).catch(function () {}); + } + }, + // API ready + ready: function ready() { + var player = this; // Ignore already setup (race condition) + + var currentId = player.media.getAttribute('id'); + + if (!is$1.empty(currentId) && currentId.startsWith('youtube-')) { + return; + } // Get the source URL or ID + + + var source = player.media.getAttribute('src'); // Get from
if needed + + if (is$1.empty(source)) { + source = player.media.getAttribute(this.config.attributes.embed.id); + } // Replace the +
``` @@ -102,13 +86,18 @@ Or the `
` non progressively enhanced method: _Note_: The `data-plyr-embed-id` can either be the video ID or URL for the media. -#### Vimeo embed +### Vimeo Much the same as YouTube above. ```html
- +
``` @@ -118,13 +107,15 @@ Or the `
` non progressively enhanced method:
``` -### JavaScript +## JavaScript Include the `plyr.js` script before the closing `` tag and then in your JS create a new instance of Plyr as below. ```html - + ``` See [initialising](#initialising) for more information on advanced setups. @@ -132,35 +123,35 @@ See [initialising](#initialising) for more information on advanced setups. You can use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript. There's 2 versions; one with and one without [polyfills](#polyfills). My recommendation would be to manage polyfills seperately as part of your application but to make life easier you can use the polyfilled build. ```html - + ``` ...or... ```html - + ``` -### CSS +## CSS Include the `plyr.css` stylsheet into your `` ```html - + ``` If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the default CSS, you can use the following: ```html - + ``` -### SVG Sprite +## SVG Sprite The SVG sprite is loaded automatically from our CDN (provided by [Fastly](https://www.fastly.com/)). To change this, see the [options](#options) below. For -reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.4.7/plyr.svg`. +reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.5.2/plyr.svg`. -## Ads +# Ads Plyr has partnered up with [vi.ai](https://vi.ai/publisher-video-monetization/?aid=plyrio) to offer monetization options for your videos. Getting setup is easy: @@ -170,9 +161,9 @@ Plyr has partnered up with [vi.ai](https://vi.ai/publisher-video-monetization/?a Any questions regarding the ads can be sent straight to vi.ai and any issues with rendering raised through GitHub issues. -## Advanced +# Advanced -### SASS +## SASS You can use `bundle.scss` file included in `/src` as part of your build and change variables to suit your design. The SASS require you to use the [autoprefixer](https://www.npmjs.com/package/gulp-autoprefixer) plugin (you should be already!) as all declarations use the W3C definitions. @@ -180,12 +171,12 @@ use the [autoprefixer](https://www.npmjs.com/package/gulp-autoprefixer) plugin ( The HTML markup uses the BEM methodology with `plyr` as the block, e.g. `.plyr__controls`. You can change the class hooks in the options to match any custom CSS you write. Check out the JavaScript source for more on this. -### SVG +## SVG The icons used in the Plyr controls are loaded in an SVG sprite. The sprite is automatically loaded from our CDN by default. If you already have an icon build system in place, you can include the source plyr icons (see `/src/sprite` for source icons). -#### Using the `iconUrl` option +### Using the `iconUrl` option You can however specify your own `iconUrl` option and Plyr will determine if the url is absolute and requires loading by AJAX/CORS due to current browser limitations or if it's a relative path, just use the path directly. @@ -195,32 +186,31 @@ If you're using the `` tag on your site, you may need to use something lik More info on SVG sprites here: [http://css-tricks.com/svg-sprites-use-better-icon-fonts/](http://css-tricks.com/svg-sprites-use-better-icon-fonts/) and the AJAX technique here: [http://css-tricks.com/ajaxing-svg-sprite/](http://css-tricks.com/ajaxing-svg-sprite/) -### Cross Origin (CORS) +## Cross Origin (CORS) You'll notice the `crossorigin` attribute on the example `