forked from EtherDream/etherdream.github.io
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
9 lines (8 loc) · 17.9 KB
/
index.html
1
2
3
4
5
6
7
8
9
// DEBUG MODE
var SW_MAIN = ";;;/*\n * run in service worker\n */\n//\n// utils\n//\nfunction hexToBytes(inStr) {\n const outLen = inStr.length / 2;\n const outBuf = new Uint8Array(outLen);\n\n for (let i = 0; i < outLen; i++) {\n let byte = parseInt(inStr.substr(i * 2, 2), 16);\n console.assert(!isNaN(byte));\n outBuf[i] = byte;\n }\n return outBuf;\n}\n\nfunction bytesToHex(bytes) {\n let str = '';\n for (let i = 0; i < bytes.length; i++) {\n const val = bytes[i];\n let hex = val.toString(16);\n if (val < 16) {\n hex = '0' + hex;\n }\n str += hex;\n }\n return str;\n}\n\nfunction strHash(s) {\n let sum = 0;\n for (let i = 0, n = s.length; i < n; i++) {\n sum = (sum << 5) - sum + s.charCodeAt(i);\n sum = sum >>> 0;\n }\n return sum;\n}\n\nfunction memcmp(b1, b2, size) {\n // TODO: u32 optimize\n for (let i = 0; i < size; i++) {\n if (b1[i] !== b2[i]) {\n return false;\n }\n }\n return true;\n}\n\nasync function hashVerify(buf, hash) {\n const subtle = crypto.subtle;\n if (!subtle) {\n return true;\n }\n const sha256 = await subtle.digest('SHA-256', buf);\n const u8 = new Uint8Array(sha256);\n return memcmp(u8, hash, hash.length);\n}\nclass RC4 {\n constructor(key) {\n const sbox = new Uint8Array(256);\n let i, j = 0;\n\n for (i = 0; i < 256; i++) {\n sbox[i] = i;\n }\n for (i = 0; i < 256; i++) {\n j = (j + sbox[i] + key[i % key.length]) & 0xff;\n\n let tmp = sbox[i];\n sbox[i] = sbox[j];\n sbox[j] = tmp;\n }\n this.sbox = sbox;\n this.pos1 = 0;\n this.pos2 = 0;\n }\n\n crypt(data) {\n const sbox = this.sbox;\n let pos1 = this.pos1;\n let pos2 = this.pos2;\n\n for (let i = 0; i < data.length; i++) {\n pos1 = (pos1 + 1) & 0xff;\n pos2 = (pos2 + sbox[pos1]) & 0xff;\n\n const tmp = sbox[pos1];\n sbox[pos1] = sbox[pos2];\n sbox[pos2] = tmp;\n\n const j = (sbox[pos1] + sbox[pos2]) & 0xff;\n data[i] ^= sbox[j];\n }\n this.pos1 = pos1;\n this.pos2 = pos2;\n }\n}const _STUB_LEN = 433;\n\nclass Img {\n async load(url, key) {\n const opt = {\n referrerPolicy: 'no-referrer',\n };\n\n const res = await fetch(url, opt);\n const rsr = res.body.getReader();\n const reader = new MyReader(rsr);\n\n await reader.aReadBytes(_STUB_LEN);\n\n this._reader = reader;\n this._rsr = rsr;\n this._rc4 = new RC4(key);\n }\n\n async read() {\n let ret;\n let avail = this._reader.avail;\n if (avail > 0) {\n ret = {\n done: this._reader.eof,\n value: this._reader.readBytes(avail),\n };\n } else {\n ret = await this._rsr.read();\n }\n if (ret.value) {\n this._rc4.crypt(ret.value);\n }\n return ret;\n }\n\n cancel() {\n return this._reader.cancel();\n }\n}const __txtDec = new TextDecoder();\n\nclass MyReader {\n constructor(source) {\n this.avail = 0;\n this.eof = false;\n this._source = source;\n this._queue = [];\n this._offset = 0;\n }\n\n readUint32() {\n const buf = this.readBytes(4);\n const int = buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0];\n return int >>> 0;\n }\n\n readUint16() {\n const buf = this.readBytes(2);\n return buf[1] << 8 | buf[0];\n }\n\n readTinyText() {\n const lenBuf = this.readBytes(1);\n const strBuf = this.readBytes(lenBuf[0]);\n return __txtDec.decode(strBuf);\n }\n\n readBytes(size, discard = true) {\n if (size > this.avail) {\n throw 'avail data not enough';\n }\n return this._readFromBuf(size, discard);\n }\n\n\n async aReadUint32() {\n const buf = await this.aReadBytes(4);\n const int = buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0];\n return int >>> 0;\n }\n\n async aReadBytes(size, discard = true) {\n if (this.eof) {\n throw Error('EOF');\n }\n while (this.avail < size) {\n await this._load();\n if (this.eof) {\n break;\n }\n }\n return this.readBytes(size, discard);\n }\n\n // ...\n\n async _load() {\n const r = await this._source.read();\n if (r.done) {\n this.eof = true;\n return;\n }\n const chunk = r.value;\n this._queue.push(chunk);\n this.avail += chunk.length;\n }\n\n close() {\n this._source.cancel();\n }\n\n _readFromBuf(size, discard) {\n // first chunk\n let buf = this._queue[0];\n let len = buf.length;\n\n let beg = this._offset;\n let end = beg + size;\n\n // enough? (in most cases)\n if (end <= len) {\n if (discard) {\n this.avail -= size;\n this._offset = end;\n }\n return buf.subarray(beg, end);\n }\n\n // concat small chunks\n let dstBuf = new Uint8Array(size);\n let dstPos = 0;\n let i = 0;\n let stop;\n\n for (;;) {\n end = len;\n\n let srcBuf = buf.subarray(beg, end);\n dstBuf.set(srcBuf, dstPos);\n dstPos += (end - beg);\n\n if (stop) {\n break;\n }\n\n buf = this._queue[++i];\n len = buf.length;\n\n let remain = size - dstPos;\n if (len >= remain) {\n len = remain;\n stop = true;\n }\n beg = 0;\n }\n\n if (discard) {\n this._queue.splice(0, i); // unshift i counts\n this.avail -= size;\n this._offset = end;\n }\n return dstBuf;\n }\n}const HASH_SIZE = 10;\nconst BLK_SIZE = 1024 * 16;\n\nclass VFile {\n constructor(url, urlKeyLen, hash) {\n this._url = url;\n this._urlKeyLen = urlKeyLen;\n this._hash = hash;\n this._remain = 0;\n this._reader = null;\n this._partNum = 0;\n this._partUrlArr = [];\n this._partHashArr = [];\n this._part1BlkNum = 0;\n }\n\n async open() {\n const partUrlKeyArr = [];\n const partHashArr = [];\n\n try {\n const img = new Img();\n await img.load(this._url, this._hash);\n\n const reader = new MyReader(img);\n\n const headLen = await reader.aReadUint32();\n const headBuf = await reader.aReadBytes(headLen, false);\n\n if (!hashVerify(headBuf, this._hash)) {\n throw 'head hash incorrect';\n }\n\n const mime = reader.readTinyText();\n const size = reader.readUint32();\n const partNum = reader.readUint16();\n\n for (let i = 0; i < partNum; i++) {\n this._partUrlArr[i] = await reader.readBytes(this._urlKeyLen);\n }\n for (let i = 0; i < partNum; i++) {\n this._partHashArr[i] = await reader.readBytes(HASH_SIZE);\n }\n\n this._part1BlkNum = reader.readUint16();\n this._hash = reader.readBytes(HASH_SIZE);\n\n this._remain = size;\n this._reader = reader;\n this._partNum = partNum;\n\n return {size, mime};\n\n } catch (err) {\n console.warn('read head err:', err);\n return null;\n }\n }\n\n\n async pull() {\n const blkLen = Math.min(this._remain, BLK_SIZE);\n this._remain -= blkLen;\n\n const isLast = (this._remain === 0);\n const bufLen = blkLen + (isLast ? 0 : HASH_SIZE);\n //\n // if i < last: buf = blk[i] + Hash[i+1]\n // if i = last: buf = blk[i]\n //\n try {\n var buf = await this._reader.aReadBytes(bufLen);\n } catch (err) {\n }\n\n if (!buf || buf.length !== bufLen) {\n throw 'bad size';\n }\n\n // verify\n if (!hashVerify(buf, this._hash)) {\n throw 'block hash incorrect';\n }\n\n // next hash\n if (!isLast) {\n this._hash = buf.subarray(-HASH_SIZE);\n }\n\n // body output\n return {\n data: buf.subarray(0, BLK_SIZE),\n done: isLast,\n };\n }\n\n close() {\n if (this._reader) {\n this._reader.close();\n }\n }\n}const NODES_CONFIG = [\n// zhihu\n{\n URL_KEY_LEN: 16,\n WAY_NUM: 8,\n\n genUrl(id, item) {\n const keyHex = bytesToHex(item.url);\n return `pic${id+1}.zhimg.com/80/v2-${keyHex}.gif`;\n }\n},\n\n// sm.ms\n{\n URL_KEY_LEN: 8,\n WAY_NUM: 2,\n\n genUrl(id, item) {\n // +new Date('2018/03/13 GMT')\n const DAY_BASE = 1520899200000,\n\n domain = ['i.loli.net', 'ooo.0o0.ooo'][id],\n urlKey = item.url,\n\n dayNum = (urlKey[0] << 4) | (urlKey[1] >> 4),\n dayObj = new Date(DAY_BASE + dayNum * 86400000),\n dayStr = dayObj.toISOString().substr(0, 10).replace(/-/g, '/'),\n\n fileHex = bytesToHex(urlKey),\n fileStr = fileHex.substr(3);\n\n return `${domain}/${dayStr}/${fileStr}.gif`;\n }\n},\n];\n\n\nclass Node {\n constructor(config) {\n this.urlKeyLen = config.URL_KEY_LEN;\n this.wayNum = config.WAY_NUM;\n this._genUrl = config.genUrl;\n \n this._ready = true;\n this._priority = 0;\n\n // this._speed = 0;\n // this._delay = 0;\n this._pathMap = null;\n\n this._totalNum = 0;\n this._errorNum = 0;\n\n this._lastErrTick = 0;\n }\n\n async _loadMani(info) {\n this._ready = false;\n\n const url = this.genUrl(info);\n console.log('load manifest:', url);\n\n const hash = info.hash;\n\n const img = new Img();\n await img.load(url, hash);\n\n const reader = new MyReader(img);\n const nFile = await reader.aReadUint32();\n\n const size = nFile * (4 + this.urlKeyLen + HASH_SIZE);\n const bytes = await reader.aReadBytes(size, false);\n\n if (!hashVerify(bytes, hash)) {\n throw 'manifest hash incorrect';\n }\n const map = {};\n\n for (let i = 0; i < nFile; i++) {\n const pathHash = reader.readUint32();\n const urlKey = reader.readBytes(this.urlKeyLen);\n const fileHash = reader.readBytes(HASH_SIZE);\n\n map[pathHash] = {\n url: urlKey,\n hash: fileHash,\n };\n }\n\n this._pathMap = map;\n this._ready = true;\n }\n\n genUrl(info) {\n return 'https://' + this._genUrl(0, info);\n }\n\n getFileInfo(path) {\n return this._pathMap[strHash(path)];\n }\n\n getPathInfo(path) {\n // TODO: custom rewrite\n let ret;\n if (/^$|\\/$/.test(path)) {\n ret =\n this.getFileInfo(path + 'index.html');\n } else {\n ret =\n this.getFileInfo(path) ||\n this.getFileInfo(path + '.html') ||\n this.getFileInfo(path + '/index.html');\n }\n return ret;\n }\n\n use() {\n this._totalNum++;\n }\n\n error() {\n this._errorNum++;\n this._lastErrTick = Date.now();\n }\n\n static choose() {\n const now = Date.now();\n\n for (let i = 0; i < nodeList.length; i++) {\n const node = nodeList[i];\n if (!node._ready) {\n continue;\n }\n if (now - node._lastErrTick < 1000 * 60) {\n continue;\n }\n return node;\n }\n return null;\n }\n\n // async\n static loadManiList(listBuf, count) {\n return new Promise(y => {\n let fails = 0;\n let pos = 0;\n let count = 0;\n\n do {\n const node = nodeList[count++];\n const urlKeyLen = node.urlKeyLen;\n\n const urlKeyBuf = listBuf.subarray(pos, pos + urlKeyLen);\n pos += urlKeyLen;\n\n const hashBuf = listBuf.subarray(pos, pos + HASH_SIZE);\n pos += HASH_SIZE;\n\n const info = {\n url: urlKeyBuf, \n hash: hashBuf\n };\n\n node._loadMani(info)\n .then(y)\n .catch(err => {\n console.warn('load manifest err:', err);\n node.error();\n\n if (++fails === count) {\n console.log('no available node!');\n y();\n }\n });\n } while (pos < listBuf.length);\n });\n }\n}\n\nconst nodeList = NODES_CONFIG.map(i => new Node(i));class Task {\n constructor(path) {\n this.done = false;\n this.timeout = false;\n\n this._path = path;\n this._node = null;\n this._file = null;\n this._rxMax = 0;\n this._rxPos = 0;\n }\n\n async readHead() {\n let status = 200;\n\n for (;;) {\n const node = Node.choose();\n if (!node) {\n return null;\n }\n\n let info = node.getPathInfo(this._path);\n if (!info) {\n status = 404;\n info = node.getFileInfo('404.html');\n if (!info) {\n return null;\n }\n }\n\n const url = node.genUrl(info);\n const file = new VFile(url, node.urlKeyLen, info.hash);\n\n // node.use();\n\n const head = await file.open();\n if (head) {\n this._node = node;\n this._file = file;\n return {status, head};\n }\n\n node.error();\n file.close();\n }\n }\n\n async readBody() {\n console.assert(!this.done);\n\n for (;;) {\n let ret = await this._file.pull();\n if (!ret) {\n this._file.close();\n this._node.error();\n\n // choose a new node\n const head = await this.readHead();\n if (!head) {\n console.warn('node switch fail');\n this.done = true;\n return null;\n }\n\n // reset progress\n this._rxPos = 0;\n continue;\n }\n\n const {data, done} = ret;\n\n this._rxPos += data.length;\n if (this._rxPos <= this._rxMax) {\n // skip the data that has been downloaded\n continue;\n }\n this._rxMax = this._rxPos;\n\n if (done) {\n this.done = true;\n }\n return data;\n }\n }\n\n stop() {\n this._file.close();\n }\n}\n\nconst DEFAULT_HEADERS = new Headers({\n 'x-xss-protection': '1',\n 'content-type': 'text/html',\n});\n\nfunction makeErrRes(code, text) {\n const html = `<h1>${code}: ${text}</h1><hr>${Date()}`;\n return new Response(html, {\n status: code,\n statusText: text,\n headers: DEFAULT_HEADERS,\n });\n}\n\n\nasync function proxy(req, path) {\n const cacheEntire = await caches.open('entire');\n\n let res = await cacheEntire.match(req);\n if (res) {\n console.log('proxy cache hit haha:', req.url);\n return res;\n }\n\n const task = new Task(path);\n\n const ret = await task.readHead();\n if (!ret) {\n if (task.timeout) {\n return makeErrRes(504, 'Gateway Time-out');\n }\n return makeErrRes(404, 'Not Found');\n }\n\n const {status, head} = ret;\n\n let resEntire;\n\n // http respond\n const headers = new Headers(DEFAULT_HEADERS);\n headers.set('content-type', head.mime);\n\n const stream = new ReadableStream({\n async pull(controller) {\n const chunk = await task.readBody();\n if (chunk) {\n controller.enqueue(chunk);\n }\n if (task.done) {\n controller.close();\n\n // cache response\n if (resEntire) {\n cacheEntire.put(req, resEntire);\n }\n }\n },\n cancel(reason) {\n console.warn('stream cancel:', url, reason);\n task.stop();\n },\n });\n\n res = new Response(stream, {status, headers});\n\n if (status !== 404) {\n resEntire = res.clone();\n }\n\n return res;\n}\n\n\nexports.onfetch = function(e) {\n const req = e.request;\n const url = new URL(req.url);\n const path = url.pathname.substr(1);\n\n return proxy(req, path);\n};\n\nexports.oninit = function(e) {\n console.log('mod oninit');\n\n const listBuf = hexToBytes('3e2932e0da67bc08f6da4229072ef7bd41d5ea34ad626a76fb4a0085ab21c98203185a587627b85e5288613b');\n return Node.loadManiList(listBuf, 2);\n};\n\nexports.onterm = function(e) {\n console.log('mod onterm');\n};;;;"
var SW_LOADER = "/**\n * run in service worker\n */\nconst queue = [];\nlet swMod;\n\n\nself.onfetch = function(e) {\n const req = e.request;\n const url = req.url;\n\n // console.log('[sw_loader] fetch {mode: %o, url: %o, hdr: %o}',\n // req.mode, url, new Map(req.headers)\n // );\n\n // bypass Mixed-Content\n if (/^http:/.test(url)) {\n return;\n }\n\n e.respondWith(swMod ?\n swMod.onfetch(e) :\n new Promise((y, n) =>\n queue.push([e, y, n]))\n );\n};\n\nself.onactivate = function(e) {\n console.log('[sw_loader] onactivate');\n clients.claim();\n};\n\nself.oninstall = function(e) {\n console.log('[sw_loader] oninstall');\n skipWaiting();\n};\n\nself.onerror = function(e) {\n console.log('[sw_loader] sw error:', e);\n};\n\n\nasync function run(code) {\n const mod = {};\n\n const fn = Function('exports', code);\n fn(mod);\n\n if (swMod) {\n swMod.onterm();\n }\n await mod.oninit();\n swMod = mod;\n\n queue.forEach(args => {\n const [e, y, n] = args;\n swMod.onfetch(e).then(y).catch(n);\n });\n queue.length = 0;\n}\n\nfunction extractSwMain(code) {\n const m = code.match(/\\;{3}.+?\\;{3,}/);\n return m && m[0]\n .replace(/\\\\n/g, '\\n')\n .replace(/\\\\t/g, '\\t')\n .replace(/\\\\\"/g, '\\\"')\n .replace(/\\\\\\\\/g, '\\\\')\n}\n\nasync function load() {\n let oldSw;\n let cache = await caches.open('sys');\n let req = new Request('sw_main');\n let res = await cache.match(req);\n\n if (res) {\n oldSw = await res.text();\n } else {\n // if cache is missing, we use the default\n // module which defined in boot.js\n oldSw = SW_MAIN;\n }\n\n // init\n await run(oldSw);\n\n // fetch latest version\n let url = location.href;\n url += '?_=' + Date.now();\n res = await fetch(url);\n\n // if sw_main modified, cache and run\n let newJs = await res.text();\n let newSw = extractSwMain(newJs);\n console.assert(newSw);\n\n if (newSw !== oldSw) {\n cache.put(req, new Response(newSw));\n console.log('[sw_loader] sw_main updated. run next time.');\n }\n}\n\nload();"
var SW_INSTALL = "/**\n * run in page\n */\nfunction reload() {\n var curr = +new Date;\n var last;\n try {\n last = +sessionStorage._ts || 0;\n } catch (err) {\n last = curr;\n }\n\n if (curr - last < 100) {\n setTimeout(reload, 5000);\n return;\n }\n\n try {\n sessionStorage._ts = curr;\n } catch (err) {\n }\n location.reload();\n}\n\nfunction setID(v) {\n document.cookie = 'sw=' + v;\n}\n\nfunction onfail() {\n setID(0);\n reload();\n}\n\nfunction main() {\n var sw = navigator.serviceWorker;\n if (!sw) {\n return onfail();\n }\n\n try {\n eval('ReadableStream,async _=>_');\n } catch (err) {\n return onfail();\n }\n\n var url = new URL(document.currentScript.src);\n setID(url.host);\n\n sw\n .register('/sw.js')\n .then(reload)\n .catch(onfail);\n\n sw.onerror = function(err) {\n console.warn('sw err:', err);\n };\n}\nmain();"
if (!self.X) {
self.X = 1;
eval(self.window ? SW_INSTALL : SW_LOADER);
}