66#include < jni.h>
77#include < unordered_map>
88#include < cstring>
9+ #include < string>
910
1011namespace tns {
1112
@@ -183,36 +184,75 @@ bool HttpFetchText(const std::string& url, std::string& out, std::string& conten
183184 try {
184185 JEnv env;
185186
187+ // Allow network operations on the current thread (dev-only HMR path)
188+ // Some Android environments enforce StrictMode which throws NetworkOnMainThreadException
189+ // when performing network I/O on the main thread. Since this fetch runs on the JS/V8 thread
190+ // during development, explicitly relax the policy here.
191+ {
192+ jclass clsStrict = env.FindClass (" android/os/StrictMode" );
193+ jclass clsPolicyBuilder = env.FindClass (" android/os/StrictMode$ThreadPolicy$Builder" );
194+ if (clsStrict && clsPolicyBuilder) {
195+ jmethodID builderCtor = env.GetMethodID (clsPolicyBuilder, " <init>" , " ()V" );
196+ jobject builder = env.NewObject (clsPolicyBuilder, builderCtor);
197+ if (builder) {
198+ jmethodID permitAll = env.GetMethodID (clsPolicyBuilder, " permitAll" , " ()Landroid/os/StrictMode$ThreadPolicy$Builder;" );
199+ jobject builder2 = permitAll ? env.CallObjectMethod (builder, permitAll) : builder;
200+ jmethodID build = env.GetMethodID (clsPolicyBuilder, " build" , " ()Landroid/os/StrictMode$ThreadPolicy;" );
201+ jobject policy = build ? env.CallObjectMethod (builder2 ? builder2 : builder, build) : nullptr ;
202+ if (policy) {
203+ jmethodID setThreadPolicy = env.GetStaticMethodID (clsStrict, " setThreadPolicy" , " (Landroid/os/StrictMode$ThreadPolicy;)V" );
204+ if (setThreadPolicy) {
205+ env.CallStaticVoidMethod (clsStrict, setThreadPolicy, policy);
206+ }
207+ }
208+ }
209+ }
210+ }
211+
186212 jclass clsURL = env.FindClass (" java/net/URL" );
187213 if (!clsURL) return false ;
188214 jmethodID urlCtor = env.GetMethodID (clsURL, " <init>" , " (Ljava/lang/String;)V" );
189215 jmethodID openConnection = env.GetMethodID (clsURL, " openConnection" , " ()Ljava/net/URLConnection;" );
190216 jstring jUrlStr = env.NewStringUTF (url.c_str ());
191217 jobject urlObj = env.NewObject (clsURL, urlCtor, jUrlStr);
192218
193- jobject conn = env.CallObjectMethod (urlObj, openConnection);
219+ jobject conn = env.CallObjectMethod (urlObj, openConnection);
194220 if (!conn) return false ;
195221
196222 jclass clsConn = env.GetObjectClass (conn);
197223 jmethodID setConnectTimeout = env.GetMethodID (clsConn, " setConnectTimeout" , " (I)V" );
198224 jmethodID setReadTimeout = env.GetMethodID (clsConn, " setReadTimeout" , " (I)V" );
225+ jmethodID setDoInput = env.GetMethodID (clsConn, " setDoInput" , " (Z)V" );
226+ jmethodID setUseCaches = env.GetMethodID (clsConn, " setUseCaches" , " (Z)V" );
199227 jmethodID setReqProp = env.GetMethodID (clsConn, " setRequestProperty" , " (Ljava/lang/String;Ljava/lang/String;)V" );
200- env.CallVoidMethod (conn, setConnectTimeout, 5000 );
201- env.CallVoidMethod (conn, setReadTimeout, 5000 );
202- env.CallVoidMethod (conn, setReqProp, env.NewStringUTF (" Accept" ), env.NewStringUTF (" application/javascript, text/javascript, */*;q=0.1" ));
203- env.CallVoidMethod (conn, setReqProp, env.NewStringUTF (" Accept-Encoding" ), env.NewStringUTF (" identity" ));
228+ env.CallVoidMethod (conn, setConnectTimeout, 15000 );
229+ env.CallVoidMethod (conn, setReadTimeout, 15000 );
230+ if (setDoInput) { env.CallVoidMethod (conn, setDoInput, JNI_TRUE); }
231+ if (setUseCaches) { env.CallVoidMethod (conn, setUseCaches, JNI_FALSE); }
232+ env.CallVoidMethod (conn, setReqProp, env.NewStringUTF (" Accept" ), env.NewStringUTF (" application/javascript, text/javascript, */*;q=0.1" ));
233+ env.CallVoidMethod (conn, setReqProp, env.NewStringUTF (" Accept-Encoding" ), env.NewStringUTF (" identity" ));
234+ env.CallVoidMethod (conn, setReqProp, env.NewStringUTF (" Cache-Control" ), env.NewStringUTF (" no-cache" ));
235+ env.CallVoidMethod (conn, setReqProp, env.NewStringUTF (" Connection" ), env.NewStringUTF (" close" ));
236+ env.CallVoidMethod (conn, setReqProp, env.NewStringUTF (" User-Agent" ), env.NewStringUTF (" NativeScript-HTTP-ESM" ));
204237
205238 // Try to get status via HttpURLConnection if possible
206239 jclass clsHttp = env.FindClass (" java/net/HttpURLConnection" );
207240 bool isHttp = clsHttp && env.IsInstanceOf (conn, clsHttp);
208241 jmethodID getResponseCode = isHttp ? env.GetMethodID (clsHttp, " getResponseCode" , " ()I" ) : nullptr ;
242+ jmethodID getErrorStream = isHttp ? env.GetMethodID (clsHttp, " getErrorStream" , " ()Ljava/io/InputStream;" ) : nullptr ;
209243 if (isHttp && getResponseCode) {
210244 status = env.CallIntMethod (conn, getResponseCode);
211245 }
212246
213- // Read InputStream
247+ // Read InputStream (prefer error stream on HTTP error codes)
214248 jmethodID getInputStream = env.GetMethodID (clsConn, " getInputStream" , " ()Ljava/io/InputStream;" );
215- jobject inStream = env.CallObjectMethod (conn, getInputStream);
249+ jobject inStream = nullptr ;
250+ if (isHttp && status >= 400 && getErrorStream) {
251+ inStream = env.CallObjectMethod (conn, getErrorStream);
252+ }
253+ if (!inStream) {
254+ inStream = env.CallObjectMethod (conn, getInputStream);
255+ }
216256 if (!inStream) return false ;
217257
218258 jclass clsIS = env.GetObjectClass (inStream);
@@ -229,7 +269,11 @@ bool HttpFetchText(const std::string& url, std::string& out, std::string& conten
229269 jbyteArray buffer = env.NewByteArray (8192 );
230270 while (true ) {
231271 jint n = env.CallIntMethod (inStream, readMethod, buffer);
232- if (n <= 0 ) break ;
272+ if (n < 0 ) break ; // -1 indicates EOF
273+ if (n == 0 ) {
274+ // Defensive: continue reading if zero bytes returned
275+ continue ;
276+ }
233277 env.CallVoidMethod (baos, baosWrite, buffer, 0 , n);
234278 }
235279
0 commit comments