mitm.html 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. <!--
  2. mitm.html is the lite "man in the middle"
  3. This is only meant to signal the opener's messageChannel to
  4. the service worker - when that is done this mitm can be closed
  5. but it's better to keep it alive since this also stops the sw
  6. from restarting
  7. The service worker is capable of intercepting all request and fork their
  8. own "fake" response - wish we are going to craft
  9. when the worker then receives a stream then the worker will tell the opener
  10. to open up a link that will start the download
  11. -->
  12. <script>
  13. // This will prevent the sw from restarting
  14. let keepAlive = () => {
  15. keepAlive = () => {}
  16. var ping = location.href.substr(0, location.href.lastIndexOf('/')) + '/ping'
  17. var interval = setInterval(() => {
  18. if (sw) {
  19. sw.postMessage('ping')
  20. } else {
  21. fetch(ping).then(res => res.text(!res.ok && clearInterval(interval)))
  22. }
  23. }, 10000)
  24. }
  25. // message event is the first thing we need to setup a listner for
  26. // don't want the opener to do a random timeout - instead they can listen for
  27. // the ready event
  28. // but since we need to wait for the Service Worker registration, we store the
  29. // message for later
  30. let messages = []
  31. window.onmessage = evt => messages.push(evt)
  32. let sw = null
  33. let scope = ''
  34. function registerWorker() {
  35. return navigator.serviceWorker.getRegistration('./').then(swReg => {
  36. return swReg || navigator.serviceWorker.register('sw.js', { scope: './' })
  37. }).then(swReg => {
  38. const swRegTmp = swReg.installing || swReg.waiting
  39. scope = swReg.scope
  40. return (sw = swReg.active) || new Promise(resolve => {
  41. swRegTmp.addEventListener('statechange', fn = () => {
  42. if (swRegTmp.state === 'activated') {
  43. swRegTmp.removeEventListener('statechange', fn)
  44. sw = swReg.active
  45. resolve()
  46. }
  47. })
  48. })
  49. })
  50. }
  51. // Now that we have the Service Worker registered we can process messages
  52. function onMessage (event) {
  53. let { data, ports, origin } = event
  54. // It's important to have a messageChannel, don't want to interfere
  55. // with other simultaneous downloads
  56. if (!ports || !ports.length) {
  57. throw new TypeError("[StreamSaver] You didn't send a messageChannel")
  58. }
  59. if (typeof data !== 'object') {
  60. throw new TypeError("[StreamSaver] You didn't send a object")
  61. }
  62. // the default public service worker for StreamSaver is shared among others.
  63. // so all download links needs to be prefixed to avoid any other conflict
  64. data.origin = origin
  65. // if we ever (in some feature versoin of streamsaver) would like to
  66. // redirect back to the page of who initiated a http request
  67. data.referrer = data.referrer || document.referrer || origin
  68. // pass along version for possible backwards compatibility in sw.js
  69. data.streamSaverVersion = new URLSearchParams(location.search).get('version')
  70. if (data.streamSaverVersion === '1.2.0') {
  71. console.warn('[StreamSaver] please update streamsaver')
  72. }
  73. /** @since v2.0.0 */
  74. if (!data.headers) {
  75. console.warn("[StreamSaver] pass `data.headers` that you would like to pass along to the service worker\nit should be a 2D array or a key/val object that fetch's Headers api accepts")
  76. } else {
  77. // test if it's correct
  78. // should thorw a typeError if not
  79. new Headers(data.headers)
  80. }
  81. /** @since v2.0.0 */
  82. if (typeof data.filename === 'string') {
  83. console.warn("[StreamSaver] You shouldn't send `data.filename` anymore. It should be included in the Content-Disposition header option")
  84. // Do what File constructor do with fileNames
  85. data.filename = data.filename.replace(/\//g, ':')
  86. }
  87. /** @since v2.0.0 */
  88. if (data.size) {
  89. console.warn("[StreamSaver] You shouldn't send `data.size` anymore. It should be included in the content-length header option")
  90. }
  91. /** @since v2.0.0 */
  92. if (data.readableStream) {
  93. console.warn("[StreamSaver] You should send the readableStream in the messageChannel, not throught mitm")
  94. }
  95. /** @since v2.0.0 */
  96. if (!data.pathname) {
  97. console.warn("[StreamSaver] Please send `data.pathname` (eg: /pictures/summer.jpg)")
  98. data.pathname = Math.random().toString().slice(-6) + '/' + data.filename
  99. }
  100. // remove all leading slashes
  101. data.pathname = data.pathname.replace(/^\/+/g, '')
  102. // set the absolute pathname to the download url.
  103. data.url = new URL(`${scope}/${data.pathname}`).toString()
  104. if (!data.url.startsWith(`${scope}/`)) {
  105. throw new TypeError('[StreamSaver] bad `data.pathname`')
  106. }
  107. // This sends the message data as well as transferring
  108. // messageChannel.port2 to the service worker. The service worker can
  109. // then use the transferred port to reply via postMessage(), which
  110. // will in turn trigger the onmessage handler on messageChannel.port1.
  111. const transferable = data.readableStream
  112. ? [ ports[0], data.readableStream ]
  113. : [ ports[0] ]
  114. if (!(data.readableStream || data.transferringReadable)) {
  115. keepAlive()
  116. }
  117. return sw.postMessage(data, transferable)
  118. }
  119. if (window.opener) {
  120. // The opener can't listen to onload event, so we need to help em out!
  121. // (telling them that we are ready to accept postMessage's)
  122. window.opener.postMessage('StreamSaver::loadedPopup', '*')
  123. }
  124. if (navigator.serviceWorker) {
  125. registerWorker().then(() => {
  126. window.onmessage = onMessage
  127. messages.forEach(window.onmessage)
  128. })
  129. } else {
  130. // FF can ping sw with fetch from a secure hidden iframe
  131. // shouldn't really be possible?
  132. keepAlive()
  133. }
  134. </script>