diff --git a/package-lock.json b/package-lock.json index d5fdbc4e..713cfa3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,8 @@ "prettier": "^3.8.3", "prettier-plugin-astro": "^1.0.0", "rescript": "13.0.0-alpha.5", - "sharp": "^0.35.3" + "sharp": "^0.35.3", + "ws": "^8.22.1" }, "peerDependencies": { "rescript": ">=13.0.0-alpha.5 <13.0.0" @@ -6087,6 +6088,28 @@ } } }, + "node_modules/ws": { + "version": "8.22.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.1.tgz", + "integrity": "sha512-oyL6OPYme/CmiFvNuGzeoxCJ4nOrZE2O1FwM9MWM1AsGri6nLXB2pg+lNA8aLeXMSav0J6eIVS1j6vUPzUtK9g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yaml": { "version": "2.9.1", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", @@ -9610,6 +9633,13 @@ "dev": true, "requires": {} }, + "ws": { + "version": "8.22.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.1.tgz", + "integrity": "sha512-oyL6OPYme/CmiFvNuGzeoxCJ4nOrZE2O1FwM9MWM1AsGri6nLXB2pg+lNA8aLeXMSav0J6eIVS1j6vUPzUtK9g==", + "dev": true, + "requires": {} + }, "yaml": { "version": "2.9.1", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", diff --git a/package.json b/package.json index 0597fd98..d2d0f65d 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,8 @@ "prettier": "^3.8.3", "prettier-plugin-astro": "^1.0.0", "rescript": "13.0.0-alpha.5", - "sharp": "^0.35.3" + "sharp": "^0.35.3", + "ws": "^8.22.1" }, "peerDependencies": { "rescript": ">=13.0.0-alpha.5 <13.0.0" diff --git a/scripts/check-consumer.mjs b/scripts/check-consumer.mjs index e75c37e6..2300d4d5 100644 --- a/scripts/check-consumer.mjs +++ b/scripts/check-consumer.mjs @@ -198,6 +198,11 @@ const checkConsumers = async () => { fixture: "scripts/consumer-safe-fetch.res", features: ["WebAPI.SafeFetch"], }, + { + name: "Crypto", + fixture: "scripts/consumer-crypto.res", + features: ["WebAPI.Crypto"], + }, { name: "Fetch-only SafeFetch rejection", fixture: "scripts/consumer-safe-fetch-disabled.res", diff --git a/scripts/consumer-crypto.res b/scripts/consumer-crypto.res new file mode 100644 index 00000000..2aeffa63 --- /dev/null +++ b/scripts/consumer-crypto.res @@ -0,0 +1,53 @@ +@scope("globalThis") +external crypto: WebAPI.Crypto.t = "crypto" + +let subtle = crypto.subtle +let data = + WebAPI.SubtleCrypto.binaryDataFromUint8Array(Uint8Array.fromArray([65, 66, 67])) + +let digest: ArrayBuffer.t = await subtle->WebAPI.SubtleCrypto.digest(~algorithm={name: "SHA-256"}, ~data) +let digestBytes: Uint8Array.t = %raw(`new Uint8Array(digest)`) +assert(%raw(`digestBytes instanceof Uint8Array && digestBytes.buffer === digest`)) + +let hmacKey: WebAPI.SubtleCrypto.cryptoKey = await subtle->WebAPI.SubtleCrypto.generateKeySymmetric( + ~hash={name: "SHA-256"}, + ~length=256, + ~extractable=true, + ~keyUsages=[WebAPI.SubtleCrypto.signUsage, WebAPI.SubtleCrypto.verifyUsage], +) + +let signature = await subtle->WebAPI.SubtleCrypto.sign(~algorithm={name: "HMAC"}, ~key=hmacKey, ~data) +let verified: bool = await subtle->WebAPI.SubtleCrypto.verify( + ~algorithm={name: "HMAC"}, + ~key=hmacKey, + ~signature=WebAPI.SubtleCrypto.binaryDataFromArrayBuffer(signature), + ~data, +) + +let rawKey: ArrayBuffer.t = await subtle->WebAPI.SubtleCrypto.exportKeyBinary( + ~format=WebAPI.SubtleCrypto.Raw, + ~key=hmacKey, +) +let jwk: JSON.t = await subtle->WebAPI.SubtleCrypto.exportKeyJwk( + ~format=WebAPI.SubtleCrypto.Jwk, + ~key=hmacKey, +) + +let keyPair: WebAPI.SubtleCrypto.cryptoKeyPair = await subtle->WebAPI.SubtleCrypto.generateEcdsaKeyPair( + ~namedCurve="P-256", + ~extractable=true, + ~keyUsages=[WebAPI.SubtleCrypto.signUsage, WebAPI.SubtleCrypto.verifyUsage], +) + +let pairSignature = await subtle->WebAPI.SubtleCrypto.sign( + ~algorithm={name: "ECDSA", hash: {name: "SHA-256"}}, + ~key=keyPair.privateKey, + ~data, +) + +let pairVerified: bool = await subtle->WebAPI.SubtleCrypto.verify( + ~algorithm={name: "ECDSA", hash: {name: "SHA-256"}}, + ~key=keyPair.publicKey, + ~signature=WebAPI.SubtleCrypto.binaryDataFromArrayBuffer(pairSignature), + ~data, +) diff --git a/scripts/fixtures/negative/AnalyserRejectsOrdinaryArray.res b/scripts/fixtures/negative/AnalyserRejectsOrdinaryArray.res new file mode 100644 index 00000000..01713928 --- /dev/null +++ b/scripts/fixtures/negative/AnalyserRejectsOrdinaryArray.res @@ -0,0 +1,7 @@ +let context = WebAPI.OfflineAudioContext.fromChannelCountLengthAndSampleRate( + ~numberOfChannels=1, + ~length=128, + ~sampleRate=44100., +) +let analyser = context->WebAPI.OfflineAudioContext.createAnalyser +analyser->WebAPI.AnalyserNode.getFloatTimeDomainData([0., 0.]) diff --git a/scripts/fixtures/negative/BroadElementHasNoClassName.res b/scripts/fixtures/negative/BroadElementHasNoClassName.res new file mode 100644 index 00000000..822dcb94 --- /dev/null +++ b/scripts/fixtures/negative/BroadElementHasNoClassName.res @@ -0,0 +1,2 @@ +let broadElement: WebAPI.Element.t = %raw(`document.createElement("div")`) +let _className: string = broadElement.className diff --git a/scripts/fixtures/negative/BytesAreNotArray.res b/scripts/fixtures/negative/BytesAreNotArray.res new file mode 100644 index 00000000..5f19a35b --- /dev/null +++ b/scripts/fixtures/negative/BytesAreNotArray.res @@ -0,0 +1,2 @@ +let _bytes: array = + await WebAPI.Request.fromURL("https://example.invalid/")->WebAPI.Request.bytes diff --git a/scripts/fixtures/negative/CanvasRejectsNonNullableCallback.res b/scripts/fixtures/negative/CanvasRejectsNonNullableCallback.res new file mode 100644 index 00000000..c06a371f --- /dev/null +++ b/scripts/fixtures/negative/CanvasRejectsNonNullableCallback.res @@ -0,0 +1,2 @@ +let canvas: WebAPI.HTMLCanvasElement.t = %raw(`document.createElement("canvas")`) +WebAPI.HTMLCanvasElement.toBlob(canvas, ~callback=(_blob: WebAPI.Blob.t) => ()) diff --git a/scripts/fixtures/negative/ClosestCannotNarrow.res b/scripts/fixtures/negative/ClosestCannotNarrow.res new file mode 100644 index 00000000..01ac4703 --- /dev/null +++ b/scripts/fixtures/negative/ClosestCannotNarrow.res @@ -0,0 +1,2 @@ +let element: WebAPI.Element.t = %raw(`document.createElement("div")`) +let _input: Null.t = element->WebAPI.Element.closest("input") diff --git a/scripts/fixtures/negative/CustomElementRequiresConstructor.res b/scripts/fixtures/negative/CustomElementRequiresConstructor.res new file mode 100644 index 00000000..984a80ac --- /dev/null +++ b/scripts/fixtures/negative/CustomElementRequiresConstructor.res @@ -0,0 +1,9 @@ +@scope("globalThis") +external customElements: WebAPI.DOM.customElementRegistry = "customElements" + +let element: WebAPI.HTMLElement.t = %raw(`document.createElement("div")`) +WebAPI.CustomElementRegistry.define( + customElements, + ~name="x-invalid-binding-element", + ~constructor=element, +) diff --git a/scripts/fixtures/negative/DOMMatrixRejectsOrdinaryArray.res b/scripts/fixtures/negative/DOMMatrixRejectsOrdinaryArray.res new file mode 100644 index 00000000..10d391ff --- /dev/null +++ b/scripts/fixtures/negative/DOMMatrixRejectsOrdinaryArray.res @@ -0,0 +1 @@ +let _matrix = WebAPI.DOMMatrix.fromFloat32Array([1., 0., 0., 1., 0., 0.]) diff --git a/scripts/fixtures/negative/GeneratePairRejectsAlgorithm.res b/scripts/fixtures/negative/GeneratePairRejectsAlgorithm.res new file mode 100644 index 00000000..bdba253f --- /dev/null +++ b/scripts/fixtures/negative/GeneratePairRejectsAlgorithm.res @@ -0,0 +1,10 @@ +@scope("globalThis") +external crypto: WebAPI.Crypto.t = "crypto" + +let _pair = WebAPI.SubtleCrypto.generateEcdsaKeyPair( + crypto.subtle, + ~algorithm={name: "HMAC", hash: {name: "SHA-256"}, length: 256}, + ~namedCurve="P-256", + ~extractable=true, + ~keyUsages=[WebAPI.SubtleCrypto.signUsage], +) diff --git a/scripts/fixtures/negative/GenerateSymmetricRejectsAlgorithm.res b/scripts/fixtures/negative/GenerateSymmetricRejectsAlgorithm.res new file mode 100644 index 00000000..a6046b49 --- /dev/null +++ b/scripts/fixtures/negative/GenerateSymmetricRejectsAlgorithm.res @@ -0,0 +1,11 @@ +@scope("globalThis") +external crypto: WebAPI.Crypto.t = "crypto" + +let _key = WebAPI.SubtleCrypto.generateKeySymmetric( + crypto.subtle, + ~algorithm={name: "ECDSA", namedCurve: "P-256"}, + ~hash={name: "SHA-256"}, + ~length=256, + ~extractable=true, + ~keyUsages=[WebAPI.SubtleCrypto.signUsage], +) diff --git a/scripts/fixtures/negative/IDBDeleteResultIsNotDatabase.res b/scripts/fixtures/negative/IDBDeleteResultIsNotDatabase.res new file mode 100644 index 00000000..05870380 --- /dev/null +++ b/scripts/fixtures/negative/IDBDeleteResultIsNotDatabase.res @@ -0,0 +1,5 @@ +@scope("globalThis") +external indexedDB: WebAPI.IndexedDbTypes.idbFactory = "indexedDB" + +let request = WebAPI.IDBFactory.deleteDatabase(indexedDB, "compile-only") +let _database: string = request.result diff --git a/scripts/fixtures/negative/JwkIsNotBinaryKeyFormat.res b/scripts/fixtures/negative/JwkIsNotBinaryKeyFormat.res new file mode 100644 index 00000000..bd667d71 --- /dev/null +++ b/scripts/fixtures/negative/JwkIsNotBinaryKeyFormat.res @@ -0,0 +1,15 @@ +@scope("globalThis") +external crypto: WebAPI.Crypto.t = "crypto" + +let key = await WebAPI.SubtleCrypto.generateKeySymmetric( + crypto.subtle, + ~hash={name: "SHA-256"}, + ~length=256, + ~extractable=true, + ~keyUsages=[WebAPI.SubtleCrypto.signUsage], +) +let _export = WebAPI.SubtleCrypto.exportKeyBinary( + crypto.subtle, + ~format=WebAPI.SubtleCrypto.Jwk, + ~key, +) diff --git a/scripts/fixtures/negative/NotificationHasNoInstancePermission.res b/scripts/fixtures/negative/NotificationHasNoInstancePermission.res new file mode 100644 index 00000000..62ba23c2 --- /dev/null +++ b/scripts/fixtures/negative/NotificationHasNoInstancePermission.res @@ -0,0 +1,2 @@ +let notification = WebAPI.Notification.make(~title="compile-only") +let _permission = notification.permission diff --git a/scripts/fixtures/negative/RawIsNotJwkKeyFormat.res b/scripts/fixtures/negative/RawIsNotJwkKeyFormat.res new file mode 100644 index 00000000..ec0aea6b --- /dev/null +++ b/scripts/fixtures/negative/RawIsNotJwkKeyFormat.res @@ -0,0 +1,15 @@ +@scope("globalThis") +external crypto: WebAPI.Crypto.t = "crypto" + +let key = await WebAPI.SubtleCrypto.generateKeySymmetric( + crypto.subtle, + ~hash={name: "SHA-256"}, + ~length=256, + ~extractable=true, + ~keyUsages=[WebAPI.SubtleCrypto.signUsage], +) +let _export = WebAPI.SubtleCrypto.exportKeyJwk( + crypto.subtle, + ~format=WebAPI.SubtleCrypto.Raw, + ~key, +) diff --git a/scripts/fixtures/negative/ResizeObserverRejectsCurriedCallback.res b/scripts/fixtures/negative/ResizeObserverRejectsCurriedCallback.res new file mode 100644 index 00000000..535c4644 --- /dev/null +++ b/scripts/fixtures/negative/ResizeObserverRejectsCurriedCallback.res @@ -0,0 +1 @@ +let _observer = WebAPI.ResizeObserver.make(_entries => _observer => ()) diff --git a/scripts/fixtures/negative/WebLocksRejectsNonNullableCallback.res b/scripts/fixtures/negative/WebLocksRejectsNonNullableCallback.res new file mode 100644 index 00000000..b1d35061 --- /dev/null +++ b/scripts/fixtures/negative/WebLocksRejectsNonNullableCallback.res @@ -0,0 +1,9 @@ +@scope("globalThis") +external navigator: WebAPI.Navigator.t = "navigator" + +let callback: WebAPI.WebLocksTypes.lock => promise = _lock => %raw(`Promise.resolve(null)`) +let _request = navigator->WebAPI.Navigator.locks->WebAPI.LockManager.request2( + ~name="compile-only", + ~options={ifAvailable: true}, + ~callback, +) diff --git a/scripts/negative-fixtures.json b/scripts/negative-fixtures.json index 3dec583c..571aebea 100644 --- a/scripts/negative-fixtures.json +++ b/scripts/negative-fixtures.json @@ -4,5 +4,95 @@ "fixture": "scripts/fixtures/negative/DiagnosticMatching.res", "features": null, "expectedDiagnostic": "This has type: string" + }, + { + "name": "native request bytes are not an ordinary array", + "fixture": "scripts/fixtures/negative/BytesAreNotArray.res", + "features": ["WebAPI.Fetch"], + "expectedDiagnostic": "This has type:" + }, + { + "name": "closest cannot narrow to an unrelated element type", + "fixture": "scripts/fixtures/negative/ClosestCannotNarrow.res", + "features": ["WebAPI.HTML"], + "expectedDiagnostic": "This has type:" + }, + { + "name": "broad Element has no platform-dependent className field", + "fixture": "scripts/fixtures/negative/BroadElementHasNoClassName.res", + "features": ["WebAPI.DOMNodes"], + "expectedDiagnostic": "The field className does not belong to type" + }, + { + "name": "Canvas callback requires nullable Blob", + "fixture": "scripts/fixtures/negative/CanvasRejectsNonNullableCallback.res", + "features": ["WebAPI.Canvas"], + "expectedDiagnostic": "but a pattern was expected which matches values of type Null.t" + }, + { + "name": "Notification permission is not an instance field", + "fixture": "scripts/fixtures/negative/NotificationHasNoInstancePermission.res", + "features": ["WebAPI.Messaging"], + "expectedDiagnostic": "The field permission does not belong to type" + }, + { + "name": "IndexedDB delete result is not an opened database", + "fixture": "scripts/fixtures/negative/IDBDeleteResultIsNotDatabase.res", + "features": ["WebAPI.Storage"], + "expectedDiagnostic": "This has type:" + }, + { + "name": "Web Locks callback accepts nullable Lock", + "fixture": "scripts/fixtures/negative/WebLocksRejectsNonNullableCallback.res", + "features": ["WebAPI.Navigator"], + "expectedDiagnostic": "Null.t" + }, + { + "name": "custom element definition rejects element instances", + "fixture": "scripts/fixtures/negative/CustomElementRequiresConstructor.res", + "features": ["WebAPI.Window"], + "expectedDiagnostic": "This has type:" + }, + { + "name": "resize observer rejects nested callbacks", + "fixture": "scripts/fixtures/negative/ResizeObserverRejectsCurriedCallback.res", + "features": ["WebAPI.Observers"], + "expectedDiagnostic": "This function is expected" + }, + { + "name": "DOMMatrix requires native Float32Array input", + "fixture": "scripts/fixtures/negative/DOMMatrixRejectsOrdinaryArray.res", + "features": ["WebAPI.Geometry"], + "expectedDiagnostic": "This has type:" + }, + { + "name": "Analyser requires native Float32Array output buffer", + "fixture": "scripts/fixtures/negative/AnalyserRejectsOrdinaryArray.res", + "features": ["WebAPI.WebAudio"], + "expectedDiagnostic": "This has type:" + }, + { + "name": "symmetric key generation fixes the algorithm", + "fixture": "scripts/fixtures/negative/GenerateSymmetricRejectsAlgorithm.res", + "features": ["WebAPI.Crypto"], + "expectedDiagnostic": "This function call is incorrect. The function has type:" + }, + { + "name": "key-pair generation fixes the algorithm", + "fixture": "scripts/fixtures/negative/GeneratePairRejectsAlgorithm.res", + "features": ["WebAPI.Crypto"], + "expectedDiagnostic": "This function call is incorrect. The function has type:" + }, + { + "name": "JWK format cannot be used for binary export", + "fixture": "scripts/fixtures/negative/JwkIsNotBinaryKeyFormat.res", + "features": ["WebAPI.Crypto"], + "expectedDiagnostic": "belongs to the variant type" + }, + { + "name": "raw format cannot be used for JWK export", + "fixture": "scripts/fixtures/negative/RawIsNotJwkKeyFormat.res", + "features": ["WebAPI.Crypto"], + "expectedDiagnostic": "belongs to the variant type" } ] diff --git a/src/canvas/HTMLCanvasElement.res b/src/canvas/HTMLCanvasElement.res index 885e3f40..a60d303d 100644 --- a/src/canvas/HTMLCanvasElement.res +++ b/src/canvas/HTMLCanvasElement.res @@ -116,7 +116,7 @@ external toDataURL: (t, ~type_: string=?, ~quality: JSON.t=?) => string = "toDat @throws(JsExn) @send external toBlob: ( t, - ~callback: FileTypes.blob => unit, + ~callback: Null.t => unit, ~type_: string=?, ~quality: JSON.t=?, ) => unit = "toBlob" diff --git a/src/core/DOM.res b/src/core/DOM.res index 32c6ef5f..77140bac 100644 --- a/src/core/DOM.res +++ b/src/core/DOM.res @@ -80,6 +80,7 @@ type remotePlaybackState = | @as("disconnected") Disconnected type referrerPolicy = + | @as("") Empty | @as("no-referrer") NoReferrer | @as("no-referrer-when-downgrade") NoReferrerWhenDowngrade | @as("origin") Origin diff --git a/src/crypto/Crypto.res b/src/crypto/Crypto.res index 93d641c0..485e9bd6 100644 --- a/src/crypto/Crypto.res +++ b/src/crypto/Crypto.res @@ -9,3 +9,4 @@ external getRandomValues: (WebCryptoTypes.crypto, 't) => 't = "getRandomValues" */ @send external randomUUID: WebCryptoTypes.crypto => string = "randomUUID" +type t = WebCryptoTypes.crypto diff --git a/src/crypto/SubtleCrypto.res b/src/crypto/SubtleCrypto.res index 8f8dc483..95ed5931 100644 --- a/src/crypto/SubtleCrypto.res +++ b/src/crypto/SubtleCrypto.res @@ -1,11 +1,27 @@ +type t = WebCryptoTypes.subtleCrypto +type binaryData = ArrayBufferTypedArrayOrDataView.t +type keyType = WebCryptoTypes.keyType +type keyUsage = WebCryptoTypes.keyUsage +type keyFormat = WebCryptoTypes.keyFormat +type binaryKeyFormat = + | @as("raw") Raw + | @as("pkcs8") Pkcs8 + | @as("spki") Spki +type jwkKeyFormat = | @as("jwk") Jwk +type keyAlgorithm = WebCryptoTypes.keyAlgorithm +type algorithmIdentifier = WebCryptoTypes.algorithmIdentifier +type algorithm = WebCryptoTypes.algorithm +type cryptoKey = WebCryptoTypes.cryptoKey +type cryptoKeyPair = WebCryptoTypes.cryptoKeyPair + /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/encrypt) */ @send external encrypt: ( - WebCryptoTypes.subtleCrypto, - ~algorithm: WebCryptoTypes.algorithmIdentifier, - ~key: WebCryptoTypes.cryptoKey, + t, + ~algorithm: algorithm, + ~key: cryptoKey, ~data: ArrayBufferTypedArrayOrDataView.t, ) => promise = "encrypt" @@ -14,9 +30,9 @@ external encrypt: ( */ @send external decrypt: ( - WebCryptoTypes.subtleCrypto, - ~algorithm: WebCryptoTypes.algorithmIdentifier, - ~key: WebCryptoTypes.cryptoKey, + t, + ~algorithm: algorithm, + ~key: cryptoKey, ~data: ArrayBufferTypedArrayOrDataView.t, ) => promise = "decrypt" @@ -25,77 +41,101 @@ external decrypt: ( */ @send external sign: ( - WebCryptoTypes.subtleCrypto, - ~algorithm: WebCryptoTypes.algorithmIdentifier, - ~key: WebCryptoTypes.cryptoKey, + t, + ~algorithm: algorithm, + ~key: cryptoKey, ~data: ArrayBufferTypedArrayOrDataView.t, -) => promise = "sign" +) => promise = "sign" /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/verify) */ @send external verify: ( - WebCryptoTypes.subtleCrypto, - ~algorithm: WebCryptoTypes.algorithmIdentifier, - ~key: WebCryptoTypes.cryptoKey, + t, + ~algorithm: algorithm, + ~key: cryptoKey, ~signature: ArrayBufferTypedArrayOrDataView.t, ~data: ArrayBufferTypedArrayOrDataView.t, -) => promise = "verify" +) => promise = "verify" /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/digest) */ @send external digest: ( - WebCryptoTypes.subtleCrypto, - ~algorithm: WebCryptoTypes.algorithmIdentifier, + t, + ~algorithm: algorithm, ~data: ArrayBufferTypedArrayOrDataView.t, -) => promise = "digest" +) => promise = "digest" /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/generateKey) */ @send external generateKey: ( - WebCryptoTypes.subtleCrypto, - ~algorithm: WebCryptoTypes.algorithm, + t, + ~algorithm: algorithm, ~extractable: bool, - ~keyUsages: array, -) => promise = "generateKey" + ~keyUsages: array, +) => promise = "generateKey" /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/generateKey) */ @send external generateKey2: ( - WebCryptoTypes.subtleCrypto, + t, ~algorithm: string, ~extractable: bool, - ~keyUsages: array, -) => promise = "generateKey" + ~keyUsages: array, +) => promise = "generateKey" + +let generateKeySymmetric = ( + subtle: t, + ~hash: algorithmIdentifier, + ~length: int, + ~extractable: bool, + ~keyUsages: array, +): promise => { + let _ = (subtle, hash, length, extractable, keyUsages) + %raw(`subtle.generateKey({name: "HMAC", hash, length}, extractable, keyUsages)`) +} + +/** +Generates an ECDSA public/private key pair. The fixed algorithm guarantees the native result shape. +*/ +let generateEcdsaKeyPair = ( + subtle: t, + ~namedCurve: string, + ~extractable: bool, + ~keyUsages: array, +): promise => { + let _ = (subtle, namedCurve, extractable, keyUsages) + %raw(`subtle.generateKey({name: "ECDSA", namedCurve}, extractable, keyUsages)`) +} /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/deriveKey) */ @send external deriveKey: ( - WebCryptoTypes.subtleCrypto, - ~algorithm: WebCryptoTypes.algorithmIdentifier, - ~baseKey: WebCryptoTypes.cryptoKey, - ~derivedKeyType: WebCryptoTypes.algorithmIdentifier, + t, + ~algorithm: algorithm, + ~baseKey: cryptoKey, + ~derivedKeyType: algorithm, ~extractable: bool, - ~keyUsages: array, -) => promise = "deriveKey" + ~keyUsages: array, +) => promise = "deriveKey" /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/deriveBits) */ @send external deriveBits: ( - WebCryptoTypes.subtleCrypto, - ~algorithm: WebCryptoTypes.algorithm, - ~baseKey: WebCryptoTypes.cryptoKey, + t, + ~algorithm: algorithm, + ~baseKey: cryptoKey, ~length: int=?, ) => promise = "deriveBits" @@ -104,9 +144,9 @@ external deriveBits: ( */ @send external deriveBits2: ( - WebCryptoTypes.subtleCrypto, + t, ~algorithm: string, - ~baseKey: WebCryptoTypes.cryptoKey, + ~baseKey: cryptoKey, ~length: int=?, ) => promise = "deriveBits" @@ -115,59 +155,75 @@ external deriveBits2: ( */ @send external importKey: ( - WebCryptoTypes.subtleCrypto, - ~format: unknown, + t, + ~format: keyFormat, ~keyData: ArrayBufferTypedArrayOrDataView.t, - ~algorithm: WebCryptoTypes.algorithmIdentifier, + ~algorithm: algorithm, ~extractable: bool, - ~keyUsages: array, -) => promise = "importKey" + ~keyUsages: array, +) => promise = "importKey" /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/exportKey) */ @send -external exportKey: ( - WebCryptoTypes.subtleCrypto, - ~format: WebCryptoTypes.keyFormat, - ~key: WebCryptoTypes.cryptoKey, -) => promise = "exportKey" +external exportKey: (t, ~format: keyFormat, ~key: cryptoKey) => promise = "exportKey" /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/wrapKey) */ @send external wrapKey: ( - WebCryptoTypes.subtleCrypto, - ~format: WebCryptoTypes.keyFormat, - ~key: WebCryptoTypes.cryptoKey, - ~wrappingKey: WebCryptoTypes.cryptoKey, - ~wrapAlgorithm: WebCryptoTypes.algorithm, -) => promise = "wrapKey" + t, + ~format: keyFormat, + ~key: cryptoKey, + ~wrappingKey: cryptoKey, + ~wrapAlgorithm: algorithm, +) => promise = "wrapKey" /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/wrapKey) */ @send external wrapKey2: ( - WebCryptoTypes.subtleCrypto, - ~format: WebCryptoTypes.keyFormat, - ~key: WebCryptoTypes.cryptoKey, - ~wrappingKey: WebCryptoTypes.cryptoKey, + t, + ~format: keyFormat, + ~key: cryptoKey, + ~wrappingKey: cryptoKey, ~wrapAlgorithm: string, -) => promise = "wrapKey" +) => promise = "wrapKey" /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/unwrapKey) */ @send external unwrapKey: ( - WebCryptoTypes.subtleCrypto, - ~format: WebCryptoTypes.keyFormat, + t, + ~format: keyFormat, ~wrappedKey: ArrayBufferTypedArrayOrDataView.t, - ~unwrappingKey: WebCryptoTypes.cryptoKey, - ~unwrapAlgorithm: WebCryptoTypes.algorithmIdentifier, - ~unwrappedKeyAlgorithm: WebCryptoTypes.algorithmIdentifier, + ~unwrappingKey: cryptoKey, + ~unwrapAlgorithm: algorithm, + ~unwrappedKeyAlgorithm: algorithm, ~extractable: bool, - ~keyUsages: array, -) => promise = "unwrapKey" + ~keyUsages: array, +) => promise = "unwrapKey" + +let decryptUsage = WebCryptoTypes.Decrypt +let deriveBitsUsage = WebCryptoTypes.DeriveBits +let deriveKeyUsage = WebCryptoTypes.DeriveKey +let encryptUsage = WebCryptoTypes.Encrypt +let signUsage = WebCryptoTypes.Sign +let unwrapKeyUsage = WebCryptoTypes.UnwrapKey +let verifyUsage = WebCryptoTypes.Verify +let wrapKeyUsage = WebCryptoTypes.WrapKey + +external binaryDataFromUint8Array: Uint8Array.t => binaryData = "%identity" +external binaryDataFromArrayBuffer: ArrayBuffer.t => binaryData = "%identity" +external binaryDataFromDataView: DataView.t => binaryData = "%identity" + +@send +external exportKeyBinary: (t, ~format: binaryKeyFormat, ~key: cryptoKey) => promise = + "exportKey" + +@send +external exportKeyJwk: (t, ~format: jwkKeyFormat, ~key: cryptoKey) => promise = "exportKey" diff --git a/src/crypto/WebCryptoTypes.res b/src/crypto/WebCryptoTypes.res index 2d69697c..92d778a2 100644 --- a/src/crypto/WebCryptoTypes.res +++ b/src/crypto/WebCryptoTypes.res @@ -66,6 +66,16 @@ type cryptoKey = { usages: array, } -type algorithmIdentifier = unknown +type cryptoKeyPair = { + privateKey: cryptoKey, + publicKey: cryptoKey, +} + +type algorithmIdentifier = {name: string} -type algorithm = {mutable name: string} +type algorithm = { + mutable name: string, + mutable hash?: algorithmIdentifier, + mutable length?: int, + mutable namedCurve?: string, +} diff --git a/src/device/WebLocksTypes.res b/src/device/WebLocksTypes.res index 2815c01f..69cea70c 100644 --- a/src/device/WebLocksTypes.res +++ b/src/device/WebLocksTypes.res @@ -42,4 +42,4 @@ type lockOptions = { mutable signal?: DOM.abortSignal, } -type lockGrantedCallback = lock => promise +type lockGrantedCallback = Null.t => promise diff --git a/src/dom-nodes/DOMTree.res b/src/dom-nodes/DOMTree.res index d0c70248..9611fd51 100644 --- a/src/dom-nodes/DOMTree.res +++ b/src/dom-nodes/DOMTree.res @@ -190,11 +190,6 @@ TODO: mark as private once mutating fields of private records is allowed [Read more on MDN](https://developer.mozilla.org/docs/Web/API/Element/id) */ mutable id: string, - /** - Returns the value of element's class content attribute. Can be set to change it. - [Read more on MDN](https://developer.mozilla.org/docs/Web/API/Element/className) - */ - mutable className: string, /** Allows for manipulation of element's class content attribute as a set of whitespace-separated tokens through a DOMTokenList object. [Read more on MDN](https://developer.mozilla.org/docs/Web/API/Element/classList) diff --git a/src/dom-nodes/Element.res b/src/dom-nodes/Element.res index 768e384a..7ae7d68e 100644 --- a/src/dom-nodes/Element.res +++ b/src/dom-nodes/Element.res @@ -100,7 +100,7 @@ Returns the first (starting at element) inclusive ancestor that matches selector [Read more on MDN](https://developer.mozilla.org/docs/Web/API/Element/closest) */ @throws(JsExn) @send - external closest: (T.t, string) => Null.t<'e> = "closest" + external closest: (T.t, string) => Null.t = "closest" /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/Element/computedStyleMap) diff --git a/src/fetch/FetchTypes.res b/src/fetch/FetchTypes.res index 3394e12c..7a4648fd 100644 --- a/src/fetch/FetchTypes.res +++ b/src/fetch/FetchTypes.res @@ -9,6 +9,7 @@ type responseType = | @as("opaqueredirect") Opaqueredirect type requestDestination = + | @as("") Empty | @as("audio") Audio | @as("audioworklet") Audioworklet | @as("document") Document @@ -30,6 +31,7 @@ type requestDestination = | @as("xslt") Xslt type referrerPolicy = + | @as("") Empty | @as("no-referrer") NoReferrer | @as("no-referrer-when-downgrade") NoReferrerWhenDowngrade | @as("origin") Origin @@ -149,7 +151,7 @@ type request = private { /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/Request/body) */ - body: Null.t>>, + body: Null.t>, /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/Request/bodyUsed) */ @@ -194,7 +196,7 @@ type response = private { /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/Request/body) */ - body: Null.t>>, + body: Null.t>, /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/Request/bodyUsed) */ diff --git a/src/fetch/Request.res b/src/fetch/Request.res index b9575e64..f51ad3fd 100644 --- a/src/fetch/Request.res +++ b/src/fetch/Request.res @@ -34,7 +34,7 @@ external blob: t => promise = "blob" [Read more on MDN](https://developer.mozilla.org/docs/Web/API/Request/bytes) */ @send -external bytes: t => promise> = "bytes" +external bytes: t => promise = "bytes" /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/Request/formData) diff --git a/src/fetch/Response.res b/src/fetch/Response.res index a0428602..006ad748 100644 --- a/src/fetch/Response.res +++ b/src/fetch/Response.res @@ -82,7 +82,7 @@ external blob: t => promise = "blob" [Read more on MDN](https://developer.mozilla.org/docs/Web/API/Request/bytes) */ @send -external bytes: t => promise> = "bytes" +external bytes: t => promise = "bytes" /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/Request/formData) diff --git a/src/file/Blob.res b/src/file/Blob.res index ba46dc15..a4142b94 100644 --- a/src/file/Blob.res +++ b/src/file/Blob.res @@ -24,7 +24,7 @@ module Impl = ( [Read more on MDN](https://developer.mozilla.org/docs/Web/API/Blob/stream) */ @send - external stream: T.t => ReadableStream.t> = "stream" + external stream: T.t => ReadableStream.t = "stream" /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/Blob/text) @@ -42,7 +42,7 @@ module Impl = ( [Read more on MDN](https://developer.mozilla.org/docs/Web/API/Blob/bytes) */ @send - external bytes: T.t => promise> = "bytes" + external bytes: T.t => promise = "bytes" } include Impl({type t = t}) diff --git a/src/geometry/DOMMatrix.res b/src/geometry/DOMMatrix.res index 6466cf7a..183fe864 100644 --- a/src/geometry/DOMMatrix.res +++ b/src/geometry/DOMMatrix.res @@ -45,7 +45,7 @@ external asDOMMatrixReadOnly: DOM.domMatrix => DOM.domMatrixReadOnly = "%identit external fromMatrix: (~other: DOM.domMatrixInit=?) => DOM.domMatrixReadOnly = "fromMatrix" @throws(JsExn) @scope("DOMMatrix") -external fromFloat32Array: array => DOM.domMatrixReadOnly = "fromFloat32Array" +external fromFloat32Array: Float32Array.t => DOM.domMatrixReadOnly = "fromFloat32Array" @throws(JsExn) @scope("DOMMatrix") external fromFloat64Array: Float64Array.t => DOM.domMatrixReadOnly = "fromFloat64Array" @@ -123,7 +123,7 @@ external transformPoint: (DOM.domMatrix, ~point: DOM.domPointInit=?) => DOM.domP "transformPoint" @send -external toFloat32Array: DOM.domMatrix => array = "toFloat32Array" +external toFloat32Array: DOM.domMatrix => Float32Array.t = "toFloat32Array" @send external toFloat64Array: DOM.domMatrix => Float64Array.t = "toFloat64Array" @@ -135,7 +135,7 @@ external toJSON: DOM.domMatrix => Dict.t = "toJSON" external fromMatrixD: (~other: DOM.domMatrixInit=?) => DOM.domMatrix = "fromMatrix" @throws(JsExn) @scope("DOMMatrix") -external fromFloat32ArrayD: array => DOM.domMatrix = "fromFloat32Array" +external fromFloat32ArrayD: Float32Array.t => DOM.domMatrix = "fromFloat32Array" @throws(JsExn) @scope("DOMMatrix") external fromFloat64ArrayD: Float64Array.t => DOM.domMatrix = "fromFloat64Array" diff --git a/src/geometry/DOMMatrixReadOnly.res b/src/geometry/DOMMatrixReadOnly.res index 8f099453..39bd42f2 100644 --- a/src/geometry/DOMMatrixReadOnly.res +++ b/src/geometry/DOMMatrixReadOnly.res @@ -44,7 +44,7 @@ external fromArray: array => DOM.domMatrixReadOnly = "DOMMatrixReadOnly" external fromMatrix: (~other: DOM.domMatrixInit=?) => DOM.domMatrixReadOnly = "fromMatrix" @throws(JsExn) @scope("DOMMatrixReadOnly") -external fromFloat32Array: array => DOM.domMatrixReadOnly = "fromFloat32Array" +external fromFloat32Array: Float32Array.t => DOM.domMatrixReadOnly = "fromFloat32Array" @throws(JsExn) @scope("DOMMatrixReadOnly") external fromFloat64Array: Float64Array.t => DOM.domMatrixReadOnly = "fromFloat64Array" @@ -131,7 +131,7 @@ external transformPoint: (DOM.domMatrixReadOnly, ~point: DOM.domPointInit=?) => "transformPoint" @send -external toFloat32Array: DOM.domMatrixReadOnly => array = "toFloat32Array" +external toFloat32Array: DOM.domMatrixReadOnly => Float32Array.t = "toFloat32Array" @send external toFloat64Array: DOM.domMatrixReadOnly => Float64Array.t = "toFloat64Array" diff --git a/src/media/DomHTMLMediaElement.res b/src/media/DomHTMLMediaElement.res index 2751c58e..757430d8 100644 --- a/src/media/DomHTMLMediaElement.res +++ b/src/media/DomHTMLMediaElement.res @@ -1,6 +1,7 @@ type mediaProvider = unknown type canPlayTypeResult = + | @as("") Empty | @as("maybe") Maybe | @as("probably") Probably diff --git a/src/messaging/NotificationTypes.res b/src/messaging/NotificationTypes.res index fc0f02ab..f3336f70 100644 --- a/src/messaging/NotificationTypes.res +++ b/src/messaging/NotificationTypes.res @@ -17,10 +17,6 @@ This Notifications API interface is used to configure and display desktop notifi @editor.completeFrom(Notification) type notification = private { ...DOM.eventTarget, - /** - [Read more on MDN](https://developer.mozilla.org/docs/Web/API/Notification/permission_static) - */ - permission: notificationPermission, /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/Notification/title) */ diff --git a/src/messaging/WebSocket.res b/src/messaging/WebSocket.res index 7c9ee7b8..daf80866 100644 --- a/src/messaging/WebSocket.res +++ b/src/messaging/WebSocket.res @@ -56,19 +56,19 @@ external send: (t, DataView.t) => unit = "send" Transmits data using the WebSocket connection. data can be a string, a Blob, an ArrayBuffer, or an ArrayBufferView. [Read more on MDN](https://developer.mozilla.org/docs/Web/API/WebSocket/send) */ -@throws(JsExn) +@throws(JsExn) @send external sendArrayBuffer: (t, ArrayBuffer.t) => unit = "send" /** Transmits data using the WebSocket connection. data can be a string, a Blob, an ArrayBuffer, or an ArrayBufferView. [Read more on MDN](https://developer.mozilla.org/docs/Web/API/WebSocket/send) */ -@throws(JsExn) +@throws(JsExn) @send external sendBlob: (t, Blob.t) => unit = "send" /** Transmits data using the WebSocket connection. data can be a string, a Blob, an ArrayBuffer, or an ArrayBufferView. [Read more on MDN](https://developer.mozilla.org/docs/Web/API/WebSocket/send) */ -@throws(JsExn) +@throws(JsExn) @send external sendString: (t, string) => unit = "send" diff --git a/src/observers/ResizeObserverTypes.res b/src/observers/ResizeObserverTypes.res index 4916a850..fdce78e7 100644 --- a/src/observers/ResizeObserverTypes.res +++ b/src/observers/ResizeObserverTypes.res @@ -53,4 +53,4 @@ type resizeObserver = private {} type resizeObserverOptions = {mutable box?: resizeObserverBoxOptions} -type resizeObserverCallback = array => resizeObserver => unit +type resizeObserverCallback = (array, resizeObserver) => unit diff --git a/src/safe-fetch/SafeFetch.resi b/src/safe-fetch/SafeFetch.resi index b3c76c52..1ac61f44 100644 --- a/src/safe-fetch/SafeFetch.resi +++ b/src/safe-fetch/SafeFetch.resi @@ -95,11 +95,11 @@ Returns the body with the original response, or a `bodyError` when the native re let blob: Response.t => promise, [> bodyError]>> /** -Reads the response body as an array of bytes. +Reads the response body as a native `Uint8Array.t`. Returns the body with the original response, or a `bodyError` when the native reader rejects. */ -let bytes: Response.t => promise>, [> bodyError]>> +let bytes: Response.t => promise, [> bodyError]>> /** Reads the response body as `FormData.t`. diff --git a/src/storage/IDBFactory.res b/src/storage/IDBFactory.res index c5085273..712e695a 100644 --- a/src/storage/IDBFactory.res +++ b/src/storage/IDBFactory.res @@ -14,7 +14,7 @@ Attempts to delete the named database. If the database already exists and there [Read more on MDN](https://developer.mozilla.org/docs/Web/API/IDBFactory/deleteDatabase) */ @send -external deleteDatabase: (IndexedDbTypes.idbFactory, string) => IndexedDbTypes.idbOpenDBRequest = +external deleteDatabase: (IndexedDbTypes.idbFactory, string) => IndexedDbTypes.idbRequest = "deleteDatabase" /** diff --git a/src/svg/SVGElement.res b/src/svg/SVGElement.res index 2de4853b..252b1a26 100644 --- a/src/svg/SVGElement.res +++ b/src/svg/SVGElement.res @@ -4,6 +4,11 @@ Used for attributes of type SVGPreserveAspectRatio which can be animated. */ type svgAnimatedPreserveAspectRatio = {} +type svgAnimatedString = { + mutable baseVal: string, + animVal: string, +} + /** Correspond to the basic data type. [See SVGLength on MDN](https://developer.mozilla.org/docs/Web/API/SVGLength) @@ -32,6 +37,7 @@ All of the SVG WebApiDOM interfaces that correspond directly to elements in the */ type t = { ...DOMTree.element, + className: svgAnimatedString, /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/HTMLElement/dataset) */ diff --git a/src/web-audio/AnalyserNode.res b/src/web-audio/AnalyserNode.res index 80b54ff1..6610b376 100644 --- a/src/web-audio/AnalyserNode.res +++ b/src/web-audio/AnalyserNode.res @@ -13,26 +13,26 @@ external make: ( [Read more on MDN](https://developer.mozilla.org/docs/Web/API/AnalyserNode/getFloatFrequencyData) */ @send -external getFloatFrequencyData: (WebAudioTypes.analyserNode, array) => unit = +external getFloatFrequencyData: (WebAudioTypes.analyserNode, Float32Array.t) => unit = "getFloatFrequencyData" /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/AnalyserNode/getByteFrequencyData) */ @send -external getByteFrequencyData: (WebAudioTypes.analyserNode, array) => unit = +external getByteFrequencyData: (WebAudioTypes.analyserNode, Uint8Array.t) => unit = "getByteFrequencyData" /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/AnalyserNode/getFloatTimeDomainData) */ @send -external getFloatTimeDomainData: (WebAudioTypes.analyserNode, array) => unit = +external getFloatTimeDomainData: (WebAudioTypes.analyserNode, Float32Array.t) => unit = "getFloatTimeDomainData" /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/AnalyserNode/getByteTimeDomainData) */ @send -external getByteTimeDomainData: (WebAudioTypes.analyserNode, array) => unit = +external getByteTimeDomainData: (WebAudioTypes.analyserNode, Uint8Array.t) => unit = "getByteTimeDomainData" diff --git a/src/web-audio/AudioBuffer.res b/src/web-audio/AudioBuffer.res index b1e6f496..01878002 100644 --- a/src/web-audio/AudioBuffer.res +++ b/src/web-audio/AudioBuffer.res @@ -8,7 +8,7 @@ external make: WebAudioTypes.audioBufferOptions => WebAudioTypes.audioBuffer = " [Read more on MDN](https://developer.mozilla.org/docs/Web/API/AudioBuffer/getChannelData) */ @throws(JsExn) @send -external getChannelData: (WebAudioTypes.audioBuffer, int) => array = "getChannelData" +external getChannelData: (WebAudioTypes.audioBuffer, int) => Float32Array.t = "getChannelData" /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/AudioBuffer/copyFromChannel) @@ -16,7 +16,7 @@ external getChannelData: (WebAudioTypes.audioBuffer, int) => array = "get @throws(JsExn) @send external copyFromChannel: ( WebAudioTypes.audioBuffer, - ~destination: array, + ~destination: Float32Array.t, ~channelNumber: int, ~bufferOffset: int=?, ) => unit = "copyFromChannel" @@ -27,7 +27,7 @@ external copyFromChannel: ( @throws(JsExn) @send external copyToChannel: ( WebAudioTypes.audioBuffer, - ~source: array, + ~source: Float32Array.t, ~channelNumber: int, ~bufferOffset: int=?, ) => unit = "copyToChannel" diff --git a/src/web-audio/BiquadFilterNode.res b/src/web-audio/BiquadFilterNode.res index 745fab30..4c227c64 100644 --- a/src/web-audio/BiquadFilterNode.res +++ b/src/web-audio/BiquadFilterNode.res @@ -15,7 +15,7 @@ external make: ( @throws(JsExn) @send external getFrequencyResponse: ( WebAudioTypes.biquadFilterNode, - ~frequencyHz: array, - ~magResponse: array, - ~phaseResponse: array, + ~frequencyHz: Float32Array.t, + ~magResponse: Float32Array.t, + ~phaseResponse: Float32Array.t, ) => unit = "getFrequencyResponse" diff --git a/src/web-audio/IIRFilterNode.res b/src/web-audio/IIRFilterNode.res index aa10af56..c10f1cca 100644 --- a/src/web-audio/IIRFilterNode.res +++ b/src/web-audio/IIRFilterNode.res @@ -15,7 +15,7 @@ external make: ( @throws(JsExn) @send external getFrequencyResponse: ( WebAudioTypes.iirFilterNode, - ~frequencyHz: array, - ~magResponse: array, - ~phaseResponse: array, + ~frequencyHz: Float32Array.t, + ~magResponse: Float32Array.t, + ~phaseResponse: Float32Array.t, ) => unit = "getFrequencyResponse" diff --git a/src/window/CustomElementRegistry.res b/src/window/CustomElementRegistry.res index a21f1d64..badd6ce4 100644 --- a/src/window/CustomElementRegistry.res +++ b/src/window/CustomElementRegistry.res @@ -5,7 +5,7 @@ external define: ( DOM.customElementRegistry, ~name: string, - ~constructor: DOMTree.htmlElement, + ~constructor: DOM.customElementConstructor, ~options: DOM.elementDefinitionOptions=?, ) => unit = "define" diff --git a/src/workers/Worker.res b/src/workers/Worker.res index 32bc9941..be152a42 100644 --- a/src/workers/Worker.res +++ b/src/workers/Worker.res @@ -41,6 +41,7 @@ let response = await self->Worker.fetch(myRequest) [Read more on MDN](https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/fetch) */ + @send external fetchWithRequest: (T.t, Request.t, ~init: Request.requestInit=?) => promise = "fetch" } diff --git a/tests/BindingByteTypes__test.res b/tests/BindingByteTypes__test.res new file mode 100644 index 00000000..ff4da9af --- /dev/null +++ b/tests/BindingByteTypes__test.res @@ -0,0 +1,22 @@ +let _requestBytes: promise = Request.fromURL("/")->Request.bytes +let _responseBytes: promise = Response.fromString("")->Response.bytes +let _blobBytes: promise = Blob.make()->Blob.bytes +let _blobStream: ReadableStream.t = Blob.make()->Blob.stream + +let _analyserFloat: ( + WebAudioTypes.analyserNode, + Float32Array.t, +) => unit = AnalyserNode.getFloatTimeDomainData +let _analyserByte: ( + WebAudioTypes.analyserNode, + Uint8Array.t, +) => unit = AnalyserNode.getByteTimeDomainData +let _audioBuffer: (WebAudioTypes.audioBuffer, int) => Float32Array.t = AudioBuffer.getChannelData + +let _sign: SubtleCrypto.keyUsage = SubtleCrypto.signUsage +let _sendArrayBuffer = WebSocket.sendArrayBuffer +let _sendBlob = WebSocket.sendBlob +let _sendString = WebSocket.sendString +let _workerRequestFetch = Worker.fetchWithRequest +let _sharedWorkerRequestFetch = SharedWorkerScope.fetchWithRequest +let _serviceWorkerRequestFetch = ServiceWorkerScope.fetchWithRequest diff --git a/tests/FetchAPI/SafeFetch__test.res b/tests/FetchAPI/SafeFetch__test.res index bfc020fb..f954f76e 100644 --- a/tests/FetchAPI/SafeFetch__test.res +++ b/tests/FetchAPI/SafeFetch__test.res @@ -1,6 +1,9 @@ @scope("Object") external isSame: ('value, 'value) => bool = "is" +let isBytesABC = _bytes => + %raw(`_bytes instanceof Uint8Array && _bytes.length === 3 && _bytes[0] === 65 && _bytes[2] === 67`) + let okResponse = Response.fromString("ok", ~init={status: 200}) switch okResponse->SafeFetch.checkOk { | Ok(response) => assert(isSame(response, okResponse)) @@ -196,11 +199,18 @@ let blobResult: result< > = await Response.fromString("blob")->SafeFetch.blob assert(blobResult->Result.isOk) +let bytesResponse = Response.fromString("ABC") let bytesResult: result< - SafeFetch.response>, + SafeFetch.response, [> SafeFetch.bodyError], -> = await Response.fromString("bytes")->SafeFetch.bytes -assert(bytesResult->Result.isOk) +> = await bytesResponse->SafeFetch.bytes +switch bytesResult { +| Ok({response, body}) => { + assert(isSame(response, bytesResponse)) + assert(isBytesABC(body)) + } +| Error(_) => assert(false) +} let formDataResponse = Response.fromString( "fruit=peach", diff --git a/tests/browser/BindingContracts__test.res b/tests/browser/BindingContracts__test.res new file mode 100644 index 00000000..4a5495d4 --- /dev/null +++ b/tests/browser/BindingContracts__test.res @@ -0,0 +1,291 @@ +@scope("globalThis") +external crypto: Crypto.t = "crypto" + +@scope("globalThis") +external document: DOM.document = "document" + +@scope("globalThis") +external indexedDB: IndexedDbTypes.idbFactory = "indexedDB" + +@scope("globalThis") +external navigator: Navigator.t = "navigator" + +@send +external readByteStreamChunk: FileTypes.readableStreamReader => promise = + "read" + +@send +external cancelByteStreamReader: FileTypes.readableStreamReader => promise = + "cancel" + +let withResolver = (_run: (unit => unit) => unit): promise => + %raw(`new Promise(resolve => _run(resolve))`) + +let hasResizeCallbackArgs = (_entries, _observer) => + %raw(`_entries.length > 0 && _observer instanceof ResizeObserver`) +let createResizeSignals = () => %raw(`({count: 0, resolvers: []})`) +let waitForResizeSignal = (_signals: unknown): promise => + %raw(`new Promise(resolve => _signals.resolvers.push(resolve))`) +let resolveNextResizeSignal = (_signals: unknown): unit => %raw(`_signals.resolvers.shift()()`) +let incrementResizeCount = (_signals: unknown): int => %raw(`++_signals.count`) +let setSize = (_target: DOMTree.element, _pixels: int): unit => + %raw(`(_target.style.width = _pixels + "px", _target.style.height = _pixels + "px")`) + +let isNull = _value => %raw(`_value === null`) +let isUndefined = _value => %raw(`_value === undefined`) +let containsABCByteChunk = _result => + %raw(`_result.value instanceof Uint8Array && _result.value.length === 3 && _result.value[0] === 65 && _result.value[2] === 67`) +let appendChild = (_parent, _child) => %raw(`_parent.appendChild(_child)`) + +let appendToDocument = _target => %raw(`document.body.appendChild(_target)`) +let onSuccess = (_request, _callback) => + %raw(`_request.addEventListener("success", _callback, {once: true})`) +let releaseLock = () => %raw(`globalThis.__releaseBindingLock()`) + +let isUint8ArrayWithABC = _bytes => + %raw(`_bytes instanceof Uint8Array && _bytes.length === 3 && _bytes[0] === 65 && _bytes[2] === 67`) + +let isNonIdentityFloat32Matrix = _values => + %raw(`_values instanceof Float32Array && _values.length === 16 && _values[0] === 2 && _values[5] === 3 && _values[12] === 4 && _values[13] === 5 && _values[15] === 1`) + +let bytes = await Response.fromString("ABC")->Response.bytes +assert(isUint8ArrayWithABC(bytes)) + +let requestBytes = await Request.fromURL( + "https://example.invalid/", + ~init={method: "POST", body: Nullable.make(BodyInit.fromString("ABC"))}, +)->Request.bytes +assert(isUint8ArrayWithABC(requestBytes)) + +let emptyStateRequest = Request.fromURL("https://example.invalid/") +assert(%raw(`emptyStateRequest.destination === "" && emptyStateRequest.referrerPolicy === ""`)) + +let blobBytes = await Blob.make(~blobParts=[String("ABC")])->Blob.bytes +assert(isUint8ArrayWithABC(blobBytes)) + +let byteStream = Blob.make(~blobParts=[String("ABC")])->Blob.stream +let byteStreamReader = byteStream->ReadableStream.getReader +let firstChunk = await byteStreamReader->readByteStreamChunk +assert(containsABCByteChunk(firstChunk)) +await byteStreamReader->cancelByteStreamReader + +let audio: HTMLAudioElement.t = %raw(`document.createElement("audio")`) +assert(audio->HTMLAudioElement.canPlayType("application/x-webapi-unsupported") == + DomHTMLMediaElement.Empty) + +switch Notification.permission { +| NotificationTypes.Default | NotificationTypes.Denied | NotificationTypes.Granted => () +} + +switch Document.createElement(document, "canvas")->HTMLCanvasElement.classify { +| Some(canvas) => { + canvas.width = 0 + canvas.height = 0 + await withResolver(resolve => + canvas->HTMLCanvasElement.toBlob(~callback=_blob => { + assert(isNull(_blob)) + resolve() + }) + ) + } +| None => assert(false) +} + +%raw(`globalThis.BindingContractsElement = class extends HTMLElement {}`) + +@scope("globalThis") +external customElements: DOM.customElementRegistry = "customElements" + +@scope("globalThis") +external bindingContractsConstructor: DOM.customElementConstructor = "BindingContractsElement" + +let customElementName = "webapi-binding-contracts" +CustomElementRegistry.define( + customElements, + ~name=customElementName, + ~constructor=bindingContractsConstructor, +) +let customElement = Document.createElement(document, customElementName) +assert(%raw(`customElement instanceof globalThis.BindingContractsElement`)) + +let inputAncestor: DOMTree.element = Document.createElement(document, "input") +let spanDescendant: DOMTree.element = Document.createElement(document, "span") +appendToDocument(inputAncestor) +appendChild(inputAncestor, spanDescendant) +let closestInput = spanDescendant->Element.closest("input") +assert(%raw(`closestInput !== null && closestInput.tagName === "INPUT"`)) +spanDescendant->Element.remove +inputAncestor->Element.remove + +let svg: SVGElement.t = %raw(`document.createElementNS("http://www.w3.org/2000/svg", "svg")`) +svg.className.baseVal = "binding-contracts-svg" +assert(svg.className.baseVal == "binding-contracts-svg") +let circle: SVGElement.t = %raw(`document.createElementNS("http://www.w3.org/2000/svg", "circle")`) +appendChild(svg, circle) +let broadSvgResult = svg->SVGElement.querySelector("circle") +assert(%raw(`broadSvgResult !== null && typeof broadSvgResult.className === "object" && broadSvgResult.className.baseVal === ""`)) + +let htmlParent: HTMLDivElement.t = %raw(`document.createElement("div")`) +htmlParent->HTMLDivElement.setAttribute(~qualifiedName="class", ~value="binding-contracts-html") +assert(htmlParent.className == "binding-contracts-html") +let htmlChild: HTMLSpanElement.t = %raw(`document.createElement("span")`) +htmlChild->HTMLSpanElement.setAttribute( + ~qualifiedName="class", + ~value="binding-contracts-html-child", +) +appendChild(htmlParent, htmlChild) +let broadHtmlResult = htmlParent->HTMLDivElement.querySelector(".binding-contracts-html-child") +assert(%raw(`broadHtmlResult !== null && typeof broadHtmlResult.className === "string"`)) + +let audioContext = OfflineAudioContext.fromChannelCountLengthAndSampleRate( + ~numberOfChannels=1, + ~length=128, + ~sampleRate=44100., +) +let audioBuffer = + audioContext->OfflineAudioContext.createBuffer( + ~numberOfChannels=1, + ~length=128, + ~sampleRate=44100., + ) +let sourceChannel: Float32Array.t = %raw(`new Float32Array([0.5, -0.25])`) +audioBuffer->AudioBuffer.copyToChannel(~source=sourceChannel, ~channelNumber=0) +let channelData = audioBuffer->AudioBuffer.getChannelData(0) +assert(%raw(`channelData instanceof Float32Array && channelData[0] === 0.5 && channelData[1] === -0.25`)) + +let destination: Float32Array.t = %raw(`new Float32Array(2)`) +audioBuffer->AudioBuffer.copyFromChannel(~destination, ~channelNumber=0) +assert(%raw(`destination instanceof Float32Array && destination[0] === 0.5 && destination[1] === -0.25`)) + +let analyser = audioContext->OfflineAudioContext.createAnalyser +let analyserBytes: Uint8Array.t = %raw(`new Uint8Array(32)`) +analyser->AnalyserNode.getByteTimeDomainData(analyserBytes) +assert(%raw(`analyserBytes instanceof Uint8Array && analyserBytes.length === 32 && analyserBytes[0] === 128`)) +let analyserFloats: Float32Array.t = %raw(`new Float32Array([0.5, -0.5])`) +analyser->AnalyserNode.getFloatTimeDomainData(analyserFloats) +assert(%raw(`analyserFloats instanceof Float32Array && analyserFloats[0] === 0 && analyserFloats[1] === 0`)) + +let filter = audioContext->OfflineAudioContext.createBiquadFilter +let frequencies: Float32Array.t = %raw(`new Float32Array([440, 880])`) +let magnitudes: Float32Array.t = %raw(`new Float32Array([-123, -123])`) +let phases: Float32Array.t = %raw(`new Float32Array([-456, -456])`) +filter->BiquadFilterNode.getFrequencyResponse( + ~frequencyHz=frequencies, + ~magResponse=magnitudes, + ~phaseResponse=phases, +) +assert(%raw(`magnitudes instanceof Float32Array && phases instanceof Float32Array && magnitudes.length === 2 && phases.length === 2 && magnitudes[0] !== -123 && phases[0] !== -456 && !Number.isNaN(magnitudes[0])`)) + +let iirFilter = IIRFilterNode.make( + ~context=audioContext->OfflineAudioContext.asBaseAudioContext, + ~options={feedforward: [1.], feedback: [1.]}, +) +let iirMagnitudes: Float32Array.t = %raw(`new Float32Array([-123, -123])`) +let iirPhases: Float32Array.t = %raw(`new Float32Array([-456, -456])`) +iirFilter->IIRFilterNode.getFrequencyResponse( + ~frequencyHz=frequencies, + ~magResponse=iirMagnitudes, + ~phaseResponse=iirPhases, +) +assert(%raw(`iirMagnitudes instanceof Float32Array && iirMagnitudes[0] !== -123 && !Number.isNaN(iirMagnitudes[0]) && iirPhases instanceof Float32Array && iirPhases[0] !== -456`)) + +let deleteRequest = IDBFactory.deleteDatabase(indexedDB, "binding-contracts-regression") +await withResolver(resolve => + onSuccess(deleteRequest, () => { + assert(isUndefined(deleteRequest.result)) + resolve() + }) +) + +let lockManager = navigator->Navigator.locks +await withResolver(resolve => { + let _heldRequest = lockManager->LockManager.request( + ~name="binding-contracts-contended-lock", + ~callback=lock => { + assert(!isNull(lock)) + resolve() + %raw(`new Promise(done => { globalThis.__releaseBindingLock = done })`) + }, + ) +}) +let _contendedRequest = await lockManager->LockManager.request2( + ~name="binding-contracts-contended-lock", + ~options={ifAvailable: true}, + ~callback=lock => { + assert(isNull(lock)) + %raw(`Promise.resolve(null)`) + }, +) +releaseLock() + +let matrixValues = Float32Array.fromArray([2., 0., 0., 3., 4., 5.]) +let matrix = DOMMatrix.fromFloat32Array(matrixValues) +assert(isNonIdentityFloat32Matrix(matrix->DOMMatrixReadOnly.toFloat32Array)) +let readOnlyMatrix = DOMMatrixReadOnly.fromFloat32Array(matrixValues) +assert(isNonIdentityFloat32Matrix(readOnlyMatrix->DOMMatrixReadOnly.toFloat32Array)) + +let target: DOMTree.element = %raw(`document.createElement("div")`) +appendToDocument(target) +let resizeSignals = createResizeSignals() +let initialResize = waitForResizeSignal(resizeSignals) +let resizedAgain = waitForResizeSignal(resizeSignals) +setSize(target, 10) +let observer = ResizeObserver.make((entries, observer) => { + assert(hasResizeCallbackArgs(entries, observer)) + if incrementResizeCount(resizeSignals) == 2 { + observer->ResizeObserver.disconnect + } + resolveNextResizeSignal(resizeSignals) +}) +observer->ResizeObserver.observe(~target) +await initialResize +setSize(target, 20) +await resizedAgain +target->Element.remove + +let subtle = crypto.subtle +let digest = await subtle->SubtleCrypto.digest( + ~algorithm={name: "SHA-256"}, + ~data=Uint8Array.fromArray([65, 66, 67])->SubtleCrypto.binaryDataFromUint8Array, +) +let digestBytes: Uint8Array.t = %raw(`new Uint8Array(digest)`) +assert(%raw(`digest.byteLength === 32 && digestBytes instanceof Uint8Array && digestBytes.length === 32 && digestBytes.buffer === digest`)) + +let data = Uint8Array.fromArray([65, 66, 67])->SubtleCrypto.binaryDataFromUint8Array +let hmacKey = await subtle->SubtleCrypto.generateKeySymmetric( + ~hash={name: "SHA-256"}, + ~length=256, + ~extractable=true, + ~keyUsages=[SubtleCrypto.signUsage, SubtleCrypto.verifyUsage], +) +let _publicHmacKey: SubtleCrypto.cryptoKey = hmacKey +let signature = await subtle->SubtleCrypto.sign(~algorithm={name: "HMAC"}, ~key=hmacKey, ~data) +let verified = await subtle->SubtleCrypto.verify( + ~algorithm={name: "HMAC"}, + ~key=hmacKey, + ~signature=SubtleCrypto.binaryDataFromArrayBuffer(signature), + ~data, +) +assert(verified) + +let rawKey = await subtle->SubtleCrypto.exportKeyBinary(~format=SubtleCrypto.Raw, ~key=hmacKey) +let jwk = await subtle->SubtleCrypto.exportKeyJwk(~format=SubtleCrypto.Jwk, ~key=hmacKey) +assert(%raw(`rawKey.byteLength === 32 && jwk.kty === "oct"`)) + +let keyPair = await subtle->SubtleCrypto.generateEcdsaKeyPair( + ~namedCurve="P-256", + ~extractable=true, + ~keyUsages=[SubtleCrypto.signUsage, SubtleCrypto.verifyUsage], +) +let _publicKeyPair: SubtleCrypto.cryptoKeyPair = keyPair +let pairSignature = await subtle->SubtleCrypto.sign( + ~algorithm={name: "ECDSA", hash: {name: "SHA-256"}}, + ~key=keyPair.privateKey, + ~data, +) +assert(await subtle->SubtleCrypto.verify( + ~algorithm={name: "ECDSA", hash: {name: "SHA-256"}}, + ~key=keyPair.publicKey, + ~signature=SubtleCrypto.binaryDataFromArrayBuffer(pairSignature), + ~data, +)) diff --git a/tests/browser/WebSocketBindings.res b/tests/browser/WebSocketBindings.res new file mode 100644 index 00000000..0ddf0f61 --- /dev/null +++ b/tests/browser/WebSocketBindings.res @@ -0,0 +1,4 @@ +let connect = url => WebSocket.fromURL(~url) +let sendArrayBuffer = (socket, payload: ArrayBuffer.t) => WebSocket.sendArrayBuffer(socket, payload) +let sendBlob = (socket, payload: Blob.t) => WebSocket.sendBlob(socket, payload) +let sendString = (socket, payload: string) => WebSocket.sendString(socket, payload) diff --git a/tests/browser/WorkerFetchBinding.res b/tests/browser/WorkerFetchBinding.res new file mode 100644 index 00000000..deff5654 --- /dev/null +++ b/tests/browser/WorkerFetchBinding.res @@ -0,0 +1,4 @@ +let fetchWithRequest = (request: Request.t, init: Request.requestInit) => + Worker.fetchWithRequest(Worker.current, request, ~init) + +let text = response => Response.text(response) diff --git a/tests/browser/websocket-bindings.spec.mjs b/tests/browser/websocket-bindings.spec.mjs new file mode 100644 index 00000000..2dbf6105 --- /dev/null +++ b/tests/browser/websocket-bindings.spec.mjs @@ -0,0 +1,62 @@ +import { expect, test } from "@playwright/test"; +import { spawn } from "node:child_process"; +import { once } from "node:events"; + +const startWebSocketServer = () => + new Promise((resolve, reject) => { + const child = spawn(process.execPath, ["tests/browser/websocket-server.mjs"], { + stdio: ["ignore", "pipe", "inherit"], + }); + let output = ""; + child.stdout.setEncoding("utf8"); + child.stdout.on("data", (chunk) => { + output += chunk; + const port = Number(output.trim()); + if (Number.isInteger(port) && port > 0) { + resolve({ child, port }); + } + }); + child.once("error", reject); + child.once("exit", (code) => { + if (output.length === 0) { + reject(new Error(`WebSocket server exited before startup with code ${code}`)); + } + }); + }); + +test("WebSocket binding sends text, ArrayBuffer, and Blob payloads through a native local socket", async ({ + page, +}) => { + const server = await startWebSocketServer(); + try { + await page.goto("http://127.0.0.1:4173/tests/browser/index.html"); + const received = await page.evaluate(async (port) => { + const bindings = await import("/tests/browser/WebSocketBindings.js"); + return new Promise((resolve, reject) => { + const socket = bindings.connect(`ws://127.0.0.1:${port}/binding-echo`); + const payloads = []; + socket.binaryType = "arraybuffer"; + socket.addEventListener("open", () => { + bindings.sendString(socket, "text"); + bindings.sendArrayBuffer(socket, new Uint8Array([65, 66]).buffer); + bindings.sendBlob(socket, new Blob(["CD"])); + }); + socket.addEventListener("message", ({ data }) => { + payloads.push( + typeof data === "string" ? data : Array.from(new Uint8Array(data)).join(","), + ); + if (payloads.length === 3) { + socket.close(); + resolve(payloads); + } + }); + socket.addEventListener("error", () => reject(new Error("WebSocket echo failed"))); + }); + }, server.port); + + expect(received).toEqual(["text", "65,66", "67,68"]); + } finally { + server.child.kill(); + await once(server.child, "exit"); + } +}); diff --git a/tests/browser/websocket-server.mjs b/tests/browser/websocket-server.mjs new file mode 100644 index 00000000..0ec32460 --- /dev/null +++ b/tests/browser/websocket-server.mjs @@ -0,0 +1,18 @@ +import { createServer } from "node:http"; +import { WebSocketServer } from "ws"; + +const server = createServer(); +const webSocketServer = new WebSocketServer({ server }); + +webSocketServer.on("connection", (socket) => { + socket.on("message", (message, isBinary) => { + socket.send(message, { binary: isBinary }); + }); +}); + +server.listen(0, "127.0.0.1", () => { + const address = server.address(); + if (address !== null && typeof address !== "string") { + process.stdout.write(`${address.port}\n`); + } +}); diff --git a/tests/browser/worker-fetch.mjs b/tests/browser/worker-fetch.mjs new file mode 100644 index 00000000..0f8765ab --- /dev/null +++ b/tests/browser/worker-fetch.mjs @@ -0,0 +1,15 @@ +import * as WorkerFetchBinding from "./WorkerFetchBinding.js"; + +self.addEventListener("message", async (event) => { + try { + const request = new Request(event.data); + const response = await WorkerFetchBinding.fetchWithRequest(request, { + method: "POST", + headers: { "X-Binding-Contracts": "forwarded" }, + body: "binding-request-body", + }); + self.postMessage(await WorkerFetchBinding.text(response)); + } catch (error) { + self.postMessage({ error: String(error) }); + } +}); diff --git a/tests/browser/worker-fetch.spec.mjs b/tests/browser/worker-fetch.spec.mjs new file mode 100644 index 00000000..cbd37996 --- /dev/null +++ b/tests/browser/worker-fetch.spec.mjs @@ -0,0 +1,61 @@ +import { spawn } from "node:child_process"; +import { once } from "node:events"; +import { expect, test } from "@playwright/test"; + +const startWorkerHttpServer = () => + new Promise((resolve, reject) => { + const child = spawn(process.execPath, ["tests/browser/worker-http-server.mjs"], { + stdio: ["ignore", "pipe", "inherit"], + }); + let output = ""; + child.stdout.setEncoding("utf8"); + child.stdout.on("data", (chunk) => { + output += chunk; + const port = Number(output.trim()); + if (Number.isInteger(port) && port > 0) { + resolve({ child, port }); + } + }); + child.once("error", reject); + child.once("exit", (code) => { + if (output.length === 0) { + reject(new Error(`Worker HTTP server exited before startup with code ${code}`)); + } + }); + }); + +test("Worker Request fetch forwards its receiver, request, and initializer", async ({ page }) => { + const server = await startWorkerHttpServer(); + try { + await page.goto("http://127.0.0.1:4173/tests/browser/index.html"); + const response = await page.evaluate( + (port) => + new Promise((resolve, reject) => { + const worker = new Worker("/tests/browser/worker-fetch.mjs", { type: "module" }); + worker.addEventListener( + "message", + (event) => { + worker.terminate(); + resolve(event.data); + }, + { once: true }, + ); + worker.addEventListener( + "error", + (event) => { + worker.terminate(); + reject(new Error(event.message)); + }, + { once: true }, + ); + worker.postMessage(`http://127.0.0.1:${port}/echo`); + }), + server.port, + ); + + expect(response).toBe("POST|forwarded|binding-request-body"); + } finally { + server.child.kill(); + await once(server.child, "exit"); + } +}); diff --git a/tests/browser/worker-http-server.mjs b/tests/browser/worker-http-server.mjs new file mode 100644 index 00000000..d0a52069 --- /dev/null +++ b/tests/browser/worker-http-server.mjs @@ -0,0 +1,27 @@ +import { createServer } from "node:http"; + +const server = createServer(async (request, response) => { + response.setHeader("access-control-allow-origin", "*"); + response.setHeader("access-control-allow-headers", "content-type, x-binding-contracts"); + response.setHeader("access-control-allow-methods", "POST, OPTIONS"); + if (request.method === "OPTIONS") { + response.writeHead(204).end(); + return; + } + + const body = []; + for await (const chunk of request) { + body.push(chunk); + } + response.writeHead(200, { "content-type": "text/plain" }); + response.end( + `${request.method}|${request.headers["x-binding-contracts"] ?? ""}|${Buffer.concat(body).toString()}`, + ); +}); + +server.listen(0, "127.0.0.1", () => { + const address = server.address(); + if (address !== null && typeof address !== "string") { + process.stdout.write(`${address.port}\n`); + } +}); diff --git a/tests/fixtures.json b/tests/fixtures.json index a9d90487..f066adb1 100644 --- a/tests/fixtures.json +++ b/tests/fixtures.json @@ -1,6 +1,8 @@ { "compileOnly": [ "BindingAbsence__test.res", + "BindingByteTypes__test.res", + "browser/WorkerFetchBinding.res", "CanvasAPI/Path2D__test.res", "CSSFontLoadingAPI/FontFace__test.res", "DOMAPI/AddEventListener__test.res", @@ -29,6 +31,7 @@ "ServiceWorkerAPI/ServiceWorker__test.res", "WebAudioAPI/AudioDestinationNode__.test.res", "WebAudioAPI/OfflineAudioContext__test.res", + "browser/WebSocketBindings.res", "WebSocketsAPI/WebSocket__test.res", "WebStorageAPI/Storage__test.res", "WebWorkersAPI/SharedWorkerScope__test.res", @@ -44,5 +47,5 @@ "FetchAPI/URLSearchParams__test.res", "URLAPI/URL__test.res" ], - "browserRuntime": ["browser/NativeAPI__test.res"] + "browserRuntime": ["browser/BindingContracts__test.res", "browser/NativeAPI__test.res"] }