Skip to content

Commit

Permalink
Initial implementation of headless JS task and controller
Browse files Browse the repository at this point in the history
  • Loading branch information
NoilPaw committed Mar 20, 2017
1 parent 7f9876c commit 799caaa
Show file tree
Hide file tree
Showing 6 changed files with 265 additions and 0 deletions.
17 changes: 17 additions & 0 deletions Libraries/Headless/HeadlessJsTaskSupport.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>

RCT_EXTERN NSString *const RCTHeadlessTaskDidFinishNotification;

@interface HeadlessJsTaskSupport : NSObject <RCTBridgeModule>

@end
25 changes: 25 additions & 0 deletions Libraries/Headless/HeadlessJsTaskSupport.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#import "HeadlessJsTaskSupport.h"

NSString *const RCTHeadlessTaskDidFinishNotification = @"RCTHeadlessTaskDidFinishNotification";

@implementation HeadlessJsTaskSupport

RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(notifyTaskFinished:(int)taskId)
{
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:RCTHeadlessTaskDidFinishNotification object:@{@"taskId": @(taskId)}];
});
}

@end
32 changes: 32 additions & 0 deletions Libraries/Headless/RCTHeadlessTask.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface RCTHeadlessTask: NSObject

@property (nonatomic, readonly) NSString *taskKey;
@property (nonatomic, readonly) NSDictionary *data;
@property (nonatomic, readonly) long timeout;

/**
* @param taskKey The key for the JS task to execute. This is the same key that you call
* AppRegistry.registerTask with in JS.
* @param data A map of parameters that will be passed into JS.
* @param timeout The amount of time (in ms) that the task should not exceed. A value of 0 means no timeout.
*/
- (instancetype)initWithTaskKey:(NSString *)taskKey
data:(NSDictionary *)data
timeout:(long)timeout;

@end

NS_ASSUME_NONNULL_END
27 changes: 27 additions & 0 deletions Libraries/Headless/RCTHeadlessTask.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#import "RCTHeadlessTask.h"

@implementation RCTHeadlessTask

- (instancetype)initWithTaskKey:(NSString *)taskKey
data:(NSDictionary *)data
timeout:(long)timeout
{
self = [super init];
if (self != nil) {
_taskKey = taskKey;
_data = data;
_timeout = timeout;
}
return self;
}

@end
27 changes: 27 additions & 0 deletions Libraries/Headless/RCTHeadlessTaskController.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#import <Foundation/Foundation.h>

@class RCTHeadlessTask;

NS_ASSUME_NONNULL_BEGIN

@interface RCTHeadlessTaskController : NSObject

/**
* Executes the task in JS.
*
* @param task The task to execute.
*/
- (void)executeTask:(RCTHeadlessTask *)task NS_SWIFT_NAME(execute(task:));

@end

NS_ASSUME_NONNULL_END
137 changes: 137 additions & 0 deletions Libraries/Headless/RCTHeadlessTaskController.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#import "RCTHeadlessTaskController.h"
#import "RCTHeadlessTask.h"
#import "HeadlessJsTaskSupport.h"
#import <React/RCTBridge.h>

@interface RCTHeadlessTaskController ()
@property (nonatomic, strong) RCTBridge *bridge;
@property (nonatomic, strong) NSString *module;
@property (nonatomic, strong) NSMutableDictionary<NSNumber *, RCTHeadlessTask *> *activeTasks;
@property (nonatomic) int lastTaskId;
@end

@implementation RCTHeadlessTaskController

- (instancetype)initWithBundleURL:(NSURL *)bundleURL
{
self = [super init];
if (self) {
self.lastTaskId = 0;
self.activeTasks = [NSMutableDictionary new];
self.bridge = [[RCTBridge alloc] initWithBundleURL:bundleURL
moduleProvider:nil
launchOptions:nil];

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(javaScriptDidLoad:)
name:RCTJavaScriptDidLoadNotification
object:nil];

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(javaScriptDidFailToLoad:)
name:RCTJavaScriptDidFailToLoadNotification
object:nil];

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(taskDidFinish:)
name:RCTHeadlessTaskDidFinishNotification
object:nil];
}
return self;
}

- (void)dealloc
{
[self.bridge invalidate];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)executeTask:(RCTHeadlessTask *)task
{
NSNumber *newTaskId = @(self.lastTaskId++);
self.activeTasks[newTaskId] = task;

if (self.bridge.isValid) {
[self startTask:task withTaskId:newTaskId];
}
}

- (void)startTask:(RCTHeadlessTask *)task withTaskId:(NSNumber *)taskId
{
[self.bridge enqueueJSCall:@"AppRegistry"
method:@"startHeadlessTask"
args:@[taskId, task.taskKey, task.data]
completion:NULL];

if (task.timeout > 0) {
[self startTimeoutForTask:task];
}
}

- (void)javaScriptDidLoad:(NSNotification *)notification
{
if (self.activeTasks.count > 0) {
for (NSNumber *taskId in self.activeTasks) {
RCTHeadlessTask *task = self.activeTasks[taskId];
[self startTask:task withTaskId:taskId];
}
}
}

- (void)javaScriptDidFailToLoad:(NSNotification *)notification
{
//TODO: We should not allow executeTask to be called from now on

if (self.activeTasks.count > 0) {
for (NSNumber *taskId in self.activeTasks) {
RCTHeadlessTask *task = self.activeTasks[taskId];
//TODO: Notify a task delegate about the failure
}
}
}

- (void)taskDidFinish:(NSNotification *)notification
{
NSNumber *taskId = notification.userInfo[@"taskId"];
if (taskId) {
for (NSNumber *aTaskId in self.activeTasks) {
if ([aTaskId isEqualToNumber:taskId]) {

RCTHeadlessTask *task = [self.activeTasks objectForKey:aTaskId];

[self.activeTasks removeObjectForKey:aTaskId];
[self cancelTimeoutForTask:task];

//TODO: Notify a task delegate about the success

break;
}
}
}
}

- (void)startTimeoutForTask:(RCTHeadlessTask *)task
{
//TODO: Timeout handling
}

- (void)cancelTimeoutForTask:(RCTHeadlessTask *)task
{
//TODO: Remove timeout
}

- (void)taskDidTimeout:(RCTHeadlessTask *)task
{
//TODO: Notify a task delegate about the failure
}

@end

0 comments on commit 799caaa

Please sign in to comment.