🐙
Google Apps Script の Web アプリで CORS 制約を回避して Web Woker や ES Modules を使う
外部スクリプトが使えない
GASで作ったWebアプリで下記のようなコードを書いてもエラーになります。
xxx.html
<!-- 前略 -->
<script type="module">
import { module } from "https://xxx/module.js"
cosnt worker = new Worker( "https://xxx/worker.js" )
</script>
<!-- 後略 -->
開発コンソールで確認してみると「CORS」や「SOP」といった文言のエラーメッセージが出ていると思います。
CORSはオリジン間リソース共有、SOPは同一生成元ポリシーのことで、ざっくり言うと「他のWebサイトから持ってきてはいけない」というエラーです。
つまり、他所から持って来ていないように見せれば解決します。
失敗例 ContentServiceを使ってみる
同じ失敗をしないため、まずは失敗例の紹介から。
GASにはHTMLを表示するHtmlService
に対してプレーンテキストなどを表示するContentService
というクラスがあります。
これを使ってdoGet
メソッドをURLパラメーターを渡したときだけWorker用のJSファイルを返すようにカスタマイズします。
main.gs
function doGet( e ) {
const js = e.parameter.js
if ( js != null ) {
const content = HtmlService.createHtmlOutputFromFile( js ).getContent()
return ContentService
.createTextOutput( content )
.setMimeType( ContentService.MimeType.JAVASCRIPT )
}
return HtmlService.createTemplateFromFile( "index" ).evaluate()
}
index.html
<!-- 前略 -->
<script>
const url = "<?= ScriptApp.getService().getUrl() ?>?js=worker.js"
const worker = new Worker( url )
worker.onmessage = console.log
worker.onerror = console.error
worker.postMessage( "Test Message." )
</script>
<!-- 後略 -->
worker.js.html
self.addEventListener( "message", event => {
self.postMessage( event.data )
} )
しかし、これでは上手くいきません。
何故ならHTMLがインラインフレーム(iframe
)で入れ子にされるからです。
こちらで作ったHTMLは一番内側のiframe
内に表示され、これが外側(ブラウザから見えるURL)と異なるドメインになっているため、結局同一生成元ポリシーに違反します。
成功例 文字列からBlobを起こしてObjectURLを生成する
所謂インラインワーカーを使います。
GAS側にWorkerのソースコード文字列を返すメソッドとしてgetCode
を定義します。
このメソッドをindex.html
側でgoogle.script.run
を使って呼びます。
main.gs
function doGet( e ) {
return HtmlService.createTemplateFromFile( "index" ).evaluate()
}
function getCode() {
return HtmlService.createHtmlOutputFromFile( "worker.js" ).getContent()
}
index.html
<!-- 前略 -->
<script>
google.script.run
.withSuccessHandler( code => {
const url = URL.createObjectURL( new Blob( [ code ], { type: "text/javascript" } ) )
const worker = new Worker( url )
worker.onmessage = console.log
worker.onerror = console.error
worker.postMessage( "Test Message." )
})
.withFailureHandler( console.error )
.getCode()
</script>
<!-- 後略 -->
worker.js.html
self.addEventListener( "message", event => {
self.postMessage( event.data )
} )
以上!
コードは Web Worker で説明しましたが、ES Modules の場合はObjectURLを生成するところまでは一緒で、Dynamic Importを使えば同様に実現できます。
Discussion