Skip to content

Latest commit

 

History

History

day-068

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

Day 68: Project 19: JavaScript Injection, Part Two

Follow along at https://www.hackingwithswift.com/100/68.

📒 Field Notes

This day covers the second part of Project 19: JavaScript Injection in Hacking with Swift.

I previously created projects alongside Hacking with Swift in a separate repository, and you can find Project 19 here. Even better, though, I copied it over to Day 67's folder so I could extend it for 100 Days of Swift.

With that in mind, Day 68 focuses on several specific topics:

  • Establishing communication
  • Finalizing our Extension Action

Establishing Communication

For Safari to process our extension's JavaScript, it needs to follow a few rules:

  • Define a custom JavaScript class.
  • Have this class implement a run function.
  • Create a global object (done in JavaScript with the var keyword) that’s named ExtensionPreprocessingJS.
  • Assign a new instance of our custom JavaScript class to the ExtensionPreprocessingJS object.

I was able to find Apple’s official documentation for how all of this is supposed to work here (see the “Accessing a Webpage” section), and then implement the following:

class ExtensionAction {
  run(params) {
    /**
     * Pass data to our extension when the script is run on the page.
     *
     * Here, we'll pass:
     *   - the URL of the current page,
     *   - the page title
     */
    params.completionFunction({
      URL: document.URL,
      title: document.title
    });
  }

  /**
   * Handle anything passed back from an extension when it's runtime completes
   */
  finalize(params) {
    eval(params.userJavaScript);
  }
};


var ExtensionPreprocessingJS = new ExtensionAction();

Finalizing our Extension Action

The extensionContext gives us a completeRequest method where we can return NSExtensionItems to Safari. We'll return an item with an "attachment"... which is an NSItemProvider... which contains a NSDictionary... which contains an item keyed by NSExtensionJavaScriptFinalizeArgumentKey.

This item is also an NSDictionary — and that's what will be passed to our JavaScript action class's finalize method:

navigationItem.rightBarButtonItem = UIBarButtonItem(
    barButtonSystemItem: .done, target: self, action: #selector(extensionCompleted)
)
@IBAction func extensionCompleted() {
    // Return any edited content to the host app.
    // This template doesn't do anything, so we just echo the passed in items.
    extensionContext!.completeRequest(
        returningItems: [userJavaScriptExtensionItem],
        completionHandler: nil
    )
}
var userJavaScriptExtensionItem: NSExtensionItem {
    let argument: NSDictionary = ["userJavaScript": scriptTextView.text]

    // 🔑 This is what will be sent as the argument to our script's `finalize` function
    let webDictionary: NSDictionary = [NSExtensionJavaScriptFinalizeArgumentKey: argument]

    let customJSProvider = NSItemProvider(
        item: webDictionary,
        typeIdentifier: kUTTypePropertyList as String
    )

    let extensionItem = NSExtensionItem()
    extensionItem.attachments = [customJSProvider]

    return extensionItem
}