Files: 54f45b9e3a2114fd3c234a8de046349c9a3dab1c / src / components / AppRunner.js
5651 bytesRaw
1 | /* provides a web-component to execute a PatchBoot app. It is passed the app msg rather than the blob to support |
2 | * different app formats in future. |
3 | */ |
4 | |
5 | import { default as pull, paraMap, collect } from 'pull-stream' |
6 | import MRPC from 'muxrpc' |
7 | import Util from '../Util.js'; |
8 | |
9 | class AppRunner extends HTMLElement { |
10 | constructor() { |
11 | super(); |
12 | } |
13 | connectedCallback() { |
14 | const runnerArea = this.attachShadow({ mode: 'open' }) |
15 | |
16 | const util = new Util(this.sbot) |
17 | |
18 | const getClassicAppFrameContent = () => { |
19 | const blobId = this.app.link |
20 | return util.dereferenceUriOrSigil(blobId).then(code => { |
21 | function utf8_to_b64(str) { |
22 | return btoa(unescape(encodeURIComponent(str))); |
23 | } |
24 | return ` |
25 | <!DOCTYPE html> |
26 | <html> |
27 | <head> |
28 | <title>Patchboot app</title> |
29 | </head> |
30 | <body> |
31 | |
32 | <div id="patchboot-app" style="min-width: min-content;"></div> |
33 | |
34 | <script type="module"> |
35 | import {default as ssbConnect, pull} from './scuttle-shell-browser-consumer.js' |
36 | ssbConnect().then(sbot => { |
37 | window.sbot = sbot |
38 | window.root = document.getElementById('patchboot-app') |
39 | window.pull = pull |
40 | window.root.innerHTML = '' |
41 | const script = document.createElement('script') |
42 | script.defer = true |
43 | script.src = 'data:text/javascript;base64,${utf8_to_b64(code)}' |
44 | document.head.append(script) |
45 | }, |
46 | error => { |
47 | console.log('An error occured', error) |
48 | }) |
49 | |
50 | </script> |
51 | |
52 | </body> |
53 | </html> |
54 | ` |
55 | }) |
56 | |
57 | } |
58 | |
59 | const addBaseUrl = (htmlString) => htmlString.replace('<head>',`<head><base href="${this.app.link}">`) |
60 | |
61 | const getWebappContent = () => { |
62 | const link = this.app.link |
63 | // because of same originy policy we cab't just use original link |
64 | return util.dereferenceUriOrSigil(link).then(content => { |
65 | content = (link.startsWith('&') || link.startsWith('ssb')) ? content : addBaseUrl(content) |
66 | return content |
67 | }) |
68 | } |
69 | |
70 | const getAppFrameContent = () => { |
71 | if (this.app.type === 'patchboot-app') { |
72 | return getClassicAppFrameContent() |
73 | } else if (this.app.type === 'patchboot-webapp') { |
74 | return getWebappContent() |
75 | } else { |
76 | throw new Error('unsupported: ' + this.app.type) |
77 | } |
78 | } |
79 | |
80 | const createIFrame = () => { |
81 | const iFrame = document.createElement('iframe') |
82 | runnerArea.appendChild(iFrame) // has to appended before contentWindow is accessed |
83 | iFrame.style = "width: 100%; height: 100%; border: none;" |
84 | return getAppFrameContent().then(iFrameContent => { |
85 | iFrame.contentWindow.document.open() |
86 | iFrame.contentWindow.document.write(iFrameContent) |
87 | iFrame.contentWindow.document.close() |
88 | return iFrame |
89 | }) |
90 | } |
91 | |
92 | createIFrame().then(iFrame => { |
93 | console.log(iFrame) |
94 | |
95 | this.dispatchEvent(new Event('loaded')) |
96 | |
97 | let messageDataCallback = null |
98 | let messageDataBuffer = [] |
99 | |
100 | const fromPage = function read(abort, cb) { |
101 | if (messageDataBuffer.length > 0) { |
102 | const data = messageDataBuffer[0] |
103 | messageDataBuffer = messageDataBuffer.splice(1) |
104 | cb(null, data) |
105 | } else { |
106 | messageDataCallback = cb |
107 | } |
108 | |
109 | } |
110 | |
111 | function ping() { |
112 | iFrame.contentWindow.postMessage({ |
113 | direction: "from-content-script", |
114 | action: 'ping' |
115 | }, '*'); |
116 | } |
117 | |
118 | iFrame.contentWindow.addEventListener("message", (event) => { |
119 | if (event.data && event.data.direction === "from-page-script") { |
120 | if (event.data.action === "ping") { |
121 | ping() |
122 | } else { |
123 | //new Uint8Array(event.data.message) is not accepted by muxrpc |
124 | const asBuffer = Buffer.from(event.data.message) |
125 | if (messageDataCallback) { |
126 | const _messageDataCallback = messageDataCallback |
127 | messageDataCallback = null |
128 | _messageDataCallback(null, asBuffer) |
129 | } else { |
130 | console.log('buffering....') |
131 | messageDataBuffer.push(asBuffer) |
132 | } |
133 | } |
134 | } |
135 | }) |
136 | const toPage = function (source) { |
137 | source(null, function more(end, data) { |
138 | iFrame.contentWindow.postMessage({ |
139 | direction: "from-content-script", |
140 | message: data |
141 | }, '*'); |
142 | source(null, more) |
143 | }) |
144 | } |
145 | iFrame.contentWindow.addEventListener('load', () => this.dispatchEvent(new CustomEvent('ready'))) |
146 | /*function logger(text) { |
147 | return pull.map((v) => { |
148 | console.log(text,v) |
149 | console.log(new TextDecoder("utf-8").decode(v)) |
150 | return v |
151 | }) |
152 | }*/ |
153 | this.sbot.manifest().then(manifest => { |
154 | //console.log('manifest', JSON.stringify(manifest)) |
155 | const asyncManifest = asyncifyManifest(manifest) |
156 | const server = MRPC(null, asyncManifest)(this.sbot) |
157 | const serverStream = server.createStream(() => { console.log('closed') }) |
158 | pull(fromPage, serverStream, toPage) |
159 | }) |
160 | }) |
161 | |
162 | } |
163 | } |
164 | |
165 | function asyncifyManifest(manifest) { |
166 | if (typeof manifest !== 'object') return manifest |
167 | let asyncified = {} |
168 | for (let k in manifest) { |
169 | var value = manifest[k] |
170 | // Rewrite re-exported sync methods as async, |
171 | if (value === 'sync') { |
172 | value = 'async' |
173 | } |
174 | asyncified[k] = value |
175 | } |
176 | return asyncified |
177 | } |
178 | |
179 | customElements.define("app-runner", AppRunner) |
180 |
Built with git-ssb-web