Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "SessionState.h"

#include <jsinspector-modern/RuntimeTarget.h>
#include <jsinspector-modern/RuntimeTargetGlobalStateObserver.h>
#include <jsinspector-modern/tracing/PerformanceTracer.h>

#include <utility>
Expand All @@ -16,21 +17,6 @@ using namespace facebook::jsi;

namespace facebook::react::jsinspector_modern {

namespace {

void emitSessionStatusChangeForObserverWithValue(
jsi::Runtime& runtime,
const jsi::Value& value) {
auto globalObj = runtime.global();
auto observer =
globalObj.getPropertyAsObject(runtime, "__DEBUGGER_SESSION_OBSERVER__");
auto onSessionStatusChange =
observer.getPropertyAsFunction(runtime, "onSessionStatusChange");
onSessionStatusChange.call(runtime, value);
}

} // namespace

std::shared_ptr<RuntimeTarget> RuntimeTarget::create(
const ExecutionContextDescription& executionContextDescription,
RuntimeTargetDelegate& delegate,
Expand Down Expand Up @@ -144,7 +130,11 @@ void RuntimeTarget::installBindingHandler(const std::string& bindingName) {
void RuntimeTarget::emitDebuggerSessionCreated() {
jsExecutor_([selfExecutor = executorFromThis()](jsi::Runtime& runtime) {
try {
emitSessionStatusChangeForObserverWithValue(runtime, jsi::Value(true));
emitGlobalStateObserverChange(
runtime,
"__DEBUGGER_SESSION_OBSERVER__",
"onSessionStatusChange",
true);
} catch (jsi::JSError&) {
// Suppress any errors, they should not be visible to the user
// and should not affect runtime.
Expand All @@ -155,7 +145,11 @@ void RuntimeTarget::emitDebuggerSessionCreated() {
void RuntimeTarget::emitDebuggerSessionDestroyed() {
jsExecutor_([selfExecutor = executorFromThis()](jsi::Runtime& runtime) {
try {
emitSessionStatusChangeForObserverWithValue(runtime, jsi::Value(false));
emitGlobalStateObserverChange(
runtime,
"__DEBUGGER_SESSION_OBSERVER__",
"onSessionStatusChange",
false);
} catch (jsi::JSError&) {
// Suppress any errors, they should not be visible to the user
// and should not affect runtime.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,106 +6,17 @@
*/

#include <jsinspector-modern/RuntimeTarget.h>
#include <jsinspector-modern/RuntimeTargetGlobalStateObserver.h>

namespace facebook::react::jsinspector_modern {

void RuntimeTarget::installDebuggerSessionObserver() {
jsExecutor_([](jsi::Runtime& runtime) {
auto globalObj = runtime.global();
try {
auto observer = jsi::Object(runtime);

observer.setProperty(runtime, "hasActiveSession", jsi::Value(false));

auto setFunction = globalObj.getPropertyAsFunction(runtime, "Set");
auto set = setFunction.callAsConstructor(runtime);
observer.setProperty(runtime, "subscribers", set);

observer.setProperty(
runtime,
"onSessionStatusChange",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "onSessionStatusChange"),
1,
[](jsi::Runtime& onSessionStatusChangeRuntime,
const jsi::Value& /* onSessionStatusChangeThisVal */,
const jsi::Value* onSessionStatusChangeArgs,
size_t onSessionStatusChangeArgsCount) {
if (onSessionStatusChangeArgsCount != 1 ||
!onSessionStatusChangeArgs[0].isBool()) {
throw jsi::JSError(
onSessionStatusChangeRuntime,
"Invalid arguments: onSessionStatusChange expects 1 boolean argument");
}

bool updatedStatus = onSessionStatusChangeArgs[0].getBool();

auto observerInstanceFromOnSessionStatusChange =
onSessionStatusChangeRuntime.global().getPropertyAsObject(
onSessionStatusChangeRuntime,
"__DEBUGGER_SESSION_OBSERVER__");
auto subscribersToNotify =
observerInstanceFromOnSessionStatusChange
.getPropertyAsObject(
onSessionStatusChangeRuntime, "subscribers");

observerInstanceFromOnSessionStatusChange.setProperty(
onSessionStatusChangeRuntime,
"hasActiveSession",
updatedStatus);

if (subscribersToNotify
.getProperty(onSessionStatusChangeRuntime, "size")
.asNumber() == 0) {
return jsi::Value::undefined();
}

auto forEachSubscriber =
subscribersToNotify.getPropertyAsFunction(
onSessionStatusChangeRuntime, "forEach");
auto forEachSubscriberCallback =
jsi::Function::createFromHostFunction(
onSessionStatusChangeRuntime,
jsi::PropNameID::forAscii(
onSessionStatusChangeRuntime, "forEachCallback"),
1,
[updatedStatus](
jsi::Runtime& forEachCallbackRuntime,
const jsi::Value& /* forEachCallbackThisVal */,
const jsi::Value* forEachCallbackArgs,
size_t forEachCallbackArgsCount) {
if (forEachCallbackArgsCount < 1 ||
!forEachCallbackArgs[0].isObject() ||
!forEachCallbackArgs[0]
.getObject(forEachCallbackRuntime)
.isFunction(forEachCallbackRuntime)) {
throw jsi::JSError(
forEachCallbackRuntime,
"Invalid arguments: forEachSubscriberCallback expects function as a first argument");
}

forEachCallbackArgs[0]
.getObject(forEachCallbackRuntime)
.asFunction(forEachCallbackRuntime)
.call(forEachCallbackRuntime, updatedStatus);

return jsi::Value::undefined();
});

forEachSubscriber.callWithThis(
onSessionStatusChangeRuntime,
subscribersToNotify,
forEachSubscriberCallback);

return jsi::Value::undefined();
}));

globalObj.setProperty(runtime, "__DEBUGGER_SESSION_OBSERVER__", observer);
} catch (jsi::JSError&) {
// Suppress any errors, they should not be visible to the user
// and should not affect runtime.
}
installGlobalStateObserver(
runtime,
"__DEBUGGER_SESSION_OBSERVER__",
"hasActiveSession",
"onSessionStatusChange");
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* 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.
*/

#include "RuntimeTargetGlobalStateObserver.h"

#include <string>

namespace facebook::react::jsinspector_modern {

void installGlobalStateObserver(
jsi::Runtime& runtime,
const char* globalName,
const char* statusProperty,
const char* callbackName) {
auto globalObj = runtime.global();
try {
auto observer = jsi::Object(runtime);

observer.setProperty(runtime, statusProperty, jsi::Value(false));

auto setFunction = globalObj.getPropertyAsFunction(runtime, "Set");
auto set = setFunction.callAsConstructor(runtime);
observer.setProperty(runtime, "subscribers", set);

std::string globalNameStr(globalName);
std::string statusPropertyStr(statusProperty);

observer.setProperty(
runtime,
callbackName,
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, callbackName),
1,
[globalNameStr, statusPropertyStr](
jsi::Runtime& callbackRuntime,
const jsi::Value& /* thisVal */,
const jsi::Value* args,
size_t argsCount) {
if (argsCount != 1 || !args[0].isBool()) {
throw jsi::JSError(
callbackRuntime,
"Invalid arguments: state change callback expects 1 boolean argument");
}

bool updatedStatus = args[0].getBool();

auto observerInstance =
callbackRuntime.global().getPropertyAsObject(
callbackRuntime, globalNameStr.c_str());
auto subscribersToNotify = observerInstance.getPropertyAsObject(
callbackRuntime, "subscribers");

observerInstance.setProperty(
callbackRuntime, statusPropertyStr.c_str(), updatedStatus);

if (subscribersToNotify.getProperty(callbackRuntime, "size")
.asNumber() == 0) {
return jsi::Value::undefined();
}

auto forEachSubscriber =
subscribersToNotify.getPropertyAsFunction(
callbackRuntime, "forEach");
auto forEachSubscriberCallback = jsi::Function::createFromHostFunction(
callbackRuntime,
jsi::PropNameID::forAscii(callbackRuntime, "forEachCallback"),
1,
[updatedStatus](
jsi::Runtime& forEachCallbackRuntime,
const jsi::Value& /* forEachCallbackThisVal */,
const jsi::Value* forEachCallbackArgs,
size_t forEachCallbackArgsCount) {
if (forEachCallbackArgsCount < 1 ||
!forEachCallbackArgs[0].isObject() ||
!forEachCallbackArgs[0]
.getObject(forEachCallbackRuntime)
.isFunction(forEachCallbackRuntime)) {
throw jsi::JSError(
forEachCallbackRuntime,
"Invalid arguments: forEachSubscriberCallback expects function as a first argument");
}

forEachCallbackArgs[0]
.getObject(forEachCallbackRuntime)
.asFunction(forEachCallbackRuntime)
.call(forEachCallbackRuntime, updatedStatus);

return jsi::Value::undefined();
});

forEachSubscriber.callWithThis(
callbackRuntime,
subscribersToNotify,
forEachSubscriberCallback);

return jsi::Value::undefined();
}));

globalObj.setProperty(runtime, globalName, observer);
} catch (jsi::JSError&) {
// Suppress any errors, they should not be visible to the user
// and should not affect runtime.
}
}

void emitGlobalStateObserverChange(
jsi::Runtime& runtime,
const char* globalName,
const char* callbackName,
bool value) {
auto globalObj = runtime.global();
auto observer = globalObj.getPropertyAsObject(runtime, globalName);
auto callback = observer.getPropertyAsFunction(runtime, callbackName);
callback.call(runtime, jsi::Value(value));
}

} // namespace facebook::react::jsinspector_modern
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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.
*/

#pragma once

#include <jsi/jsi.h>

namespace facebook::react::jsinspector_modern {

/**
* Installs a global state observer object on the JavaScript runtime's global
* object. The observer has a boolean status property, a Set of subscribers,
* and a callback that updates the status and notifies subscribers.
*
* @param globalName The name of the global object (e.g.,
* "__DEBUGGER_SESSION_OBSERVER__").
* @param statusProperty The name of the boolean property (e.g.,
* "hasActiveSession").
* @param callbackName The name of the state change callback (e.g.,
* "onSessionStatusChange").
*/
void installGlobalStateObserver(
jsi::Runtime &runtime,
const char *globalName,
const char *statusProperty,
const char *callbackName);

/**
* Emits a state change to an installed global state observer by calling its
* callback function.
*
* @param globalName The name of the global object.
* @param callbackName The name of the state change callback.
* @param value The new boolean state value.
*/
void emitGlobalStateObserverChange(jsi::Runtime &runtime, const char *globalName, const char *callbackName, bool value);

} // namespace facebook::react::jsinspector_modern
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,21 @@
* @format
*/

class FuseboxSessionObserver {
#hasNativeSupport: boolean;
import GlobalStateObserver from './GlobalStateObserver';

constructor() {
this.#hasNativeSupport = global.hasOwnProperty(
'__DEBUGGER_SESSION_OBSERVER__',
);
}
const observer = new GlobalStateObserver(
'__DEBUGGER_SESSION_OBSERVER__',
'hasActiveSession',
);

const FuseboxSessionObserver = {
hasActiveSession(): boolean {
if (!this.#hasNativeSupport) {
return false;
}

return global.__DEBUGGER_SESSION_OBSERVER__.hasActiveSession;
}
return observer.getStatus();
},

subscribe(callback: (status: boolean) => void): () => void {
if (!this.#hasNativeSupport) {
return () => {};
}

global.__DEBUGGER_SESSION_OBSERVER__.subscribers.add(callback);
return () => {
global.__DEBUGGER_SESSION_OBSERVER__.subscribers.delete(callback);
};
}
}
return observer.subscribe(callback);
},
};

const observerInstance: FuseboxSessionObserver = new FuseboxSessionObserver();
export default observerInstance;
export default FuseboxSessionObserver;
Loading
Loading