Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
5 changes: 5 additions & 0 deletions scripts/check-consumer.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
53 changes: 53 additions & 0 deletions scripts/consumer-crypto.res
Original file line number Diff line number Diff line change
@@ -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,
)
7 changes: 7 additions & 0 deletions scripts/fixtures/negative/AnalyserRejectsOrdinaryArray.res
Original file line number Diff line number Diff line change
@@ -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.])
2 changes: 2 additions & 0 deletions scripts/fixtures/negative/BroadElementHasNoClassName.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
let broadElement: WebAPI.Element.t = %raw(`document.createElement("div")`)
let _className: string = broadElement.className
2 changes: 2 additions & 0 deletions scripts/fixtures/negative/BytesAreNotArray.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
let _bytes: array<int> =
await WebAPI.Request.fromURL("https://example.invalid/")->WebAPI.Request.bytes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
let canvas: WebAPI.HTMLCanvasElement.t = %raw(`document.createElement("canvas")`)
WebAPI.HTMLCanvasElement.toBlob(canvas, ~callback=(_blob: WebAPI.Blob.t) => ())
2 changes: 2 additions & 0 deletions scripts/fixtures/negative/ClosestCannotNarrow.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
let element: WebAPI.Element.t = %raw(`document.createElement("div")`)
let _input: Null.t<WebAPI.HTMLInputElement.t> = element->WebAPI.Element.closest("input")
Original file line number Diff line number Diff line change
@@ -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,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
let _matrix = WebAPI.DOMMatrix.fromFloat32Array([1., 0., 0., 1., 0., 0.])
10 changes: 10 additions & 0 deletions scripts/fixtures/negative/GeneratePairRejectsAlgorithm.res
Original file line number Diff line number Diff line change
@@ -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],
)
11 changes: 11 additions & 0 deletions scripts/fixtures/negative/GenerateSymmetricRejectsAlgorithm.res
Original file line number Diff line number Diff line change
@@ -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],
)
5 changes: 5 additions & 0 deletions scripts/fixtures/negative/IDBDeleteResultIsNotDatabase.res
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions scripts/fixtures/negative/JwkIsNotBinaryKeyFormat.res
Original file line number Diff line number Diff line change
@@ -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,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
let notification = WebAPI.Notification.make(~title="compile-only")
let _permission = notification.permission
15 changes: 15 additions & 0 deletions scripts/fixtures/negative/RawIsNotJwkKeyFormat.res
Original file line number Diff line number Diff line change
@@ -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,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
let _observer = WebAPI.ResizeObserver.make(_entries => _observer => ())
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@scope("globalThis")
external navigator: WebAPI.Navigator.t = "navigator"

let callback: WebAPI.WebLocksTypes.lock => promise<JSON.t> = _lock => %raw(`Promise.resolve(null)`)
let _request = navigator->WebAPI.Navigator.locks->WebAPI.LockManager.request2(
~name="compile-only",
~options={ifAvailable: true},
~callback,
)
90 changes: 90 additions & 0 deletions scripts/negative-fixtures.json
Original file line number Diff line number Diff line change
Expand Up @@ -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<WebAPI.WebLocksTypes.lock>"
},
{
"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"
}
]
2 changes: 1 addition & 1 deletion src/canvas/HTMLCanvasElement.res
Original file line number Diff line number Diff line change
Expand Up @@ -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<FileTypes.blob> => unit,
~type_: string=?,
~quality: JSON.t=?,
) => unit = "toBlob"
Expand Down
1 change: 1 addition & 0 deletions src/core/DOM.res
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/crypto/Crypto.res
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ external getRandomValues: (WebCryptoTypes.crypto, 't) => 't = "getRandomValues"
*/
@send
external randomUUID: WebCryptoTypes.crypto => string = "randomUUID"
type t = WebCryptoTypes.crypto
Loading
Loading