Skip to content

Latest commit

 

History

History

day-067

Day 67: Project 19: JavaScript Injection, Part One

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

📒 Field Notes

This day covers the first 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 67 focuses on several specific topics:

  • Making a shell app
  • Adding an extension: NSExtensionItem
  • What do you want to get?

Making a Shell App

Safari App Extensions have "App" in the name for a reason: Every extension ships alongside a full-fledged iOS or MacOS application that serves as a "shell".

This is important to keep in mind, because the shell has to exist even if the bulk of our code and functionality is going to be in the extension.

Adding an Extension: NSExtensionItem

  • When our extension item is created, we get access to an extensionContext, which lets us control how our extension interacts with its parent app.

  • extensionContext has an inputItems array that the parent app might send it.

  • inputItems contains a list of NSExtensionItems — each of which has an optional attachments array that contains a list of "Item providers".

  • Each item provider has a loadItem function, which takes a completion closure. It's here that we can process a dictionary of data contained in the item, and use it to update data in the context of our extension.

Making this even more intricate is the fact that most of the aforementioned values are wrapped up in Objective-C, so some additional guarding, casting, and long NSDictionary key name usage is in order:

func processItemProvider() {
    // `inputItems` should be an array of data that the parent app is sending to our extension to use
    guard let inputItem = extensionContext!.inputItems.first as? NSExtensionItem else { return }

    // Our input item contains an array of attachments, which are given to us wrapped up as an `NSItemProvider`
    guard let itemProvider = inputItem.attachments?.first else { return }

    let typeIdentifier = kUTTypePropertyList as String

    // After finding the provider, we need to ask it to actually provide us with its item
    itemProvider.loadItem(
        forTypeIdentifier: typeIdentifier,
        options: nil,
        completionHandler: { [weak self] (dict, error) in
            let itemDictionary = dict as! NSDictionary
            let javaScriptValues = itemDictionary[NSExtensionJavaScriptPreprocessingResultsKey] as! NSDictionary

            DispatchQueue.main.async {
                self?.pageURL = javaScriptValues["URL"] as! String
                self?.pageTitle = javaScriptValues["title"] as! String
            }
        }
    )
}

What do you want to get?

Demanding a similar meticulousness as item-provider loading is the process of configuring the extension's Info.plist file so that its identity, capabilities, and access limits are known to Safari. Apple has more thorough documentation on these options. For this project, we're mainly concerned about NSExtensionActivationSupportsWebPageWithMaxCount, as well as NSExtensionJavaScriptPreprocessingFile:

🔗 Additional/Related Links