Skip to content

Commit

Permalink
Merge pull request #20 from johanblomgren/v0.5.0
Browse files Browse the repository at this point in the history
Code changes for V0.5.0
  • Loading branch information
MarcoEidinger authored May 1, 2017
2 parents 59fc276 + 4683a2a commit 27bab05
Show file tree
Hide file tree
Showing 11 changed files with 494 additions and 115 deletions.
16 changes: 16 additions & 0 deletions .jshintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"browser": true
, "devel": true
, "bitwise": true
, "undef": true
, "trailing": true
, "quotmark": false
, "indent": 4
, "unused": "vars"
, "latedef": "nofunc"
, "globals": {
"module": false,
"exports": false,
"require": false
}
}
12 changes: 12 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
language: objective-c
node_js:
- "4.2"
install:
- npm install -g cordova
- npm install -g ios-sim
- npm install -g ios-deploy
- npm install -g cordova-paramedic
- npm install
script:
- npm test
- cordova-paramedic --platform ios --plugin /Users/travis/build/johanblomgren/cordova-plugin-indexappcontent
96 changes: 67 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,33 @@
|Travis CI|
|:-:|
|[![Build Status](https://travis-ci.org/johanblomgren/cordova-plugin-indexappcontent.svg?branch=master)](https://travis-ci.org/johanblomgren/cordova-plugin-indexappcontent)|

## Overview
This Cordova Plugin gives you a Javascript API to interact with [Core Spotlight](https://developer.apple.com/reference/corespotlight) on iOS (=> iOS 9). You can add, update and delete items to the spotlight search index. [Spotlight](https://en.wikipedia.org/wiki/Spotlight_(software)) Search will include these items in the result list. You can deep-link the search results with your app.

## Installation
Install using ``cordova`` CLI.
* Run ``cordova plugin add https://github.com/johanblomgren/cordova-plugin-indexappcontent.git``

## Usage
Plugin should be installed on ``window.plugins.indexAppContent``.
This plugin defines a global `window.plugins.indexAppContent` object.

### Is Indexing Available
The option to index app content might not be available at all due to device limitations or user settings. Therefore it's highly recommended to check upfront if indexing is possible.

### Initialization (required)
Calling ``window.plugins.indexAppContent.init()`` will explicitly tell the native component to initialize.
``window.plugins.indexAppContent.isIndexingAvailable(fnCallback)`` will invoke the callback with a boolean value to indicate if indexing is possible or not.
```
window.plugins.indexAppContent.isIndexingAvailable(function(bIsAvailable){
if (bIsAvailable === true) {
// let's go ahead and index your content
}
})
```

Please note that the function does not consider possible time restrictions imposed by ``setIndexingInterval``.

### Set items
``window.plugins.indexAppContent.setItems(items, success, error)`` expects at least one parameter, ``items``, which is an array of objects with the following structure:
Call ``window.plugins.indexAppContent.setItems(aItems, success, error)`` to add or change items to the spotlight index. Function expects at least one parameter, ``aItems``, which is an array of objects with the following structure:
```
{
domain: 'com.my.domain',
Expand All @@ -27,7 +45,7 @@ Calling ``window.plugins.indexAppContent.init()`` will explicitly tell the nativ
Example:

```
var items = [
var aItems = [
{
domain: 'com.my.domain',
identifier: '88asdf7dsf',
Expand All @@ -44,16 +62,18 @@ var items = [
}
];
window.plugins.indexAppContent.setItems(items, function() {
console.log('Successfully set items');
}, function(error) {
// Handle error
});
window.plugins.indexAppContent.setItems(aItems, function() {
console.log('Successfully set items');
}, function(sError) {
console.log("Error when trying to add/modify index: " + sError);
});
```

Image data will be downloaded and stored in the background.

### Set handler
### On Item Pressed
If user taps on a search result in spotlight then the app will be launched. You can register a Javascript handler to get informed when this happens.

Assign a handler function to ``window.plugins.indexAppContent.onItemPressed`` that takes the payload as argument, like so:

```
Expand All @@ -62,47 +82,65 @@ window.plugins.indexAppContent.onItemPressed = function(payload) {
}
```

This handler will be called when launching the app by pressing an item in spotlight search results.

NOTE: Set this handler before calling ``window.plugins.indexAppContent.init()``. A call to ``init()`` will tell the native code that the handler is ready to be used when the app is launched by tapping on a search result.

### Clear items
Call ``window.plugins.indexAppContent.clearItemsForDomains(domains, success, error)`` to clear all items stored for a given array of domains.
Call ``window.plugins.indexAppContent.clearItemsForDomains(aDomains, fnSuccess, fnError)`` to clear all items stored for a given array of domains.

Example:

```
window.plugins.indexAppContent.clearItemsForDomains(['com.my.domain', 'com.my.other.domain'], function() {
console.log('Items removed');
}, function(error) {
// Handle error
});
}, function(sError) {
console.log("Error when trying to clear items: " + sError);
});
```

Call ``window.plugins.indexAppContent.clearItemsForIdentifiers(identifiers, success, error)`` to clear all items stored for a given array of identifiers.
Call ``window.plugins.indexAppContent.clearItemsForIdentifiers(aIdentifiers, fnSuccess, fnError)`` to clear all items stored for a given array of identifiers.

Example:

```
window.plugins.indexAppContent.clearItemsForIdentifiers(['id1', 'id2'], function() {
console.log('Items removed');
}, function(error) {
// Handle error
});
}, function(sError) {
console.log("Error when trying to clear items: " + sError);
});
```

### Set indexing interval

Call ``window.plugins.indexAppContent.setIndexingInterval(interval, success, error)`` to configure the interval (in minutes) for how often indexing should be allowed.
You might want to avoid to update spotlight index too frequently. Call ``window.plugins.indexAppContent.setIndexingInterval(iIntervalInMinutes, fnSuccess, fnError)`` to configure a time interval (in minutes) to define when indexing operations are allowed since your last spotlight index update. First parameter must be numeric and => 0.

Example:

```
window.plugins.indexAppContent.setIndexingInterval(60, function() {
// Console.log('Successfully set interval');
}, function(error) {
// Handle error
});
console.log('Successfully set interval');
}, function(sError) {
console.log("Error when trying to set time interval: " + sError);
});
```

Without calling this function a subsequent call to manipulate the index is only possible after 1440 minutes (= 24 hours) !

Example:
- You call ```setIndexingInterval``` and specify 5min. You call ```setItems``` for the first time and function will be executed successfully.
- You call ```setItems``` again after 2min. Spotlight index will NOT be updated and error callback gets invoked.
- You call ```setItems``` after 6 min and function will be executed successfully.

## Tests

The plugin is covered by automatic and manual tests implemented in Jasmine and following the [Cordova test framework](https://github.com/apache/cordova-plugin-test-framework) approach.

You can create a test application with the tests by doing the following steps:

```
cordova create indexAppContentTestApp --template cordova-template-test-framework
cd indexAppContentTestApp
cordova platform add ios
cordova plugin add https://github.com/johanblomgren/cordova-plugin-indexappcontent
cordova plugin add https://github.com/johanblomgren/cordova-plugin-indexappcontent/tests
```

As an alternative you can use [Cordova Paramedic](https://github.com/apache/cordova-paramedic) to run them.
9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cordova-plugin-indexappcontent",
"version": "0.1.0",
"version": "0.5.0",
"description": "Enable on-device searchable app content indexing.",
"cordova": {
"id": "cordova-plugin-indexappcontent",
Expand All @@ -25,9 +25,16 @@
"version": ">=3.5.0"
}
],
"scripts": {
"test": "npm run jshint",
"jshint": "node node_modules/jshint/bin/jshint www && node node_modules/jshint/bin/jshint src && node node_modules/jshint/bin/jshint tests"
},
"author": "Johan Blomgren <[email protected]>",
"license": "MIT",
"bugs": {
"url": "https://github.com/johanblomgren/cordova-plugin-indexappcontent/issues"
},
"devDependencies": {
"jshint": "^2.6.0"
}
}
16 changes: 8 additions & 8 deletions plugin.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?>
<plugin
id="cordova-plugin-indexappcontent"
version="0.1.0"
version="0.5.0"
xmlns="http://apache.org/cordova/ns/plugins/1.0">

<name>Index App Content</name>
Expand All @@ -18,12 +18,12 @@
<engine name="cordova" version=">=3.5.0"/>
</engines>

<js-module name="IndexAppContent" src="www/IndexAppContent.js">
<clobbers target="plugins.indexAppContent"/>
</js-module>

<platform name="ios">


<js-module name="IndexAppContent" src="www/IndexAppContent.js">
<clobbers target="plugins.indexAppContent"/>
</js-module>

<config-file parent="/*" target="config.xml">
<feature name="IndexAppContent">
<param name="ios-package" value="IndexAppContent"/>
Expand All @@ -35,10 +35,10 @@
<source-file src="src/ios/app/IndexAppContent.m"/>
<header-file src="src/ios/app/AppDelegate+IndexAppContent.h"/>
<source-file src="src/ios/app/AppDelegate+IndexAppContent.m"/>

<framework src="MobileCoreServices.framework" />
<framework src="CoreSpotlight.framework" />

</platform>

</plugin>
89 changes: 74 additions & 15 deletions src/ios/app/AppDelegate+IndexAppContent.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,41 +7,100 @@

#import "AppDelegate+IndexAppContent.h"
#import "IndexAppContent.h"
#import <objc/runtime.h>

#define kCALL_DELAY_MILLISECONDS 25

@implementation AppDelegate (IndexAppContent)

- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray * _Nullable))restorationHandler
/*
handle the case that another category or class in the class hierachy of AppDelegate already implements the UIApplicationDelegate and handOff method "application:continueUserActivity:restorationHandler:"
Use method swizzling to archieve the following:
- call the original implementation (which handles other use cases like universal links)
- call our own implementation to handle CSSearchableItemActionType (if userActivity was not yet handled)
*/
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{

SEL originalHandOffSEL, swizzledHandOffSEL, spotlightHandOffSEL;
Method originalHandOffMethod, swizzledHandOffMethod, spotlightHandOffMethod;

Class thisClass = [self class];

originalHandOffSEL = @selector(application:continueUserActivity:restorationHandler:);
swizzledHandOffSEL = @selector(swizzledHandOff:continueUserActivity:restorationHandler:);
spotlightHandOffSEL = @selector(indexAppContent_application:continueUserActivity:restorationHandler:);

BOOL originalHandOffExists = [self instancesRespondToSelector:originalHandOffSEL];

originalHandOffMethod = class_getInstanceMethod(thisClass, originalHandOffSEL);
swizzledHandOffMethod = class_getInstanceMethod(thisClass, swizzledHandOffSEL);
spotlightHandOffMethod = class_getInstanceMethod(thisClass, spotlightHandOffSEL);

BOOL didAddMethod = class_addMethod(thisClass, originalHandOffSEL, method_getImplementation(swizzledHandOffMethod), method_getTypeEncoding(swizzledHandOffMethod));

if (didAddMethod) {
if (!originalHandOffExists) {
class_replaceMethod(thisClass, swizzledHandOffSEL, method_getImplementation(spotlightHandOffMethod), method_getTypeEncoding(spotlightHandOffMethod));
} else {
class_replaceMethod(thisClass, swizzledHandOffSEL, method_getImplementation(originalHandOffMethod), method_getTypeEncoding(originalHandOffMethod));
}
} else {
method_exchangeImplementations(originalHandOffMethod, swizzledHandOffMethod);
}
});
}

- (BOOL)swizzledHandOff:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray * _Nullable))restorationHandler {

BOOL orginalHandOffImplementedHandledCase = [self swizzledHandOff:application continueUserActivity:userActivity restorationHandler:restorationHandler];
if (orginalHandOffImplementedHandledCase == NO) {
return [self indexAppContent_application:application continueUserActivity:userActivity restorationHandler:restorationHandler];
} else {
NSLog(@"Another implementation (e.g. plugin) already handled that userActivity");
return YES;
}
}

- (BOOL)indexAppContent_application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray * _Nullable))restorationHandler
{
if ([userActivity.activityType isEqualToString:CSSearchableItemActionType]) {
// Get the item identifier and use it
NSString *identifier = userActivity.userInfo[CSSearchableItemActivityIdentifier];

NSString *jsFunction = @"window.plugins.indexAppContent.onItemPressed";
NSString *params = [NSString stringWithFormat:@"{'identifier':'%@'}", identifier];
NSString *result = [NSString stringWithFormat:@"%@(%@)", jsFunction, params];
[self callJavascriptFunctionWhenAvailable:result];
return YES;
} else {
NSLog(@"userActivity is not related to spotlight and therefore does not get handled");
return NO;
}

return YES;
}

- (void)callJavascriptFunctionWhenAvailable:(NSString *)function {
IndexAppContent *indexAppContent = [self.viewController getCommandInstance:@"IndexAppContent"];

if (indexAppContent.initDone && indexAppContent.ready) {
[self sendCommand:function webViewEngine:indexAppContent.webViewEngine];
} else {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kCALL_DELAY_MILLISECONDS * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
[self callJavascriptFunctionWhenAvailable:function];
});
}
__weak __typeof(self) weakSelf = self;
__block NSString *command = function;

__block void (^checkAndExecute)( ) = ^void( ) {
NSString *check = @"(window && window.plugins && window.plugins.indexAppContent && typeof window.plugins.indexAppContent.onItemPressed == 'function') ? true : false";
IndexAppContent *indexAppContent = [weakSelf.viewController getCommandInstance:@"IndexAppContent"];
[weakSelf sendCommand:check webViewEngine:indexAppContent.webViewEngine completionHandler:^(id returnValue, NSError * error) {
if (error || [returnValue boolValue] == NO) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kCALL_DELAY_MILLISECONDS * NSEC_PER_MSEC), dispatch_get_main_queue(), checkAndExecute);
} else if ([returnValue boolValue] == YES) {
[self sendCommand:command webViewEngine:indexAppContent.webViewEngine completionHandler:nil];
}
}];
};

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_MSEC), dispatch_get_main_queue(), checkAndExecute);
}

- (void)sendCommand:(NSString *)command webViewEngine:(id<CDVWebViewEngineProtocol>)webViewEngine
- (void)sendCommand:(NSString *)command webViewEngine:(id<CDVWebViewEngineProtocol>)webViewEngine completionHandler:(void (^)(id, NSError*))completionHandler
{
[webViewEngine evaluateJavaScript:command completionHandler:nil];
[webViewEngine evaluateJavaScript:command completionHandler:completionHandler];
}

@end
8 changes: 1 addition & 7 deletions src/ios/app/IndexAppContent.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,9 @@
#import <MobileCoreServices/MobileCoreServices.h>
#import <CoreSpotlight/CoreSpotlight.h>

UIKIT_EXTERN NSString *kIndexAppContentDelayExecutionNotification;
UIKIT_EXTERN NSString *kIndexAppContentExecutionDelayKey;

@interface IndexAppContent : CDVPlugin

@property BOOL initDone;
@property BOOL ready;

- (void)deviceIsReady:(CDVInvokedUrlCommand *)command;
- (void)isIndexingAvailable:(CDVInvokedUrlCommand *)command;
- (void)setItems:(CDVInvokedUrlCommand *)command;
- (void)clearItemsForDomains:(CDVInvokedUrlCommand *)command;
- (void)clearItemsForIdentifiers:(CDVInvokedUrlCommand *)command;
Expand Down
Loading

0 comments on commit 27bab05

Please sign in to comment.