origins.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. var Origins = () => {
  2. var defaults = {
  3. // storage
  4. origins: {},
  5. match: '',
  6. // UI
  7. host: '',
  8. timeout: null,
  9. file: true,
  10. // chrome
  11. permissions: {},
  12. }
  13. var state = Object.assign({}, defaults)
  14. chrome.extension.isAllowedFileSchemeAccess((isAllowedAccess) => {
  15. state.file = /Firefox/.test(navigator.userAgent)
  16. ? true // ff: `Allow access to file URLs` option isn't available
  17. : isAllowedAccess
  18. m.redraw()
  19. })
  20. chrome.runtime.sendMessage({message: 'options.origins'}, (res) => {
  21. Object.assign(state, {file: state.file}, res)
  22. chrome.permissions.getAll(({origins}) => {
  23. state.permissions = origins.reduce((all, origin) =>
  24. (all[origin.replace(/(.*)\/\*$/, '$1')] = true, all), {})
  25. m.redraw()
  26. })
  27. })
  28. var events = {
  29. file: () => {
  30. chrome.tabs.create({url: `chrome://extensions/?id=${chrome.runtime.id}`})
  31. },
  32. host: (e) => {
  33. state.scheme = e.target.value.replace(/(.*):\/\/.*/i, '$1')
  34. state.domain = e.target.value.replace(/.*:\/\/([^/]+).*/i, '$1')
  35. state.host = e.target.value
  36. },
  37. add: (all) => () => {
  38. if (!all && !state.host && !['https', 'http', '*'].includes(state.scheme)) {
  39. return
  40. }
  41. var origin = all ? '*://*' : `${state.scheme}://${state.domain}`
  42. chrome.permissions.request({origins: [`${origin}/*`]}, (granted) => {
  43. if (granted) {
  44. chrome.runtime.sendMessage({message: 'origin.add', origin})
  45. state.origins[origin] = {
  46. header: true,
  47. path: true,
  48. match: state.match,
  49. }
  50. state.host = ''
  51. state.permissions[origin] = true
  52. m.redraw()
  53. }
  54. })
  55. },
  56. remove: (origin) => () => {
  57. chrome.permissions.remove({origins: [`${origin}/*`]}, (removed) => {
  58. if (removed) {
  59. chrome.runtime.sendMessage({message: 'origin.remove', origin})
  60. delete state.origins[origin]
  61. delete state.permissions[origin]
  62. m.redraw()
  63. }
  64. })
  65. },
  66. refresh: (origin) => () => {
  67. chrome.permissions.request({origins: [`${origin}/*`]}, (granted) => {
  68. if (granted) {
  69. state.permissions[origin] = true
  70. m.redraw()
  71. }
  72. })
  73. },
  74. header: (origin) => () => {
  75. state.origins[origin].header = !state.origins[origin].header
  76. var {header, path, match} = state.origins[origin]
  77. chrome.runtime.sendMessage({
  78. message: 'origin.update',
  79. origin,
  80. options: {header, path, match},
  81. })
  82. },
  83. path: (origin) => () => {
  84. state.origins[origin].path = !state.origins[origin].path
  85. var {header, path, match} = state.origins[origin]
  86. chrome.runtime.sendMessage({
  87. message: 'origin.update',
  88. origin,
  89. options: {header, path, match},
  90. })
  91. },
  92. match: (origin) => (e) => {
  93. state.origins[origin].match = e.target.value
  94. clearTimeout(state.timeout)
  95. state.timeout = setTimeout(() => {
  96. var {header, path, match} = state.origins[origin]
  97. chrome.runtime.sendMessage({
  98. message: 'origin.update',
  99. origin,
  100. options: {header, path, match},
  101. })
  102. }, 750)
  103. },
  104. }
  105. var oncreate = {
  106. ripple: (vnode) => {
  107. mdc.ripple.MDCRipple.attachTo(vnode.dom)
  108. },
  109. textfield: (vnode) => {
  110. mdc.textfield.MDCTextField.attachTo(vnode.dom)
  111. }
  112. }
  113. var onupdate = {
  114. header: (origin) => (vnode) => {
  115. if (vnode.dom.classList.contains('is-checked') !== state.origins[origin].header) {
  116. vnode.dom.classList.toggle('is-checked')
  117. }
  118. },
  119. path: (origin) => (vnode) => {
  120. if (vnode.dom.classList.contains('is-checked') !== state.origins[origin].path) {
  121. vnode.dom.classList.toggle('is-checked')
  122. }
  123. }
  124. }
  125. var render = () =>
  126. m('.m-origins',
  127. // file access
  128. m('.row',
  129. m('.col-xxl-10.col-xl-10.col-lg-9.col-md-8.col-sm-12',
  130. m('h3', 'File Access'),
  131. ),
  132. m('.col-xxl-2.col-xl-2.col-lg-3.col-md-4.col-sm-12',
  133. // file access is disabled
  134. (!state.file || null) &&
  135. m('button.mdc-button mdc-button--raised m-button m-btn-file', {
  136. oncreate: oncreate.ripple,
  137. onclick: events.file
  138. },
  139. 'Allow Access'
  140. )
  141. ),
  142. ),
  143. ...Object.keys(state.origins)
  144. .filter((origin) => origin === 'file://' && state.file)
  145. .map(callout),
  146. // site access
  147. m('.row',
  148. m('.col-xxl-10.col-xl-10.col-lg-10.col-md-9.col-sm-8',
  149. m('h3', 'Site Access'),
  150. ),
  151. (!Object.keys(state.origins).includes('*://*') || null) &&
  152. m('.col-xxl-2.col-xl-2.col-lg-2.col-md-3.col-sm-4',
  153. m('button.mdc-button mdc-button--raised m-button m-btn-all', {
  154. oncreate: oncreate.ripple,
  155. onclick: events.add(true)
  156. },
  157. 'Allow All'
  158. ),
  159. ),
  160. ),
  161. // add origin
  162. m('.bs-callout m-box-add',
  163. m('.row',
  164. m('.col-xxl-11.col-xl-11.col-lg-10.col-md-10.col-sm-12',
  165. m('.mdc-text-field m-textfield', {
  166. oncreate: oncreate.textfield,
  167. },
  168. m('input.mdc-text-field__input', {
  169. type: 'text',
  170. value: state.host,
  171. onchange: events.host,
  172. placeholder: 'Copy/paste URL address here'
  173. }),
  174. m('.mdc-line-ripple')
  175. ),
  176. ),
  177. m('.col-xxl-1.col-xl-1.col-lg-2.col-md-2.col-sm-12',
  178. m('button.mdc-button mdc-button--raised m-button m-btn-add', {
  179. oncreate: oncreate.ripple,
  180. onclick: events.add()
  181. },
  182. 'Add'
  183. ),
  184. )
  185. )
  186. ),
  187. // allowed origins
  188. ...Object.keys(state.origins)
  189. .filter((origin) => origin !== 'file://')
  190. .sort((a, b) => a < b ? 1 : a > b ? -1 : 0)
  191. .map(callout)
  192. )
  193. var callout = (origin) =>
  194. m('.bs-callout', {class: !state.permissions[origin] ? 'm-box-refresh' : undefined},
  195. // origin
  196. m('.row',
  197. m('.col-xxl-8.col-xl-8.col-lg-8.col-md-7.col-sm-12', m('span.m-origin', origin)),
  198. m('.col-xxl-4.col-xl-4.col-lg-4.col-md-5.col-sm-12',
  199. // remove
  200. (origin !== 'file://' || null) &&
  201. m('button.mdc-button mdc-button--raised m-button m-btn-remove', {
  202. oncreate: oncreate.ripple,
  203. onclick: events.remove(origin)
  204. },
  205. 'Remove'
  206. ),
  207. // refresh
  208. (!state.permissions[origin] || null) &&
  209. m('button.mdc-button mdc-button--raised m-button m-btn-refresh', {
  210. oncreate: oncreate.ripple,
  211. onclick: events.refresh(origin)
  212. },
  213. 'Refresh'
  214. )
  215. )
  216. ),
  217. // header detection
  218. m('.row',
  219. m('.col-sm-12',
  220. m('.overflow',
  221. m('label.mdc-switch m-switch', {
  222. onupdate: onupdate.header,
  223. title: 'Toggle Header Detection'
  224. },
  225. m('input.mdc-switch__native-control', {
  226. type: 'checkbox',
  227. checked: state.origins[origin].header,
  228. onchange: events.header(origin)
  229. }),
  230. m('.mdc-switch__background', m('.mdc-switch__knob')),
  231. m('span.mdc-switch-label',
  232. 'Content Type Detection: ',
  233. m('span', 'text/markdown'),
  234. ', ',
  235. m('span', 'text/x-markdown'),
  236. ', ',
  237. m('span', 'text/plain'),
  238. )
  239. ),
  240. )
  241. )
  242. ),
  243. // path matching regexp
  244. m('.row',
  245. m('.col-sm-12',
  246. m('.overflow',
  247. m('label.mdc-switch m-switch', {
  248. onupdate: onupdate.path,
  249. title: 'Toggle Path Detection'
  250. },
  251. m('input.mdc-switch__native-control', {
  252. type: 'checkbox',
  253. checked: state.origins[origin].path,
  254. onchange: events.path(origin)
  255. }),
  256. m('.mdc-switch__background', m('.mdc-switch__knob')),
  257. m('span.mdc-switch-label',
  258. 'Path Matching RegExp: '
  259. )
  260. ),
  261. m('.mdc-text-field m-textfield', {
  262. oncreate: oncreate.textfield
  263. },
  264. m('input.mdc-text-field__input', {
  265. type: 'text',
  266. onkeyup: events.match(origin),
  267. value: state.origins[origin].match,
  268. }),
  269. m('.mdc-line-ripple')
  270. )
  271. )
  272. )
  273. )
  274. )
  275. return {state, render}
  276. }