Git Product home page Git Product logo

swiftwasm / javascriptkit Goto Github PK

View Code? Open in Web Editor NEW
621.0 15.0 43.0 3.5 MB

Swift framework to interact with JavaScript through WebAssembly.

Home Page: https://swiftpackageindex.com/swiftwasm/JavaScriptKit/main/documentation/javascriptkit

License: MIT License

HTML 0.06% JavaScript 9.21% Swift 68.84% C 9.15% TypeScript 11.03% Makefile 1.25% Shell 0.46%
swift swiftwasm javascript webassembly

javascriptkit's People

Contributors

dependabot[bot] avatar fjtrujy avatar gibachan avatar ikesyo avatar j-f1 avatar kateinoigakukun avatar kuhl avatar maxdesiatov avatar mjburghard avatar omochi avatar patrickpijnappel avatar pedrovgs avatar revolter avatar strega avatar valeriyvan avatar yonihemi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

javascriptkit's Issues

print doesn't work in any event handler

While using print as a top-level invocation works, if you call print in any closure handler, such as onclick, addEventListener etc, print output is not redirected to console.log. Apparently, there isn't anything that reads from stdout and redirects to console.log at that point.

Cannot set HTMLElement.style properties

I have this really simple problem I can't seem to figure out. I'm trying to use JavaScriptKit to create an element in the browsers DOM and set some CSS style on it:

import JavaScriptKit

let document = JSObject.global.document

var spanElem = document.createElement("span")
spanElem.innerText = "hello"
spanElem.style.backgroundColor = "yellow"

_ = document.body.appendChild(spanElem)

But it seems that the assignment to spanElem.style.backgroundColor doesn't have any effect. When I run this code (as main.swift), the <span> is created but its CSS style is unchanged. Reading style.backgroundColor from the browser console also shows nothing, but changing it from there works as expected:

document.getElementsByTagName('span')[0].style.backgroundColor
""
document.getElementsByTagName('span')[0].style.backgroundColor = "yellow"
"yellow"
document.getElementsByTagName('span')[0].style.backgroundColor
"yellow"

I'm using carton 0.10.0, Swift swift-wasm-5.3.1-RELEASE, JavaScriptKit 0.10.1 and am testing with newest Chrome and Safari (with same results) on macOS 10.15.7.

What am I doing wrong here?

`JSNumber` type to support `long long` type in Web API IDL

Rust folks have a similar issue, where long long shouldn't be mapped to Int64, which is bridged to BigInt on JS side. rustwasm/wasm-bindgen#800

Checked a few other places where long long is used, like AudioData, Blob, and File and all of them return a JS number in corresponding properties, not BigInt. I'm 95% sure none of these support BigInt, remaining 5% certainty could be added by writing actual tests. I personally vote for either keeping these Int32 as proposed, or creating a new union type. Just riffing:

enum JSNumber: ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral {
case integer(Int32)
case float(Double)
// ...
}

Should we add such type to JSKit then?

cc @kateinoigakukun

Originally posted by @MaxDesiatov in swiftwasm/WebAPIKit#42 (comment)

Can't read from a file using JSPromise

I know that this is more of a support issue than a bug report, but I really don't know what else to try, and didn't find another help channel.

Here is the entire script using Tokamak:

import JavaScriptKit
import TokamakDOM

struct TokamakApp: App {
    var body: some Scene {
        WindowGroup("Tokamak App") {
            ContentView()
        }
    }
}

struct ContentView: View {
    var body: some View {
        VStack {
            HTML("input", [
                "id": "file",
                "type": "file"
            ])
            Button("Convert") {
                let document = JSObject.global.document
                let input = document.getElementById("file")
                let file = input.files.item(0).object!

                let promise = file.text.function!.callAsFunction().object!

                JSPromise<JSValue, Error>(promise)!.then { value in
                    let console = JSObject.global.console.object!
                    let log = console.log.function!

                    log(value)
                }
            }
        }
    }
}

// @main attribute is not supported in SwiftPM apps.
// See https://bugs.swift.org/browse/SR-12683 for more details.
TokamakApp.main()

which is throwing this error:

Unhandled Promise Rejection: TypeError: Can only call Blob.text on instances of Blob

Running

document.getElementById("file").files.item(0).text().then(text => console.log(text))

works though.

Improve reference behavior

I made a small change to report whenever an object ref is deleted:

---
 Runtime/src/index.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts
