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  }