diff --git a/Libraries/Settings/JSIUtils.h b/Libraries/Settings/JSIUtils.h new file mode 100644 index 00000000000000..f87c740e69ff72 --- /dev/null +++ b/Libraries/Settings/JSIUtils.h @@ -0,0 +1,50 @@ +#import +#import +#import + +using namespace facebook; + +jsi::Value convertNSNumberToJSIBoolean(jsi::Runtime &runtime, NSNumber *value); +jsi::Value convertNSNumberToJSINumber(jsi::Runtime &runtime, NSNumber *value); +jsi::String convertNSStringToJSIString(jsi::Runtime &runtime, NSString *value); +jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value);; +jsi::Object convertNSDictionaryToJSIObject(jsi::Runtime &runtime, NSDictionary *value); +jsi::Array convertNSArrayToJSIArray(jsi::Runtime &runtime, NSArray *value); +std::vector convertNSArrayToStdVector(jsi::Runtime &runtime, NSArray *value); +jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value); +id convertJSIValueToObjCObject( + jsi::Runtime &runtime, + const jsi::Value &value); +NSString* convertJSIStringToNSString(jsi::Runtime &runtime, const jsi::String &value); +NSArray* convertJSIArrayToNSArray( + jsi::Runtime &runtime, + const jsi::Array &value); +NSDictionary *convertJSIObjectToNSDictionary( + jsi::Runtime &runtime, + const jsi::Object &value); +RCTResponseSenderBlock convertJSIFunctionToCallback( + jsi::Runtime &runtime, + const jsi::Function &value); +id convertJSIValueToObjCObject( + jsi::Runtime &runtime, + const jsi::Value &value); +RCTResponseSenderBlock convertJSIFunctionToCallback( + jsi::Runtime &runtime, + const jsi::Function &value); + +struct Promise { + Promise(jsi::Runtime &rt, jsi::Function resolve, jsi::Function reject); + + void resolve(const jsi::Value &result); + void reject(const std::string &error); + + jsi::Runtime &runtime_; + jsi::Function resolve_; + jsi::Function reject_; +}; + +using PromiseSetupFunctionType = +std::function)>; +jsi::Value createPromiseAsJSIValue( + jsi::Runtime &rt, + const PromiseSetupFunctionType func); \ No newline at end of file diff --git a/Libraries/Settings/JSIUtils.mm b/Libraries/Settings/JSIUtils.mm new file mode 100644 index 00000000000000..96113c5ee50d1f --- /dev/null +++ b/Libraries/Settings/JSIUtils.mm @@ -0,0 +1,189 @@ +#include "JSIUtils.h" +#import +#import + +/** + * All helper functions are ObjC++ specific. + */ +jsi::Value convertNSNumberToJSIBoolean(jsi::Runtime &runtime, NSNumber *value) +{ + return jsi::Value((bool)[value boolValue]); +} + +jsi::Value convertNSNumberToJSINumber(jsi::Runtime &runtime, NSNumber *value) +{ + return jsi::Value([value doubleValue]); +} + +jsi::String convertNSStringToJSIString(jsi::Runtime &runtime, NSString *value) +{ + return jsi::String::createFromUtf8(runtime, [value UTF8String] ?: ""); +} + +jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value); + +jsi::Object convertNSDictionaryToJSIObject(jsi::Runtime &runtime, NSDictionary *value) +{ + jsi::Object result = jsi::Object(runtime); + for (NSString *k in value) { + id obj = value[k]; + if (obj){ + result.setProperty(runtime, [k UTF8String], convertObjCObjectToJSIValue(runtime, obj)); + } + } + return result; +} + +jsi::Array convertNSArrayToJSIArray(jsi::Runtime &runtime, NSArray *value) +{ + jsi::Array result = jsi::Array(runtime, value.count); + for (size_t i = 0; i < value.count; i++) { + result.setValueAtIndex(runtime, i, convertObjCObjectToJSIValue(runtime, value[i])); + } + return result; +} + +std::vector convertNSArrayToStdVector(jsi::Runtime &runtime, NSArray *value) +{ + std::vector result; + for (size_t i = 0; i < value.count; i++) { + result.emplace_back(convertObjCObjectToJSIValue(runtime, value[i])); + } + return result; +} + +jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value) +{ + if ([value isKindOfClass:[NSString class]]) { + return convertNSStringToJSIString(runtime, (NSString *)value); + } else if ([value isKindOfClass:[NSNumber class]]) { + if ([value isKindOfClass:[@YES class]]) { + return convertNSNumberToJSIBoolean(runtime, (NSNumber *)value); + } + return convertNSNumberToJSINumber(runtime, (NSNumber *)value); + } else if ([value isKindOfClass:[NSDictionary class]]) { + return convertNSDictionaryToJSIObject(runtime, (NSDictionary *)value); + } else if ([value isKindOfClass:[NSArray class]]) { + return convertNSArrayToJSIArray(runtime, (NSArray *)value); + } else if (value == (id)kCFNull) { + return jsi::Value::null(); + } + return jsi::Value::undefined(); +} + +id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value); + +NSString *convertJSIStringToNSString(jsi::Runtime &runtime, const jsi::String &value) +{ + return [NSString stringWithUTF8String:value.utf8(runtime).c_str()]; +} + +NSArray *convertJSIArrayToNSArray(jsi::Runtime &runtime, + const jsi::Array &value) +{ + size_t size = value.size(runtime); + NSMutableArray *result = [NSMutableArray new]; + for (size_t i = 0; i < size; i++) { + // Insert kCFNull when it's `undefined` value to preserve the indices. + [result + addObject:convertJSIValueToObjCObject(runtime, value.getValueAtIndex(runtime, i)) ?: (id)kCFNull]; + } + return [result copy]; +} + +NSDictionary *convertJSIObjectToNSDictionary(jsi::Runtime &runtime, + const jsi::Object &value) +{ + jsi::Array propertyNames = value.getPropertyNames(runtime); + size_t size = propertyNames.size(runtime); + NSMutableDictionary *result = [NSMutableDictionary new]; + for (size_t i = 0; i < size; i++) { + jsi::String name = propertyNames.getValueAtIndex(runtime, i).getString(runtime); + NSString *k = convertJSIStringToNSString(runtime, name); + id v = convertJSIValueToObjCObject(runtime, value.getProperty(runtime, name)); + if (v) { + result[k] = v; + } + } + return [result copy]; +} + +RCTResponseSenderBlock convertJSIFunctionToCallback(jsi::Runtime &runtime, + const jsi::Function &value) +{ + __block auto cb = value.getFunction(runtime); + + return ^(NSArray *responses) { + cb.call(runtime, convertNSArrayToJSIArray(runtime, responses), 2); + }; +} + +id convertJSIValueToObjCObject(jsi::Runtime &runtime, + const jsi::Value &value) +{ + if (value.isUndefined() || value.isNull()) { + return nil; + } + if (value.isBool()) { + return @(value.getBool()); + } + if (value.isNumber()) { + return @(value.getNumber()); + } + if (value.isString()) { + return convertJSIStringToNSString(runtime, value.getString(runtime)); + } + if (value.isObject()) { + jsi::Object o = value.getObject(runtime); + if (o.isArray(runtime)) { + return convertJSIArrayToNSArray(runtime, o.getArray(runtime)); + } + if (o.isFunction(runtime)) { + return convertJSIFunctionToCallback(runtime, + std::move(o.getFunction(runtime))); + } + return convertJSIObjectToNSDictionary(runtime, o); + } + + throw std::runtime_error("Unsupported jsi::jsi::Value kind"); +} + +Promise::Promise(jsi::Runtime &rt, jsi::Function resolve, jsi::Function reject) +: runtime_(rt), resolve_(std::move(resolve)), reject_(std::move(reject)) {} + +void Promise::resolve(const jsi::Value &result) +{ + resolve_.call(runtime_, result); +} + +void Promise::reject(const std::string &message) +{ + jsi::Object error(runtime_); + error.setProperty(runtime_, + "message", + jsi::String::createFromUtf8(runtime_, message)); + reject_.call(runtime_, error); +} + +jsi::Value createPromiseAsJSIValue(jsi::Runtime &rt, + const PromiseSetupFunctionType func) +{ + jsi::Function JSPromise = rt.global().getPropertyAsFunction(rt, "Promise"); + jsi::Function fn = jsi::Function::createFromHostFunction(rt, + jsi::PropNameID::forAscii(rt, "fn"), + 2, + [func](jsi::Runtime &rt2, + const jsi::Value &thisVal, + const jsi::Value *args, + size_t count) { + jsi::Function resolve = args[0].getObject(rt2).getFunction(rt2); + jsi::Function reject = args[1].getObject(rt2).getFunction(rt2); + auto wrapper = std::make_shared(rt2, + std::move(resolve), + std::move(reject)); + func(rt2, wrapper); + return jsi::Value::undefined(); + }); + + return JSPromise.callAsConstructor(rt, fn); +} \ No newline at end of file diff --git a/Libraries/Settings/NativeSettingsManager.js b/Libraries/Settings/NativeSettingsManager.js index 1edd6187218962..f2ee6363e55eac 100644 --- a/Libraries/Settings/NativeSettingsManager.js +++ b/Libraries/Settings/NativeSettingsManager.js @@ -14,12 +14,13 @@ import type {TurboModule} from '../TurboModule/RCTExport'; import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry'; export interface Spec extends TurboModule { - +getConstants: () => {| - settings: Object, - |}; - +setValues: (values: Object) => void; - +deleteValues: (values: Array) => void; -} + +getConstants: () => {| + settings: Object, + |}; + +setValues: (values: Object) => void; + +deleteValues: (values: Array) => void; + +install: () => boolean; + } export default (TurboModuleRegistry.getEnforcing( 'SettingsManager', diff --git a/Libraries/Settings/RCTSettingsManager.h b/Libraries/Settings/RCTSettingsManager.h index 4e76780a284c52..856f495c303898 100644 --- a/Libraries/Settings/RCTSettingsManager.h +++ b/Libraries/Settings/RCTSettingsManager.h @@ -12,5 +12,6 @@ @interface RCTSettingsManager : NSObject - (instancetype)initWithUserDefaults:(NSUserDefaults *)defaults NS_DESIGNATED_INITIALIZER; +@property (nonatomic, assign) BOOL setBridgeOnMainQueue; @end diff --git a/Libraries/Settings/RCTSettingsManager.mm b/Libraries/Settings/RCTSettingsManager.mm index b52c55622543be..ddc2cdd7d9e724 100644 --- a/Libraries/Settings/RCTSettingsManager.mm +++ b/Libraries/Settings/RCTSettingsManager.mm @@ -9,9 +9,14 @@ #import #import +#import #import #import #import +//#import +//#include +#include +#import "JSIUtils.h" #import "RCTSettingsPlugins.h" @@ -38,6 +43,106 @@ - (instancetype)init return [self initWithUserDefaults:[NSUserDefaults standardUserDefaults]]; } +- (void)setBridge:(RCTBridge *)bridge { + _bridge = bridge; + _setBridgeOnMainQueue = RCTIsMainQueue(); + + RCTCxxBridge *cxxBridge = (RCTCxxBridge *)self.bridge; + if (!cxxBridge.runtime) { + return; + } + + installJsiBindings(*(jsi::Runtime *)cxxBridge.runtime, self); +} + +static void installJsiBindings(jsi::Runtime &jsiRuntime, RCTSettingsManager *rctSettingsManager) { + auto fetchConfigSync = jsi::Function::createFromHostFunction( + jsiRuntime, + jsi::PropNameID::forAscii(jsiRuntime, "fetchConfig"), + 1, + [rctSettingsManager](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value { + NSUserDefaults* currentDefaults = rctSettingsManager->_defaults; + auto result = convertNSDictionaryToJSIObject(runtime, ([currentDefaults dictionaryRepresentation])); + return result; + } + ); + + auto fetchConfigAsync = jsi::Function::createFromHostFunction(jsiRuntime, jsi::PropNameID::forAscii(jsiRuntime, "fetchConfigAsync"), + 0, [rctSettingsManager](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value { + auto promise = runtime.global().getPropertyAsFunction(runtime, "Promise"); + return promise.callAsConstructor(runtime, jsi::Function::createFromHostFunction(runtime, + jsi::PropNameID::forAscii(runtime, "executor"), + 2, + [rctSettingsManager](jsi::Runtime& runtime, + const jsi::Value& thisValue, + const jsi::Value* args, + size_t count) -> jsi::Value { + + // Execte Promise stuff here + auto resolve = std::make_shared(runtime, args[0]); + auto reject = std::make_shared(runtime, args[1]); + + NSUserDefaults* currentDefaults = rctSettingsManager->_defaults; + + auto value = convertNSDictionaryToJSIObject(runtime, ([currentDefaults dictionaryRepresentation])); + resolve->asObject(runtime).asFunction(runtime).call(runtime, value); + return {}; + })); + }); + + auto setSettingASync = jsi::Function::createFromHostFunction( + jsiRuntime, + jsi::PropNameID::forAscii(jsiRuntime, "setSettingsSync"), + 1, + [rctSettingsManager](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value { + if (!arguments[0].isObject()) + throw jsi::JSError(runtime, "setSettingsSync First argument has to be a settings Object"); + + auto settings = arguments[0].asObject(runtime); + auto promise = runtime.global().getPropertyAsFunction(runtime, "Promise"); + return promise.callAsConstructor(runtime, jsi::Function::createFromHostFunction(runtime, + jsi::PropNameID::forAscii(runtime, "executor"), + 2, + [rctSettingsManager, &settings](jsi::Runtime& runtime, + const jsi::Value& thisValue, + const jsi::Value* args, + size_t count) -> jsi::Value { + + // Execte Promise stuff here + auto resolve = std::make_shared(runtime, args[0]); + auto reject = std::make_shared(runtime, args[1]); + + NSDictionary *settingsDictionary = convertJSIObjectToNSDictionary(runtime, settings); + rctSettingsManager->_ignoringUpdates = YES; + [settingsDictionary enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, BOOL *stop) { + id plist = [RCTConvert NSPropertyList:json]; + if (plist) { + [rctSettingsManager->_defaults setObject:plist forKey:key]; + } else { + [rctSettingsManager->_defaults removeObjectForKey:key]; + } + }]; + + [rctSettingsManager->_defaults synchronize]; + rctSettingsManager->_ignoringUpdates = NO; + + resolve->asObject(runtime).asFunction(runtime).call(runtime, jsi::Value::undefined()); + return {}; + })); + }); + + + @try { + jsiRuntime.global().setProperty(jsiRuntime, "fetchConfigSync", std::move(fetchConfigSync)); + jsiRuntime.global().setProperty(jsiRuntime, "fetchConfigAsync", std::move(fetchConfigAsync)); + jsiRuntime.global().setProperty(jsiRuntime, "setSettingASync", std::move(setSettingASync)); + } @catch (NSException *exception) { + + } @finally { + + } +} + - (instancetype)initWithUserDefaults:(NSUserDefaults *)defaults { if ((self = [super init])) { @@ -72,9 +177,16 @@ - (void)userDefaultsDidChange:(NSNotification *)note #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" +/** + For development mode uncomment the bottom lines if you want to + debug in chrome runtime. Else use hermes or safari JSC runtime. +**/ + // [_bridge.eventDispatcher + // sendDeviceEventWithName:@"settingsUpdated" + // body:RCTJSONClean([_defaults dictionaryRepresentation])]; [_bridge.eventDispatcher - sendDeviceEventWithName:@"settingsUpdated" - body:RCTJSONClean([_defaults dictionaryRepresentation])]; + sendDeviceEventWithName:@"settingsUpdated" + body:@"updateConfig"]; #pragma clang diagnostic pop } @@ -120,6 +232,29 @@ - (void)userDefaultsDidChange:(NSNotification *)note return std::make_shared(self, jsInvoker, nativeInvoker, perfLogger); } + +RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(install) { + NSLog(@"Installing Runtime Manually..."); + RCTBridge *bridge = [RCTBridge currentBridge]; + RCTCxxBridge *cxxBridge = (RCTCxxBridge *)bridge; + if (cxxBridge == nil) { + return @false; + } + + using namespace facebook; + + auto jsiRuntime = (jsi::Runtime *)cxxBridge.runtime; + if (jsiRuntime == nil) { + return @false; + } + auto &runtime = *jsiRuntime; + auto callInvoker = bridge.jsCallInvoker; + + + installJsiBindings(runtime, self); + return @true; +} + @end Class RCTSettingsManagerCls(void) diff --git a/Libraries/Settings/Settings.ios.js b/Libraries/Settings/Settings.ios.js index cba39734037eeb..acd1fdddae223c 100644 --- a/Libraries/Settings/Settings.ios.js +++ b/Libraries/Settings/Settings.ios.js @@ -1,5 +1,5 @@ /** - * Copyright (c) Facebook, Inc. and its affiliates. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. @@ -8,13 +8,9 @@ * @flow */ -'use strict'; - -const RCTDeviceEventEmitter = require('../EventEmitter/RCTDeviceEventEmitter'); - -const invariant = require('invariant'); - +import RCTDeviceEventEmitter from '../EventEmitter/RCTDeviceEventEmitter'; import NativeSettingsManager from './NativeSettingsManager'; +import invariant from 'invariant'; const subscriptions: Array<{ keys: Array, @@ -27,12 +23,26 @@ const Settings = { NativeSettingsManager.getConstants().settings: any), get(key: string): mixed { + // $FlowFixMe[object-this-reference] return this._settings[key]; }, set(settings: Object) { + // $FlowFixMe[object-this-reference] this._settings = Object.assign(this._settings, settings); - NativeSettingsManager.setValues(settings); + try { + if (!global.setSettingASync) { + if (NativeSettingsManager && NativeSettingsManager.install()) { + global.setSettingASync(settings); + } + } else { + global.setSettingASync(settings); + } + } catch (error) { + NativeSettingsManager.setValues(settings); + } + //NativeSettingsManager.setValues(settings); + // In case of any issue umcomment above line }, watchKeys(keys: string | Array, callback: Function): number { @@ -59,7 +69,9 @@ const Settings = { _sendObservations(body: Object) { Object.keys(body).forEach(key => { const newValue = body[key]; + // $FlowFixMe[object-this-reference] const didChange = this._settings[key] !== newValue; + // $FlowFixMe[object-this-reference] this._settings[key] = newValue; if (didChange) { @@ -73,9 +85,46 @@ const Settings = { }, }; -RCTDeviceEventEmitter.addListener( - 'settingsUpdated', - Settings._sendObservations.bind(Settings), -); +/** + * Uncomment this for development / Debugging in Chrome v8 runtime + */ +// RCTDeviceEventEmitter.addListener( +// 'settingsUpdated', +// Settings._sendObservations.bind(Settings), +// ); + +function fetchConfig(): void { + global + .fetchConfigAsync() + .then(val => { + Settings._sendObservations.call(Settings, val); + }) + .catch(e => { + console.error(e); + // const config = global.fetchConfigSync(); + // Settings._sendObservations.call(Settings, config); + }); +} -module.exports = Settings; +RCTDeviceEventEmitter.addListener('settingsUpdated', function (command) { + try { + if (!global.fetchConfigAsync) { + if (NativeSettingsManager && NativeSettingsManager.install()) { + fetchConfig(); + } + } else { + fetchConfig(); + } + } catch (error) { + console.error(error, 'Settings Fetch Broke'); + } + + // global.fetchConfigAsync().then((val) => { + // Settings._sendObservations.call(Settings, val); + // }).catch(() => { + // const config = global.fetchConfigSync(); + // Settings._sendObservations.call(Settings, config); + // }) +}); + +module.exports = Settings; \ No newline at end of file