[TokenScript schema 2020/03] Add error callback to setProps() to trigger when props names clash with card attributes #1808

pull/1810/head
Hwee-Boon Yar 5 years ago
parent 9ce16c34ab
commit 6feb635be8
  1. 16
      AlphaWallet/Browser/Factory/WKWebViewConfiguration.swift
  2. 59
      AlphaWallet/Tokens/Views/TokenInstanceWebView.swift

@ -146,12 +146,22 @@ extension WKWebViewConfiguration {
fileprivate static func javaScriptForTokenScriptRenderer(server: RPCServer, address: AlphaWallet.Address) -> String {
return """
window.web3CallBacks = {}
window.tokenScriptCallBacks = {}
function executeCallback (id, error, value) {
window.web3CallBacks[id](error, value)
delete window.web3CallBacks[id]
}
function executeTokenScriptCallback (id, error, value) {
let cb = window.tokenScriptCallBacks[id]
if (cb) {
window.tokenScriptCallBacks[id](error, value)
delete window.tokenScriptCallBacks[id]
} else {
}
}
web3 = {
personal: {
sign: function (msgParams, cb) {
@ -162,8 +172,10 @@ extension WKWebViewConfiguration {
}
},
action: {
setProps: function (object) {
webkit.messageHandlers.\(TokenInstanceWebView.SetProperties.setActionProps).postMessage(object)
setProps: function (object, cb) {
const id = 8888
window.tokenScriptCallBacks[id] = cb
webkit.messageHandlers.\(TokenInstanceWebView.SetProperties.setActionProps).postMessage({"object": object, id: id})
}
}
}

@ -20,7 +20,15 @@ class TokenInstanceWebView: UIView {
//Values ought to be typed. But it's just much easier to keep them as `Any` and convert them to the correct types when accessed (based on TokenScript syntax and XML tag). We don't know what those are here
typealias Properties = [String: Any]
case action(changedProperties: Properties)
case action(id: Int, changedProperties: Properties)
static func fromMessage(_ message: WKScriptMessage) -> SetProperties? {
guard message.name == SetProperties.setActionProps else { return nil }
guard var body = message.body as? [String: AnyObject] else { return nil }
guard let changedProperties = body["object"] as? SetProperties.Properties else { return nil }
guard let id = body["id"] as? Int else { return nil }
return .action(id: id, changedProperties: changedProperties)
}
}
enum BrowserMessageType {
@ -28,8 +36,8 @@ class TokenInstanceWebView: UIView {
case setActionProps(SetProperties)
static func fromMessage(_ message: WKScriptMessage) -> BrowserMessageType? {
if message.name == SetProperties.setActionProps, let changedProperties = message.body as? SetProperties.Properties {
return .setActionProps(.action(changedProperties: changedProperties))
if let action = SetProperties.fromMessage(message) {
return .setActionProps(action)
} else if let command = DappAction.fromMessage(message) {
return .dappAction(command)
}
@ -52,6 +60,8 @@ class TokenInstanceWebView: UIView {
//Used to track asynchronous calls are called for correctly
private var loadId: Int?
private var lastInjectedJavaScript: String?
//TODO remove once we refactor internals to include a TokenScriptContext
private var lastTokenHolder: TokenHolder?
var actionProperties: TokenInstanceWebView.SetProperties.Properties = .init()
private var lastActionLevelAttributeValues: [AttributeId: AssetAttributeSyntaxValue]?
@ -115,6 +125,7 @@ class TokenInstanceWebView: UIView {
//Implementation: String concatentation is slow, but it's not obvious at all
func update(withTokenHolder tokenHolder: TokenHolder, actionLevelAttributeValues updatedActionLevelAttributeValues: [AttributeId: AssetAttributeSyntaxValue]? = nil, isFungible: Bool, isFirstUpdate: Bool = true) {
lastTokenHolder = tokenHolder
let unresolvedAttributesDependentOnProps = self.unresolvedAttributesDependentOnProps(tokenHolder: tokenHolder)
let actionLevelAttributeValues = (updatedActionLevelAttributeValues ?? lastActionLevelAttributeValues) ?? .init()
@ -346,16 +357,22 @@ extension TokenInstanceWebView: WKScriptMessageHandler {
switch BrowserMessageType.fromMessage(message) {
case .some(.dappAction(let command)):
handleCommandForDappAction(command)
case .some(.setActionProps(.action(let changedProperties))):
handleSetActionProperties(changedProperties)
case .some(.setActionProps(.action(let id, let changedProperties))):
handleSetActionProperties(id: id, changedProperties: changedProperties)
case .none:
break
}
}
private func handleSetActionProperties(_ changedProperties: SetProperties.Properties) {
private func handleSetActionProperties(id: Int, changedProperties: SetProperties.Properties) {
guard !changedProperties.isEmpty else { return }
let oldProperties = actionProperties
let errorMessage = checkPropsNameClashErrorWithCardAttributes()
notifyTokenScriptFinish(callbackID: id, errorMessage: errorMessage)
guard errorMessage == nil else { return }
for (key, value) in changedProperties {
actionProperties[key] = value
}
@ -366,6 +383,25 @@ extension TokenInstanceWebView: WKScriptMessageHandler {
}
}
private func checkPropsNameClashErrorWithCardAttributes() -> String? {
guard let lastTokenHolder = lastTokenHolder else { return nil }
let xmlHandler = XMLHandler(contract: lastTokenHolder.contractAddress, assetDefinitionStore: assetDefinitionStore)
let attributes = xmlHandler.fields
let attributeIds: [AttributeId]
if let lastActionLevelAttributeValues = lastActionLevelAttributeValues {
attributeIds = Array(attributes.keys) + Array(lastActionLevelAttributeValues.keys)
} else {
attributeIds = Array(attributes.keys)
}
let propsClashed = actionProperties.keys.filter { attributeIds.contains($0) }
if propsClashed.isEmpty {
return nil
} else {
let propsListThatClashed = propsClashed.joined(separator: ", ")
return "Error in setProps() because these props clash with attribute(s): \(propsListThatClashed)"
}
}
private func handleCommandForDappAction(_ command: DappCommand) {
//limited signing capability exposed for TokenScript for now. Be careful not to expose more than we want to
switch command.name {
@ -470,6 +506,17 @@ extension TokenInstanceWebView {
}()
webView.evaluateJavaScript(script, completionHandler: nil)
}
func notifyTokenScriptFinish(callbackID: Int, errorMessage: String?) {
let script: String = {
if let errorMessage = errorMessage {
return "executeTokenScriptCallback(\(callbackID), \"\(errorMessage)\")"
} else {
return "executeTokenScriptCallback(\(callbackID), null)"
}
}()
webView.evaluateJavaScript(script, completionHandler: nil)
}
}
private func generateContainerCssId(forTokenHolder tokenHolder: TokenHolder) -> String {

Loading…
Cancel
Save