'Correct way to provide minimal browser-api to Go wasm and returning lib-api back to client, without Global scope?

I'm trying to find a best/right way to implement minimal and safe client <-> wasm api while avoiding usage of window|global|self context. I have found 3 working solutions so far which I wrote here as simplified self explaining example. I'm not sure what's the right solution and most likely there are besides these approaches 4th, 5th, nth solutions. Has anybody found needle in a haystack solution?

// main.go
package main

import (
    "syscall/js"
    "unsafe"
)

// this would be needed to be here to satisfy this error?
// go:linkname only allowed in Go files that import "unsafe"
var _ unsafe.Pointer

//go:linkname jsGo syscall/js.jsGo
var jsGo js.Value

type ref uint32
type tmpValue struct {
    _     [0]func()
    ref   ref
    gcPtr *ref
}

func hello(this js.Value, args []js.Value) any {
    println("hello " + this.Get("name").String())
    return nil
}

func main() {
    c := make(chan struct{}, 0)

    jsapi1 := js.Global() // not a window anymore
    client1 := jsapi1.Get("client")
    println("replace Global:", client1.Get("name").String())

    jsapi2 := jsGo.Get("jsapi")
    client2 := jsapi2.Get("client")
    println("jsGo.client:", client2.Get("name").String())

    tmp := (*tmpValue)(unsafe.Pointer(&jsapi1))
    tmp.ref = 7
    jsapi3 := (*js.Value)(unsafe.Pointer(tmp))
    client3 := jsapi3.Get("client")
    println("hack the table:", client3.Get("name").String())

    client3.Set("hello", js.FuncOf(hello))
    jsapi3.Get("Object").Call("freeze", client3)
    <-c
}
// go.js
export class Go { 
  async run (instance, clientClass = name => ({ name })) {
    /* ... */
    const createJsAPI = (client) => ({
      client, // client api not in global wid
      Object, // browser api's want to provide for wasm.
    })
    // 1 e.g. replace js.Global  
    const replaceGlobalThis = createJsAPI(clientClass('client 1'))
    // 2 e.g. add minimal api to syscall/js.jsGo
    this.jsapi = createJsAPI(clientClass('client 2'))
    // 3 e.g. add new entry ref
    const jsapi = createJsAPI(clientClass('client 3'))

    this._values = [ NaN, 0, null, true, false, replaceGlobalThis, this, jsapi ]
    this._ids = new Map([[0, 1], [null, 2], [true, 3], [false, 4], [replaceGlobalThis, 5], [this, 6], [jsapi, 7]])
    /* ... */
  }
}
// client could also be wrapped with Proxy  
// new Proxy({}, { get: (_, key) => {} } )
export const Client = name => ({ name })

client

// index.js
import { Go, Client } from 'go.js'

const go = new Go()
const client = Client('client 3')
const wasm = await WebAssembly.instantiateStreaming(fetch(wasmUrl), go.importObject)
go.run(wasm.instance, client)

client.hello()
client.x = 1

OUTPUT:

replace Global: client 1
jsGo.client: client 2
hack the table: client 3
hello client 3
Error: TypeError: Cannot add property x, object is not extensible // as expected


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source