github.com/0chain/gosdk@v1.17.11/wasmsdk/demo/zcn.js (about) 1 /* 2 * This file is part of the 0chain @zerochain/0chain distribution 3 * (https://github.com/0chain/client-sdk). Copyright (c) 2018 0chain LLC. 4 * 5 * 0chain @zerochain/0chain program is free software: you can redistribute it 6 * and/or modify it under the terms of the GNU General Public License as 7 * published by the Free Software Foundation, version 3. 8 * 9 * This program is distributed in the hope that it will be useful, but 10 * WITHOUT ANY WARRANTY without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 * General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 'use strict' 19 20 const g = window 21 22 function hexStringToByte(str) { 23 if (!str) return new Uint8Array() 24 25 const a = [] 26 for (let i = 0, len = str.length; i < len; i += 2) { 27 a.push(parseInt(str.substr(i, 2), 16)) 28 } 29 30 return new Uint8Array(a) 31 } 32 33 function blsSign(hash, secretKey) { 34 const { jsProxy } = g.__zcn_wasm__ 35 36 if (!jsProxy || !secretKey) { 37 const errMsg = 'err: bls.secretKey is not initialized' 38 console.warn(errMsg) 39 throw new Error(errMsg) 40 } 41 42 const bytes = hexStringToByte(hash) 43 const sk = bls.deserializeHexStrToSecretKey(secretKey) 44 const sig = sk.sign(bytes) 45 46 if (!sig) { 47 const errMsg = 'err: wasm blsSign function failed to sign transaction' 48 console.warn(errMsg) 49 throw new Error(errMsg) 50 } 51 52 return sig.serializeToHexStr() 53 } 54 55 async function createObjectURL(buf, mimeType) { 56 var blob = new Blob([buf], { type: mimeType }) 57 return URL.createObjectURL(blob) 58 } 59 60 61 const readChunk = (offset, chunkSize, file) => 62 new Promise((res,rej) => { 63 const fileReader = new FileReader() 64 const blob = file.slice(offset, chunkSize+offset) 65 fileReader.onload = e => { 66 const t = e.target 67 if (t.error == null) { 68 res({ 69 size: t.result.byteLength, 70 buffer: new Uint8Array(t.result) 71 }) 72 }else{ 73 rej(t.error) 74 } 75 } 76 77 fileReader.readAsArrayBuffer(blob) 78 }) 79 80 81 /** 82 * Sleep is used when awaiting for Go Wasm to initialize. 83 * It uses the lowest possible sane delay time (via requestAnimationFrame). 84 * However, if the window is not focused, requestAnimationFrame never returns. 85 * A timeout will ensure to be called after 50 ms, regardless of whether or not 86 * the tab is in focus. 87 * 88 * @returns {Promise} an always-resolving promise when a tick has been 89 * completed. 90 */ 91 const sleep = (ms = 1000) => 92 new Promise(res => { 93 requestAnimationFrame(res) 94 setTimeout(res, ms) 95 }) 96 97 98 99 /** 100 * The maximum amount of time that we would expect Wasm to take to initialize. 101 * If it doesn't initialize after this time, we send a warning to console. 102 * Most likely something has gone wrong if it takes more than 3 seconds to 103 * initialize. 104 */ 105 const maxTime = 10 * 1000 106 107 // Initialize __zcn_wasm__ 108 g.__zcn_wasm__ = g.__zcn_wasm_ || { 109 glob:{ 110 index:0, 111 }, 112 jsProxy: { 113 secretKey: null, 114 publicKey: null, 115 sign: blsSign, 116 verify: blsVerify, 117 verifyWith: blsVerifyWith, 118 createObjectURL, 119 sleep, 120 }, 121 sdk: {}, //proxy object for go to expose its methods 122 } 123 124 /** 125 * bridge is an easier way to refer to the Go WASM object. 126 */ 127 const bridge = g.__zcn_wasm__ 128 129 // bulk upload files with FileReader 130 // objects: the list of upload object 131 // - allocationId: string 132 // - remotePath: string 133 // - file: File 134 // - thumbnailBytes: []byte 135 // - encrypt: bool 136 // - isUpdate: bool 137 // - isRepair: bool 138 // - numBlocks: int 139 // - callback: function(totalBytes,completedBytes,error) 140 async function bulkUpload(options) { 141 const start = bridge.glob.index 142 const opts = options.map(obj=>{ 143 const i = bridge.glob.index; 144 bridge.glob.index++ 145 const readChunkFuncName = "__zcn_upload_reader_"+i.toString() 146 const callbackFuncName = "__zcn_upload_callback_"+i.toString() 147 var md5HashFuncName = "" 148 g[readChunkFuncName] = async (offset,chunkSize) => { 149 const chunk = await readChunk(offset,chunkSize,obj.file) 150 return chunk.buffer 151 } 152 if (obj.file.size > 25*1024*1024) { 153 md5HashFuncName = "__zcn_md5_hash_"+i.toString() 154 const md5Res = md5Hash(obj.file) 155 g[md5HashFuncName] = async () => { 156 const hash = await md5Res 157 return hash 158 } 159 } 160 161 if(obj.callback) { 162 g[callbackFuncName] = async (totalBytes,completedBytes,error)=> obj.callback(totalBytes,completedBytes,error) 163 } 164 165 return { 166 allocationId:obj.allocationId, 167 remotePath:obj.remotePath, 168 readChunkFuncName:readChunkFuncName, 169 fileSize: obj.file.size, 170 thumbnailBytes:obj.thumbnailBytes?obj.thumbnailBytes.toString():"", 171 encrypt:obj.encrypt, 172 webstreaming:obj.webstreaming, 173 isUpdate:obj.isUpdate, 174 isRepair:obj.isRepair, 175 numBlocks:obj.numBlocks, 176 callbackFuncName:callbackFuncName, 177 md5HashFuncName:md5HashFuncName, 178 } 179 }) 180 181 // md5Hash(options[0].file).then(hash=>{ 182 // console.log("md5 hash: ",hash) 183 // }).catch(err=>{ 184 // console.log("md5 hash error: ",err) 185 // }) 186 187 const end = bridge.glob.index 188 const result = await bridge.__proxy__.sdk.multiUpload(JSON.stringify(opts)) 189 for (let i=start; i<end;i++){ 190 g["__zcn_upload_reader_"+i.toString()] = null; 191 g["__zcn_upload_callback_"+i.toString()] =null; 192 g["__zcn_md5_hash_"+i.toString()] = null; 193 } 194 return result 195 } 196 197 198 async function md5Hash(file) { 199 const result = new Promise((resolve, reject) => { 200 const worker = new Worker('md5worker.js') 201 worker.postMessage(file) 202 worker.onmessage = e => { 203 resolve(e.data) 204 worker.terminate() 205 } 206 worker.onerror = reject 207 }) 208 return result 209 } 210 211 212 async function blsSign(hash, secretKey) { 213 if (!bridge.jsProxy && !secretKey) { 214 const errMsg = 'err: bls.secretKey is not initialized' 215 console.warn(errMsg) 216 throw new Error(errMsg) 217 } 218 219 const bytes = hexStringToByte(hash) 220 const sk = bls.deserializeHexStrToSecretKey(secretKey) 221 const sig = sk.sign(bytes) 222 223 if (!sig) { 224 const errMsg = 'err: wasm blsSign function failed to sign transaction' 225 console.warn(errMsg) 226 throw new Error(errMsg) 227 } 228 229 return sig.serializeToHexStr() 230 } 231 232 async function blsVerifyWith(pk, signature, hash) { 233 const publicKey = bls.deserializeHexStrToPublicKey(pk); 234 const bytes = hexStringToByte(hash) 235 const sig = bls.deserializeHexStrToSignature(signature) 236 return publicKey.verify(sig, bytes) 237 } 238 239 async function blsVerify(signature, hash) { 240 if (!bridge.jsProxy && !bridge.jsProxy.publicKey) { 241 const errMsg = 'err: bls.publicKey is not initialized' 242 console.warn(errMsg) 243 throw new Error(errMsg) 244 } 245 246 const bytes = hexStringToByte(hash) 247 const sig = bridge.jsProxy.bls.deserializeHexStrToSignature(signature) 248 return bridge.jsProxy.publicKey.verify(sig, bytes) 249 } 250 251 async function setWallet(bls, 252 clientID, 253 clientKey, 254 peerPublicKey, 255 sk, 256 pk, 257 mnemonic, 258 isSplit) { 259 if (!bls) throw new Error('bls is undefined, on wasm setWallet fn') 260 if (!sk) throw new Error('secret key is undefined, on wasm setWallet fn') 261 if (!pk) throw new Error('public key is undefined, on wasm setWallet fn') 262 263 console.log('setWallet: ', clientID, sk, pk) 264 bridge.jsProxy.bls = bls 265 bridge.jsProxy.secretKey = bls.deserializeHexStrToSecretKey(sk) 266 bridge.jsProxy.publicKey = bls.deserializeHexStrToPublicKey(pk) 267 268 // use proxy.sdk to detect if sdk is ready 269 await bridge.__proxy__.sdk.setWallet(clientID, clientKey, peerPublicKey, pk, sk, mnemonic, isSplit) 270 bridge.walletId = clientID 271 } 272 273 async function loadWasm(go) { 274 // If instantiateStreaming doesn't exists, polyfill/create it on top of instantiate 275 if (!WebAssembly?.instantiateStreaming) { 276 WebAssembly.instantiateStreaming = async (resp, importObject) => { 277 const source = await (await resp).arrayBuffer() 278 return await WebAssembly.instantiate(source, importObject) 279 } 280 } 281 282 const result = await WebAssembly.instantiateStreaming( 283 await fetch('test/zcn.wasm'), 284 go.importObject 285 ) 286 287 setTimeout(() => { 288 if (g.__zcn_wasm__?.__wasm_initialized__ !== true) { 289 console.warn( 290 'wasm window.__zcn_wasm__ (zcn.__wasm_initialized__) still not true after max time' 291 ) 292 } 293 }, maxTime) 294 295 go.run(result.instance) 296 } 297 298 async function createWasm() { 299 if (bridge.__proxy__) { 300 return bridge.__proxy__ 301 } 302 303 const go = new g.Go() 304 305 loadWasm(go) 306 307 const sdkGet = 308 (_, key) => 309 (...args) => 310 // eslint-disable-next-line 311 new Promise(async (resolve, reject) => { 312 if (!go || go.exited) { 313 return reject(new Error('The Go instance is not active.')) 314 } 315 316 while (bridge.__wasm_initialized__ !== true) { 317 await sleep(1000) 318 } 319 320 if (typeof bridge.sdk[key] !== 'function') { 321 resolve(bridge.sdk[key]) 322 323 if (args.length !== 0) { 324 reject( 325 new Error( 326 'Retrieved value from WASM returned function type, however called with arguments.' 327 ) 328 ) 329 } 330 return 331 } 332 333 try { 334 let resp = bridge.sdk[key].apply(undefined, args) 335 336 // support wasm.BindAsyncFunc 337 if (resp && typeof resp.then === 'function') { 338 resp = await Promise.race([resp]) 339 } 340 341 if (resp && resp.error) { 342 reject(resp.error) 343 } else { 344 resolve(resp) 345 } 346 } catch (e) { 347 reject(e) 348 } 349 }) 350 351 const sdkProxy = new Proxy( 352 { 353 354 }, 355 { 356 get: sdkGet, 357 } 358 ) 359 360 const jsProxy = new Proxy( 361 {}, 362 { 363 get: (_, key) => bridge.jsProxy[key], 364 set: (_, key, value) => { 365 bridge.jsProxy[key] = value 366 }, 367 } 368 ) 369 370 const proxy = { 371 bulkUpload: bulkUpload, 372 setWallet: setWallet, 373 sdk: sdkProxy, //expose sdk methods for js 374 jsProxy, //expose js methods for go 375 } 376 377 bridge.__proxy__ = proxy 378 379 return proxy 380 }