Skip to content

Commit b8fa64c

Browse files
committed
project init
1 parent 806b285 commit b8fa64c

File tree

409 files changed

+23053
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

409 files changed

+23053
-0
lines changed

BridgeKit.podspec

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#
2+
# Be sure to run `pod lib lint BridgeKit.podspec' to ensure this is a
3+
# valid spec before submitting.
4+
#
5+
# Any lines starting with a # are optional, but their use is encouraged
6+
# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html
7+
#
8+
9+
Pod::Spec.new do |s|
10+
s.name = 'BridgeKit'
11+
s.version = '0.1.0'
12+
s.summary = 'Simplifying Communication Between Your App and Webviews'
13+
14+
# This description is used to generate tags and improve search results.
15+
# * Think: What does it do? Why did you write it? What is the focus?
16+
# * Try to keep it short, snappy and to the point.
17+
# * Write the description between the DESC delimiters below.
18+
# * Finally, don't worry about the indent, CocoaPods strips it!
19+
20+
s.description = <<-DESC
21+
s.swift_versions = ['5.0']
22+
23+
**What is BridgeKit?**
24+
25+
BridgeKit is a lightweight Swift library designed to simplify communication between your native iOS app and JavaScript code running within a `WKWebView`. It provides a clean and intuitive API for sending and receiving messages, enabling seamless interaction between your native and web content.
26+
27+
**Why use BridgeKit?**
28+
29+
* **Simplified Message Handling:** BridgeKit abstracts away the complexities of `WKWebView` communication, providing a streamlined approach to sending and receiving messages.
30+
* **Flexible Data Exchange:** Send and receive complex data structures (using `Codable` objects) between your app and the webview.
31+
* **Improved Maintainability:** Separate message handling logic from your view code for better organization and easier maintenance.
32+
33+
DESC
34+
35+
s.homepage = 'https://github.com/aybarska/BridgeKit'
36+
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
37+
s.license = { :type => 'MIT', :file => 'LICENSE' }
38+
s.author = { 'Ayberk Mogol' => '[email protected]' }
39+
s.source = { :git => 'https://github.com/aybarska/BridgeKit.git', :tag => s.version.to_s }
40+
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
41+
42+
s.ios.deployment_target = '13.0'
43+
44+
s.source_files = 'BridgeKit/Classes/**/*'
45+
46+
# s.resource_bundles = {
47+
# 'BridgeKit' => ['BridgeKit/Assets/*.png']
48+
# }
49+
50+
# s.public_header_files = 'Pod/Classes/**/*.h'
51+
# s.frameworks = 'UIKit', 'MapKit'
52+
# s.dependency 'AFNetworking', '~> 2.3'
53+
end

BridgeKit/Assets/.gitkeep

Whitespace-only changes.

BridgeKit/Classes/.gitkeep

Whitespace-only changes.

BridgeKit/Classes/BridgeKit.swift