index 0e641ff..ddfba4e 100644
--- a/Runtime/src/index.ts
+++ b/Runtime/src/index.ts
@@ -93,6 +93,7 @@ class SwiftRuntimeHeap {
     release(ref: ref) {
         const value = this._heapValueById.get(ref);
         const isObject = typeof value == "object"
+        console.log('dereferencing', value);
         if (isObject) {
             const entry = this._heapEntryByValue.get(value)!;
             entry.rc--;

(copy the content of the code block and run pbpaste | git apply in your terminal to make this change)

It reports many deallocations. The thing that caught my attention was the repeated deallocation of functions (like toString and hasOwnProperty). I wonder if there’s a way to recognize functions that are part of the JavaScript language and either dynamically call them when needed or mark them so they don’t get released.

TypedArray improvement?

Currently, creating a TypedArray using swjs_create_typed_array requires passing an enumerated value, JavaScriptTypedArrayKind, to determine which kind of typed array should be created. I don’t remember exactly why I did this — would it make more sense to just pass a reference to the constructor function for the particular typed array we want?

Asynchronous calls in JSClosure

What is the best way to handle calls to asynchronous code inside a JSClosure, especially if one needs to return the result of such call from the JSClosure? Consider the following example:

let closure = .object(JSClosure { (arguments: [JSValue]) in
    guard let url = arguments.first?.string else {
        return JSValue.undefined
    }
    return await fetch(url)
})

This results in the following error:

cannot pass function of type '([JSValue]) async -> JSValue' to parameter expecting synchronous function type

The reason is obvious, but is there a nice way to work around this?

Occasional JSClosure has been already released by Swift side

During development of an animated webassembly feature a function that takes and returns a large typed array started failing occasionally with an error similar to:

The JSClosure has been already released by Swift side. The closure is created at ...

This occurs approximately 50% of frames

I've tried to create a minimal reproduction here, details in the README:

https://github.com/vkhougaz-vifive/swiftwasm-bug

import Foundation

import JavaScriptKit

func reverseArray(bytes: [Float32]) -> [Float32] {
    return [Float32](bytes.reversed())
}

let jsClosure = JSClosure { (input: [JSValue]) in
    let bytes: [Float32] = try! JSValueDecoder().decode(from: input[0])

    return reverseArray(bytes: bytes).jsValue
}

@_cdecl("main")
func main(_ i: Int32, _ j: Int32) -> Int32 {
    JSObject.global.reverseFloat32Array = .object(jsClosure)

    return 0
}
window.onload = async () => {
  const output = document.getElementById("output")!;

  function* generator() {
    for (let step = 0; step < 10000; step++) {
      yield Math.random();
    }
  }

  await loadWasm(bugWasm);

  function animate() {
    try {
      const bytes = Float32Array.from(generator());

      const reversed = reverseFloat32Array(bytes);

      output.innerHTML = reversed.join("\n");
    } catch (e) {
      console.error(e);
    }
    requestAnimationFrame(animate);
  }
  animate();
};

The hacky patch included there is... concerning, pointing towards either a dramatic misunderstanding of how swift works or a crazy low level memory issue.

/// Returns true if the host function has been already released, otherwise false.
@_cdecl("_call_host_function_impl")
func _call_host_function_impl(
    _ hostFuncRef: JavaScriptHostFuncRef,
    _ argv: UnsafePointer<RawJSValue>, _ argc: Int32,
    _ callbackFuncRef: JavaScriptObjectRef
) -> Bool {
    // TODO: This is some sort of horrible hack due to some sort of horrible wasm thing
    // Otherwise the sharedClone SOMETIMES fails
    let sharedClone = Dictionary(uniqueKeysWithValues: zip(JSClosure.sharedClosures.keys, JSClosure.sharedClosures.values))

    guard let (_, hostFunc) = sharedClone[hostFuncRef] else {
        return true
    }
    let arguments = UnsafeBufferPointer(start: argv, count: Int(argc)).map(\.jsValue)
    let result = hostFunc(arguments)
    let callbackFuncRef = JSFunction(id: callbackFuncRef)
    _ = callbackFuncRef(result)
    return false
}

Infinite loop with `JSDecoder`

Consider this code:

struct Response: Codable {
  let uuid: String
}

let response = try JSValueDecoder().decode(from: jsResponse) as Response

This decodes correctly with no issue, but changing it just slightly:

let response = try JSValueDecoder().decode(Response.self, from: jsResponse)

apparently leads to infinite recursion or some other issue with this error:

[Error] Unhandled Promise Rejection: RangeError: Maximum call stack size exceeded.
	<?>.wasm-function[$s13JavaScriptKit14JSValueDecoderC13OpenCombineJSE6decode_4fromxxm_AA0D0OtKSeRzlF]
[repeated calls skipped here]
	<?>.wasm-function[$s13JavaScriptKit14JSValueDecoderC13OpenCombineJSE6decode_4fromxxm_AA0D0OtKSeRzlF]
	<?>.wasm-function[$s13JavaScriptKit14JSValueDecoderC13OpenCombineJSE6decode_4fromxxm_AA0D0OtKSeRzlF]
[Error] RuntimeError: call_indirect to a signature that does not match (evaluating 'exports.swjs_call_host_function(host_func_id, argv, argc, callback_func_ref)')
	<?>.wasm-function[$sSD8_VariantV6lookupyq_SgxF]
	<?>.wasm-function[$sSDyq_Sgxcig]
	<?>.wasm-function[$s13JavaScriptKit24_call_host_function_implyys6UInt32V_SPySo10RawJSValueaGs5Int32VADtF]
	<?>.wasm-function[_call_host_function_impl]
	<?>.wasm-function[_call_host_function]
	wasm-stub
	8655
	callHostFunction (index.js:110)

Errors building example: undefined symbols

When building according to instructions, I get errors like undefined symbol: _create_typed_array. Is this a JavaScriptKit issue, or my own issue? If it's mine, advice appreciated! :)

➤ swift --version
Swift version 5.3-dev (LLVM 0b0ebf2f41, Swift 5084c1851d)
Target: x86_64-apple-darwin19.6.0

➤ make build
cd JavaScriptKitExample && \
	    swift build --triple wasm32-unknown-wasi
[1/4] Compiling _CJavaScriptKit _CJavaScriptKit.c
[2/20] Compiling JavaScriptKit JSBridgedType.swift
[3/20] Compiling JavaScriptKit JSTimer.swift
[4/20] Compiling JavaScriptKit Deprecated.swift
[5/20] Compiling JavaScriptKit JSError.swift
[6/20] Compiling JavaScriptKit XcodeSupport.swift
[7/20] Compiling JavaScriptKit ConstructibleFromJSValue.swift
[8/20] Compiling JavaScriptKit JSObject.swift
[9/20] Compiling JavaScriptKit JSValue.swift
[10/20] Compiling JavaScriptKit JSArray.swift
[11/20] Compiling JavaScriptKit JSFunction.swift
[12/20] Compiling JavaScriptKit JSTypedArray.swift
[13/20] Compiling JavaScriptKit JSString.swift
[14/20] Compiling JavaScriptKit JSDate.swift
[15/20] Compiling JavaScriptKit JSPromise.swift
[16/20] Compiling JavaScriptKit ConvertibleToJSValue.swift
[17/20] Compiling JavaScriptKit JSValueDecoder.swift
[18/21] Merging module JavaScriptKit
[19/22] Wrapping AST for JavaScriptKit for debugging
[20/22] Compiling JavaScriptKitExample main.swift
[redacted]/JavaScriptKit/Example/JavaScriptKitExample/Sources/JavaScriptKitExample/main.swift:3:13: warning: 'JSObjectRef' is deprecated: renamed to 'JSObject'
let alert = JSObjectRef.global.alert.function!
            ^
[redacted]/JavaScriptKit/Example/JavaScriptKitExample/Sources/JavaScriptKitExample/main.swift:3:13: note: use 'JSObject' instead
let alert = JSObjectRef.global.alert.function!
            ^~~~~~~~~~~
            JSObject
[redacted]/JavaScriptKit/Example/JavaScriptKitExample/Sources/JavaScriptKitExample/main.swift:4:16: warning: 'JSObjectRef' is deprecated: renamed to 'JSObject'
let document = JSObjectRef.global.document.object!
               ^
[redacted]/JavaScriptKit/Example/JavaScriptKitExample/Sources/JavaScriptKitExample/main.swift:4:16: note: use 'JSObject' instead
let document = JSObjectRef.global.document.object!
               ^~~~~~~~~~~
               JSObject
[redacted]/JavaScriptKit/Example/JavaScriptKitExample/Sources/JavaScriptKitExample/main.swift:13:26: warning: 'function' is deprecated: Please create JSClosure directly and manage its lifetime manually.
buttonElement.onclick = .function { _ in
                         ^
[21/23] Merging module JavaScriptKitExample
[22/23] Wrapping AST for JavaScriptKitExample for debugging
wasm-ld: error: [redacted]/JavaScriptKit/Example/JavaScriptKitExample/.build/wasm32-unknown-wasi/debug/JavaScriptKit.build/BasicObjects/JSTypedArray.swift.o: undefined symbol: _create_typed_array
wasm-ld: error: [redacted]/JavaScriptKit/Example/JavaScriptKitExample/.build/wasm32-unknown-wasi/debug/JavaScriptKit.build/FundamentalObjects/JSFunction.swift.o: undefined symbol: _call_function
wasm-ld: error: [redacted]/JavaScriptKit/Example/JavaScriptKitExample/.build/wasm32-unknown-wasi/debug/JavaScriptKit.build/FundamentalObjects/JSFunction.swift.o: undefined symbol: _call_function_with_this
wasm-ld: error: [redacted]/JavaScriptKit/Example/JavaScriptKitExample/.build/wasm32-unknown-wasi/debug/JavaScriptKit.build/FundamentalObjects/JSFunction.swift.o: undefined symbol: _call_new
wasm-ld: error: [redacted]/JavaScriptKit/Example/JavaScriptKitExample/.build/wasm32-unknown-wasi/debug/JavaScriptKit.build/FundamentalObjects/JSFunction.swift.o: undefined symbol: _create_function
wasm-ld: error: [redacted]/JavaScriptKit/Example/JavaScriptKitExample/.build/wasm32-unknown-wasi/debug/JavaScriptKit.build/FundamentalObjects/JSObject.swift.o: undefined symbol: _instanceof
wasm-ld: error: [redacted]/JavaScriptKit/Example/JavaScriptKitExample/.build/wasm32-unknown-wasi/debug/JavaScriptKit.build/FundamentalObjects/JSObject.swift.o: undefined symbol: _release
wasm-ld: error: [redacted]/JavaScriptKit/Example/JavaScriptKitExample/.build/wasm32-unknown-wasi/debug/JavaScriptKit.build/FundamentalObjects/JSString.swift.o: undefined symbol: _decode_string
wasm-ld: error: [redacted]/JavaScriptKit/Example/JavaScriptKitExample/.build/wasm32-unknown-wasi/debug/JavaScriptKit.build/FundamentalObjects/JSString.swift.o: undefined symbol: _encode_string
wasm-ld: error: [redacted]/JavaScriptKit/Example/JavaScriptKitExample/.build/wasm32-unknown-wasi/debug/JavaScriptKit.build/FundamentalObjects/JSString.swift.o: undefined symbol: _load_string
wasm-ld: error: [redacted]/JavaScriptKit/Example/JavaScriptKitExample/.build/wasm32-unknown-wasi/debug/JavaScriptKit.build/FundamentalObjects/JSObject.swift.o: undefined symbol: _release
wasm-ld: error: [redacted]/JavaScriptKit/Example/JavaScriptKitExample/.build/wasm32-unknown-wasi/debug/JavaScriptKit.build/FundamentalObjects/JSObject.swift.o: undefined symbol: _release
wasm-ld: error: [redacted]/JavaScriptKit/Example/JavaScriptKitExample/.build/wasm32-unknown-wasi/debug/JavaScriptKit.build/JSValue.swift.o: undefined symbol: _get_prop
wasm-ld: error: [redacted]/JavaScriptKit/Example/JavaScriptKitExample/.build/wasm32-unknown-wasi/debug/JavaScriptKit.build/JSValue.swift.o: undefined symbol: _set_prop
wasm-ld: error: [redacted]/JavaScriptKit/Example/JavaScriptKitExample/.build/wasm32-unknown-wasi/debug/JavaScriptKit.build/JSValue.swift.o: undefined symbol: _get_subscript
wasm-ld: error: [redacted]/JavaScriptKit/Example/JavaScriptKitExample/.build/wasm32-unknown-wasi/debug/JavaScriptKit.build/JSValue.swift.o: undefined symbol: _set_subscript
clang-10: error: linker command failed with exit code 1 (use -v to see invocation)
<unknown>:0: error: link command failed with exit code 1 (use -v to see invocation)
[22/23] Linking JavaScriptKitExample
make: *** [JavaScriptKitExample] Error 1

Export function returning a JSValue

I've followed the basic docs to call an exported function that takes in string/numbers and returns string/numbers but I'm struggling to figure out how to get the JS runtime to await a returned JSPromise from the Swift runtime.

I started with this approach of returning a pointer to the JSPromise:

@_cdecl("fetch")
func fetch() -> UnsafeRawPointer {
    let promise = JSPromise { resolve in
        resolve(.success(5))
    }
    var value = promise.jsValue()
    return withUnsafePointer(to: &value) { UnsafeRawPointer($0) }
}

But it's obviously not so clear how to deserialize this to an actual Promise in JS:

async function jsFetch() {
  let ptr = instance.exports.fetch()
  let promise = /** ??? */
  await promise
}

I'm very new to WASM so I apologize if this is a completely wrong approach, I just needed to get the ball rolling because I can't find anything else online about returning more complex values. I've been trying to understand how its accomplished in Rust but they have fancy annotations from the wasm-bindgen crate which is frankly over my head.

Arguments in JSClosure

How is one supposed to use arguments in JSClosure? Is it necessary to unpack them manually from the JSValue array? If I try to do that, I get an error message about ambiguous use of subscript(dynamicMember:):

JSClosure { (arguments: [JSValue]) in
    guard let item = arguments.first,  let deviceId = item.string else {
        // item in item.string causes error: ambiguous use of 'subscript(dynamicMember:)'
        return .undefined
    }
    // ...
}

Generate JavaScriptKit `.js` runtime with SwiftPM build tool

I'm proposing to declare Runtime/lib directory as SwiftPM resource as described in SE-0271. With a few adjustments to carton, this will allow us to separate carton entrypoint files from JSKit runtime files, and simplify our release process as well. The end result would be that we can simply tag a new JSKit release without a need to update carton entrypoints and tag a new carton release each time.

The complication is that we need to keep .js files in Runtime/lib fresh and in sync with the .ts source files. I'm currently investigating whether SwiftPM build tools proposal can help us generate .js files at SwiftPM build time. While rollup.js requires Node.js to be installed to work, I'm looking at using swc.rs as a build tool, as it provides self-contained binaries for all most popular platforms. Bundling and minification with SWC is still marked as unstable, but with ES6 modules supported in all recent browsers, I wonder if we can keep JSKit runtime supplied in separate files, which could make it slightly easier to debug as well.

Bundling and minification of resulting .js files can then be left to the user, making it more flexible. As a future direction, rollup.js config could be supplied with carton, which it then could use when it runs carton bundle and detects that Node.js and rollup.js are installed and available on user's machine.

WebSockets implementation

I am trying to open a WebSocket in JavaScriptKit.

I have found a related issue on this, but the implementation did not work for me.

I currently have let socket = JSObject.global.WebSocket.function!.
When I print it out, I get a value.

But I don't know how to go from here?

How do I send and receive data with this socket? I've tried some things, but nothing that would compile.

UserAgent support?

I was wondering if we can run the equivalent of JavaScript's navigator.userAgent? I can't seem to find any references to it.

Plugin infrastructure

Currently, JavaScript-based features must be directly implemented in JavaScriptKit to be able to access important features like the heap and the JSValue parsing/serializing routines. This means that less-common things like TypedArray or potential Combine-Promise integration (see #62) would have to increase the bundle size for everyone. One solution to this would be defining some sort of public JS-side API that allows providing custom functions to the WASM runtime. As long as we encourage users of this API to prefix their methods there should be no collision issues.

It would be nice to restructure JavaScriptKit itself to make use of the plugin API too:

  • swjs_core: release
  • swjs_object: get_prop, set_prop, get_subscript, set_subscript, instanceof
  • swjs_string: decode, encode, load
  • swjs_function: call, apply, call_new, create
  • swjs_typedarray: create

Rename the default branch to `main`

It would make this repository consistent with the rest of the projects in this organization, especially now that swiftwasm/swift#1786 is closed for the main repo. Additionally, GitHub is going to make main the default branch name starting 1 October, if I remember correctly.

PUT request using Fetch

I am trying to figure out how to use JSObject.global.fetch.function! along with JSPromise to assemble a PUT request with a body and headers. I keep falling flat on my face 😣 I am able to use fetch and preform GET requests, parse the JSON, and the whole-nine-yards, but I don't understand where to go for sending data.

I have tried assembling a params object using something like this JSObject.construct(from: ["method": "PUT", "body": json.jsString].jsValue) and passing it as a second parameter to fetch. Doing this though, when handling the .then statement I receive an error of:

/Users/lorenalexm/Projects/SwiftScheduleCheck/Sources/SwiftScheduleCheck/Views/ContentView.swift:118:16: Cannot convert value of type '()' to closure result type 'ConvertibleToJSValue'

Might anyone help provide a bit of guidance on how to proceed from here? Thank you!

JavaScriptKit as a "system" library?

It would be great if we could provide shims for Dispatch and Foundation APIs that rely on JS hosts directly instead of WASI. The problem is that Dispatch and Foundation are "system" modules shipped with the SwiftWasm SDK, while JavaScriptKit is consumed by users via SwiftPM.

I wonder if we could consider promoting JavaScriptKit to a "system" library, I guess Swift for Tensorflow does a similar thing with PythonKit already. This will obviously require our API to become much more stable, but I hope it would be worth it.

WDYT?

Support for JS Arrays “holes”, including the test suite

Not entirely sure how this should be handled, but JS Arrays support “holes”, where [1,,3] creates an array without anything at index 1. From the outside, that index appears to contain undefined, but array methods like map and forEach skip over those indices while iterating, and flat removes them, changing the indices of everything that comes after the hole. You can tell the difference between a hole and an undefined value with either index in array or array.hasOwnProperty(index).

I think it’s perfectly fine to simply say that holes are not handled properly since they’re quite rare in practice but I wanted to make sure this language quirk wasn’t missed.

Originally posted by @j-f1 in #38 (comment)

`make test` crashes due to `JSClosure` memory issues

I'm not sure why this isn't reproducible on CI, but I got it crashing off the latest code in the main branch with a simple make && make test, here's the full output:

Here's the full output:

cd IntegrationTests && \
	    CONFIGURATION=debug make test && \
	    CONFIGURATION=release make test
cd .. && npm run build

> [email protected] build
> npm run build:clean && npm run build:ts


> [email protected] build:clean
> rm -rf Runtime/lib


> [email protected] build:ts
> cd Runtime; tsc -b

swift build --package-path TestSuites \
	            --product PrimaryTests \
	            --triple wasm32-unknown-wasi \
	            --configuration debug
[1/1] Linking PrimaryTests.wasm
mkdir -p dist
cp TestSuites/.build/debug/PrimaryTests.wasm dist/PrimaryTests.wasm
node bin/primary-tests.js
Fatal error: The function was already released: file JavaScriptKit/JSClosure.swift, line 143

Fatal error: release() must be called on JSClosure objects manually before they are deallocated: file JavaScriptKit/JSClosure.swift, line 97

Fatal error: The function was already released: file JavaScriptKit/JSClosure.swift, line 143

rm TestSuites/.build/debug/PrimaryTests.wasm
cd .. && npm run build

> [email protected] build
> npm run build:clean && npm run build:ts


> [email protected] build:clean
> rm -rf Runtime/lib


> [email protected] build:ts
> cd Runtime; tsc -b

swift build --package-path TestSuites \
	            --product PrimaryTests \
	            --triple wasm32-unknown-wasi \
	            --configuration release
[1/1] Linking PrimaryTests.wasm
mkdir -p dist
cp TestSuites/.build/release/PrimaryTests.wasm dist/PrimaryTests.wasm
node bin/primary-tests.js
Fatal error: The function was already released: file JavaScriptKit/JSClosure.swift, line 143

Fatal error: release() must be called on JSClosure objects manually before they are deallocated: file JavaScriptKit/JSClosure.swift, line 97

Fatal error: The function was already released: file JavaScriptKit/JSClosure.swift, line 143

rm TestSuites/.build/release/PrimaryTests.wasm

Can't get properties from functions (Object.keys)

This snippet crashes:

let object = JSObjectRef.global.Object.object!

as global Object is a function. This function has properties though, one of them being Object.keys. There is currently no way to access such property in JavaScriptKit, as this snippet crashes too:

let keys = JSObjectRef.global.Object.object!.keys!(anotherObject)

Calling `sleep()` causes `TypeError: can't convert null to BigInt`

Title says it. Code:

import Foundation
import JavaScriptKit

print("Before sleep()")
sleep(1000) // wasm bails out
print("After sleep()") // Never happens

Project for the above code: https://github.com/ShonFrazier/swiftwasmsleep

The full error is:

 TypeError: can't convert null to BigInt
     poll_oneoff webpack:///./node_modules/@wasmer/wasi/lib/index.esm.js?:128
     start webpack:///./node_modules/@wasmer/wasi/lib/index.esm.js?:131
     startWasiTask webpack:///./entrypoint/dev.js?:92
     async* webpack:///./entrypoint/dev.js?:103
     js http://127.0.0.1:8181/dev.js:97
     __webpack_require__ http://127.0.0.1:8181/dev.js:20
     <anonymous> http://127.0.0.1:8181/dev.js:84
     <anonymous> http://127.0.0.1:8181/dev.js:87
 dev.js:96:11
     handleError webpack:///./entrypoint/dev.js?:96
     (Async: promise callback)
     <anonymous> webpack:///./entrypoint/dev.js?:103
     js http://127.0.0.1:8181/dev.js:97
     __webpack_require__ http://127.0.0.1:8181/dev.js:20
     <anonymous> http://127.0.0.1:8181/dev.js:84
     <anonymous> http://127.0.0.1:8181/dev.js:87

Automatic performance testing

I’ve seen repositories before that have a bot post a comment on all new PRs with a comparison of the perf tests between main and the PR branch. This would be very nice to have so we don’t accidentally introduce regressions, and to celebrate perf improvements when they’re added.

BigInt support

BigInt is supported in Safari 14 on both macOS Catalina/Big Sur and iOS 14, makes perfect sense to support it on our side too, especially to allow proper handling of Int64.

Need to be careful about browser compatibility though. Should JavaScriptKit just fatalError on older browsers when users attempt to use JSBigInt in their code?

Also a good case for a pitch for proper browser versioning in Swift, I'll open a separate toolchain issue for it.

JSPromise(resolver:) usage

Could somebody provide an example of how to use JSPromise(resolver:)? I'm having hard time decoding the following comment in the source code:

Creates a new JSPromise instance from a given resolver closure. resolver takes two closure that your code should call to either resolve or reject this JSPromise instance.

Compile error on macOS 12.2.1

/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JobQueue.swift:9:30: error: 'UnownedJob' is only available in macOS 10.15 or newer
    fileprivate var headJob: UnownedJob? = nil
                             ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JobQueue.swift:8:8: note: add @available attribute to enclosing struct
struct QueueState: Sendable {
       ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JobQueue.swift:15:37: error: 'UnownedJob' is only available in macOS 10.15 or newer
    func insertJobQueue(job newJob: UnownedJob) {
                                    ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JobQueue.swift:15:10: note: add @available attribute to enclosing instance method
    func insertJobQueue(job newJob: UnownedJob) {
         ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JobQueue.swift:13:1: note: add @available attribute to enclosing extension
extension JavaScriptEventLoop {
^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JobQueue.swift:49:34: error: 'UnownedJob' is only available in macOS 10.15 or newer
    func claimNextFromQueue() -> UnownedJob? {
                                 ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JobQueue.swift:49:10: note: add @available attribute to enclosing instance method
    func claimNextFromQueue() -> UnownedJob? {
         ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JobQueue.swift:13:1: note: add @available attribute to enclosing extension
extension JavaScriptEventLoop {
^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JobQueue.swift:69:48: error: 'UnownedJob' is only available in macOS 10.15 or newer
    func nextInQueue() -> UnsafeMutablePointer<UnownedJob?> {
                                               ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JobQueue.swift:69:10: note: add @available attribute to enclosing instance method
    func nextInQueue() -> UnsafeMutablePointer<UnownedJob?> {
         ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JobQueue.swift:58:13: note: add @available attribute to enclosing extension
fileprivate extension UnownedJob {
            ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JobQueue.swift:58:23: error: 'UnownedJob' is only available in macOS 10.15 or newer
fileprivate extension UnownedJob {
                      ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JobQueue.swift:58:13: note: add @available attribute to enclosing extension
fileprivate extension UnownedJob {
            ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JobQueue.swift:17:48: error: 'UnownedJob' is only available in macOS 10.15 or newer
            var position: UnsafeMutablePointer<UnownedJob?> = headJobPtr
                                               ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JobQueue.swift:17:48: note: add 'if #available' version check
            var position: UnsafeMutablePointer<UnownedJob?> = headJobPtr
                                               ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JobQueue.swift:15:10: note: add @available attribute to enclosing instance method
    func insertJobQueue(job newJob: UnownedJob) {
         ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JobQueue.swift:13:1: note: add @available attribute to enclosing extension
extension JavaScriptEventLoop {
^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JobQueue.swift:71:84: error: 'UnownedJob' is only available in macOS 10.15 or newer
            let nextJobPtr = UnsafeMutableRawPointer(rawNextJobPtr).bindMemory(to: UnownedJob?.self, capacity: 1)
                                                                                   ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JobQueue.swift:71:84: note: add 'if #available' version check
            let nextJobPtr = UnsafeMutableRawPointer(rawNextJobPtr).bindMemory(to: UnownedJob?.self, capacity: 1)
                                                                                   ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JobQueue.swift:69:10: note: add @available attribute to enclosing instance method
    func nextInQueue() -> UnsafeMutablePointer<UnownedJob?> {
         ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JobQueue.swift:58:13: note: add @available attribute to enclosing extension
fileprivate extension UnownedJob {
            ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift:81:33: error: 'UnownedJob' is only available in macOS 10.15 or newer
    private func enqueue(_ job: UnownedJob, withDelay nanoseconds: UInt64) {
                                ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift:81:18: note: add @available attribute to enclosing instance method
    private func enqueue(_ job: UnownedJob, withDelay nanoseconds: UInt64) {
                 ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift:6:20: note: add @available attribute to enclosing class
public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
                   ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift:88:32: error: 'UnownedJob' is only available in macOS 10.15 or newer
    public func enqueue(_ job: UnownedJob) {
                               ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift:88:17: note: add @available attribute to enclosing instance method
    public func enqueue(_ job: UnownedJob) {
                ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift:6:20: note: add @available attribute to enclosing class
public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
                   ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift:92:46: error: 'UnownedSerialExecutor' is only available in macOS 10.15 or newer
    public func asUnownedSerialExecutor() -> UnownedSerialExecutor {
                                             ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift:92:17: note: add @available attribute to enclosing instance method
    public func asUnownedSerialExecutor() -> UnownedSerialExecutor {
                ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift:6:20: note: add @available attribute to enclosing class
public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
                   ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift:60:73: error: 'UnownedJob' is only available in macOS 10.15 or newer
        typealias swift_task_enqueueGlobal_hook_Fn = @convention(thin) (UnownedJob, swift_task_enqueueGlobal_original) -> Void
                                                                        ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift:57:24: note: add @available attribute to enclosing static method
    public static func installGlobalExecutor() {
                       ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift:6:20: note: add @available attribute to enclosing class
public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
                   ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift:66:90: error: 'UnownedJob' is only available in macOS 10.15 or newer
        typealias swift_task_enqueueGlobalWithDelay_hook_Fn = @convention(thin) (UInt64, UnownedJob, swift_task_enqueueGlobalWithDelay_original) -> Void
                                                                                         ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift:57:24: note: add @available attribute to enclosing static method
    public static func installGlobalExecutor() {
                       ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift:6:20: note: add @available attribute to enclosing class
public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
                   ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift:72:79: error: 'UnownedJob' is only available in macOS 10.15 or newer
        typealias swift_task_enqueueMainExecutor_hook_Fn = @convention(thin) (UnownedJob, swift_task_enqueueMainExecutor_original) -> Void
                                                                              ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift:57:24: note: add @available attribute to enclosing static method
    public static func installGlobalExecutor() {
                       ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift:6:20: note: add @available attribute to enclosing class
public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
                   ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift:93:16: error: 'UnownedSerialExecutor' is only available in macOS 10.15 or newer
        return UnownedSerialExecutor(ordinary: self)
               ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift:93:16: note: add 'if #available' version check
        return UnownedSerialExecutor(ordinary: self)
               ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift:92:17: note: add @available attribute to enclosing instance method
    public func asUnownedSerialExecutor() -> UnownedSerialExecutor {
                ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift:6:20: note: add @available attribute to enclosing class
public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
                   ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift:101:23: error: 'withUnsafeThrowingContinuation' is only available in macOS 10.15 or newer
            try await withUnsafeThrowingContinuation { [self] continuation in
                      ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift:101:23: note: add 'if #available' version check
            try await withUnsafeThrowingContinuation { [self] continuation in
                      ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift:99:9: note: add @available attribute to enclosing property
    var value: JSValue {
        ^
/Users/xxxxxxxxxx/Documents/JavaScriptKit/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift:97:8: note: add @available attribute to enclosing extension
public extension JSPromise {
       ^

Making JSObject hashable

Is there anything that would prevent using the object id for hashing in JSObject? Since hash values in Swift change between sessions anyway, I can't immediately see why using id would be a bad idea. All that would be needed to make JSObject conform to Hashable is to add:

public func hash(into hasher: inout Hasher) {
    hasher.combine(id)
}

DOMKit?

It would be great to have some sort of type-safe DOM API that better matches DOM as exposed to JS (for example document.querySelector('foo')?.appendChild(document.createElement('bar'))) without having to go through the ceremony of .object! and .function!. It looks like there’s some work in this direction in webidl2swift but I’m not sure how much more is needed.

LICENSE file is absent

I see package.json files specify MIT, but the LICENSE file itself is not present in the repository.

Avoid manual memory management with JSClosure

I wonder if reference types could lift our requirement to manage references to JS closures manually?

With externrefs, the host gives Wasm limited access to host objects, but the host also needs to know when Wasm is finished with those objects, so it can clean them up. That clean up might involve closing a file handle or simply deallocating memory.

This means that JS closures should be passed to the Swift code as externref. Deallocating all references to externref would deallocate the closure on the JS side.

Reference types are now available in all browsers except Safari, and in the latter case they seem to only be hidden behind JSC_useWebAssemblyReferences flag. The only question is externref support in our toolchain...

Doesn't look like LLVM had much progress in that direction? 🤔 Also see WebAssembly/tool-conventions#122

Accessing localStorage fails

Here's a simple example:

let localStorage = JSObjectRef.global.localStorage.object!
print(localStorage.getItem!("hello"))
_ = localStorage.setItem!("hello", "world")
print(localStorage.getItem!("hello"))

Prints null then .string("world") as expected.

However, if you refresh the site after the value is set, it crashes with Reflect.get called on non-object.

Cannot perform DataView.prototype.setBigUint64 on a detached ArrayBuffer

hey hi

JavascriptKit 0.17.0

I'm using SwifWeb framework which is built on top of JavaScriptKit. Here is my view controller which contains a lot of DOM element objects created through JavaScriptKit. I'm not doing anything illegal. The weird things is that at some point app start crashing with Cannot perform DataView.prototype.setBigUint64 on a detached ArrayBuffer error. I think it is not related to some specific code. Before JavaScriptKit v0.17 I used v0.12 and these errors appeared very often. Now I thought this problem has been completely fixed, but it seems it still appears.

app.js:139 TypeError: Cannot perform DataView.prototype.setBigUint64 on a detached ArrayBuffer
    at DataView.setBigUint64 ()
    at clock_res_get (index.esm.js:114:167)
    at __wasi_clock_res_get (1ddb7b2a:0x3b79363)
    at clock_getres (1ddb7b2a:0x3b767a8)
    at __CFDateInitialize (1ddb7b2a:0x2ff76e7)
    at __CFInitialize (1ddb7b2a:0x2fb8851)
    at __wasm_call_ctors (1ddb7b2a:0x76f37)
    at _initialize (1ddb7b2a:0x76f56)
    at startWasiTask (app.js:133:26)

as a note as soon I removed sectionSelectFiled
i was able to add many more elements

this is the source code

//
//  FiscalConceptDestinationView.swift
//
//
//  Created by Victor Cantu on 9/28/22.
//

import Foundation
import TCFoundation
import TCCodyFire
import Web

class FiscalConceptDestinationView: Div {
    
    override class var name: String { "div" }
    
    let pocid: UUID
    
    @State var units: Int64
    
    private var callback: ((
        _ selectedPlace: InventoryPlaceType,
        _ storeid: UUID?,
        _ storeName: String,
        _ custAccountId: UUID?,
        _ placeid: UUID?,
        _ placeName: String,
        _ bodid: UUID?,
        _ bodName: String,
        _ secid: UUID?,
        _ secName: String,
        _ units: Int64,
        _ series: [String]
    ) -> ())
    
    init(
        pocid: UUID,
        units: Int64,
        callback: @escaping ((
            _ selectedPlace: InventoryPlaceType,
            _ storeid: UUID?,
            _ storeName: String,
            _ custAccountId: UUID?,
            _ placeid: UUID?,
            _ placeName: String,
            _ bodid: UUID?,
            _ bodName: String,
            _ secid: UUID?,
            _ secName: String,
            _ units: Int64,
            _ series: [String]
        ) -> ())
    ) {
        self.pocid = pocid
        self.units = units
        self.callback = callback
        
        super.init()
    }
    
    required init() {
        fatalError("init() has not been implemented")
    }
    
    @State var unitsToDisperse: String = ""
    
    @State var unitsToDisperseSubTitle: String = ""
    
    @State var selectedPlace: InventoryPlaceType? = nil
    
    /// Incase its going to a store  witch store will it be
    @State var selectedStoreId: UUID? = nil
    
    @State var selectedStoreName: String = ""
    
    var selectedBodId: UUID? = nil
    
    var preSelectedSectId: UUID? = nil
    
    @State var selectedBodIdListener: String = ""
    
    @State var selectedSecIdListener: String = ""
    
    @State var searchFolioString: String = ""
    
    @State var sectionSelectText = ""
    
    var availableSection: [CustStoreSeccionesSinc] = []
    
    @State var displayedSection: [CustStoreSeccionesSinc] = []
    
    @State var sectionSelectResultIsHidden = true
    
    /// Selected place EG: Order ID,  Store ID...
    var placeid: UUID? =  nil
    
    lazy var selecctionBox = Div()
        .margin(all: 3.px)
        .padding(all: 3.px)
    
    lazy var availableStoreBox = Div()
        .class(.roundDarkBlue)
        .padding(all: 3.px)
        .margin(all: 3.px)
        .maxHeight(250.px)
        .overflow(.auto)
    
    lazy var unitsToDisperseField = InputText(self.$unitsToDisperse)
        .class(.textFiledBlackDark)
        .placeholder("0")
        .textAlign(.right)
        .width(50.px)
        .height(28.px)
        .onKeyDown { tf, event in
            guard let _ = Float(event.key) else {
                if !ignoredKeys.contains(event.key) {
                    event.preventDefault()
                }
                return
            }
        }
        .onFocus { tf in
            tf.select()
        }
    
    lazy var bodegaSelect = Select(self.$selectedBodIdListener)
        .class(.textFiledBlackDark)
    
    lazy var sectionSelect = Select(self.$selectedSecIdListener)
        .class(.textFiledBlackDark)
    

    lazy var sectionSelectFiled = InputText(self.$sectionSelectText)
        .class(.textFiledBlackDark)
        .placeholder("Seleccione Seccion")
        .width(90.percent)
        .fontSize(23.px)
        .height(28.px)
        .onFocus {
            self.sectionSelectResultIsHidden = false
        }
        .onBlur{
            Dispatch.asyncAfter(1.3) {
                self.sectionSelectResultIsHidden = true
            }
        }
    
    lazy var searchFolioField = InputText(self.$searchFolioString)
    
    lazy var sectionSelectResult = Div{
        
    }
        .hidden(self.$sectionSelectResultIsHidden)
        .backgroundColor(.grayBlackDark)
        .borderRadius(all: 24.px)
        .minHeight(100.px)
        .maxHeight(400.px)
        .overflow(.auto)
    
    @DOM override var body: DOM.Content {
        
        /// Main Seccion
        Div{
            
            /// Header
            Div {
                
                Img()
                    .closeButton(.uiView3)
                    .onClick{
                        self.remove()
                    }
                 
                H2(self.$units.map{ ($0 == 1) ? "Ubicacion de 1 producto" : "Ubicacion de \($0.toString) productos" })
                    .maxWidth(90.percent)
                    .class(.oneLineText)
                    .marginLeft(7.px)
                    .color(.cornflowerBlue)
                
            }
            .paddingBottom(3.px)
            
            Div().class(.clear)
            
            Div {
                Span("Colocar")
                    .marginRight(7.px)
                
                self.unitsToDisperseField
                    .marginRight(7.px)
                
                Span(self.$unitsToDisperse.map{
                    
                    guard let int = Int($0) else {
                        return "unidades"
                    }
                    
                    if int == 1 {
                        return "unidad"
                    }
                    else{
                        return "unidades"
                    }
                    
                })
                
            }
            .fontSize(23.px)
            .paddingBottom(3.px)
            
            Div().class(.clear)
            
            self.selecctionBox
            
        }
        .top(25.percent)
        .backgroundColor(.grayBlack)
        .borderRadius(all: 24.px)
        .position(.absolute)
        .padding(all: 12.px)
        .width(40.percent)
        .left(30.percent)
        .color(.white)
        .hidden(self.$selectedPlace.map { $0 != nil })
        
        /// Store
        Div{
            /// Header
            Div {
                
                Img()
                    .closeButton(.uiView3)
                    .onClick{
                        self.remove()
                    }
                 
                H2(self.$unitsToDisperseSubTitle)
                    .color(.cornflowerBlue)
                    .maxWidth(90.percent)
                    .class(.oneLineText)
                    .marginLeft(7.px)
                
            }
            .paddingBottom(3.px)
            .align(.left)
            
            Div().class(.clear)
            
            self.availableStoreBox
                .hidden(self.$selectedStoreId.map { $0 != nil })
             
            Div {
                H2( self.$selectedStoreName )
                    .color(.white)
                
                Div().class(.clear).height(7.px)
                
                Div {
                    Label("Bodega")
                        .fontSize(18.px)
                        .color(.gray)
                    
                    Div{
                        self.bodegaSelect
                            .width(90.percent)
                            .fontSize(23.px)
                            .height(28.px)
                            
                    }
                }
                .class(.section)
                
                Div().class(.clear).height(7.px)
                
                Div {
                    Label("Seccion")
                        .fontSize(18.px)
                        .color(.gray)
                    
                    Div{
//                        self.sectionSelect
//                            .width(90.percent)
//                            .fontSize(23.px)
//                            .height(28.px)
                        self.sectionSelectFiled
                    }
                    
                   
                    
                    
                }
                .class(.section)
                
                
                Div{
                    self.sectionSelectResult
                }
                .position(.absolute)
                .class(.section)
                .width(95.percent)
                
                
                Div().class(.clear).height(7.px)
                
                Div {
                    Div("Agregar")
                        .onClick {
                            
                            guard let _store = self.selectedStoreId else{
                                showAlert(.alerta, "Seleccione Tienda")
                                return
                            }
                            
                            guard let _bod = UUID(uuidString: self.selectedBodIdListener) else {
                                showAlert(.alerta, "Seleccione Bodega")
                                return
                            }
                            
                            guard let _sec = UUID(uuidString: self.selectedSecIdListener) else {
                                showAlert(.alerta, "Seleccione Bodega")
                                return
                            }
                            
                            self.callback(
                                .store, // selectedPlace
                                _store, // storeid
                                self.selectedStoreName, // storeName
                                nil, // custAccountId
                                nil, // placeid
                                "", // placeName
                                _bod, // bodid
                                (bodegas[_bod]?.name ?? "N/A"), // bodName
                                _sec, // secid
                                (seccions[_sec]?.name ?? "N/A"), // secName
                                self.units, // units
                                [] // series
                            )
                            
                            self.remove()
                        }
                    .class(.uibtnLargeOrange)
                }
                .align(.right)
                
            }
            .hidden(self.$selectedStoreId.map { $0 == nil })
            
        }
        .top(25.percent)
        .backgroundColor(.grayBlack)
        .borderRadius(all: 24.px)
        .position(.absolute)
        .padding(all: 12.px)
        .width(40.percent)
        .left(30.percent)
        .color(.white)
        .hidden(self.$selectedPlace.map { $0 != .store })
        
        /// Order
        Div{
            
            /// Header
            Div {
                
                Img()
                    .closeButton(.uiView3)
                    .onClick{
                        self.remove()
                    }
                 
                H2(self.$unitsToDisperseSubTitle)
                    .maxWidth(90.percent)
                    .class(.oneLineText)
                    .marginLeft(7.px)
                    .color(.cornflowerBlue)
                
            }
            .paddingBottom(3.px)
            
            Div().class(.clear).height(12.px)
            
            Div{
                Label("Ingrese Folio")
                    .color(.lightGray)
                    .fontSize(23.px)
                
                Div{
                    self.searchFolioField
                        .class(.textFiledBlackDarkLarge)
                        .placeholder("Ingrese Folio")
                        .width(90.percent)
                        .fontSize(23.px)
                        .height(27.px)
                        .onEnter { tf in
                            self.searchFolio()
                        }
                }
            }
            .class(.section)
            
            Div().class(.clear).height(12.px)
            
            Div{
                Div("Buscar Folio")
                    .onClick {
                        self.searchFolio()
                    }
                .class(.uibtnLargeOrange)
            }
            .align(.right)
            
        }
        .top(25.percent)
        .backgroundColor(.grayBlack)
        .borderRadius(all: 24.px)
        .position(.absolute)
        .padding(all: 12.px)
        .width(40.percent)
        .left(30.percent)
        .color(.white)
        .hidden(self.$selectedPlace.map { $0 != .order })
        
        /*
        /// Sold
        Div{
            
            /// Header
            Div {
                
                Img()
                    .closeButton(.uiView3)
                    .onClick{
                        self.remove()
                    }
                 
                H2(self.$unitsToDisperseSubTitle)
                    .maxWidth(90.percent)
                    .class(.oneLineText)
                    .marginLeft(7.px)
                    .color(.cornflowerBlue)
                
            }
            .paddingBottom(3.px)
            
            Div().class(.clear)
        }
        .top(25.percent)
        .backgroundColor(.grayBlack)
        .borderRadius(all: 24.px)
        .position(.absolute)
        .padding(all: 12.px)
        .width(40.percent)
        .left(30.percent)
        .color(.white)
        .hidden(self.$selectedPlace.map { $0 != .sold })
        
        /// Merm
        Div{
            
            /// Header
            Div {
                
                Img()
                    .closeButton(.uiView3)
                    .onClick{
                        self.remove()
                    }
                 
                H2(self.$unitsToDisperseSubTitle)
                    .maxWidth(90.percent)
                    .class(.oneLineText)
                    .marginLeft(7.px)
                    .color(.cornflowerBlue)
                
            }
            .paddingBottom(3.px)
            
            Div().class(.clear)
        }
        .top(25.percent)
        .backgroundColor(.grayBlack)
        .borderRadius(all: 24.px)
        .position(.absolute)
        .padding(all: 12.px)
        .width(40.percent)
        .left(30.percent)
        .color(.white)
        .hidden(self.$selectedPlace.map { $0 != .merm })
        
        /// Return to vendor
        Div{
            
            /// Header
            Div {
                
                Img()
                    .closeButton(.uiView3)
                    .onClick{
                        self.remove()
                    }
                 
                H2(self.$unitsToDisperseSubTitle)
                    .maxWidth(90.percent)
                    .class(.oneLineText)
                    .marginLeft(7.px)
                    .color(.cornflowerBlue)
                
            }
            .paddingBottom(3.px)
            
            Div().class(.clear)
        }
        .top(25.percent)
        .backgroundColor(.grayBlack)
        .borderRadius(all: 24.px)
        .position(.absolute)
        .padding(all: 12.px)
        .width(40.percent)
        .left(30.percent)
        .color(.white)
        .hidden(self.$selectedPlace.map { $0 != .returnToVendor })
         */
    }
    
    override func buildUI() {
        super.buildUI()
        
        self.class(.transparantBlackBackGround)
        position(.absolute)
        height(100.percent)
        width(100.percent)
        top(0.px)
        left(0.px)
        
        $unitsToDisperse.listen {
            
            guard let int = Int64($0) else{
                self.unitsToDisperseSubTitle = "Ubicacion de \($0) productos"
                return
            }
            
            if int == 1 {
                self.unitsToDisperseSubTitle = "Ubicacion de \($0) productos"
            }
            else{
                self.unitsToDisperseSubTitle = "Ubicacion de \($0) productos"
            }
            
            self.units = int
            
        }
        /*
        $selectedBodIdListener.listen {
            
            self.sectionSelect.innerHTML = ""
            
            self.selectedSecIdListener = ""
            
            self.sectionSelect.appendChild(
                Option("Seleccione Seccion")
                    .value("")
            )
            
            let _bodid = UUID(uuidString: $0)
            
            if self.selectedBodId == _bodid {
                return
            }
            
            guard let _bodid = _bodid else {
                return
            }
//
//            var sectionRefrence: [String:CustStoreSeccionesSinc] = [:]
//
//            seccions.forEach { item, sect in
//                if sect.custStoreBodegas == _bodid {
//                    sectionRefrence[sect.name] = sect
//                }
//            }
//
//            let sorted = sectionRefrence.sorted { $0.0 > $1.0 }
//
//            self.availableSection = sorted.map{$1}
            
            /*
            _sect.forEach { sect in
                
                let opt = Option(sect.name)
                    .value(sect.id.uuidString)
                
                if sect.id == self.preSelectedSectId {
                    self.selectedSecIdListener = sect.id.uuidString
                    opt.selected(true)
                }
                
                self.sectionSelect.appendChild(opt)
                
            }
            */
        }
        
        $sectionSelectText.listen {
            
            let term = $0.uppercased()
            
            if term.isEmpty {
                self.displayedSection = self.availableSection
                return
            }
            
            var sections: [CustStoreSeccionesSinc] = []
            
            var included: [String] = []
            
            // Starts with
            self.availableSection.forEach { section in
                if section.name.uppercased().hasPrefix(term) {
                    sections.append(section)
                    included.append(section.name)
                }
            }
            
            // Contains
            self.availableSection.forEach { section in
                if section.name.uppercased().hasPrefix(term) && !included.contains(section.name){
                    sections.append(section)
                }
            }
        }
        
        $displayedSection.listen {
            
            self.sectionSelectResult.innerHTML = ""
            
            $0.forEach { section in
                self.sectionSelectResult.appendChild(
                    Div(section.name)
                        .class(.uibtn)
                        .color(.white)
                )
            }
            
        }
        
        $selectedSecIdListener.listen {
            
        }
        
        stores.forEach { id, store in
            if id == custCatchStore {
                availableStoreBox.appendChild(
                    Div(store.name)
                        .class(.uibtnLargeOrange)
                        .width(97.percent)
                        .onClick {
                            self.selectStore(storeid: id, storeName: store.name)
                        }
                )
            }
        }
        
        stores.forEach { id, store in
            if id != custCatchStore {
                availableStoreBox.appendChild(
                    Div(store.name)
                        .class(.uibtnLarge)
                        .width(97.percent)
                        .onClick {
                            self.selectStore(storeid: id, storeName: store.name)
                        }
                )
            }
        }
        
        InventoryPlaceType.allCases.forEach { type in
            selecctionBox.appendChild(
                Div(type.description)
                    .class(.uibtnLarge)
                    .width(97.percent)
                    .onClick {
                        self.proccessUnits(type: type)
                    }
            )
        }
        
        unitsToDisperse = units.toString
        */
    }
    
    override func didAddToDOM() {
        super.didAddToDOM()
        
        self.unitsToDisperseField.select()
        
    }
    
    func proccessUnits(type: InventoryPlaceType) {
        
        guard let _units = Int64(unitsToDisperse) else{
            showAlert(.alerta, "Ingrese una cantidad valida")
            self.unitsToDisperseField.select()
            return
        }
        
        if _units > units {
            showAlert(.alerta, "Ingrese menos unidades, no tiene \(_units.toString) unidades")
            self.unitsToDisperseField.select()
            return
        }
        
        if _units < units {
            showAlert(.alerta, "Ingrese mas de una unidad")
            self.unitsToDisperseField.select()
            return
        }
        
        guard self.units > 0 else {
            showAlert(.alerta, "Ingrese una cantidad valida")
            self.unitsToDisperseField.select()
            return
        }
        
        switch type {
        case .store:
            
            print("select .store")
            
            print("No of strores \(stores.count)")
            
            if stores.count == 1 {
                
                guard let store = stores.first?.value else {
                    return
                }
                
                self.selectStore(storeid: store.id, storeName: store.name)
            }
            
            self.selectedPlace = type
            
        case .order:
            self.selectedPlace = type
            self.searchFolioField.select()
        case .sold:
            
            addToDom(ConfirmView(
                type: .yesNo,
                title: "Confirme Accion",
                message: "Confirme que va marcar como vendido.",
                callback: { isConfirmed in
                    if isConfirmed {
                        
                        self.callback(
                            .sold, // selectedPlace
                            custCatchStore, // storeid
                            "", // storeName
                            nil, // custAccountId
                            nil, // placeid
                            "", // placeName
                            nil, // bodid
                            "", // bodName
                            nil, // secid
                            "", // secName
                            self.units, // units
                            [] // series
                        )
                        
                        self.remove()
                    }
                }
            ))
            
        case .merm:
            
            addToDom(ConfirmView(
                type: .yesNo,
                title: "Confirme Accion",
                message: "Confirme que va marcar como mermado.",
                callback: { isConfirmed in
                    if isConfirmed {
                        
                        self.callback(
                            .merm, // selectedPlace
                            custCatchStore, // storeid
                            "", // storeName
                            nil, // custAccountId
                            nil, // placeid
                            "", // placeName
                            nil, // bodid
                            "", // bodName
                            nil, // secid
                            "", // secName
                            self.units, // units
                            [] // series
                        )
                        
                        self.remove()
                    }
                }
            ))
            
        case .returnToVendor:
            
            addToDom(ConfirmView(
                type: .yesNo,
                title: "Confirme Accion",
                message: "Confirme que va regrear vendedor.",
                callback: { isConfirmed in
                    if isConfirmed {
                        
                        self.callback(
                            .returnToVendor, // selectedPlace
                            custCatchStore, // storeid
                            "", // storeName
                            nil, // custAccountId
                            nil, // placeid
                            "", // placeName
                            nil, // bodid
                            "", // bodName
                            nil, // secid
                            "", // secName
                            self.units, // units
                            [] // series
                        )
                        
                        self.remove()
                    }
                }
            ))
        }
        
    }
    
    func selectStore( storeid: UUID, storeName: String) {
        
        /// The store is not same so i will only add to oder since reciving store must decide where it fisicly goes
        if storeid != custCatchStore {
            
            self.callback(
                .store, // selectedPlace
                storeid, // storeid
                storeName, // storeName
                nil, // custAccountId
                nil, // placeid
                "", // placeName
                nil, // bodid
                "Por Eligir", // bodName
                nil, // secid
                "Por Eligir", // secName
                self.units, // units
                [] // series
            )
            
            self.remove()
        }
        /// will load place since  it goes in the order
        else{
            
            loadingView(show: true)
            
            API.custPOCV1.getPOCBodSec(pocid: pocid, storeid: custCatchStore) { resp in
            
                loadingView(show: false)
                
                guard let resp = resp else {
                    showError(.errorDeCommunicacion, .serverConextionError)
                    return
                }
                
                guard resp.status == .ok else {
                    showError(.errorGeneral, resp.msg)
                    return
                }
                
                guard let data = resp.data else {
                    showError(.errorGeneral, "No se obtuvo data de la respuesta")
                    return
                }
                
                self.preSelectedSectId = data.section
                
                var bods: [CustStoreBodegasSinc] = []
                
                bodegas.forEach { bodid, bodData in
                    if bodData.custStore == custCatchStore {
                        bods.append(bodData)
                    }
                }
                
                var _selectedBodegaId: UUID? = nil
                
                if bods.count == 1 {
                    _selectedBodegaId = bods.first?.id
                }
                else if data.bodega != nil {
                    _selectedBodegaId = data.bodega
                }
                
                bods.forEach { bod in
                    
                    let opt = Option(bod.name)
                        .value(bod.id.uuidString)
                    
                    if let _sel = _selectedBodegaId {
                        if _sel ==  bod.id {
                            
                            self.selectedBodIdListener = _sel.uuidString
                            
                            opt.selected(true)
                            
                        }
                    }
                    
                    self.bodegaSelect.appendChild(opt)
                    
                }
                
                self.selectedStoreId = custCatchStore
                
                self.selectedStoreName = storeName
                
                
            }
        }
    }
    
    func searchFolio() {
        
        var term = searchFolioString
            .purgeSpaces
            .pseudo
        
        if searchFolioString.isEmpty {
            showAlert( .alerta, "Ingrese Folio")
            return
        }
        
        if !term.contains("-") {
            term = "-\(term)"
        }
        
        loadingView(show: true)
        
        API.custOrderV1.searchFolio(
            term: term,
            accountid: nil,
            tag1: "",
            tag2: "",
            tag3: "",
            tag4: "",
            description: nil,
            timeEnd: nil,
            timeInit: nil
        ) { resp in
            
            loadingView(show: false)
            
            guard let resp = resp else {
                showError(.errorDeCommunicacion, .serverConextionError)
                return
            }
            
            guard resp.status == .ok else {
                showError(.errorGeneral, resp.msg)
                return
            }
            
            guard let order = resp.data.orders.first else {
                showError( .errorGeneral, "No se localizo folio, revice de nuevo.")
                return
            }
            
            
            self.callback(
                .order, // selectedPlace
                custCatchStore, // storeid
                "Pendiente elegir tienda", // storeName
                order.custAcct, // custAccountId
                order.id, // placeid
                order.folio, // placeName
                nil, // bodid
                "", // bodName
                nil, // secid
                "", // secName
                self.units, // units
                [] // series
            )
            
            self.remove()
            
        }
    }
}

Build fails with the unsafe flags error

I have a very trivial app here that depends on JavaScriptKit, but it fails to build with this error:

% swift build --triple wasm32-unknown-wasi                                     
error: the target 'JavaScriptKit' in product 'JavaScriptKit' contains unsafe build flags
% swiftenv version
wasm-DEVELOPMENT-SNAPSHOT-2020-05-02-a (set by /Users/maxd/Documents/carton/TestApp/.swift-version)
% which swift
/Users/maxd/.swiftenv/shims/swift
% swift --version
Swift version 5.3-dev (LLVM 11086900c5, Swift 52a06ad8f3)
Target: x86_64-apple-darwin19.4.0

Support JS errors

Currently, making a call from Swift to JS that results in an error being thrown on the JS side will leave the code in a strange state. I’m not sure if wasm is able to recover from this. Maybe there could be an additional parameter passed to the JS function that allows it to send back a JSObjectRef pointing to a thrown error (kind of like Objective-C!).

Since we don’t have type info (which could be partially alleviated by #21) we have to assume that every property access and function call potentially throws. Not sure what the proper balance is here.

We’d also need a way to bridge errors over to the Swift side. Perhaps this could be somewhat type-safe, with distinct subclasses for common/host-provided errors and a generic JSError class for everything else.

It would also be good to make JSClosures able to throw, then wrap those errors into a SwiftError on the JS side.

Separate namespaces for methods and properties?

Over time, it’s likely that more and more methods will be added to JSObjectRefs. Currently, there are get, set, jsValue, subscript, and (shortly) instanceof. However, at least some of these methods are likely to appear in regular JS objects (specifically get and set). While there is the escape hatch of calling object[dynamicMember: "get"] or object.get("get"), this is less than optimal and it would be great if there was a way to have a separate namespace for the JavaScriptKit-defined methods so they’d never clash with something from JS.

Maybe it would be sufficient to just add a regular subscript method object["get"] or object[1]?

TypeError when trying to implement a JSBridgedClass for WebSocket.send

I've set up a JSBridgeClass for the WebSocket class using the following code, but I get "TypeError: 'send' called on an object that does not implement WebSocket" or "expression unexpectedly raised an error: TypeError: Can only call WebSocket.send on instances of WebSocket: file JavaScriptKit/JSFunction.swift, line"

class WebSocket: JSBridgedClass {
	public class var constructor: JSFunction { JSObject.global.WebSocket.function! }
	
	public convenience init(uri: String) {
		print("trying to connect to: \(uri)")
		self.init(unsafelyWrapping: Self.constructor.new(uri)) //This works, my server connects
	}
	

	public func sendMessage(data: Data) {
		if let string = String(data: data, encoding: .ascii) {
			
			//Tried Importing DOMKit to use Blob
			//let l = BufferSourceOrBlobOrString.string(string)
			//let blob = Blob(blobParts: [l])
			if let function = self.jsObject.send.function {
				function(string)  //TypeError: 'send' called on an object that does not implement interface
				//function(blob.jsObject)
				function("MAYBE THIS WILL WORK?") //TypeError: 'send' called on an object that does not implement interface
			}
		}
	}
}

I would appreciate some direction 😩 . Perhaps I am misunderstanding how to implement JSBridgedClass properly.

Add an implementation for `JSValueEncoder`

Do I understand correctly that there's no easy way right now to convert an arbitrary Encodable type to JSValue? We already have JSValueDecoder, but apparently lack the opposite for Encodable...

BigInt Support

As part of #26, I created an API that uses BigInt values in some cases. They are not currently implemented, however. An important design question is whether bey should be represented as Int64/UInt64 — sacrificing precision for code size and ease of use — or whether some sort of BigInt library should be made a dependency so we can guarantee precision at the cost of code size and performance.

Use FinalizationRegistry to auto-deinit Closures

There is a new-ish JS API called FinalizationRegistry that lets you add a finalization callback to values in Javascript that gets run sometime after a value is Garbage Collected in Javascript.

Together with WeakRef, this API could be used to automatically release/deinit JSClosures which currently need to managed manually.

Here's an article describing it's use within JS: https://v8.dev/features/weak-references
Another article describing it's use for Wasm/Wasi: https://rob-blackbourn.github.io/blog/webassembly/wasm/wasi/javascript/c/clang/wasi-sdk/marshalling/finalizer/finalizationregistry/2020/07/07/wasi-finalizers-1.html

There is probably some cost associated with using this API, so I don't think that all JSClosures should have this behaviour. Frameworks such as Tokamak can probably handle event listeners just fine, but in other cases it would make things much easier.

The feature is supported in all modern browsers (including Safari 14.1 +)

0.7.0 unavailable on NPM

I know that new versions take time to show on the NPM frontend, but usually installing a newly published version is possible almost immediately. 0.7.0 currently isn't available when installing with NPM, is this because 0.7.0 hasn't been published yet, or is this because of NPM caches not being updated?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.