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.
- package/android-template/app/src/main/java/com/pakstr/app/LocalServer.kt +92 -1
- package/android-template/app/src/main/java/com/pakstr/app/RuntimeConfig.kt +51 -0
- package/android-template/app/src/main/java/com/pakstr/app/ShellRuntime.kt +17 -1
- package/dist/android/permissions.js +18 -3
- package/dist/commands/build.js +6 -0
- package/dist/runtime/runtimeConfig.js +17 -0
- package/package.json +1 -1
|
@@ -6,14 +6,37 @@ import fi.iki.elonen.NanoHTTPD
|
|
|
6
6
|
import java.io.IOException
|
|
7
7
|
import java.util.Locale
|
|
8
8
|
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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");
|
package/dist/commands/build.js
CHANGED
|
@@ -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
|
+
}
|