+199
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import Foundation
2+
import WebKit
3+
4+
public protocol JavaScriptEvaluation {
5+
func evaluateJavaScript(
6+
_ javaScriptString: String,
7+
completionHandler: (@MainActor @Sendable (Any?, (any Error)?) -> Void)?
8+
)
9+
}
10+
11+
extension WKWebView: JavaScriptEvaluation {}
12+
13+
private extension Encodable {
14+
func jsonData() throws -> Data {
15+
return try JSONEncoder().encode(self)
16+
}
17+
18+
func jsonObject() throws -> Any {
19+
return try jsonData().jsonObject()
20+
}
21+
}
22+
23+
private extension Data {
24+
func jsonObject() throws -> Any {
25+
return try JSONSerialization.jsonObject(with: self, options: [])
26+
}
27+
28+
func jsonString() throws -> String {
29+
guard let jsonString = String(data: self, encoding: .utf8) else {
30+
throw BridgeKit.BridgeError.encodingError(message: "Could not convert data to UTF-8 string")
31+
}
32+
return jsonString
33+
}
34+
}
35+
36+
private extension Decodable {
37+
static func from(jsonObject: Any) -> Self? {
38+
guard let data = try? JSONSerialization.data(withJSONObject: jsonObject, options: []),
39+
let decodedValue = try? JSONDecoder().decode(Self.self, from: data) else {
40+
return nil
41+
}
42+
return decodedValue
43+
}
44+
}
45+
46+
public protocol Topic {
47+
var name: String { get }
48+
}
49+
50+
struct AnyTopic: Topic {
51+
let name: String
52+
}
53+
54+
public final class BridgeKit {
55+
private let webView: JavaScriptEvaluation
56+
private var messageHandlers: [String: (Data, BridgeKit) -> Void] = [:]
57+
58+
public init(webView: JavaScriptEvaluation) {
59+
self.webView = webView
60+
}
61+
}
62+
63+
extension BridgeKit {
64+
65+
public enum BridgeError: Error, LocalizedError {
66+
case illegalPayloadFormat(message: String)
67+
case missingField(key: String)
68+
case missingMessageHandler(topic: String)
69+
case invalidDataForHandler(topic: String, expectedType: String)
70+
case encodingError(message: String)
71+
72+
public var errorDescription: String? {
73+
switch self {
74+
case .illegalPayloadFormat(let message):
75+
return message
76+
case .missingField(let key):
77+
return "Missing field: \(key)"
78+
case .missingMessageHandler(let topic):
79+
return "Missing message handler for topic: \(topic)"
80+
case .invalidDataForHandler(let topic, let expectedType):
81+
return "Invalid data for topic '\(topic)'. Expected type: '\(expectedType)'"
82+
case .encodingError(let message):
83+
return message
84+
}
85+
}
86+
87+
var errorCode: Int { // This part remains the same
88+
switch self {
89+
case .illegalPayloadFormat: return 1
90+
case .missingField: return 2
91+
case .missingMessageHandler: return 3
92+
case .invalidDataForHandler: return 4
93+
case .encodingError: return 5
94+
}
95+
}
96+
}
97+
98+
private struct ErrorResponse: Codable {
99+
let errors: [ErrorDetail]
100+
}
101+
102+
private struct ErrorDetail: Codable {
103+
let message: String
104+
let errorCode: Int
105+
}
106+
107+
public enum BridgeResponse {
108+
case success(Any?)
109+
case failure(BridgeError)
110+
}
111+
112+
/// Sends a message to the JavaScript side.
113+
public func postMessage<T: Codable>(data: T, to topic: Topic, completion: ((BridgeResponse) -> Void)? = nil) {
114+
do {
115+
let jsonData = try data.jsonData() // Convert to Data here
116+
postMessage(data: jsonData, to: topic.name, completion: completion)
117+
} catch {
118+
completion?(.failure(.encodingError(message: error.localizedDescription)))
119+
}
120+
}
121+
122+
private func postMessage(data: Data, to topic: String, completion: ((BridgeResponse) -> Void)? = nil) {
123+
do {
124+
let jsonObject = try data.jsonObject()
125+
let payload: [String: Any] = ["topic": topic, "data": jsonObject]
126+
var payloadString = ""
127+
if #available(iOS 11.0, *) {
128+
payloadString = try JSONSerialization.data(withJSONObject: payload, options: [.sortedKeys]).jsonString()
129+
} else {
130+
payloadString = try JSONSerialization.data(withJSONObject: payload).jsonString()
131+
}
132+
postMessage(json: payloadString, completion: completion)
133+
} catch {
134+
completion?(.failure(.encodingError(message: error.localizedDescription)))
135+
}
136+
}
137+
138+
private func postMessage(json: String, completion: ((BridgeResponse) -> Void)? = nil) {
139+
let js = "window.dispatchEvent(new CustomEvent('bridgekit', {detail: \(json)}));"
140+
webView.evaluateJavaScript(js) { (result, error) in
141+
if let error = error {
142+
completion?(.failure(.encodingError(message: error.localizedDescription)))
143+
} else {
144+
completion?(.success(result))
145+
}
146+
}
147+
}
148+
149+
private func sendErrorResponse(errors: [BridgeError], forTopic topic: String? = nil) {
150+
let errorDetails = errors.map { ErrorDetail(message: $0.errorDescription ?? "", errorCode: $0.errorCode) }
151+
let errorResponse = ErrorResponse(errors: errorDetails)
152+
153+
// self.postMessage(data: errorResponse, to: topic ?? "error")
154+
let errorTopic = topic != nil ? AnyTopic(name: topic!) : AnyTopic(name: "error")
155+
postMessage(data: errorResponse, to: errorTopic)
156+
}
157+
158+
/// Registers a handler for incoming messages from JavaScript.
159+
public func registerMessageHandler<T: Codable>(for topic: Topic, handler: @escaping (T, BridgeKit) -> Void) {
160+
messageHandlers[topic.name] = { [weak self] data, bridge in
161+
guard let self = self else { return }
162+
guard let decodedData = T.from(jsonObject: try! data.jsonObject()) else { // Force-unwrap is safe here because we check for jsonObject earlier in the process.
163+
self.sendErrorResponse(errors: [.invalidDataForHandler(topic: topic.name, expectedType: String(describing: T.self))], forTopic: topic.name)
164+
return
165+
}
166+
handler(decodedData, bridge)
167+
}
168+
}
169+
170+
/// Handles incoming messages from the JavaScript side. This should be called from your WKScriptMessageHandler's userContentController(_:didReceive:) method.
171+
public func handleReceivedMessage(_ message: Any) {
172+
guard let payload = message as? [String: Any] else {
173+
sendErrorResponse(errors: [.illegalPayloadFormat(message: "Payload is not a dictionary")])
174+
return
175+
}
176+
177+
guard let topic = payload["topic"] as? String else {
178+
sendErrorResponse(errors: [.missingField(key: "topic")])
179+
return
180+
}
181+
182+
guard let data = payload["data"] else {
183+
sendErrorResponse(errors: [.missingField(key: "data")])
184+
return
185+
}
186+
187+
guard let handler = messageHandlers[topic] else {
188+
sendErrorResponse(errors: [.missingMessageHandler(topic: topic)])
189+
return
190+
}
191+
192+
do {
193+
let jsonData = try JSONSerialization.data(withJSONObject: data)
194+
handler(jsonData, self)
195+
} catch {
196+
sendErrorResponse(errors: [.encodingError(message: "Could not serialize data")])
197+
}
198+
}
199+
}

0 commit comments

Comments
 (0)