Commit ad4b9dcb9b for freeswitch.com

commit ad4b9dcb9bac1270fa60f55149c79c0152a994ba
Author: Andrey Volk <andywolk@gmail.com>
Date:   Wed Oct 28 14:48:33 2026 +0300

    [mod_v8, Build-System] Port to the V8 13.6 API of Node.js 24 libnode. Windows: Upgrade libnode to v24.22.1_1 and take it from windows-deps (#3249)

diff --git a/src/mod/languages/mod_v8/Makefile.am b/src/mod/languages/mod_v8/Makefile.am
index bb14928ae9..743d073448 100644
--- a/src/mod/languages/mod_v8/Makefile.am
+++ b/src/mod/languages/mod_v8/Makefile.am
@@ -5,7 +5,7 @@ AUTOMAKE_OPTIONS += foreign
 MODNAME=mod_v8

 AM_CFLAGS    += -I. -I./include -I$(switch_srcdir)/src/mod/languages/mod_v8/include $(V8_CFLAGS)
-AM_CPPFLAGS  += -I. -I./include -I$(switch_srcdir)/src/mod/languages/mod_v8/include $(V8_CFLAGS) -std=c++17
+AM_CPPFLAGS  += -I. -I./include -I$(switch_srcdir)/src/mod/languages/mod_v8/include $(V8_CFLAGS) -std=c++20
 AM_LDFLAGS   += -avoid-version -module -no-undefined -shared

 AM_LIBADD     = $(switch_builddir)/libfreeswitch.la
diff --git a/src/mod/languages/mod_v8/include/javascript.hpp b/src/mod/languages/mod_v8/include/javascript.hpp
index 35d30ea570..769e2f7e68 100644
--- a/src/mod/languages/mod_v8/include/javascript.hpp
+++ b/src/mod/languages/mod_v8/include/javascript.hpp
@@ -205,7 +205,7 @@ static inline v8::Local<v8::StackFrame> js_stack_frame(v8::Local<v8::StackTrace>

 /* Macro for easy V8 "get property" callback definition */
 #define JS_GET_PROPERTY_DEF(method_name, class_name) \
-	static void method_name(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& info)\
+	static void method_name(v8::Local<v8::Name> property, const v8::PropertyCallbackInfo<v8::Value>& info)\
 	{\
 		JS_CHECK_SCRIPT_STATE();\
 		class_name *obj = JSBase::GetInstance<class_name>(info.Holder());\
@@ -220,11 +220,11 @@ static inline v8::Local<v8::StackFrame> js_stack_frame(v8::Local<v8::StackTrace>
 			info.GetReturnValue().Set(false);\
 		}\
 	}\
-	void method_name##Impl(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& info)
+	void method_name##Impl(v8::Local<v8::Name> property, const v8::PropertyCallbackInfo<v8::Value>& info)

 /* Macro for easy V8 "set property" callback definition */
 #define JS_SET_PROPERTY_DEF(method_name, class_name) \
-	static void method_name(v8::Local<v8::String> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info)\
+	static void method_name(v8::Local<v8::Name> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info)\
 	{\
 		JS_CHECK_SCRIPT_STATE();\
 		class_name *obj = JSBase::GetInstance<class_name>(info.Holder());\
@@ -239,20 +239,20 @@ static inline v8::Local<v8::StackFrame> js_stack_frame(v8::Local<v8::StackTrace>
 			/* A property setter's ReturnValue is void; V8 7+ rejects Set() on it. */\
 		}\
 	}\
-	void method_name##Impl(v8::Local<v8::String> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info)
+	void method_name##Impl(v8::Local<v8::Name> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info)

 /* Macro for easy V8 "function" callback definition */
 #define JS_FUNCTION_DEF(method_name, class_name) \
 	static void method_name(const v8::FunctionCallbackInfo<v8::Value>& info)\
 	{\
 		JS_CHECK_SCRIPT_STATE();\
-		class_name *obj = JSBase::GetInstance<class_name>(info.Holder());\
+		class_name *obj = JSBase::GetInstance<class_name>(info.This());\
 		if (obj) {\
 			obj->method_name##Impl(info);\
 		} else {\
 			int line;\
 			char *file = JSMain::GetStackInfo(info.GetIsolate(), &line);\
-			JsUtf8Value str(info.Holder());\
+			JsUtf8Value str(info.This());\
 			switch_log_printf(SWITCH_CHANNEL_ID_LOG, file, "mod_v8", line, NULL, SWITCH_LOG_DEBUG, "No valid internal data available for %s when calling %s\n", *str ? *str : "[unknown]", #class_name "::" #method_name "()");\
 			free(file);\
 			info.GetReturnValue().Set(false);\
@@ -261,18 +261,18 @@ static inline v8::Local<v8::StackFrame> js_stack_frame(v8::Local<v8::StackTrace>
 	void method_name##Impl(const v8::FunctionCallbackInfo<v8::Value>& info)

 /* Macros for V8 callback implementations */
-#define JS_GET_PROPERTY_IMPL(method_name, class_name) void class_name::method_name##Impl(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& info)
-#define JS_SET_PROPERTY_IMPL(method_name, class_name) void class_name::method_name##Impl(v8::Local<v8::String> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info)
+#define JS_GET_PROPERTY_IMPL(method_name, class_name) void class_name::method_name##Impl(v8::Local<v8::Name> property, const v8::PropertyCallbackInfo<v8::Value>& info)
+#define JS_SET_PROPERTY_IMPL(method_name, class_name) void class_name::method_name##Impl(v8::Local<v8::Name> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info)
 #define JS_FUNCTION_IMPL(method_name, class_name) void class_name::method_name##Impl(const v8::FunctionCallbackInfo<v8::Value>& info)

 /* Macros for V8 callback definitions (class static version) */
-#define JS_GET_PROPERTY_DEF_STATIC(method_name) static void method_name(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& info)
-#define JS_SET_PROPERTY_DEF_STATIC(method_name) static void method_name(v8::Local<v8::String> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info)
+#define JS_GET_PROPERTY_DEF_STATIC(method_name) static void method_name(v8::Local<v8::Name> property, const v8::PropertyCallbackInfo<v8::Value>& info)
+#define JS_SET_PROPERTY_DEF_STATIC(method_name) static void method_name(v8::Local<v8::Name> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info)
 #define JS_FUNCTION_DEF_STATIC(method_name) static void method_name(const v8::FunctionCallbackInfo<v8::Value>& info)

 /* Macros for V8 callback implementations (class static version) */
-#define JS_GET_PROPERTY_IMPL_STATIC(method_name, class_name) void class_name::method_name(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& info)
-#define JS_SET_PROPERTY_IMPL_STATIC(method_name, class_name) void class_name::method_name(v8::Local<v8::String> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info)
+#define JS_GET_PROPERTY_IMPL_STATIC(method_name, class_name) void class_name::method_name(v8::Local<v8::Name> property, const v8::PropertyCallbackInfo<v8::Value>& info)
+#define JS_SET_PROPERTY_IMPL_STATIC(method_name, class_name) void class_name::method_name(v8::Local<v8::Name> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info)
 #define JS_FUNCTION_IMPL_STATIC(method_name, class_name) void class_name::method_name(const v8::FunctionCallbackInfo<v8::Value>& info)

 /* Macro for basic script state check (to know if the script is being terminated), should be called before calling any callback actual code */
@@ -321,8 +321,8 @@ typedef struct {
 /* JS Property definition */
 typedef struct {
 	const char *name;						/* Name of the property */
-	v8::AccessorGetterCallback get;			/* The property getter */
-	v8::AccessorSetterCallback set;			/* The property setter */
+	v8::AccessorNameGetterCallback get;			/* The property getter */
+	v8::AccessorNameSetterCallback set;			/* The property setter */
 } js_property_t;

 /* JS Class definition */
@@ -395,13 +395,18 @@ public:
 	template <typename T> static T *GetInstance(const v8::FunctionCallbackInfo<v8::Value>& info)
 	{
 		v8::HandleScope scope(info.GetIsolate());
-		return GetInstance<T>(info.Holder());
+		return GetInstance<T>(info.This());
 	}

 	/* Get a JSBase instance from a JavaScript object */
 	template <typename T> static T *GetInstance(const v8::Local<v8::Object>& self)
 	{
-		v8::Local<v8::Value> val = self->GetInternalField(0);
+		/* Scripts can hand any object in here (as a receiver or an argument); only ours carry field 0 */
+		if (self.IsEmpty() || self->InternalFieldCount() < 1) {
+			return NULL;
+		}
+
+		v8::Local<v8::Value> val = self->GetInternalField(0).As<v8::Value>();

 		if (!val.IsEmpty() && val->IsExternal()) {
 			v8::Local<v8::External> wrap = v8::Local<v8::External>::Cast(val);
@@ -416,7 +421,7 @@ public:
 	static v8::Local<v8::Function> GetFunctionFromArg(v8::Isolate *isolate, const v8::Local<v8::Value>& arg);

 	/* Default JS setter callback, to be used for read only values */
-	static void DefaultSetProperty(v8::Local<v8::String> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info);
+	static void DefaultSetProperty(v8::Local<v8::Name> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info);

 	/* Get the name of the JavaScript class - must be overridden by the actual implementation */
 	virtual std::string GetJSClassName() = 0;
diff --git a/src/mod/languages/mod_v8/src/fsevent.cpp b/src/mod/languages/mod_v8/src/fsevent.cpp
index 745e238913..09640992e3 100644
--- a/src/mod/languages/mod_v8/src/fsevent.cpp
+++ b/src/mod/languages/mod_v8/src/fsevent.cpp
@@ -348,7 +348,7 @@ JS_EVENT_FUNCTION_IMPL_STATIC(Fire)
 {
 	JS_CHECK_SCRIPT_STATE();
 	HandleScope handle_scope(info.GetIsolate());
-	FSEvent *obj = JSBase::GetInstance<FSEvent>(info.Holder());
+	FSEvent *obj = JSBase::GetInstance<FSEvent>(info.This());

 	if (obj && obj->_event) {
 		switch_event_fire(&obj->_event);
@@ -393,7 +393,7 @@ JS_EVENT_FUNCTION_IMPL_STATIC(Destroy)
 {
 	JS_CHECK_SCRIPT_STATE();
 	HandleScope handle_scope(info.GetIsolate());
-	FSEvent *obj = JSBase::GetInstance<FSEvent>(info.Holder());
+	FSEvent *obj = JSBase::GetInstance<FSEvent>(info.This());

 	if (obj) {
 		delete obj;
diff --git a/src/mod/languages/mod_v8/src/fseventhandler.cpp b/src/mod/languages/mod_v8/src/fseventhandler.cpp
index 2fa40bba8f..b7a0777ffd 100644
--- a/src/mod/languages/mod_v8/src/fseventhandler.cpp
+++ b/src/mod/languages/mod_v8/src/fseventhandler.cpp
@@ -572,7 +572,7 @@ JS_EVENTHANDLER_FUNCTION_IMPL_STATIC(Destroy)
 {
 	JS_CHECK_SCRIPT_STATE();

-	FSEventHandler *obj = JSBase::GetInstance<FSEventHandler>(info.Holder());
+	FSEventHandler *obj = JSBase::GetInstance<FSEventHandler>(info.This());

 	if (obj) {
 		delete obj;
diff --git a/src/mod/languages/mod_v8/src/fssession.cpp b/src/mod/languages/mod_v8/src/fssession.cpp
index c055cc3854..f478975507 100644
--- a/src/mod/languages/mod_v8/src/fssession.cpp
+++ b/src/mod/languages/mod_v8/src/fssession.cpp
@@ -1836,7 +1836,7 @@ JS_SESSION_FUNCTION_IMPL(Bridge)

 			cb_state.jss_a = this;
 			cb_state.jss_b = jss_b;
-			cb_state.session_obj_a.Reset(info.GetIsolate(), info.Holder());
+			cb_state.session_obj_a.Reset(info.GetIsolate(), info.This());
 			cb_state.session_obj_b.Reset(info.GetIsolate(), obj_b);
 			cb_state.session_state = this;
 			cb_state.context.Reset(info.GetIsolate(), info.GetIsolate()->GetCurrentContext());
diff --git a/src/mod/languages/mod_v8/src/fsxml.cpp b/src/mod/languages/mod_v8/src/fsxml.cpp
index 93de0547da..76a379bf15 100644
--- a/src/mod/languages/mod_v8/src/fsxml.cpp
+++ b/src/mod/languages/mod_v8/src/fsxml.cpp
@@ -129,7 +129,7 @@ Local<Value> FSXML::GetJSObjFromXMLObj(const switch_xml_t xml, const v8::Functio
 		/* Create a new object if it wasn't found in the hash */
 		if ((newObj = new FSXML(info))) {
 			if (_rootJSObject.IsEmpty()) {
-				newObj->_rootJSObject.Reset(info.GetIsolate(), info.Holder()); /* The caller is the owner */
+				newObj->_rootJSObject.Reset(info.GetIsolate(), info.This()); /* The caller is the owner */
 			} else {
 				newObj->_rootJSObject.Reset(info.GetIsolate(), _rootJSObject); /* The owner is stored in the persistent object */
 			}
@@ -354,7 +354,7 @@ JS_XML_IMPL_STATIC(Remove)
 		}
 		delete obj;
 	} else {
-		JsUtf8Value str(info.Holder());
+		JsUtf8Value str(info.This());
 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "No valid internal data available for %s when calling FSXML::Remove()\n", *str ? *str : "[unknown]");
 	}
 }
diff --git a/src/mod/languages/mod_v8/src/jsbase.cpp b/src/mod/languages/mod_v8/src/jsbase.cpp
index 85df1715b7..f4ae4e3451 100644
--- a/src/mod/languages/mod_v8/src/jsbase.cpp
+++ b/src/mod/languages/mod_v8/src/jsbase.cpp
@@ -217,7 +217,7 @@ void JSBase::Register(Isolate *isolate, const js_class_definition_t *desc)
 	// Add properties to the object
 	for (int i = 0;; i++) {
 		if (!desc->properties[i].get) break;
-		function->InstanceTemplate()->SetAccessor(js_new_string(isolate, desc->properties[i].name), desc->properties[i].get, desc->properties[i].set);
+		function->InstanceTemplate()->SetNativeDataProperty(js_new_string(isolate, desc->properties[i].name), desc->properties[i].get, desc->properties[i].set);
 	}

 #if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
@@ -287,7 +287,7 @@ Local<Function> JSBase::GetFunctionFromArg(Isolate *isolate, const Local<Value>&
 	}
 }

-void JSBase::DefaultSetProperty(v8::Local<v8::String> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info)
+void JSBase::DefaultSetProperty(v8::Local<v8::Name> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info)
 {
 	info.GetIsolate()->ThrowException(js_new_string(info.GetIsolate(), "this property cannot be changed!"));
 }
diff --git a/src/mod/languages/mod_v8/src/jsmain.cpp b/src/mod/languages/mod_v8/src/jsmain.cpp
index 93662f68eb..fc4a8bf5cf 100644
--- a/src/mod/languages/mod_v8/src/jsmain.cpp
+++ b/src/mod/languages/mod_v8/src/jsmain.cpp
@@ -173,7 +173,9 @@ JSMain::~JSMain(void)
 	if (forcedTerminationMessage) free(forcedTerminationMessage);
 	if (forcedTerminationScriptFile) free(forcedTerminationScriptFile);

-	isolate->Dispose();
+	if (isolate) {
+		isolate->Dispose();
+	}
 }

 const string JSMain::GetExceptionInfo(Isolate* isolate, TryCatch* try_catch)
@@ -471,7 +473,6 @@ void JSMain::Dispose()
 	// Make sure to cleanup properly!
 #if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
 	v8::Isolate::GetCurrent()->LowMemoryNotification();
-	while (!v8::Isolate::GetCurrent()->IdleNotificationDeadline(0.500)) {}
 	V8::Dispose();
 #if V8_MAJOR_VERSION >= 10
   /* ShutdownPlatform() became a deprecated alias for DisposePlatform() in
diff --git a/w32/libnode-version.props b/w32/libnode-version.props
index 1204ab2b59..2a7565207a 100644
--- a/w32/libnode-version.props
+++ b/w32/libnode-version.props
@@ -4,7 +4,8 @@
     <Import Project="basedir.props" Condition=" '$(BaseDirImported)' == ''"/>
   </ImportGroup>
   <PropertyGroup Label="UserMacros">
-    <LibNodeVersion>20.19.2</LibNodeVersion>
+    <LibNodeVersion>24.22.1</LibNodeVersion>
+    <LibNodeBuildNumber>1</LibNodeBuildNumber>
   </PropertyGroup>
   <PropertyGroup>
     <LibNodeVersionImported>true</LibNodeVersionImported>
diff --git a/w32/libnode.props b/w32/libnode.props
index 35971bace2..afe68a0fbe 100644
--- a/w32/libnode.props
+++ b/w32/libnode.props
@@ -6,21 +6,21 @@
   </ImportGroup>

   <PropertyGroup Label="UserMacros">
-    <LibNodeDir>$(BaseDir)libs\libnode-$(LibNodeVersion)</LibNodeDir>
+    <LibNodeDir>$(BaseDir)libs\libnode-$(LibNodeVersion)_$(LibNodeBuildNumber)</LibNodeDir>
   </PropertyGroup>

-  <!--
+  <!--
        Download Target.
-       Name must be unique.
+       Name must be unique.
        By design, targets are executed only once per project.
-
+
        Usage:
-
+
        package: URI

        expectfileordirectory: Skips the download and extraction if exists

-       outputfolder: Folder to store a downloaded file.
+       outputfolder: Folder to store a downloaded file.
                      By default "$(BaseDir)libs", if empty

        outputfilename: If not empty, overrides filename from URI.
@@ -31,7 +31,7 @@

   <Target Name="libnodeBinariesDownloadTarget" BeforeTargets="CustomBuild" DependsOnTargets="7za">
     <DownloadPackageTask
-        package="https://github.com/freeswitch/libnode-packaging/releases/download/libnode-v$(LibNodeVersion)/libnode-$(LibNodeVersion)-binaries-$(Platform.ToLower())-$(Configuration.ToLower()).zip"
+        package="https://github.com/freeswitch/windows-deps/releases/download/libnode-v$(LibNodeVersion)_$(LibNodeBuildNumber)/libnode-$(LibNodeVersion)_$(LibNodeBuildNumber)-binaries-$(Platform.ToLower())-$(Configuration.ToLower()).zip"
         expectfileordirectory="$(LibNodeDir)\binaries\$(Platform)\$(Configuration)\libnode.dll"
         outputfolder=""
         outputfilename=""
@@ -40,7 +40,7 @@
   </Target>
   <Target Name="libnodeHeadersDownloadTarget" BeforeTargets="CustomBuild" DependsOnTargets="7za">
     <DownloadPackageTask
-        package="https://github.com/freeswitch/libnode-packaging/releases/download/libnode-v$(LibNodeVersion)/libnode-$(LibNodeVersion)-headers.zip"
+        package="https://github.com/freeswitch/windows-deps/releases/download/libnode-v$(LibNodeVersion)_$(LibNodeBuildNumber)/libnode-$(LibNodeVersion)_$(LibNodeBuildNumber)-headers.zip"
         expectfileordirectory="$(LibNodeDir)\include\v8.h"
         outputfolder=""
         outputfilename=""
@@ -48,12 +48,15 @@
     />
   </Target>

+  <!-- Copied whenever it differs, not only when absent: a libnode.dll of another
+       version left in the output folder would not match the libnode.lib mod_v8
+       links against. -->
   <Target Name="libnodeCopyTarget" BeforeTargets="CustomBuild" DependsOnTargets="libnodeBinariesDownloadTarget">
     <Message Text="Copying libnode.dll to the freeswitch output folder." Importance="High" />
     <ItemGroup>
       <libnodeFiles Include="$(LibNodeDir)\binaries\$(Platform)\$(Configuration)\*.dll"/>
     </ItemGroup>
-    <Copy Condition="!exists('$(BaseDir)\$(Platform)\$(Configuration)\libnode.dll')"
+    <Copy SkipUnchangedFiles="true"
           SourceFiles="@(libnodeFiles)"
           DestinationFiles="@(libnodeFiles->'$(BaseDir)\$(Platform)\$(Configuration)\%(Filename)%(Extension)')"
     />
@@ -68,11 +71,11 @@
            `#if __cplusplus <= 201703L` fires even with /std:c++20. Set both here. -->
       <LanguageStandard>stdcpp20</LanguageStandard>
       <AdditionalOptions>/Zc:__cplusplus %(AdditionalOptions)</AdditionalOptions>
-      <AdditionalIncludeDirectories>$(SolutionDir)libs\libnode-$(LibNodeVersion)\include;$(SolutionDir)src\mod\languages\mod_v8;$(SolutionDir)src\mod\languages\mod_v8\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>$(LibNodeDir)\include;$(SolutionDir)src\mod\languages\mod_v8;$(SolutionDir)src\mod\languages\mod_v8\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>USING_V8_SHARED;USING_V8_PLATFORM_SHARED;%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <Link>
-      <AdditionalLibraryDirectories>$(SolutionDir)libs\libnode-$(LibNodeVersion)\binaries\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalLibraryDirectories>$(LibNodeDir)\binaries\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <AdditionalDependencies>libnode.lib;%(AdditionalDependencies)</AdditionalDependencies>
     </Link>
   </ItemDefinitionGroup>