tomjohn
tomjohn

Reputation: 321

Safari App Extensions: Load HTML file on HTTPS page

I'm developing a Safari App Extension (because Safari Extensions are now officially deprecated) and I want to inject some HTML into a page via JS. But when I make a request to my safari-extension:// URL the request is made without SSL, and Safari currently blocks mixed content and does not allow any way to change that policy. So I have two questions.

  1. How can I get around this issue for my development environment?

  2. I read in the comments here that the production packaged extension (old Safari Extension) will load resources with SSL. Is this true for Safari App Extensions?

EDIT

I got an Apple Developer account, signed my extension and still no luck.

Upvotes: 1

Views: 655

Answers (1)

tomjohn
tomjohn

Reputation: 321

I use the messaging protocol safari exposes to the Safari App Extension to send the HTML as a string in a response.

Content script requests HTML template (JS)

let sendMessage = (msgObj, callback) => {
  msgObj.callbackIndex = (safariCallbackCount++).toString(); 
  let callbackKey = msgObj['type']+'_'+msgObj.callbackIndex;
  if (typeof callback === 'function')
    messageCallbacks[callbackKey] = callback;
  safari.extension.dispatchMessage(msgObj['type'], msgObj);
}
sendMessage({type: 'loadTemplate', path: path}, callback );

Note the messageCallbacks hash that stores the callback function as well as a counter used for index uniqueness. You are passing messaged to a Swift process and the responses can come back out of order when sending multiple messages.

SFSafariExtensionHandler listens for messages and handles (swift)

func loadTemplate (withPage page: SFSafariPage, withUrl url: URL, withCallbackIndex callbackIndex: String) {
    let pathExtention = url.pathExtension
    let pathPrefix = url.path.replacingOccurrences(of: "." + pathExtention, with: "", options: .literal, range: nil)
    if let filepath = Bundle.main.path(forResource: pathPrefix, ofType: pathExtention) {
        do {
            let contents = try String(contentsOfFile: filepath, encoding: .utf8)
            page.dispatchMessageToScript(withName: "loadTemplate", userInfo: ["htmlData": contents, "callbackIndex": callbackIndex])
        } catch {
            // contents could not be loaded
            NSLog("ERROR CONTENTS COULD NOT BE LOADED FROM \(filepath)")
        }
    } else {
        NSLog("Could not build file path")
    }
}
        // This method will be called when a content script provided by your extension calls safari.extension.dispatchMessage("message").
override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String : Any]?) {
    page.getPropertiesWithCompletionHandler { properties in
        switch messageName {
        case "loadTemplate":
            self.loadTemplate(withPage: page, withUrl: URL(string: userInfo?["path"] as! String)!, withCallbackIndex: userInfo?["callbackIndex"] as! String)
        default:
            NSLog("NO DEFINITION FOR STRING VALUE")
        }
    }
}

Content script handles response (JS)

let handleMessage = event => {
  let callbackIndex = event.message.callbackIndex;
  delete event.message.callbackIndex;
  let callbackKey = event.name+'_'+callbackIndex;
  if ( typeof messageCallbacks[callbackKey] === 'function' ) {
    messageCallbacks[callbackKey](event.message);
    delete messageCallbacks[callbackKey]
  }
};
safari.self.addEventListener("message", handleMessage);
//// html data is accessible from event.message.htmlData

Upvotes: 1

Related Questions