diff --git a/.gitignore b/.gitignore index 49cfcb58..4d7ffea4 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ ios/Pods google-services.json GoogleService-Info.plist + +package-lock.json diff --git a/ios/ScratchJr.xcodeproj/project.pbxproj b/ios/ScratchJr.xcodeproj/project.pbxproj index 5c156f1d..d7e214f6 100644 --- a/ios/ScratchJr.xcodeproj/project.pbxproj +++ b/ios/ScratchJr.xcodeproj/project.pbxproj @@ -12,6 +12,9 @@ 204D80C418A4140600ECBB8B /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 204D80AC18A4140600ECBB8B /* UIKit.framework */; }; 204D80CC18A4140600ECBB8B /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 204D80CA18A4140600ECBB8B /* InfoPlist.strings */; }; 204D80CE18A4140600ECBB8B /* ScratchJrTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 204D80CD18A4140600ECBB8B /* ScratchJrTests.m */; }; + 40F0482024D44803006B2336 /* JsBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 40F0481F24D44803006B2336 /* JsBridge.m */; }; + 40F0482324D44825006B2336 /* JsRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 40F0482224D44825006B2336 /* JsRequest.m */; }; + 40F0482E24D4D1D8006B2336 /* View.xib in Resources */ = {isa = PBXBuildFile; fileRef = 40F0482C24D4D1D8006B2336 /* View.xib */; }; 7465F19843DCA9DDC62CC852 /* libPods-ScratchJr Free.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7ACBA0233542F96AC72D4D7E /* libPods-ScratchJr Free.a */; }; A7FDEB52BFEDCD9E4520FF89 /* libPods-ScratchJrTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 512C88D0E9938AB5972ACF38 /* libPods-ScratchJrTests.a */; }; D92D0F951C33381B00C573AD /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2001BEB918E237AE008E563F /* MessageUI.framework */; }; @@ -22,7 +25,6 @@ D92D0F9A1C33381B00C573AD /* libsqlite3.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 20B2226E18A46327003BDE44 /* libsqlite3.0.dylib */; }; D92D0F9D1C33381B00C573AD /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 20B2226818A4404B003BDE44 /* Settings.bundle */; }; D92D0F9E1C33381B00C573AD /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 204D80B118A4140600ECBB8B /* InfoPlist.strings */; }; - D92D0F9F1C33381B00C573AD /* View.xib in Resources */ = {isa = PBXBuildFile; fileRef = 204D80DC18A4195E00ECBB8B /* View.xib */; }; D92D0FCA1C3346D200C573AD /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = D92D0FB31C33469A00C573AD /* AppDelegate.m */; }; D92D0FCB1C3346D200C573AD /* CameraMask.m in Sources */ = {isa = PBXBuildFile; fileRef = D92D0FB41C33469A00C573AD /* CameraMask.m */; }; D92D0FCC1C3346D200C573AD /* CameraView.m in Sources */ = {isa = PBXBuildFile; fileRef = D92D0FB51C33469A00C573AD /* CameraView.m */; }; @@ -62,10 +64,12 @@ 204D80C918A4140600ECBB8B /* ScratchJrTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ScratchJrTests-Info.plist"; sourceTree = ""; }; 204D80CB18A4140600ECBB8B /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 204D80CD18A4140600ECBB8B /* ScratchJrTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ScratchJrTests.m; sourceTree = ""; }; - 204D80DC18A4195E00ECBB8B /* View.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = View.xib; sourceTree = ""; }; 20B2226818A4404B003BDE44 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = SOURCE_ROOT; }; 20B2226E18A46327003BDE44 /* libsqlite3.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.0.dylib; path = usr/lib/libsqlite3.0.dylib; sourceTree = SDKROOT; }; 20B2227818A53688003BDE44 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; + 40F0481F24D44803006B2336 /* JsBridge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JsBridge.m; sourceTree = ""; }; + 40F0482224D44825006B2336 /* JsRequest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JsRequest.m; sourceTree = ""; }; + 40F0482C24D4D1D8006B2336 /* View.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = View.xib; path = src/View.xib; sourceTree = ""; }; 512C88D0E9938AB5972ACF38 /* libPods-ScratchJrTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ScratchJrTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 5A3230A01AEC02BC09345F16 /* Pods-ScratchJrTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ScratchJrTests.debug.xcconfig"; path = "Target Support Files/Pods-ScratchJrTests/Pods-ScratchJrTests.debug.xcconfig"; sourceTree = ""; }; 72E2C9BF3BE9F67099F0180F /* Pods-ScratchJrTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ScratchJrTests.release.xcconfig"; path = "Target Support Files/Pods-ScratchJrTests/Pods-ScratchJrTests.release.xcconfig"; sourceTree = ""; }; @@ -166,7 +170,7 @@ children = ( D92D0FC91C3346AC00C573AD /* src */, 204D80AF18A4140600ECBB8B /* Supporting Files */, - 204D80DC18A4195E00ECBB8B /* View.xib */, + 40F0482C24D4D1D8006B2336 /* View.xib */, ); path = ScratchJr; sourceTree = ""; @@ -256,6 +260,8 @@ D92D0FE01C3347D100C573AD /* IO */ = { isa = PBXGroup; children = ( + 40F0481F24D44803006B2336 /* JsBridge.m */, + 40F0482224D44825006B2336 /* JsRequest.m */, D92D0FB61C33469A00C573AD /* Database.m */, D92D0FB71C33469A00C573AD /* IO.m */, ); @@ -356,7 +362,7 @@ D92D0F9D1C33381B00C573AD /* Settings.bundle in Resources */, D965E3971C3EC63A005D792F /* Free-Images.xcassets in Resources */, D92D0F9E1C33381B00C573AD /* InfoPlist.strings in Resources */, - D92D0F9F1C33381B00C573AD /* View.xib in Resources */, + 40F0482E24D4D1D8006B2336 /* View.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -438,7 +444,9 @@ files = ( D92D0FCD1C3346DC00C573AD /* Database.m in Sources */, D92D0FCE1C3346DC00C573AD /* IO.m in Sources */, + 40F0482024D44803006B2336 /* JsBridge.m in Sources */, D92D0FCF1C3346DC00C573AD /* main.m in Sources */, + 40F0482324D44825006B2336 /* JsRequest.m in Sources */, D92D0FD01C3346DC00C573AD /* RecordSound.m in Sources */, D92D0FD11C3346DC00C573AD /* ScratchJr.m in Sources */, D92D0FD21C3346DC00C573AD /* ViewController.m in Sources */, @@ -644,7 +652,7 @@ "COCOAPODS=1", ); INFOPLIST_FILE = "$(SRCROOT)/../editions/free/Free-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; PRODUCT_BUNDLE_IDENTIFIER = edu.mitmedialab.scratchjrfree; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; @@ -669,7 +677,7 @@ "COCOAPODS=1", ); INFOPLIST_FILE = "$(SRCROOT)/../editions/free/Free-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; PRODUCT_BUNDLE_IDENTIFIER = edu.mitmedialab.scratchjrfree; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; diff --git a/ios/ScratchJr/JsBridge.m b/ios/ScratchJr/JsBridge.m new file mode 100644 index 00000000..ea78f970 --- /dev/null +++ b/ios/ScratchJr/JsBridge.m @@ -0,0 +1,190 @@ +// +// JsBridge.m +// ScratchJr Free +// +// Created by Yueyu on 2020/7/31. +// Copyright © 2020 Playful Invention Company. All rights reserved. +// + +#import "ScratchJr.h" +#import +@import Firebase; + +@implementation JsBridge + +-(void) userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message +{ + NSDictionary *dict = (NSDictionary *)message.body; + + JsRequest *request = [[JsRequest alloc] initWithDictionary:dict]; + + NSString *method = [request.method stringByAppendingString:@":"]; + SEL selector = NSSelectorFromString(method); + if (![self respondsToSelector:selector]) { + NSLog(@"method %@ not exists", request.method); + return; + } + // [self performSelector:selector withObject:request]; + // to disable warning: PerformSelector may cause a leak because its selector is unknown + // thanks wbyoung for https://stackoverflow.com/a/20058585 + IMP imp = [self methodForSelector:selector]; + void (*func)(id, SEL, JsRequest *) = (void *)imp; + func(self, selector, request); +} + +-(void) askForPermission: (JsRequest *) request +{ + [RecordSound setPermission]; + [request callback:@"ok"]; +} + +-(void) database_stmt: (JsRequest *) request { + [request callback:[Database stmt:request.params[0]]]; +} + +-(void) database_query: (JsRequest *) request { + [request callback:[Database query:request.params[0]]]; +} + +-(void) io_getmd5: (JsRequest *) request { + [request callback:[IO getMD5:request.params[0]]]; +} + +-(void) io_getsettings: (JsRequest *) request { + [request callback:[IO getsettings]]; +} + +-(void) io_cleanassets: (JsRequest *) request { + [IO cleanassets: request.params[0]]; + [request callback:@"ok"]; +} + +-(void) io_setfile: (JsRequest *) request { + [request callback:[IO setfile:request.params[0]:request.params[1]]]; +} + +-(void) io_getfile: (JsRequest *) request { + [request callback:[IO getfile:request.params[0]]]; +} + +-(void) io_setmedia: (JsRequest *) request { + [request callback:[IO setmedia:request.params[0] :request.params[1]]]; +} + +-(void) io_setmedianame: (JsRequest *) request { + [request callback:[IO setmedianame:request.params[0] :request.params[1] :request.params[2]]]; +} + +-(void) io_getmedia: (JsRequest *) request { + [request callback:[IO getmedia:request.params[0]]]; +} + +-(void) io_getmediadata: (JsRequest *) request { + int offset = [request.params[1] intValue]; + int length = [request.params[2] intValue]; + NSString *key = [NSString stringWithFormat:@"%@", request.params[0]]; + [request callback:[IO getmediadata:key :offset :length]]; +} + +-(void) io_getmedialen: (JsRequest *) request { + NSString *key = [NSString stringWithFormat:@"%@", request.params[1]]; + [request callback:[IO getmedialen:request.params[0] :key]]; +} + +-(void) io_getmediadone: (JsRequest *) request { + [request callback:[IO getmediadone:request.params[0]]]; +} + +-(void) io_remove: (JsRequest *) request { + [request callback:[IO remove:request.params[0]]]; +} + +-(void) io_registersound: (JsRequest *) request { + [request callback:[IO registerSound:request.params[0] :request.params[1]]]; +} + +-(void) io_playsound: (JsRequest *) request { + [request callback:[IO playSound:request.params[0]]]; +} + +-(void) io_stopsound: (JsRequest *) request { + [request callback:[IO stopSound:request.params[0]]]; +} + +-(void) recordsound_recordstart: (JsRequest *) request { + [request callback:[RecordSound startRecord]]; +} + +-(void) recordsound_recordstop: (JsRequest *) request { + [request callback:[RecordSound stopRecording]]; +} + +-(void) recordsound_volume: (JsRequest *) request { + [request callback:[NSString stringWithFormat:@"%f", [RecordSound getVolume]]]; +} + +-(void) recordsound_startplay: (JsRequest *) request { + [request callback:[RecordSound startPlay]]; +} + +-(void) recordsound_stopplay: (JsRequest *) request { + [request callback:[RecordSound stopPlay]]; +} + +-(void) recordsound_recordclose: (JsRequest *) request { + [request callback:[RecordSound recordclose:request.params[0]]]; +} + +-(void) scratchjr_cameracheck: (JsRequest *) request { + [request callback:[ScratchJr cameracheck]]; +} + +-(void) scratchjr_has_multiple_cameras: (JsRequest *) request { + [request callback:@"YES"]; +} + +-(void) scratchjr_startfeed: (JsRequest *) request { + [request callback:[ScratchJr startfeed: request.params[0]]]; +} + +-(void) scratchjr_stopfeed: (JsRequest *) request { + [request callback:[ScratchJr stopfeed]]; +} + +-(void) scratchjr_choosecamera: (JsRequest *) request { + [request callback:[ScratchJr choosecamera:request.params[0]]]; +} + +-(void) scratchjr_captureimage: (JsRequest *) request { + [request callback:[ScratchJr captureimage:request.params[0]]]; +} + +-(void) sendSjrUsingShareDialog: (JsRequest *) request { + int shareType = [request.params[3] intValue]; + NSString *res = [IO sendSjrUsingShareDialog:request.params[0] :request.params[1] :request.params[2] :shareType : request.params[4]]; + [request callback:res]; +} + +- (void) hideSplash: (JsRequest *) request { + [request callback:[ScratchJr hideSplash:nil]]; +} + +-(void) analyticsEvent: (JsRequest *) request { + [FIRAnalytics logEventWithName:request.params[1] // action + parameters:@{ + kFIRParameterItemName:request.params[2], // label + kFIRParameterItemCategory:request.params[0] // category + }]; +} + +-(void) setAnalyticsPlacePref: (JsRequest *) request { + [FIRAnalytics setUserPropertyString:request.params[0] forName:@"place_preference"]; + [request callback:@"ok"]; +} + +// iPad name (used for information in the name/sharing dialog to help people using Airdrop) +- (void) deviceName: (JsRequest *) request { + [request callback:[[UIDevice currentDevice] name]]; +} + +@end diff --git a/ios/ScratchJr/JsRequest.m b/ios/ScratchJr/JsRequest.m new file mode 100644 index 00000000..92a85919 --- /dev/null +++ b/ios/ScratchJr/JsRequest.m @@ -0,0 +1,33 @@ +// +// JsRequest.m +// ScratchJr Free +// +// Created by Yueyu on 2020/7/31. +// Copyright © 2020 Playful Invention Company. All rights reserved. +// + +#import "ScratchJr.h" + +@implementation JsRequest + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary { + self = [super init]; + self.method = dictionary[@"method"]; + self.callId = dictionary[@"id"]; + self.params = dictionary[@"params"]; + return self; +} + +- (void) callback:(NSString *)res { + NSString *js = nil; + if ([res hasPrefix:@"["] || [res hasPrefix:@"{"]) { + js = [NSString stringWithFormat:@"iOS.resolve('%@', %@);", self.callId, res]; + } else { + js = [NSString stringWithFormat:@"iOS.resolve('%@', '%@');", self.callId, res]; + } + dispatch_async(dispatch_get_main_queue(), ^{ + [ViewController.webview evaluateJavaScript:js completionHandler:nil]; + }); +} + +@end diff --git a/ios/ScratchJr/View.xib b/ios/ScratchJr/View.xib deleted file mode 100644 index e95e0726..00000000 --- a/ios/ScratchJr/View.xib +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ios/ScratchJr/src/IO.m b/ios/ScratchJr/src/IO.m index f2eea809..77f517ec 100644 --- a/ios/ScratchJr/src/IO.m +++ b/ios/ScratchJr/src/IO.m @@ -283,9 +283,9 @@ + (void)soundEnded:(NSTimer*)timer { NSString *soundName = [[timer userInfo] objectForKey:@"soundName"]; if (sounds[soundName] == nil) return; NSString *callback = [NSString stringWithFormat:@"iOS.soundDone('%@');", soundName]; - UIWebView *webview = [ViewController webview]; + WKWebView *webview = [ViewController webview]; dispatch_async(dispatch_get_main_queue(), ^{ - [webview stringByEvaluatingJavaScriptFromString:callback]; + [webview evaluateJavaScript:callback completionHandler:nil]; }); } diff --git a/ios/ScratchJr/src/ScratchJr.h b/ios/ScratchJr/src/ScratchJr.h index 873bf715..b553886f 100644 --- a/ios/ScratchJr/src/ScratchJr.h +++ b/ios/ScratchJr/src/ScratchJr.h @@ -3,6 +3,7 @@ #import #import #import +#import @interface Database : NSObject @@ -85,54 +86,8 @@ + (NSString *)recordclose:(NSString *)keep; @end -@protocol JSExports -/* Functions exported to JavaScript */ -- (NSString *)hideSplash:(NSString *)body; -- (void) askForPermission; -- (NSString *)database_stmt:(NSString *) json; -- (NSString *)database_query:(NSString *) json; -- (NSString *)io_getmd5:(NSString *) str; -- (NSString *)io_getsettings; -- (void)io_cleanassets:(NSString *)fileType; -- (NSString *)io_setfile:(NSString *)filename :(NSString *)base64ContentStr; -- (NSString *)io_getfile:(NSString *)filename; -- (NSString *)io_setmedia:(NSString *)base64ContentStr :(NSString *)extension; -- (NSString *)io_setmedianame:(NSString *)contents :(NSString *)key :(NSString *)ext; -- (NSString *)io_getmedia:(NSString *)filename; -- (NSString *)io_getmediadata:(NSString *)filename :(int)offset :(int)length; -- (NSString *)io_getmedialen:(NSString *)file :(NSString *)key; -- (NSString *)io_getmediadone:(NSString *)filename; -- (NSString *)io_remove:(NSString *)filename; -- (NSString *)io_registersound:(NSString *)dir :(NSString *)name; -- (NSString *)io_playsound:(NSString *)name; -- (NSString *)io_stopsound:(NSString *)name; - -- (NSString *)recordsound_recordstart; -- (NSString *)recordsound_recordstop; -- (NSString *)recordsound_volume; -- (NSString *)recordsound_startplay; -- (NSString *)recordsound_stopplay; -- (NSString *)recordsound_recordclose:(NSString *)keep; - -- (NSString *)scratchjr_cameracheck; -- (bool) scratchjr_has_multiple_cameras; -- (NSString *)scratchjr_startfeed:(NSString *)str; -- (NSString *)scratchjr_stopfeed; -- (NSString *)scratchjr_choosecamera:(NSString *)body; -- (NSString *)scratchjr_captureimage:(NSString *)onCameraCaptureComplete; -- (NSString *)sendSjrUsingShareDialog:(NSString *)fileName - :(NSString *)emailSubject - :(NSString *)emailBody - :(int)shareType - :(NSString *)b64data; -- (NSString *) deviceName; -- (NSString *) analyticsEvent:(NSString *)category :(NSString *)action :(NSString *)label; -- (void) setAnalyticsPlacePref:(NSString *)place; -@end - -@interface ViewController : UIViewController -@property (nonatomic, readwrite, strong) JSContext *js; -+ (UIWebView *)webview; +@interface ViewController : UIViewController ++ (WKWebView *)webview; + (UIImageView *)splashScreen; - (void)receiveProject:(NSString *)project; - (void)registerDefaultsFromSettingsBundle; @@ -147,6 +102,23 @@ - (void)showShareAirdrop:(NSURL *)projectURL; @end +@interface JsBridge: NSObject + +@property(weak, nonatomic) ViewController *controller; + +@end + +@interface JsRequest : NSObject + +@property(nonatomic, readwrite) NSString* callId; +@property(nonatomic, readwrite) NSString* method; +@property(nonatomic, readwrite) NSArray* params; + +- (instancetype) initWithDictionary: (NSDictionary *)dictionary; + +- (void) callback: (NSString *) res; + +@end @interface IO : NSObject diff --git a/ios/ScratchJr/src/ScratchJr.m b/ios/ScratchJr/src/ScratchJr.m index e5b6cca1..1396f279 100644 --- a/ios/ScratchJr/src/ScratchJr.m +++ b/ios/ScratchJr/src/ScratchJr.m @@ -113,10 +113,10 @@ + (NSString *)captureimage:(NSString *)oc{ + (void)reportImageError { NSString *callback = [NSString stringWithFormat: @"%@('error getting a still');",oncomplete]; - UIWebView *webview = [ViewController webview]; + WKWebView *webview = [ViewController webview]; dispatch_async(dispatch_get_main_queue(), ^{ - [webview stringByEvaluatingJavaScriptFromString: callback]; + [webview evaluateJavaScript:callback completionHandler:nil]; }); } @@ -124,9 +124,9 @@ + (void)sendBase64Image:(NSData *)imagedata { NSString *base64img = [cameraView getImageBase64:imagedata]; [self closefeed]; NSString *callback = [NSString stringWithFormat: @"%@('%@');",oncomplete, base64img]; - UIWebView *webview = [ViewController webview]; + WKWebView *webview = [ViewController webview]; dispatch_async(dispatch_get_main_queue(), ^{ - [webview stringByEvaluatingJavaScriptFromString: callback]; + [webview evaluateJavaScript:callback completionHandler:nil]; }); } diff --git a/ios/ScratchJr/src/View.xib b/ios/ScratchJr/src/View.xib new file mode 100644 index 00000000..18387900 --- /dev/null +++ b/ios/ScratchJr/src/View.xib @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/ScratchJr/src/ViewController.m b/ios/ScratchJr/src/ViewController.m index 0827e9ff..b0013c91 100644 --- a/ios/ScratchJr/src/ViewController.m +++ b/ios/ScratchJr/src/ViewController.m @@ -1,12 +1,12 @@ #import "ScratchJr.h" +#import // @import MessageUI; @import Firebase; -UIWebView *webview; +WKWebView *webview; NSDate* startDate; UIImageView *splashScreen; NSDate *startDate; -JSContext *js; @interface ViewController () @@ -26,14 +26,18 @@ - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil - (void)viewDidLoad { [super viewDidLoad]; + + webview = [[WKWebView alloc] initWithFrame:CGRectZero configuration:[self webViewConfig]]; + webview.backgroundColor = UIColor.blackColor; + webview.navigationDelegate = self; + self.view = webview; + [self registerDefaultsFromSettingsBundle]; - webview = (UIWebView*)[self view] ; // disable webview scroll // to fix https://github.com/LLK/scratchjr/issues/243 webview.scrollView.scrollEnabled = false; - [webview setDelegate:self]; [Database open:@"ScratchJr"]; [ScratchJr cameraInit]; [self reload]; @@ -44,6 +48,18 @@ - (void)viewDidLoad [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil]; } +- (WKWebViewConfiguration*) webViewConfig { + WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; + config.allowsInlineMediaPlayback = true; + config.allowsAirPlayForMediaPlayback = true; + [config.preferences setValue:@YES forKey:@"allowFileAccessFromFileURLs"]; + + WKUserContentController *controller = [[WKUserContentController alloc] init]; + [controller addScriptMessageHandler:[[JsBridge alloc] init] name:@"jsBridge"]; + config.userContentController = controller; + return config; +} + - (void) showSplash { UIImage *loadingImage = [UIImage imageNamed:@"Default-Landscape~ipad.png"]; splashScreen = [[UIImageView alloc] initWithImage:loadingImage]; @@ -59,7 +75,7 @@ - (void)didReceiveMemoryWarning // Dispose of any resources that can be recreated. } -+ (UIWebView*) webview {return webview;} ++ (WKWebView*) webview {return webview;} + (UIImageView*) splashScreen {return splashScreen;} - (void)registerDefaultsFromSettingsBundle { @@ -86,11 +102,11 @@ - (void)registerDefaultsFromSettingsBundle { } - (void)reload { - UIWebView *webview = (UIWebView*)[self view]; + WKWebView *webview = (WKWebView*)[self view]; NSString *location = [[NSUserDefaults standardUserDefaults] stringForKey:@"html"]; if ([location length] > 3) location = [location substringFromIndex:3]; NSString *path = [[NSBundle mainBundle] pathForResource: @"HTML5/index" ofType:@"html"]; - NSURL *url = [NSURL URLWithString: [path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; + NSURL *url = [NSURL fileURLWithPath:path]; NSURLRequest* request = [NSURLRequest requestWithURL:url]; [[NSURLCache sharedURLCache] removeAllCachedResponses]; [webview loadRequest:request]; @@ -103,46 +119,10 @@ - (void)viewDidAppear:(BOOL)animated{ startDate = [NSDate date]; } -// UIWebView delegate methods - -- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{ - //read your request here - //before the webview will load your request - return YES; -} - -- (void)webViewDidStartLoad:(UIWebView *)webView{ - //access your request -} - -- (void)webViewDidFinishLoad:(UIWebView *)webView{ - // Inject a reference for the dispatch method into the UIWebView - // This happens after the page is loaded and the page's onLoad method is called - js = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; - js[@"tablet"] = self; - [self disableWebViewLongPressGestures:webView]; - - NSString *debugChoice =[[NSUserDefaults standardUserDefaults] stringForKey:@"debugstate"]; - - // Patch through app "advanced"->debug to allow users to display long-form errors - if (![debugChoice isEqualToString:@""] && ![debugChoice isEqualToString:@"0"]) { - [webView stringByEvaluatingJavaScriptFromString:@"window.reloadDebug = true;"]; - } - - NSURL* screenName = webView.request.URL.filePathURL; - NSString* screenString =[screenName absoluteString]; - NSArray* parts = [screenString componentsSeparatedByString:@"/"]; - NSString* page = [[[parts lastObject] componentsSeparatedByString:@"?"] firstObject]; - - // Track pageview in Firebase? - [FIRAnalytics setScreenName:page screenClass:NULL]; - -} - // Disables iOS 9 webview touch tooltip by disabling the long-press gesture recognizer in subviews // Thanks to Rye: // http://stackoverflow.com/questions/32687368/how-to-completely-disable-magnifying-glass-for-uiwebview-ios9 -- (void) disableWebViewLongPressGestures:(UIWebView *)webview { +- (void) disableWebViewLongPressGestures:(UIView *)webview { for(id subView in webview.subviews) { if([subView isKindOfClass:[UIScrollView class]]) { UIScrollView *scrollView = (UIScrollView *)subView; @@ -160,33 +140,27 @@ - (void) disableWebViewLongPressGestures:(UIWebView *)webview { } } -- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error{ - NSLog(@"could not load the website caused by error DESC: %@", error); - NSDictionary *userInfo = [error userInfo]; - NSString *desc = [NSString stringWithFormat:@"%@", ([userInfo objectForKey: @"NSLocalizedDescription"] == NULL)? [error localizedDescription]: [userInfo objectForKey: @"NSLocalizedDescription"]]; - NSString *callback = [NSString stringWithFormat: @"iOS.pageError('%@');",desc]; - UIWebView *webview = [ViewController webview]; - dispatch_async(dispatch_get_main_queue(), ^{ - [webview stringByEvaluatingJavaScriptFromString: callback]; - }); -} - - (void) receiveProject:(NSString *)project{ NSString *callback = [NSString stringWithFormat:@"iOS.loadProjectFromSjr('%@');", project]; - UIWebView *webview = [ViewController webview]; + WKWebView *webview = [ViewController webview]; dispatch_async(dispatch_get_main_queue(), ^{ - NSString *res = [webview stringByEvaluatingJavaScriptFromString:callback]; - if ([res isEqualToString:@"1"]) { - // Success - return; - } else if ([res isEqualToString:@"0"]) { - // Processing error - return; - } else { - // Loading the project failed - reschedule for a time when the WebView has hopefully loaded - // A little bit roundabout, but simpler than queueing projects to be loaded - [self performSelector:@selector(receiveProject:) withObject:project afterDelay:2.0]; - } + [webview evaluateJavaScript:callback completionHandler:^(id result, NSError * _Nullable error) { + if (error != nil) { + return; + } + NSString *res = [NSString stringWithFormat:@"%@", result]; + if ([res isEqualToString:@"1"]) { + // Success + return; + } else if ([res isEqualToString:@"0"]) { + // Processing error + return; + } else { + // Loading the project failed - reschedule for a time when the WebView has hopefully loaded + // A little bit roundabout, but simpler than queueing projects to be loaded + [self performSelector:@selector(receiveProject:) withObject:project afterDelay:2.0]; + } + }]; }); } @@ -194,148 +168,37 @@ - (BOOL)prefersStatusBarHidden{ return YES; } -/* -* JavaScript Interface Exports -*/ - --(void) askForPermission { - [RecordSound setPermission]; -} - --(NSString*) database_stmt: (NSString*) json { - return [Database stmt:json]; -} - --(NSString*) database_query: (NSString*) json { - return [Database query:json]; -} - --(NSString*) io_getmd5: (NSString*) str { - return [IO getMD5:str]; -} - --(NSString*) io_getsettings { - return [IO getsettings]; -} +// WKNavigationDelegate --(void) io_cleanassets:(NSString*) fileType { - [IO cleanassets:fileType]; -} - --(NSString*) io_setfile:(NSString*)filename :(NSString*)base64ContentStr { - return [IO setfile:filename:base64ContentStr]; -} - --(NSString*) io_getfile:(NSString*)filename { - return [IO getfile:filename]; -} - --(NSString*) io_setmedia:(NSString*) base64ContentStr :(NSString*) extension { - return [IO setmedia:base64ContentStr:extension]; -} - --(NSString*) io_setmedianame:(NSString*) contents :(NSString*) key :(NSString*) ext { - return [IO setmedianame:contents:key:ext]; -} - --(NSString*) io_getmedia:(NSString*) filename { - return [IO getmedia:filename]; -} - --(NSString*) io_getmediadata:(NSString*)filename :(int) offset :(int) length { - return [IO getmediadata:filename:offset:length]; -} - --(NSString*) io_getmedialen:(NSString*)file :(NSString*)key { - return [IO getmedialen:file:key]; -} - --(NSString*) io_getmediadone:(NSString*)filename { - return [IO getmediadone:filename]; -} - --(NSString*) io_remove:(NSString*)filename { - return [IO remove:filename]; -} - --(NSString*) io_registersound:(NSString*)dir :(NSString*)name { - return [IO registerSound:dir:name]; -} - --(NSString*) io_playsound:(NSString*) name { - return [IO playSound:name]; -} - --(NSString*) io_stopsound:(NSString*) name { - return [IO stopSound:name]; -} - --(NSString*) recordsound_recordstart { - return [RecordSound startRecord]; -} --(NSString*) recordsound_recordstop { - return [RecordSound stopRecording]; -} --(NSString*) recordsound_volume { - return [NSString stringWithFormat:@"%f", [RecordSound getVolume]]; -} --(NSString*) recordsound_startplay { - return [RecordSound startPlay]; -} --(NSString*) recordsound_stopplay { - return [RecordSound stopPlay]; -} --(NSString*) recordsound_recordclose:(NSString*) keep { - return [RecordSound recordclose:keep]; -} - --(NSString*) scratchjr_cameracheck { - return [ScratchJr cameracheck]; -} --(bool) scratchjr_has_multiple_cameras { - return YES; -} --(NSString*) scratchjr_startfeed:(NSString*)str { - return [ScratchJr startfeed:str]; -} --(NSString*) scratchjr_stopfeed { - return [ScratchJr stopfeed]; -} - --(NSString*) scratchjr_choosecamera:(NSString *)body { - return [ScratchJr choosecamera:body]; -} - --(NSString*) scratchjr_captureimage: (NSString*)onCameraCaptureComplete { - return [ScratchJr captureimage:onCameraCaptureComplete]; -} - -//iOS.sendSjrToShareDialog = function(fileName, emailSubject, emailBody, shareType, b64data) { +- (void) webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { + [webview evaluateJavaScript:@"window.tablet = window.webkit.messageHandlers.jsBridge" completionHandler:nil]; + [self disableWebViewLongPressGestures:webView]; --(NSString*) sendSjrUsingShareDialog:(NSString*) fileName :(NSString*) emailSubject :(NSString*) emailBody :(int) shareType :(NSString*) b64data { - return [IO sendSjrUsingShareDialog:fileName :emailSubject :emailBody :shareType : b64data]; -} + NSString *debugChoice =[[NSUserDefaults standardUserDefaults] stringForKey:@"debugstate"]; -- (NSString *) hideSplash :(NSString *)body { - return [ScratchJr hideSplash:body]; -} + // Patch through app "advanced"->debug to allow users to display long-form errors + if (![debugChoice isEqualToString:@""] && ![debugChoice isEqualToString:@"0"]) { + [webView evaluateJavaScript:@"window.reloadDebug = true;" completionHandler:nil]; + } --(NSString*) analyticsEvent:(NSString*) category :(NSString*) action :(NSString*) label { - [FIRAnalytics logEventWithName:action - parameters:@{ - kFIRParameterItemName:label, - kFIRParameterItemCategory:category - }]; - return @"1"; -} + NSURL* screenName = webView.URL.filePathURL; + NSString* screenString =[screenName absoluteString]; + NSArray* parts = [screenString componentsSeparatedByString:@"/"]; + NSString* page = [[[parts lastObject] componentsSeparatedByString:@"?"] firstObject]; --(void) setAnalyticsPlacePref:(NSString*)place { - [FIRAnalytics setUserPropertyString:place forName:@"place_preference"]; + // Track pageview in Firebase? + [FIRAnalytics setScreenName:page screenClass:NULL]; } -// iPad name (used for information in the name/sharing dialog to help people using Airdrop) -- (NSString*) deviceName { - return [[UIDevice currentDevice] name]; +- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error { + NSLog(@"could not load the website caused by error DESC: %@", error); + NSDictionary *userInfo = [error userInfo]; + NSString *desc = [NSString stringWithFormat:@"%@", ([userInfo objectForKey: @"NSLocalizedDescription"] == NULL)? [error localizedDescription]: [userInfo objectForKey: @"NSLocalizedDescription"]]; + NSString *callback = [NSString stringWithFormat: @"iOS.pageError('%@');",desc]; + WKWebView *webview = [ViewController webview]; + dispatch_async(dispatch_get_main_queue(), ^{ + [webview evaluateJavaScript:callback completionHandler:nil]; + }); } diff --git a/package.json b/package.json index d4f02f6e..34670a8e 100644 --- a/package.json +++ b/package.json @@ -15,10 +15,12 @@ }, "homepage": "https://github.com/llk/scratchjr", "devDependencies": { + "@babel/polyfill": "^7.10.4", "babel-core": "^6.4.0", "babel-eslint": "^4.1.6", "babel-loader": "^7.1.4", "babel-preset-es2015": "^6.3.13", + "babel-preset-stage-3": "^6.24.1", "esformatter": "^0.8.1", "esformatter-braces": "^1.2.1", "esformatter-dot-notation": "^1.3.1", diff --git a/src/iPad/IO.js b/src/iPad/IO.js index db9727e6..617f682e 100644 --- a/src/iPad/IO.js +++ b/src/iPad/IO.js @@ -90,11 +90,8 @@ export default class IO { IO.requestFromServer(md5, gotit); // get url contents return; } - if ((IO.getExtension(md5) == 'png') && iOS.path) { - fcn(iOS.path + md5); // only if it is not in debug mode - } else { - iOS.getmedia(md5, nextStep); - } // get url contents + + iOS.getmedia(md5, nextStep); function gotit (str) { var base64 = IO.getImageDataURL(md5, btoa(str)); @@ -109,14 +106,10 @@ export default class IO { function nextStep (dataurl) { // iOS 7 requires to read the internal base64 images before returning contents var str = atob(dataurl); - if ((str.indexOf('xlink:href') < 0) && iOS.path) { - fcn(iOS.path + md5); // does not have embedded images - } else { - var base64 = IO.getImageDataURL(md5, dataurl); - IO.getImagesInSVG(str, function () { - fcn(base64); - }); // base64 dataurl - } + var base64 = IO.getImageDataURL(md5, dataurl); + IO.getImagesInSVG(str, function () { + fcn(base64); + }); } } diff --git a/src/iPad/iOS.js b/src/iPad/iOS.js index 8062bfec..d8da0654 100644 --- a/src/iPad/iOS.js +++ b/src/iPad/iOS.js @@ -3,6 +3,7 @@ import IO from './IO'; import Lobby from '../lobby/Lobby'; import Alert from '../editor/ui/Alert'; import ScratchAudio from '../utils/ScratchAudio'; +import '@babel/polyfill'; ////////////////////////////////////////////////// // Tablet interface functions @@ -17,6 +18,7 @@ let camera; let database = 'projects'; let mediacounter = 0; let tabletInterface = null; +let callbacks = {}; export default class iOS { // Getters/setters for properties used in other classes @@ -70,17 +72,24 @@ export default class iOS { // Database functions static stmt (json, fcn) { - var result = tabletInterface.database_stmt(JSON.stringify(json)); - if (typeof (fcn) !== 'undefined') { - fcn(result); - } + (async () => { + var result = await iOS.call('database_stmt', JSON.stringify(json)); + if (typeof (fcn) !== 'undefined') { + fcn(result); + } + })(); } static query (json, fcn) { - var result = tabletInterface.database_query(JSON.stringify(json)); - if (typeof (fcn) !== 'undefined') { - fcn(result); - } + (async () => { + var result = await iOS.call('database_query', JSON.stringify(json)); + if (typeof result == 'object') { + result = JSON.stringify(result); + } + if (typeof (fcn) !== 'undefined') { + fcn(result); + } + })(); } static setfield (db, id, fieldname, val, fcn) { @@ -94,23 +103,28 @@ export default class iOS { // IO functions static cleanassets (ft, fcn) { - tabletInterface.io_cleanassets(ft); fcn(); + iOS.call('io_cleanassets', ft); + fcn(); } static getmedia (file, fcn) { mediacounter++; var nextStep = function (file, key, whenDone) { - var result = tabletInterface.io_getmedialen(file, key); - iOS.processdata(key, 0, result, '', whenDone); + (async () => { + var result = await iOS.call('io_getmedialen', file, key); + iOS.processdata(key, 0, result * 1, '', whenDone); + })(); }; nextStep(file, mediacounter, fcn); } static getmediadata (key, offset, len, fcn) { - var result = tabletInterface.io_getmediadata(key, offset, len); - if (fcn) { - fcn(result); - } + (async () => { + var result = await iOS.call('io_getmediadata', key, offset, len); + if (fcn) { + fcn(result); + } + })(); } static processdata (key, off, len, oldstr, fcn) { @@ -126,82 +140,104 @@ export default class iOS { } static getsettings (fcn) { - var result = tabletInterface.io_getsettings(); - if (fcn) { - fcn(result); - } + (async () => { + var result = await iOS.call('io_getsettings'); + if (fcn) { + fcn(result); + } + })(); } static getmediadone (file, fcn) { - var result = tabletInterface.io_getmediadone(file); - if (fcn) { - fcn(result); - } + (async () => { + var result = await iOS.call('io_getmediadone', file); + if (fcn) { + fcn(result); + } + })(); } static setmedia (str, ext, fcn) { - var result = tabletInterface.io_setmedia(str, ext); - if (fcn) { - fcn(result); - } + (async () => { + var result = await iOS.call('io_setmedia', str, ext); + if (fcn) { + fcn(result); + } + })(); } static setmedianame (str, name, ext, fcn) { - var result = tabletInterface.io_setmedianame(str, name, ext); - if (fcn) { - fcn(result); - } + (async () => { + var result = await iOS.call('io_setmedianame', str, name, ext); + if (fcn) { + fcn(result); + } + })(); } static getmd5 (str, fcn) { - var result = tabletInterface.io_getmd5(str); - if (fcn) { - fcn(result); - } + (async () => { + var result = await iOS.call('io_getmd5', str); + if (fcn) { + fcn(result); + } + })(); } static remove (str, fcn) { - var result = tabletInterface.io_remove(str); - if (fcn) { - fcn(result); - } + (async () => { + var result = await iOS.call('io_remove', str); + if (fcn) { + fcn(result); + } + })(); } static getfile (str, fcn) { - var result = tabletInterface.io_getfile(str); - if (fcn) { - fcn(result); - } + (async () => { + var result = await iOS.call('io_getfile', str); + if (fcn) { + fcn(result); + } + })(); } static setfile (name, str, fcn) { - var result = tabletInterface.io_setfile(name, btoa(str)); - if (fcn) { - fcn(result); - } + (async () => { + var result = await iOS.call('io_setfile', name, btoa(str)); + if (fcn) { + fcn(result); + } + })(); } // Sound functions static registerSound (dir, name, fcn) { - var result = tabletInterface.io_registersound(dir, name); - if (fcn) { - fcn(result); - } + (async () => { + var result = await iOS.call('io_registersound', dir, name); + if (fcn) { + fcn(result); + } + })(); } static playSound (name, fcn) { - var result = tabletInterface.io_playsound(name); - if (fcn) { - fcn(result); - } + (async () => { + var result = await iOS.call('io_playsound', name); + if (fcn) { + fcn(result); + } + })(); } static stopSound (name, fcn) { - var result = tabletInterface.io_stopsound(name); - if (fcn) { - fcn(result); - } + (async () => { + var result = await iOS.call('io_stopsound', name); + if (fcn) { + fcn(result); + } + })(); } // Web Wiew delegate call backs @@ -211,89 +247,115 @@ export default class iOS { } static sndrecord (fcn) { - var result = tabletInterface.recordsound_recordstart(); - if (fcn) { - fcn(result); - } + (async () => { + var result = await iOS.call('recordsound_recordstart'); + if (fcn) { + fcn(result); + } + })(); } static recordstop (fcn) { - var result = tabletInterface.recordsound_recordstop(); - if (fcn) { - fcn(result); - } + (async () => { + var result = await iOS.call('recordsound_recordstop'); + if (fcn) { + fcn(result); + } + })(); } static volume (fcn) { - var result = tabletInterface.recordsound_volume(); - if (fcn) { - fcn(result); - } + (async () => { + var result = await iOS.call('recordsound_volume'); + if (fcn) { + fcn(result); + } + })(); } static startplay (fcn) { - var result = tabletInterface.recordsound_startplay(); - if (fcn) { - fcn(result); - } + (async () => { + var result = await iOS.call('recordsound_startplay'); + if (fcn) { + fcn(result); + } + })(); } static stopplay (fcn) { - var result = tabletInterface.recordsound_stopplay(); - if (fcn) { - fcn(result); - } + (async () => { + var result = await iOS.call('recordsound_stopplay'); + if (fcn) { + fcn(result); + } + })(); } static recorddisappear (b, fcn) { - var result = tabletInterface.recordsound_recordclose(b); - if (fcn) { - fcn(result); - } + (async () => { + var result = iOS.call('recordsound_recordclose', b); + if (fcn) { + fcn(result); + } + })(); } // Record state static askpermission () { if (isiOS) { - tabletInterface.askForPermission(); + iOS.call('askForPermission'); } } // camera functions static hascamera () { - camera = tabletInterface.scratchjr_cameracheck(); + (async () => { + camera = await iOS.call('scratchjr_cameracheck'); + })(); } static startfeed (data, fcn) { - var str = JSON.stringify(data); - var result = tabletInterface.scratchjr_startfeed(str); - if (fcn) { - fcn(result); - } + (async () => { + var str = JSON.stringify(data); + var result = await iOS.call('scratchjr_startfeed', str); + if (fcn) { + fcn(result); + } + })(); } static stopfeed (fcn) { - var result = tabletInterface.scratchjr_stopfeed(); - if (fcn) { - fcn(result); - } + (async () => { + var result = await iOS.call('scratchjr_stopfeed'); + if (fcn) { + fcn(result); + } + })(); } static choosecamera (mode, fcn) { - var result = tabletInterface.scratchjr_choosecamera(mode); - if (fcn) { - fcn(result); - } + (async () => { + var result = await iOS.call('scratchjr_choosecamera', mode); + if (fcn) { + fcn(result); + } + })(); } static captureimage (fcn) { - tabletInterface.scratchjr_captureimage(fcn); + iOS.call('scratchjr_captureimage', fcn); } static hidesplash (fcn) { if (isiOS) { - tabletInterface.hideSplash(); + (async () => { + iOS.call('hideSplash'); + if (fcn) { + fcn(); + } + })(); + return; } if (fcn) { fcn(); @@ -312,6 +374,60 @@ export default class iOS { console.log(atob(str)); // eslint-disable-line no-console } + static getId () { + do { + var id = 'jr' + ((new Date()).getTime()) + Math.floor(Math.random() * 10000); + if (!callbacks[id]) { + return id; + } + } while (true); + } + + static call (method) { + if (tabletInterface[method]) { + switch (arguments.length) { + case 1: + return tabletInterface[method](); + case 2: + return tabletInterface[method](arguments[1]); + case 3: + return tabletInterface[method](arguments[1], arguments[2]); + case 4: + return tabletInterface[method](arguments[1], arguments[2], arguments[3]); + case 5: + return tabletInterface[method](arguments[1], arguments[2], arguments[3], arguments[4]); + case 6: + return tabletInterface[method](arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]); + } + return; + } + return new Promise((resolve) => { + var id = iOS.getId(); + callbacks[id] = resolve; + var args = [].slice.call(arguments); + args.shift(); + tabletInterface.postMessage({ + id: id, + method: method, + params: args, + }); + }); + } + + static resolve (id, res) { + if (!id) { + return; + } + const callbackFn = callbacks[id]; + if (!callbackFn) { + return; + } + if (typeof callbackFn === 'function') { + callbackFn(res); + } + delete callbacks[id]; + } + ignore () { } @@ -328,7 +444,7 @@ export default class iOS { // b64data: base-64 encoded .SJR file to share static sendSjrToShareDialog (fileName, emailSubject, emailBody, shareType, b64data) { - tabletInterface.sendSjrUsingShareDialog(fileName, emailSubject, emailBody, shareType, b64data); + iOS.call('sendSjrUsingShareDialog', fileName, emailSubject, emailBody, shareType, b64data); } // Called on the Objective-C side. The argument is a base64-encoded .SJR file, @@ -348,15 +464,17 @@ export default class iOS { // Name of the device/iPad to display on the sharing dialog page // fcn is called with the device name as an arg static deviceName (fcn) { - fcn(tabletInterface.deviceName()); + (async () => { + fcn(await iOS.call('deviceName')); + })(); } static analyticsEvent (category, action, label) { - tabletInterface.analyticsEvent(category, action, label); + iOS.call('analyticsEvent', category, action, label); } static setAnalyticsPlacePref (preferredPlace) { - tabletInterface.setAnalyticsPlacePref(preferredPlace); + iOS.call('setAnalyticsPlacePref', preferredPlace); } // Web Wiew delegate call backs diff --git a/webpack.config.js b/webpack.config.js index ec9e14eb..6546b66c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -27,7 +27,7 @@ module.exports = { exclude: /node_modules/, test: /\.jsx?$/, query: { - presets: ['es2015'] + presets: ['es2015', 'stage-3'] } } ]