// Load HTML (requires Browser API!)
import { MasaoJSONFormat, make } from './format';
import { sanitize } from './param';
export function html(source: string): Promise {
return new Promise((resolve, _reject) => {
// DOMParser
const parser = new DOMParser();
const htmldoc = parser.parseFromString(source, 'text/html');
const titleelm = htmldoc.querySelector('title');
const title = (titleelm && titleelm.textContent) || null;
// 正男を探す(まずはapplet/object)
const applets = Array.from(htmldoc.querySelectorAll(
'applet, object',
) as any);
for (let a of applets) {
if (a.tagName === 'APPLET') {
// applet!
const code = a.getAttribute('code') || '';
const archive = a.getAttribute('archive') || '';
const version = getVersion(code, archive);
if (version == null) {
continue;
}
const ps = Array.from(
a.getElementsByTagName('param'),
);
const params: Record = {};
for (let p of ps) {
params[p.name] = p.value;
}
resolve(
make({
params,
version,
metadata: title
? {
title,
}
: void 0,
}),
);
return;
} else if (a.tagName === 'OBJECT') {
// object!
const type = a.getAttribute('type') || '';
if (/^application\/x-java-applet$/i.test(type)) {
// 正男かも
const ps = Array.from(
a.getElementsByTagName('param'),
);
const params: Record = {};
let code;
let archive;
for (let p of ps) {
if (/^classid$/i.test(p.name)) {
const re = p.value.match(/^java:(.+)$/);
if (re == null) {
continue;
}
code = re[1];
} else if (/^archive$/i.test(p.name)) {
archive = p.value;
} else {
params[p.name] = p.value;
}
}
if (code == null || archive == null) {
continue;
}
const version = getVersion(code, archive);
if (version == null) {
continue;
}
resolve(
make({
params: sanitize(params, version),
version,
metadata: title
? {
title,
}
: void 0,
}),
);
return;
}
}
}
// applet, objectは無かった
resolve(runCanvas(htmldoc));
});
}
function runCanvas(htmldoc: HTMLDocument): Promise {
const titleelm = htmldoc.querySelector('title');
const title = (titleelm && titleelm.textContent) || null;
// canvas正男を動作させる
// Web Workerを作る
let worker = `
CanvasMasao = {
Game: function Game(params, id, options){
if (options == null) {
options = {};
}
result = {
params: params,
'advanced-map': options['advance-map'] || options['advanced-map'],
};
postMessage(result);
close();
},
};
JSMasao = CanvasMasao;
`;
const scripts = htmldoc.querySelectorAll('script');
for (const s of Array.from(scripts)) {
if (
!s.src &&
(!s.type || /^(?:text|application)\/javascript$/.test(s.type))
) {
// srcがないものを全部突っ込む
worker += `;${s.textContent};`;
}
}
// sandbox iframeを作る
worker = worker.replace(/\u2028|\u2029/g, ' ');
const inject = `
sandbox
`;
// これをiframeで開く
const blob = new Blob([inject], {
type: 'text/html',
});
const url = URL.createObjectURL(blob);
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = url;
iframe.sandbox.add('allow-scripts');
return new Promise(resolve => {
// random
const handler = (ev: MessageEvent) => {
if (ev.data != null) {
// ゲームを受け取った
if (timeout != null) {
clearTimeout(timeout);
}
timeout = null;
window.removeEventListener('message', handler, false);
// makeがthrowするかも
resolve(
make({
params: ev.data.params,
version: 'fx16',
'advanced-map': ev.data['advanced-map'],
metadata: title
? {
title,
}
: void 0,
}),
);
}
};
let timeout: any = setTimeout(() => {
window.removeEventListener('message', handler, false);
resolve(null);
}, 3000);
window.addEventListener('message', handler, false);
// 通信開始
document.body.appendChild(iframe);
setTimeout(() => {
if (iframe.contentWindow != null) {
iframe.contentWindow.postMessage('ping', '*');
}
}, 250);
}).then(result => {
document.body.removeChild(iframe);
return result;
});
}
// code, archiveからversionを推定
function getVersion(
code: string,
archive: string,
): '2.8' | 'fx16' | 'kani2' | null {
if (/^(?:MasaoConstruction|MasaoJSS)(?:\.class)?$/i.test(code)) {
// 正男っぽい
// バージョン判定
if (/\.zip$/i.test(archive)) {
return '2.8';
} else {
return 'fx16';
}
} else if (/^MasaoKani2?(?:\.class)$/i.test(code)) {
return 'kani2';
}
return null;
}