In app purchases in TVML apps

I've been thinking of a way to implement In App Purchases in TVML apps.


Is there a way you could fire an event back to the Swift/Obj C app and let StoreKit take over control?

Answered by kuvisit in 79036022

I managed to handle this in Objective-C. I've made an actual implementation here, but here's a sample of what it might look like:


First let's take a look at the header file:

// MyClass.h

#import <JavaScriptCore/JavaScriptCore.h> // This is where JSExport lies

/*
* This is where the magic happens. Any method declared inside a JSExport protocol
* will be translated into JavaScript. Quite useful.
*/

@protocol MyClassJavascriptMethods <JSExport>

- (BOOL) method;
- (NSString*) methodWithAnArgument:(NSDictionary*)argument;

@end

/*
* This class must implement the above protocol
*/

@interface MyClass : NSObject <..., MyClassJavaScriptMethods>

@end


Notice the protocol, it's where the Obj-C - JS binding actually happens. Now let's move over to the implementation file:

// MyClass.m
#import "MyClass.h"

@implementation MyClass

// implement the MyClassJavascriptMethods protocol:

- (BOOL) method {
     // do stuff
}

- (NSString*) methodWithAnArgument:(NSDictionary*)argument {
     // do some other stuff
}

@end


Now go to your TVApplicationControllerDelegate:

#import "MFInAppPurchaseManager.h"

// make a property to prevent ARC from destroying your object
@property (strong) MyClass* myClassObject;

-(void)appController:(TVApplicationController *)appController evaluateAppJavaScriptInContext:(JSContext *)jsContext { 
     MyClass *createdObject = [[MyClass alloc] init]; 
     self.myClassObject = createdObject; 
     [jsContext setObject:createdObject forKeyedSubscript:@"mySuperCoolObject"]; // mySuperCoolObject will be your object's name in JS
}


The last thing to do is to go to your JavaScript file, where mySuperCoolObject is now a global variable:

mySuperCoolObject.method();
mySuperCoolObject.methodWithAnArgument(someArgument);


And that's pretty much it!

You would need to create new class (example below) or add method to JSContext in appController:evaluateAppJavaScriptInContext: delegate method. Now these objects/methods can be called from JavaScript.


@objc protocol MyJSClass : JSExport {
    func getItem(key:String) -> String?
  
    func setItem(key:String, data:String)
}
class MyClass: NSObject, MyJSClass {
    func getItem(key: String) -> String? {
        // read from keychain
      
        return "String value"
    }
  
    func setItem(key: String, data: String) {
        // write to keychain
    }
}
// In the TVApplicationControllerDelegate
func appController(appController: TVApplicationController, evaluateAppJavaScriptInContext jsContext: JSContext)
{
    let myClass: MyClass = MyClass();
    jsContext.setObject(myClass, forKeyedSubscript: "keychain");
}

I have tried this but i cannot imagine the javascript part.

is "keychain" a function, var, return in the javasript file?


Is there a chance to see also this snippet of js code please?

I'm in the same boat. . I have a TVML app that I want to add in-app to. However, there are currently no hooks in TVJS for me to do this. With the above suggestion from Nurinder M I'm unable to see how the call backs go straight back to the JSContext.


Please let me know if you get an answer for this.


--M

I'm currently digging into it using Objective-C. If I find a way, I'll make sure to publish it.

We have it working (can't yet share the code, but here is the basic idea):


1) define an objective C block to be invoked via a TVJS call (see http://nshipster.com/javascriptcore/)

2) create blocks for: get product list, purchase product, restore product

3) When TVJS calls the necessary block, pass a success and failure callback function name (which are stored)

4) after the store delegate methods have triggered, the swift code invokes the appropriate callback function with appropriate arguments


BE AWARE that TVJS seems to need to run on a specific thread, so whenever the swift blocks are invoked, store the current thread and then when calling back into JS perform that on the stored thread

Accepted Answer

I managed to handle this in Objective-C. I've made an actual implementation here, but here's a sample of what it might look like:


First let's take a look at the header file:

// MyClass.h

#import <JavaScriptCore/JavaScriptCore.h> // This is where JSExport lies

/*
* This is where the magic happens. Any method declared inside a JSExport protocol
* will be translated into JavaScript. Quite useful.
*/

@protocol MyClassJavascriptMethods <JSExport>

- (BOOL) method;
- (NSString*) methodWithAnArgument:(NSDictionary*)argument;

@end

/*
* This class must implement the above protocol
*/

@interface MyClass : NSObject <..., MyClassJavaScriptMethods>

@end


Notice the protocol, it's where the Obj-C - JS binding actually happens. Now let's move over to the implementation file:

// MyClass.m
#import "MyClass.h"

@implementation MyClass

// implement the MyClassJavascriptMethods protocol:

- (BOOL) method {
     // do stuff
}

- (NSString*) methodWithAnArgument:(NSDictionary*)argument {
     // do some other stuff
}

@end


Now go to your TVApplicationControllerDelegate:

#import "MFInAppPurchaseManager.h"

// make a property to prevent ARC from destroying your object
@property (strong) MyClass* myClassObject;

-(void)appController:(TVApplicationController *)appController evaluateAppJavaScriptInContext:(JSContext *)jsContext { 
     MyClass *createdObject = [[MyClass alloc] init]; 
     self.myClassObject = createdObject; 
     [jsContext setObject:createdObject forKeyedSubscript:@"mySuperCoolObject"]; // mySuperCoolObject will be your object's name in JS
}


The last thing to do is to go to your JavaScript file, where mySuperCoolObject is now a global variable:

mySuperCoolObject.method();
mySuperCoolObject.methodWithAnArgument(someArgument);


And that's pretty much it!

Thank you so much for this example. I was able to get a native function called within minutes!
In app purchases in TVML apps
 
 
Q