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

Android #1048

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open

Android #1048

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: 32 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Tests

on:
push

jobs:
macos-tests:
runs-on: macos-latest

steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup Swift
uses: SwiftyLab/setup-swift@latest

- name: Run Swift tests
run: swift test

android-tests:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Run tests
uses: skiptools/swift-android-action@v2
25 changes: 25 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"object": {
"pins": [
{
"package": "swift-asn1",
"repositoryURL": "https://github.com/apple/swift-asn1.git",
"state": {
"branch": null,
"revision": "7faebca1ea4f9aaf0cda1cef7c43aecd2311ddf6",
"version": "1.3.0"
}
},
{
"package": "swift-crypto",
"repositoryURL": "https://github.com/apple/swift-crypto.git",
"state": {
"branch": null,
"revision": "ff0f781cf7c6a22d52957e50b104f5768b50c779",
"version": "3.10.0"
}
}
]
},
"version": 1
}
44 changes: 29 additions & 15 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.3
// swift-tools-version:6.0

//
// Package.Swift
Expand All @@ -23,18 +23,32 @@
import PackageDescription

let package = Package(
name: "Starscream",
products: [
.library(name: "Starscream", targets: ["Starscream"])
],
dependencies: [],
targets: [
.target(name: "Starscream",
path: "Sources",
resources: [.copy("PrivacyInfo.xcprivacy")])
]
name: "Starscream",
platforms: [
.iOS(.v13),
.macOS(.v10_15),
.tvOS(.v13),
.watchOS(.v6)
],
products: [
.library(name: "Starscream", targets: ["Starscream"])
],
dependencies: [
.package(url: "https://github.com/apple/swift-crypto.git", from: "3.10.0"),
],
targets: [
.target(
name: "Starscream",
dependencies: [
.product(name: "Crypto", package: "swift-crypto"),
],
path: "Sources",
resources: [.copy("PrivacyInfo.xcprivacy")],
linkerSettings: [
.linkedLibrary("z")
]
),
.testTarget(name: "StarscreamTests", dependencies: [.target(name: "Starscream")])
],
swiftLanguageModes: [.v6]
)

#if os(Linux)
package.dependencies.append(.package(url: "https://github.com/apple/swift-nio-zlib-support.git", from: "1.0.0"))
#endif
4 changes: 2 additions & 2 deletions Sources/Compression/WSCompression.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ class Decompressor {
}

