Skip to content

Commit

Permalink
Merge pull request #536 from player-ui/enhance-beacon-plugin-ios
Browse files Browse the repository at this point in the history
iOS: Expose builldBeacon and cancelBeacon hooks inside of BaseBeaconPlugin and make BeaconPlugin open class
  • Loading branch information
sakuntala-motukuri authored Nov 20, 2024
2 parents 35f98d7 + cdb07d6 commit cb18aac
Show file tree
Hide file tree
Showing 5 changed files with 250 additions and 127 deletions.
60 changes: 30 additions & 30 deletions ios/core/Sources/Types/Hooks/Hook.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ import JavaScriptCore
/// A base for implementing JS backed hooks
open class BaseJSHook {
private let baseValue: JSValue

/// The JS reference to the hook
public var hook: JSValue { baseValue.objectForKeyedSubscript("hooks").objectForKeyedSubscript(name) }

/// The JSContext for the hook
public var context: JSContext { hook.context }

/// The name of the hook
public let name: String

/// Retrieves a hook by name from an object in JS
/// - Parameters:
/// - baseValue: The object that has `hooks`
Expand All @@ -39,7 +39,7 @@ public class Hook<T>: BaseJSHook where T: CreatedFromJSValue {
/**
Attach a closure to the hook, so when the hook is fired in the JS runtime
we receive the event in the native runtime

- parameters:
- hook: A function to run when the JS hook is fired
*/
Expand All @@ -51,7 +51,7 @@ public class Hook<T>: BaseJSHook where T: CreatedFromJSValue {
else { return }
hook(hookValue)
}

self.hook.invokeMethod("tap", withArguments: [name, JSValue(object: tapMethod, in: context) as Any])
}
}
Expand All @@ -64,21 +64,21 @@ public class Hook2<T, U>: BaseJSHook where T: CreatedFromJSValue, U: CreatedFrom
/**
Attach a closure to the hook, so when the hook is fired in the JS runtime
we receive the event in the native runtime

- parameters:
- hook: A function to run when the JS hook is fired
*/
public func tap(_ hook: @escaping (T, U) -> Void) {
let tapMethod: @convention(block) (JSValue?, JSValue?) -> Void = { value, value2 in
public func tap<R>(_ hook: @escaping (T, U) -> R) {
let tapMethod: @convention(block) (JSValue?, JSValue?) -> Any? = { value, value2 in
guard
let val = value,
let val2 = value2,
let hookValue = T.createInstance(value: val) as? T,
let hookValue2 = U.createInstance(value: val2) as? U
else { return }
hook(hookValue, hookValue2)
else { return nil }
return hook(hookValue, hookValue2)
}

self.hook.invokeMethod("tap", withArguments: [name, JSValue(object: tapMethod, in: context) as Any])
}
}
Expand All @@ -91,7 +91,7 @@ public class HookDecode<T>: BaseJSHook where T: Decodable {
/**
Attach a closure to the hook, so when the hook is fired in the JS runtime
we receive the event in the native runtime

- parameters:
- hook: A function to run when the JS hook is fired
*/
Expand All @@ -103,7 +103,7 @@ public class HookDecode<T>: BaseJSHook where T: Decodable {
else { return }
hook(hookValue)
}

self.hook.invokeMethod("tap", withArguments: [name, JSValue(object: tapMethod, in: context) as Any])
}
}
Expand All @@ -116,13 +116,13 @@ public class Hook2Decode<T, U>: BaseJSHook where T: Decodable, U: Decodable {
/**
Attach a closure to the hook, so when the hook is fired in the JS runtime
we receive the event in the native runtime

- parameters:
- hook: A function to run when the JS hook is fired
*/
public func tap(_ hook: @escaping (T, U) -> Void) {
let tapMethod: @convention(block) (JSValue?, JSValue?) -> Void = { value, value2 in

let decoder = JSONDecoder()
guard
let val = value,
Expand All @@ -132,7 +132,7 @@ public class Hook2Decode<T, U>: BaseJSHook where T: Decodable, U: Decodable {
else { return }
hook(hookValue, hookValue2)
}

self.hook.invokeMethod("tap", withArguments: [name, JSValue(object: tapMethod, in: context) as Any])
}
}
Expand All @@ -143,13 +143,13 @@ public class Hook2Decode<T, U>: BaseJSHook where T: Decodable, U: Decodable {
*/
public class AsyncHook<T>: BaseJSHook where T: CreatedFromJSValue {
private var handler: AsyncHookHandler?

public typealias AsyncHookHandler = (T) async throws -> JSValue?

/**
Attach a closure to the hook, so when the hook is fired in the JS runtime
we receive the event in the native runtime

- parameters:
- hook: A function to run when the JS hook is fired
*/
Expand All @@ -159,7 +159,7 @@ public class AsyncHook<T>: BaseJSHook where T: CreatedFromJSValue {
let val = value,
let hookValue = T.createInstance(value: val) as? T
else { return JSValue() }

let promise =
JSUtilities.createPromise(context: self.context, handler: { (resolve, _) in
Task {
Expand All @@ -169,10 +169,10 @@ public class AsyncHook<T>: BaseJSHook where T: CreatedFromJSValue {
}
}
})

return promise ?? JSValue()
}

self.hook.invokeMethod("tap", withArguments: [name, JSValue(object: tapMethod, in: context) as Any])
}
}
Expand All @@ -184,13 +184,13 @@ public class AsyncHook<T>: BaseJSHook where T: CreatedFromJSValue {
*/
public class AsyncHook2<T, U>: BaseJSHook where T: CreatedFromJSValue, U: CreatedFromJSValue {
private var handler: AsyncHookHandler?

public typealias AsyncHookHandler = (T, U) async throws -> JSValue?

/**
Attach a closure to the hook, so when the hook is fired in the JS runtime
we receive the event in the native runtime

- parameters:
- hook: A function to run when the JS hook is fired
*/
Expand All @@ -202,8 +202,8 @@ public class AsyncHook2<T, U>: BaseJSHook where T: CreatedFromJSValue, U: Create
let hookValue = T.createInstance(value: val) as? T,
let hookValue2 = U.createInstance(value: val2) as? U
else { return JSValue() }


let promise =
JSUtilities.createPromise(context: self.context, handler: { (resolve, _) in
Task {
Expand All @@ -213,10 +213,10 @@ public class AsyncHook2<T, U>: BaseJSHook where T: CreatedFromJSValue, U: Create
}
}
})

return promise ?? JSValue()
}

self.hook.invokeMethod("tap", withArguments: [name, JSValue(object: tapMethod, in: context) as Any])
}
}
Expand Down
57 changes: 40 additions & 17 deletions plugins/beacon/ios/Sources/BaseBeaconPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,19 @@ import PlayerUI
public struct DefaultBeacon: Codable, Hashable {
/// The action the user performed
public let action: String

/// The element that performed the action
public let element: String

/// The ID of the asset triggering the beacon
public let assetId: String?

/// The ID of the view triggering the beacon
public let viewId: String?

/// Additional data added from the asset or metaData
public let data: AnyType?

/// Construct a ``DefaultBeacon``
/// - Parameters:
/// - action: The action the user performed
Expand All @@ -52,37 +52,50 @@ public struct DefaultBeacon: Codable, Hashable {
Used as a base for framework specific integrations
*/
open class BaseBeaconPlugin<BeaconStruct: Decodable>: JSBasePlugin {

public var hooks: BeaconPluginHooks?
/// The callback to call when a beacon is fired from the plugin
public var callback: ((BeaconStruct) -> Void)?

/// BeaconPluginPlugin's to use for this BeaconPlugin
public var plugins: [JSBasePlugin] = []

/**
Constructs a BeaconPlugin
- parameters:
- context: The context to load the plugin into
- onBeacon: A callback to receive beacon events
- context: The context to load the plugin into
- onBeacon: A callback to receive beacon events
*/
public convenience init(plugins: [JSBasePlugin] = [], onBeacon: ((BeaconStruct) -> Void)?) {
self.init(fileName: "BeaconPlugin.native", pluginName: "BeaconPlugin.BeaconPlugin")
self.callback = onBeacon
self.plugins = plugins
}


override open func setup(context: JSContext) {
super.setup(context: context)
guard let pluginRef = self.pluginRef else {
fatalError("pluginRef is nil after setup")
}
self.hooks = BeaconPluginHooks(
buildBeacon: AsyncHook2(baseValue: pluginRef, name: "buildBeacon"),
cancelBeacon: Hook2(baseValue: pluginRef, name: "cancelBeacon")
)
}

override open func getUrlForFile(fileName: String) -> URL? {
#if SWIFT_PACKAGE
#if SWIFT_PACKAGE
ResourceUtilities.urlForFile(name: fileName, ext: "js", bundle: Bundle.module)
#else
#else
ResourceUtilities.urlForFile(
name: fileName,
ext: "js",
bundle: Bundle(for: BaseBeaconPlugin<DefaultBeacon>.self),
pathComponent: "PlayerUI_BaseBeaconPlugin.bundle"
)
#endif
#endif
}

/**
Retrieves the arguments for constructing this plugin, this is necessary because the arguments need to be supplied after
construction of the swift object, once the context has been provided
Expand All @@ -105,12 +118,12 @@ open class BaseBeaconPlugin<BeaconStruct: Decodable>: JSBasePlugin {
let jsCallback = JSValue(object: callback, in: context) as Any
return [["callback": jsCallback, "plugins": plugins.map { $0.pluginRef }]]
}

/**
Function to send a beacon event through the plugin for processing
- parameters:
- action: The action that was taken for the beacon
- args: The context of the beacon
- action: The action that was taken for the beacon
- args: The context of the beacon
*/
public func beacon(assetBeacon: AssetBeacon) {
guard
Expand All @@ -120,4 +133,14 @@ open class BaseBeaconPlugin<BeaconStruct: Decodable>: JSBasePlugin {
else { return }
pluginRef?.invokeMethod("beacon", withArguments: [beaconObject])
}

public struct BeaconPluginHooks {
public let buildBeacon: AsyncHook2<JSValue, JSValue>
public let cancelBeacon: Hook2<JSValue, JSValue>

public init(buildBeacon: AsyncHook2<JSValue, JSValue>, cancelBeacon: Hook2<JSValue, JSValue>) {
self.buildBeacon = buildBeacon
self.cancelBeacon = cancelBeacon
}
}
}
Loading

0 comments on commit cb18aac

Please sign in to comment.