Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop #2

Merged
merged 11 commits into from
Nov 3, 2023
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import PackageDescription

let package = Package(
name: "ECMASwift",
platforms: [.macOS(.v10_15), .iOS(.v13)],
platforms: [.macOS(.v11), .iOS(.v13)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
Expand Down
79 changes: 79 additions & 0 deletions Sources/ECMASwift/API/Blob/Blob.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import Foundation
import JavaScriptCore

@objc
protocol BlobExports: JSExport {
func text() -> JSValue?
func arrayBuffer() -> JSValue
}

/// This implmenets the `Blob` browser API.
///
/// Reference: [Blob Reference on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Blob)
final class Blob: NSObject, BlobExports {
var content: String

weak var context: JSContext?

init(content: String) {
self.content = content
}

func text() -> JSValue? {
guard let context = context else {
fatalError("JSContext is nil")
}

return JSValue(newPromiseIn: context) { resolve, _ in
let blobObject = JSValue(object: self.content, in: context)!
resolve?.call(withArguments: [blobObject])
}
}

func arrayBuffer() -> JSValue {
return JSValue(newPromiseIn: context) { [weak self] resolve, reject in
guard let data = self?.content.data(using: .utf8) else {
let errorDescription = "Failed to convert blob content to ArrayBuffer"
reject?.call(withArguments: [errorDescription])
return
}

// Convert Data to [UInt8]
let byteArray = [UInt8](data)

// Convert [UInt8] to JavaScript ArrayBuffer
let jsArrayBufferConstructor = self?.context?.evaluateScript("ArrayBuffer")
let jsUint8ArrayConstructor = self?.context?.evaluateScript("Uint8Array")
guard let arrayBuffer = jsArrayBufferConstructor?.construct(withArguments: [byteArray.count]),
let uint8Array = jsUint8ArrayConstructor?.construct(withArguments: [arrayBuffer])
else {
let errorDescription = "Failed to create ArrayBuffer"
reject?.call(withArguments: [errorDescription])
return
}

// Set bytes to ArrayBuffer
for (index, byte) in byteArray.enumerated() {
uint8Array.setValue(byte, at: index)
}

resolve?.call(withArguments: [arrayBuffer])
}
}
}

/// Helper to register the ``Blob`` API with a context.
struct BlobAPI {
func registerAPIInto(context: JSContext) {
let blobClass: @convention(block) (String) -> Blob = { text in
let blob = Blob(content: text)
blob.context = context
return blob
}

context.setObject(
unsafeBitCast(blobClass, to: AnyObject.self),
forKeyedSubscript: "Blob" as NSString
)
}
}
17 changes: 12 additions & 5 deletions Sources/ECMASwift/API/Console/Console.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import JavaScriptCore
import os

@objc protocol ConsoleExports: JSExport {
static func log(_ msg: String)
Expand All @@ -7,24 +8,30 @@ import JavaScriptCore
static func error(_ msg: String)
}

class Console: NSObject, ConsoleExports {
/// This implmenets the `Console` browser API.
///
/// Reference: [Console Reference on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Console)
final class Console: NSObject, ConsoleExports {
static let logger = Logger(subsystem: "JSRuntime", category: "Console")

public class func log(_ msg: String) {
print(msg)
logger.log("\(msg)")
}

public class func info(_ msg: String) {
print(msg)
logger.info("\(msg)")
}

public class func warn(_ msg: String) {
print(msg)
logger.warning("\(msg)")
}

public class func error(_ msg: String) {
print(msg)
logger.error("\(msg)")
}
}

/// Helper to register the ``Console`` API with a context.
public struct ConsoleAPI {
public func registerAPIInto(context: JSContext) {
context.setObject(
Expand Down
36 changes: 20 additions & 16 deletions Sources/ECMASwift/API/Crypto/Crypto.swift
Original file line number Diff line number Diff line change
@@ -1,40 +1,44 @@
import Foundation
import JavaScriptCore
import CommonCrypto
import JavaScriptCore

@objc protocol CryptoExports: JSExport {
func getRandomValues(_ array: [UInt32]) -> [UInt32]
func getRandomValues(_ array: [UInt]) -> [UInt]
func randomUUID() -> String

var subtle: SubtleCryptoExports { get }
}

@objc class Crypto: NSObject, CryptoExports {
func getRandomValues(_ array: [UInt32]) -> [UInt32] {
// Calculate the size of the buffer needed (in bytes).
let size = array.count * MemoryLayout<UInt32>.size

// Create an empty buffer of appropriate size.
/// This implmenets the `Crypto` browser API.
///
/// Reference: [Crypto Reference on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Crypto)
@objc final class Crypto: NSObject, CryptoExports {
func getRandomValues(_ array: [UInt]) -> [UInt] {
let size = array.count * MemoryLayout<UInt>.size

var buffer = [UInt8](repeating: 0, count: size)

// Fill the buffer with secure random bytes.

let result = SecRandomCopyBytes(kSecRandomDefault, size, &buffer)

if result == errSecSuccess {
return stride(from: 0, to: buffer.count, by: MemoryLayout<UInt32>.size).map { i in
return buffer.withUnsafeBytes { ptr -> UInt32 in
let base = ptr.baseAddress!.assumingMemoryBound(to: UInt32.self)
return base[i / MemoryLayout<UInt32>.size]
return stride(from: 0, to: buffer.count, by: MemoryLayout<UInt>.size).map { i in
buffer.withUnsafeBytes { ptr -> UInt in
let base = ptr.baseAddress!.assumingMemoryBound(to: UInt.self)
return base[i / MemoryLayout<UInt>.size]
}
}
} else {
return []
}
}

func randomUUID() -> String {
return UUID().uuidString
}

lazy var subtle: SubtleCryptoExports = SubtleCrypto()
}

/// Helper to register the ``Crypto`` API with a context.
struct CryptoAPI {
public func registerAPIInto(context: JSContext) {
context.setObject(
Expand Down
72 changes: 72 additions & 0 deletions Sources/ECMASwift/API/Crypto/SublteCrypto.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import CommonCrypto
import JavaScriptCore

@objc protocol SubtleCryptoExports: JSExport {
func digest(_ algorithm: String, _ data: [UInt8]) -> [UInt8]?
func encrypt(_ algorithm: String, _ key: [UInt8], _ iv: [UInt8], _ data: [UInt8]) -> [UInt8]?
func decrypt(_ algorithm: String, _ key: [UInt8], _ iv: [UInt8], _ data: [UInt8]) -> [UInt8]?
}

/// This implmenets the `SubtleCrypto` browser API.
///
/// Reference: [SubtleCrypto Reference on MDN](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto)
@objc final class SubtleCrypto: NSObject, SubtleCryptoExports {
func digest(_ algorithm: String, _ data: [UInt8]) -> [UInt8]? {
guard algorithm == "SHA-256" else { return nil }
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
_ = data.withUnsafeBytes {
CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
}
return hash
}

func encrypt(_ algorithm: String, _ key: [UInt8], _ iv: [UInt8], _ data: [UInt8]) -> [UInt8]? {
guard algorithm == "AES-GCM", key.count == kCCKeySizeAES128 else { return nil }
var buffer = [UInt8](repeating: 0, count: data.count + kCCBlockSizeAES128)
var numBytesEncrypted: size_t = 0

let cryptStatus = CCCrypt(
CCOperation(kCCEncrypt),
CCAlgorithm(kCCAlgorithmAES),
CCOptions(kCCOptionPKCS7Padding),
key,
key.count,
iv,
data,
data.count,
&buffer,
buffer.count,
&numBytesEncrypted
)

if cryptStatus == kCCSuccess {
return Array(buffer.prefix(numBytesEncrypted))
}
return nil
}

func decrypt(_ algorithm: String, _ key: [UInt8], _ iv: [UInt8], _ data: [UInt8]) -> [UInt8]? {
guard algorithm == "AES-GCM", key.count == kCCKeySizeAES128 else { return nil }
var buffer = [UInt8](repeating: 0, count: data.count + kCCBlockSizeAES128)
var numBytesDecrypted: size_t = 0

let cryptStatus = CCCrypt(
CCOperation(kCCDecrypt),
CCAlgorithm(kCCAlgorithmAES),
CCOptions(kCCOptionPKCS7Padding),
key,
key.count,
iv,
data,
data.count,
&buffer,
buffer.count,
&numBytesDecrypted
)

if cryptStatus == kCCSuccess {
return Array(buffer.prefix(numBytesDecrypted))
}
return nil
}
}
55 changes: 55 additions & 0 deletions Sources/ECMASwift/API/Fetch/AbortController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import JavaScriptCore

@objc
protocol AbortSignalExports: JSExport {
var aborted: Bool { get set }
}

class AbortSignal: NSObject, AbortSignalExports {
private var _aborted: Bool = false
var aborted: Bool {
get { return _aborted }
set {
_aborted = newValue
if newValue == true {
self.onAbort?()
}
}
}

var onAbort: (() -> Void)?
}

@objc
protocol AbortControllerExports: JSExport {
var signal: AbortSignal { get set }
func abort()
}

class AbortController: NSObject, AbortControllerExports {
var signal = AbortSignal()

func abort() {
signal.aborted = true
}
}

struct AbortControllerAPI {
func registerAPIInto(context: JSContext) {
let abortControllerClass: @convention(block) () -> AbortController = {
AbortController()
}
let abortSignalClass: @convention(block) () -> AbortSignal = {
AbortSignal()
}

context.setObject(
abortSignalClass,
forKeyedSubscript: "AbortSignal" as NSString
)
context.setObject(
abortControllerClass,
forKeyedSubscript: "AbortController" as NSString
)
}
}
Loading