func decompress(_ data: Data, finish: Bool) throws -> Data {
return try data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Data in
return try data.starStreamWithUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Data in
return try decompress(bytes: bytes, count: data.count, finish: finish)
}
}
Expand Down Expand Up @@ -215,7 +215,7 @@ class Compressor {

var compressed = Data()
var res: CInt = 0
data.withUnsafeBytes { (ptr:UnsafePointer<UInt8>) -> Void in
data.starStreamWithUnsafeBytes { (ptr:UnsafePointer<UInt8>) -> Void in
strm.next_in = UnsafeMutablePointer<UInt8>(mutating: ptr)
strm.avail_in = CUnsignedInt(data.count)

Expand Down
4 changes: 2 additions & 2 deletions Sources/DataBytes/Data+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ internal extension Data {
struct ByteError: Swift.Error {}

#if swift(>=5.0)
func withUnsafeBytes<ResultType, ContentType>(_ completion: (UnsafePointer<ContentType>) throws -> ResultType) rethrows -> ResultType {
func starStreamWithUnsafeBytes<ResultType, ContentType>(_ completion: (UnsafePointer<ContentType>) throws -> ResultType) rethrows -> ResultType {
return try withUnsafeBytes {
if let baseAddress = $0.baseAddress, $0.count > 0 {
return try completion(baseAddress.assumingMemoryBound(to: ContentType.self))
Expand All @@ -40,7 +40,7 @@ internal extension Data {
#endif

#if swift(>=5.0)
mutating func withUnsafeMutableBytes<ResultType, ContentType>(_ completion: (UnsafeMutablePointer<ContentType>) throws -> ResultType) rethrows -> ResultType {
mutating func starStreamWithUnsafeMutableBytes<ResultType, ContentType>(_ completion: (UnsafeMutablePointer<ContentType>) throws -> ResultType) rethrows -> ResultType {
return try withUnsafeMutableBytes {
if let baseAddress = $0.baseAddress, $0.count > 0 {
return try completion(baseAddress.assumingMemoryBound(to: ContentType.self))
Expand Down
7 changes: 5 additions & 2 deletions Sources/Engine/Engine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
//////////////////////////////////////////////////////////////////////////////////////////////////

import Foundation
#if os(Android)
import FoundationNetworking
#endif

public protocol EngineDelegate: AnyObject {
func didReceive(event: WebSocketEvent)
Expand All @@ -31,6 +34,6 @@ public protocol Engine {
func start(request: URLRequest)
func stop(closeCode: UInt16)
func forceStop()
func write(data: Data, opcode: FrameOpCode, completion: (() -> ())?)
func write(string: String, completion: (() -> ())?)
func write(data: Data, opcode: FrameOpCode) async throws
func write(string: String) async throws
}
71 changes: 38 additions & 33 deletions Sources/Engine/NativeEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,79 +21,84 @@
//////////////////////////////////////////////////////////////////////////////////////////////////

import Foundation
#if os(Android)
import FoundationNetworking
#endif

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public class NativeEngine: NSObject, Engine, URLSessionDataDelegate, URLSessionWebSocketDelegate {
public class NativeEngine: NSObject, Engine, URLSessionDataDelegate, URLSessionWebSocketDelegate, @unchecked Sendable {
private var task: URLSessionWebSocketTask?
weak var delegate: EngineDelegate?

public func register(delegate: EngineDelegate) {
self.delegate = delegate
}

public func start(request: URLRequest) {
let session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil)
task = session.webSocketTask(with: request)
doRead()
task?.resume()
}

public func stop(closeCode: UInt16) {
let closeCode = URLSessionWebSocketTask.CloseCode(rawValue: Int(closeCode)) ?? .normalClosure
task?.cancel(with: closeCode, reason: nil)
}

public func forceStop() {
stop(closeCode: UInt16(URLSessionWebSocketTask.CloseCode.abnormalClosure.rawValue))
}

public func write(string: String, completion: (() -> ())?) {
task?.send(.string(string), completionHandler: { (error) in
completion?()
})

public func write(string: String) async throws {
try await task?.send(.string(string))
}

public func write(data: Data, opcode: FrameOpCode, completion: (() -> ())?) {
public func write(data: Data, opcode: FrameOpCode) async throws {
switch opcode {
case .binaryFrame:
task?.send(.data(data), completionHandler: { (error) in
completion?()
})
try await task?.send(.data(data))
case .textFrame:
let text = String(data: data, encoding: .utf8)!
write(string: text, completion: completion)
guard let text = String(data: data, encoding: .utf8) else { return }
try await write(string: text)
case .ping:
task?.sendPing(pongReceiveHandler: { (error) in
completion?()
})
return try await withCheckedThrowingContinuation { continuation in
task?.sendPing(pongReceiveHandler: { error in
if let error {
continuation.resume(throwing: error)
} else {
continuation.resume()
}
})
}
default:
break //unsupported
}
}

private func doRead() {
task?.receive { [weak self] (result) in
switch result {
case .success(let message):
Task { [weak self] in
guard let self else { return }
do {
guard let task = self.task else { return }
let message = try await task.receive()
switch message {
case .string(let string):
self?.broadcast(event: .text(string))
self.broadcast(event: .text(string))
case .data(let data):
self?.broadcast(event: .binary(data))
self.broadcast(event: .binary(data))
@unknown default:
break
}
break
case .failure(let error):
self?.broadcast(event: .error(error))
return
self.doRead() // Continue reading
} catch {
self.broadcast(event: .error(error))
}
self?.doRead()
}
}

private func broadcast(event: WebSocketEvent) {
delegate?.didReceive(event: event)
self.delegate?.didReceive(event: event)
}

public func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol protocol: String?) {
Expand Down
33 changes: 24 additions & 9 deletions Sources/Engine/WSEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
//////////////////////////////////////////////////////////////////////////////////////////////////

import Foundation
#if os(Android)
import FoundationNetworking
#endif

public class WSEngine: Engine, TransportEventClient, FramerEventClient,
FrameCollectorDelegate, HTTPHandlerDelegate {
public class WSEngine: Engine, TransportEventClient, FramerEventClient, FrameCollectorDelegate, HTTPHandlerDelegate, @unchecked Sendable {
private let transport: Transport
private let framer: Framer
private let httpHandler: HTTPHandler
Expand Down Expand Up @@ -91,10 +93,11 @@ FrameCollectorDelegate, HTTPHandlerDelegate {
var pointer = [UInt8](repeating: 0, count: capacity)
writeUint16(&pointer, offset: 0, value: closeCode)
let payload = Data(bytes: pointer, count: MemoryLayout<UInt16>.size)
write(data: payload, opcode: .connectionClose, completion: { [weak self] in
Task { [weak self] in
try await self?.write(data: payload, opcode: .connectionClose)
self?.reset()
self?.forceStop()
})
}
}

public func forceStop() {
Expand All @@ -105,12 +108,24 @@ FrameCollectorDelegate, HTTPHandlerDelegate {
transport.disconnect()
}

public func write(string: String, completion: (() -> ())?) {
public func write(string: String) async throws {
let data = string.data(using: .utf8)!
write(data: data, opcode: .textFrame, completion: completion)
try await write(data: data, opcode: .textFrame)
}

public func write(data: Data, opcode: FrameOpCode, completion: (() -> ())?) {
public func write(data: Data, opcode: FrameOpCode) async throws {
return try await withCheckedThrowingContinuation { cont in
write(data: data, opcode: opcode, completion: { error in
if let error {
cont.resume(throwing: error)
} else {
cont.resume(returning: ())
}
})
}
}

private func write(data: Data, opcode: FrameOpCode, completion: (@Sendable (Error?) -> ())?) {
writeQueue.async { [weak self] in
guard let s = self else { return }
s.mutex.wait()
Expand All @@ -128,8 +143,8 @@ FrameCollectorDelegate, HTTPHandlerDelegate {
}

let frameData = s.framer.createWriteFrame(opcode: opcode, payload: sendData, isCompressed: isCompressed)
s.transport.write(data: frameData, completion: {_ in
completion?()
s.transport.write(data: frameData, completion: { error in
completion?(error)
})
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Framer/FoundationHTTPHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
//////////////////////////////////////////////////////////////////////////////////////////////////

import Foundation
#if os(watchOS)
#if os(watchOS) || os(Android)
public typealias FoundationHTTPHandler = StringHTTPHandler
#else
public class FoundationHTTPHandler: HTTPHandler {
Expand Down
Loading