Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS Content Blocking Extension Loading Multiple JSON Files

Is it possible to return multiple JSON files from a Content Blocker Extension? In my UI users enable / disable different filters and each filter is represented by a separate file. I currently have (which only loads one despite iterating through multiple):

func beginRequestWithExtensionContext(context: NSExtensionContext) {
    var items = Array <NSExtensionItem>()

    let resources = ["a", "b", "c"]
    for resource in resources {
        let url = NSBundle.mainBundle().URLForResource(resource, withExtension: "json")

        if let attachment = NSItemProvider(contentsOfURL: url) {
            let item = NSExtensionItem()
            item.attachments = [attachment]
            items.append(item)
        }
    }
    context.completeRequestReturningItems(items, completionHandler: nil)
}

I've tried doing multiple items and a single item with multiple attachments. If it isn't possible to have separate files, any way to combine multiple (or generate programmatically)?

like image 959
Kevin Sylvestre Avatar asked Sep 15 '15 04:09

Kevin Sylvestre


3 Answers

It is possible to have multiple JSON files and use it for the Content Blocker extension.

1) Throws SFContentBlockerErrorDomain when you pass multiple extension items to completeRequestReturningItems method.

2) Can't attach multiple attachments to NSExtension. The comment on the source code says, the attachment is not meant to be an array of alternate data formats/types, but instead a collection to include in a social media post for example. These items are always typed NSItemProvider. I reckon you wouldn't be able to add multiple JSON data as attachments, since they are not a series of attachments to create a message.

My Solution (Verified it works):

NSItemProvider can be initialised with item (NSData) and typeIdentifier.

let aData = NSData(contentsOfURL: NSBundle.mainBundle().URLForResource("a", withExtension: "json")!)
let bData = NSData(contentsOfURL: NSBundle.mainBundle().URLForResource("b", withExtension: "json")!)

aJSON = `convert aData to JSON`
bJSON = `convert bData to JSON`
combinedJSON = `aJSON + bJSON`
combinedData = 'convert combinedJSON to NSData'

let attachment = NSItemProvider(item: combinedData, typeIdentifier: kUTTypeJSON as String)

Now you could create the extension with the attachment, combinedData as per your preferences.

like image 102
Hari Balamani Avatar answered Oct 18 '22 09:10

Hari Balamani


For those curious I ended up adding code to dynamically generate a JSON file (persisted to disk). From other answers it seems like the step of saving could be avoided by returning an NSData representation of the file instead - although that attempt failed for me. Here's my snippet:

import UIKit
import MobileCoreServices

class ActionRequestHandler: NSObject, NSExtensionRequestHandling {

    func beginRequestWithExtensionContext(context: NSExtensionContext) {
        let item = NSExtensionItem()
        let items = [item]

        let url = buildJSONFileURL()
        if let attachment = NSItemProvider(contentsOfURL: url) { item.attachments = [attachment] }

        context.completeRequestReturningItems(items, completionHandler: nil)
    }

    func buildJSONFileURL() -> NSURL {
        let directories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
        let directory = directories[0]

        let path = directory.stringByAppendingFormat("/block.json")

        let selector = [...] // Dynamically Generated
        let dictionary = [[
            "action": [ "type": "css-display-none", "selector": selector ],
            "trigger": [ "url-filter": ".*" ]
            ]]

        let data = try! NSJSONSerialization.dataWithJSONObject(dictionary, options: NSJSONWritingOptions.PrettyPrinted)
        let text = NSString(data: data, encoding: NSASCIIStringEncoding)!

        try! text.writeToFile(path, atomically: true, encoding: NSASCIIStringEncoding)

        return NSURL(fileURLWithPath: path)
    }

}
like image 3
Kevin Sylvestre Avatar answered Oct 18 '22 09:10

Kevin Sylvestre


You can combine two JSON rule files in to one file and use that file.

    import UIKit
    import MobileCoreServices
    class ContentBlockerRequestHandler: NSObject, NSExtensionRequestHandling {

        func beginRequest(with context: NSExtensionContext) {

        let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "you app group identifier")

                let sourceURLRules = sharedContainerURL?.appendingPathComponent("Rules1.json")
                let sourceURLRules2 = sharedContainerURL?.appendingPathComponent("Rules2.json")
                do {
                    let jsonDecoder = JSONDecoder()

                    let dataFormRules1 = try Data(contentsOf: sourceURLRules1!, options: .mappedIfSafe)// Rule is Decode able Swift class            
                   let  rulesArray1 = try? jsonDecoder.decode(Array<Rule>.self,from: dataFormRules1)

                    let dataFormRules2 = try Data(contentsOf: sourceURLRules2!, options: .mappedIfSafe)
                    let  rulesArray2 = try? jsonDecoder.decode(Array<Rule>.self,from: dataFormRules2)

                    saveCombinedRuleFile(ruleList: rulesArray1! + rulesArray2!)

                } catch {
                    //handle error condition
                }

                let sourceURLCombinedRule = sharedContainerURL?.appendingPathComponent("CombinedRule.json")
                let combinedRuleAttachment = NSItemProvider(contentsOf: sourceURLCombinedRule)
                let item = NSExtensionItem()
                item.attachments = [combinedRuleAttachment]
                context.completeRequest(returningItems: [item], completionHandler: nil)
            }

            func saveCombinedRuleFile(ruleList:[Rule]) {
                let encoder = JSONEncoder()
                if let encoded = try? encoder.encode(ruleList) {
                    let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "you app group identifier")
                    if let json = String(data: encoded, encoding: .utf8) {
                        print(json)
                    }
                    if let destinationURL = sharedContainerURL?.appendingPathComponent("CombinedRule.json") {
                        do {
                            try  encoded.write(to: destinationURL)
                        } catch {
                            print ("catchtry")
                        }
                    }
                }
            }
        }
like image 1
Imran Avatar answered Oct 18 '22 08:10

Imran