pakstr 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,14 +6,37 @@ import fi.iki.elonen.NanoHTTPD
6
6
  import java.io.IOException
7
7
  import java.util.Locale
8
8
 
9
- class LocalServer(private val context: Context, port: Int) : NanoHTTPD(port) {
9
+ import java.net.HttpURLConnection
10
+ import java.net.URL
11
+
12
+ class LocalServer(
13
+
14
+ private val context: Context,
15
+
16
+ port: Int
17
+
18
+ ) : NanoHTTPD(port) {
10
19
 
11
20
  override fun serve(session: IHTTPSession?): Response {
12
21
  // Fallback to root if URI is null
13
22
  val uri =
23
+
14
24
  (session?.uri ?: "/")
25
+
15
26
  .replace("..", "")
16
27
 
28
+ if (uri.startsWith("/api/")) {
29
+
30
+ return proxyApi(
31
+
32
+ session,
33
+
34
+ uri
35
+
36
+ )
37
+
38
+ }
39
+
17
40
  // Map the root URL to index.html, otherwise target the requested file in the assets folder
18
41
  val filePath = when {
19
42
 
@@ -73,4 +96,72 @@ class LocalServer(private val context: Context, port: Int) : NanoHTTPD(port) {
73
96
  ?: "application/octet-stream"
74
97
  }
75
98
  }
99
+
100
+ private fun proxyApi(
101
+ session: IHTTPSession?,
102
+ uri: String
103
+ ): Response {
104
+
105
+ val apiBase =
106
+ RuntimeConfig.apiBase(context)
107
+ ?: return newFixedLengthResponse(
108
+ Response.Status.INTERNAL_ERROR,
109
+ "application/json",
110
+ """{"error":"apiBase missing"}"""
111
+ )
112
+
113
+
114
+ return try {
115
+
116
+ val url =
117
+ URL(
118
+ apiBase.removeSuffix("/") + uri
119
+ )
120
+
121
+
122
+ val connection =
123
+ url.openConnection() as HttpURLConnection
124
+
125
+
126
+ connection.requestMethod =
127
+ session?.method?.name ?: "GET"
128
+
129
+
130
+ connection.connectTimeout = 10000
131
+ connection.readTimeout = 10000
132
+
133
+
134
+ val code =
135
+ connection.responseCode
136
+
137
+
138
+ val stream =
139
+ if (code >= 400)
140
+ connection.errorStream
141
+ else
142
+ connection.inputStream
143
+
144
+
145
+ newChunkedResponse(
146
+ Response.Status.lookup(code)
147
+ ?: Response.Status.OK,
148
+ connection.contentType ?: "application/json",
149
+ stream
150
+ )
151
+
152
+
153
+ } catch (e: Exception) {
154
+
155
+ newFixedLengthResponse(
156
+ Response.Status.INTERNAL_ERROR,
157
+ "application/json",
158
+ """
159
+ {
160
+ "error":"proxy_failed",
161
+ "message":"${e.message}"
162
+ }
163
+ """.trimIndent()
164
+ )
165
+ }
166
+ }
76
167
  }
@@ -0,0 +1,51 @@
1
+ package com.pakstr.app
2
+
3
+ import android.content.Context
4
+ import org.json.JSONObject
5
+
6
+ object RuntimeConfig {
7
+
8
+ private var config: JSONObject? = null
9
+
10
+
11
+ fun load(context: Context): JSONObject {
12
+
13
+ if (config != null) {
14
+ return config!!
15
+ }
16
+
17
+
18
+ config = try {
19
+
20
+ val input =
21
+ context.assets.open("pakstr-runtime.json")
22
+
23
+
24
+ val json =
25
+ input.bufferedReader()
26
+ .use { it.readText() }
27
+
28
+
29
+ JSONObject(json)
30
+
31
+
32
+ } catch (e: Exception) {
33
+
34
+ JSONObject()
35
+
36
+ }
37
+
38
+
39
+ return config!!
40
+ }
41
+
42
+
43
+ fun apiBase(context: Context): String? {
44
+
45
+ return load(context)
46
+ .optString("apiBase")
47
+ .takeIf {
48
+ it.isNotBlank()
49
+ }
50
+ }
51
+ }
@@ -24,6 +24,8 @@ class ShellRuntime(
24
24
 
25
25
  private lateinit var controller: WebViewController
26
26
 
27
+ private var apiBase: String? = null
28
+
27
29
 
28
30
  fun start() {
29
31
 
@@ -35,6 +37,15 @@ class ShellRuntime(
35
37
  permissionHandler
36
38
  )
37
39
 
40
+ val runtimeConfig = RuntimeConfig.load(context)
41
+
42
+ apiBase = runtimeConfig.optString(
43
+
44
+ "apiBase",
45
+
46
+ null
47
+
48
+ )
38
49
 
39
50
  CoroutineScope(Dispatchers.IO).launch {
40
51
 
@@ -73,8 +84,13 @@ class ShellRuntime(
73
84
  return try {
74
85
 
75
86
  server = LocalServer(
87
+
76
88
  context,
77
- port
89
+
90
+ port,
91
+
92
+ apiBase
93
+
78
94
  )
79
95
 
80
96
 
@@ -23,10 +23,25 @@ function patchPermissions(androidRoot, manifest) {
23
23
  console.warn(`⚠️ Unknown permission: ${perm}`);
24
24
  continue;
25
25
  }
26
- const line = `<uses-permission android:name="${androidPermission}"/>`;
27
- if (!xml.includes(line)) {
28
- xml = xml.replace("<manifest", `<manifest\n ${line}`);
26
+ /**
27
+ * Check if permission already exists.
28
+ *
29
+ * Handles:
30
+ *
31
+ * <uses-permission android:name="android.permission.X"/>
32
+ *
33
+ * and
34
+ *
35
+ * <uses-permission android:name="android.permission.X" />
36
+ */
37
+ const exists = new RegExp(`<uses-permission[^>]+android:name="${androidPermission}"[^>]*/>`).test(xml);
38
+ if (exists) {
39
+ console.log(`✔ Permission already exists: ${androidPermission}`);
40
+ continue;
29
41
  }
42
+ const permissionLine = ` <uses-permission android:name="${androidPermission}" />`;
43
+ xml = xml.replace(/<manifest[^>]*>/, (match) => `${match}\n${permissionLine}`);
44
+ console.log(`➕ Added permission: ${androidPermission}`);
30
45
  }
31
46
  fs_1.default.writeFileSync(file, xml);
32
47
  console.log("🔐 Permissions patched");
@@ -18,6 +18,7 @@ const version_1 = require("../android/version");
18
18
  const icon_1 = require("../android/icon");
19
19
  const splash_1 = require("../android/splash");
20
20
  const permissions_1 = require("../android/permissions");
21
+ const runtimeConfig_1 = require("../runtime/runtimeConfig");
21
22
  async function buildCommand(args) {
22
23
  const ctx = (0, buildContext_1.createBuildContext)(args);
23
24
  const config = (0, config_1.loadConfig)(process.cwd());
@@ -56,6 +57,11 @@ async function buildCommand(args) {
56
57
  });
57
58
  copyFolder(distPath, assetsTarget);
58
59
  console.log("📦 Web assets copied successfully");
60
+ (0, runtimeConfig_1.generateRuntimeConfig)(assetsTarget, manifest);
61
+ console.log("runtime files:", fs_1.default.readdirSync(assetsTarget)
62
+ .filter(f => f.includes("pakstr")));
63
+ console.log(fs_1.default.existsSync(path_1.default.join(assetsTarget, "pakstr-runtime.json")));
64
+ console.log(fs_1.default.readdirSync(assetsTarget));
59
65
  // -----------------------------
60
66
  // 3. PATCH ANDROID PROJECT
61
67
  // -----------------------------
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generateRuntimeConfig = generateRuntimeConfig;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ function generateRuntimeConfig(webRoot, manifest) {
10
+ const runtime = manifest.runtime ?? {};
11
+ const jsContent = `
12
+ window.PAKSTR_CONFIG = ${JSON.stringify(runtime, null, 2)};
13
+ `;
14
+ fs_1.default.writeFileSync(path_1.default.join(webRoot, "pakstr-config.js"), jsContent.trim());
15
+ fs_1.default.writeFileSync(path_1.default.join(webRoot, "pakstr-runtime.json"), JSON.stringify(runtime, null, 2));
16
+ console.log("⚙️ Runtime config generated");
17
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pakstr",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "CLI for packaging Nostr web apps into Android APKs",
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.js",