@protobase/mobile 0.44.4 → 0.45.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/build.gradle +47 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/net/protobase/mobile/AuthHeaders.java +65 -0
- package/android/src/main/java/net/protobase/mobile/AuthUrl.java +71 -0
- package/android/src/main/java/net/protobase/mobile/GuardedWebViewClient.java +73 -0
- package/android/src/main/java/net/protobase/mobile/LocalServerGuard.java +22 -0
- package/android/src/main/java/net/protobase/mobile/ProtobaseAuth.java +105 -0
- package/android/src/main/java/net/protobase/mobile/Refusal.java +13 -0
- package/android/src/main/java/net/protobase/mobile/RefusedCookies.java +45 -0
- package/android/src/main/java/net/protobase/mobile/RefusedHttp.java +52 -0
- package/android/src/main/java/net/protobase/mobile/RefusedWebView.java +35 -0
- package/package.json +10 -3
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
repositories {
|
|
3
|
+
google()
|
|
4
|
+
mavenCentral()
|
|
5
|
+
maven {
|
|
6
|
+
url = "https://plugins.gradle.org/m2/"
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
dependencies {
|
|
10
|
+
classpath 'com.android.tools.build:gradle:8.13.0'
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
apply plugin: 'com.android.library'
|
|
15
|
+
|
|
16
|
+
android {
|
|
17
|
+
namespace = "net.protobase.mobile"
|
|
18
|
+
compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
|
|
19
|
+
defaultConfig {
|
|
20
|
+
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
|
|
21
|
+
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
|
|
22
|
+
versionCode 1
|
|
23
|
+
versionName "1.0"
|
|
24
|
+
}
|
|
25
|
+
buildTypes {
|
|
26
|
+
release {
|
|
27
|
+
minifyEnabled false
|
|
28
|
+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
lintOptions {
|
|
32
|
+
abortOnError = false
|
|
33
|
+
}
|
|
34
|
+
compileOptions {
|
|
35
|
+
sourceCompatibility JavaVersion.VERSION_21
|
|
36
|
+
targetCompatibility JavaVersion.VERSION_21
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
repositories {
|
|
41
|
+
google()
|
|
42
|
+
mavenCentral()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
dependencies {
|
|
46
|
+
implementation project(':capacitor-android')
|
|
47
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
package net.protobase.mobile;
|
|
2
|
+
|
|
3
|
+
import com.getcapacitor.JSObject;
|
|
4
|
+
import java.util.Arrays;
|
|
5
|
+
import java.util.HashSet;
|
|
6
|
+
import java.util.Iterator;
|
|
7
|
+
import java.util.Locale;
|
|
8
|
+
import java.util.Set;
|
|
9
|
+
import org.json.JSONObject;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The request headers ProtobaseAuth sends of those page script gives: the string-valued ones, without the Fetch spec's
|
|
13
|
+
* forbidden request header names, which a page's Request never carries. So page script cannot set the cookie, the
|
|
14
|
+
* origin, or the host a shared ingress routes by, nor override the method.
|
|
15
|
+
*/
|
|
16
|
+
final class AuthHeaders {
|
|
17
|
+
|
|
18
|
+
private static final Set<String> FORBIDDEN = new HashSet<>(
|
|
19
|
+
Arrays.asList(
|
|
20
|
+
"accept-charset",
|
|
21
|
+
"accept-encoding",
|
|
22
|
+
"access-control-request-headers",
|
|
23
|
+
"access-control-request-method",
|
|
24
|
+
"connection",
|
|
25
|
+
"content-length",
|
|
26
|
+
"cookie",
|
|
27
|
+
"cookie2",
|
|
28
|
+
"date",
|
|
29
|
+
"dnt",
|
|
30
|
+
"expect",
|
|
31
|
+
"host",
|
|
32
|
+
"keep-alive",
|
|
33
|
+
"origin",
|
|
34
|
+
"referer",
|
|
35
|
+
"set-cookie",
|
|
36
|
+
"te",
|
|
37
|
+
"trailer",
|
|
38
|
+
"transfer-encoding",
|
|
39
|
+
"upgrade",
|
|
40
|
+
"via",
|
|
41
|
+
"x-http-method",
|
|
42
|
+
"x-http-method-override",
|
|
43
|
+
"x-method-override"
|
|
44
|
+
)
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
private AuthHeaders() {}
|
|
48
|
+
|
|
49
|
+
static JSObject allowed(JSONObject given) {
|
|
50
|
+
JSObject headers = new JSObject();
|
|
51
|
+
if (given == null) return headers;
|
|
52
|
+
Iterator<String> names = given.keys();
|
|
53
|
+
while (names.hasNext()) {
|
|
54
|
+
String name = names.next();
|
|
55
|
+
Object value = given.opt(name);
|
|
56
|
+
if (value instanceof String && !forbidden(name)) headers.put(name, (String) value);
|
|
57
|
+
}
|
|
58
|
+
return headers;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private static boolean forbidden(String name) {
|
|
62
|
+
String lower = name.toLowerCase(Locale.ROOT);
|
|
63
|
+
return FORBIDDEN.contains(lower) || lower.startsWith("proxy-") || lower.startsWith("sec-");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
package net.protobase.mobile;
|
|
2
|
+
|
|
3
|
+
import java.net.MalformedURLException;
|
|
4
|
+
import java.net.URI;
|
|
5
|
+
import java.net.URISyntaxException;
|
|
6
|
+
import java.net.URL;
|
|
7
|
+
import java.util.Arrays;
|
|
8
|
+
import java.util.Locale;
|
|
9
|
+
import java.util.regex.Pattern;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Whether ProtobaseAuth may call a URL: one below the auth base path of the API origin, as the shell's
|
|
13
|
+
* nativeAuthFetch checks it. Parsed by URI and by URL, which HttpRequestHandler opens it with, and both must agree;
|
|
14
|
+
* dot segments and an encoded dot, slash or backslash are refused, since the server's URL parser decodes `%2e%2e`
|
|
15
|
+
* into `..`.
|
|
16
|
+
*/
|
|
17
|
+
final class AuthUrl {
|
|
18
|
+
|
|
19
|
+
/** Where Better Auth's endpoints are served, as packages/schema/src/auth-base-path.ts names it. */
|
|
20
|
+
static final String AUTH_BASE_PATH = "/api/auth";
|
|
21
|
+
|
|
22
|
+
private static final Pattern ENCODED_DOT_OR_SEPARATOR = Pattern.compile("%(2e|2f|5c)", Pattern.CASE_INSENSITIVE);
|
|
23
|
+
|
|
24
|
+
private AuthUrl() {}
|
|
25
|
+
|
|
26
|
+
/** The parts both parsers agree on. */
|
|
27
|
+
private static final class Parts {
|
|
28
|
+
|
|
29
|
+
final String scheme;
|
|
30
|
+
final String host;
|
|
31
|
+
final int port;
|
|
32
|
+
final String path;
|
|
33
|
+
|
|
34
|
+
Parts(String scheme, String host, int port, String path) {
|
|
35
|
+
this.scheme = scheme;
|
|
36
|
+
this.host = host;
|
|
37
|
+
this.port = port;
|
|
38
|
+
this.path = path;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static boolean allowed(String url, String apiOrigin) {
|
|
43
|
+
Parts origin = parse(apiOrigin);
|
|
44
|
+
Parts target = parse(url);
|
|
45
|
+
if (origin == null || target == null || !origin.path.isEmpty()) return false;
|
|
46
|
+
if (!target.scheme.equals(origin.scheme) || !target.host.equals(origin.host) || target.port != origin.port) return false;
|
|
47
|
+
String path = target.path;
|
|
48
|
+
if (!path.startsWith(AUTH_BASE_PATH + "/") || path.contains("\\") || ENCODED_DOT_OR_SEPARATOR.matcher(path).find()) return false;
|
|
49
|
+
return Arrays.stream(path.split("/")).noneMatch((segment) -> segment.equals(".") || segment.equals(".."));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// `null` when the parsers disagree, or the URL is not plain http or https.
|
|
53
|
+
private static Parts parse(String text) {
|
|
54
|
+
URI uri;
|
|
55
|
+
URL url;
|
|
56
|
+
try {
|
|
57
|
+
uri = new URI(text);
|
|
58
|
+
url = new URL(text);
|
|
59
|
+
} catch (URISyntaxException | MalformedURLException e) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
String scheme = uri.getScheme() == null ? "" : uri.getScheme().toLowerCase(Locale.ROOT);
|
|
63
|
+
if (!scheme.equals("http") && !scheme.equals("https")) return null;
|
|
64
|
+
if (uri.getHost() == null || uri.getRawUserInfo() != null || url.getUserInfo() != null || uri.getRawPath() == null) return null;
|
|
65
|
+
String host = uri.getHost().toLowerCase(Locale.ROOT);
|
|
66
|
+
int port = uri.getPort() == -1 ? (scheme.equals("https") ? 443 : 80) : uri.getPort();
|
|
67
|
+
int urlPort = url.getPort() == -1 ? url.getDefaultPort() : url.getPort();
|
|
68
|
+
boolean agree = url.getProtocol().equalsIgnoreCase(scheme) && url.getHost().equalsIgnoreCase(host) && urlPort == port && url.getPath().equals(uri.getRawPath());
|
|
69
|
+
return agree ? new Parts(scheme, host, port, uri.getRawPath()) : null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
package net.protobase.mobile;
|
|
2
|
+
|
|
3
|
+
import android.webkit.WebResourceRequest;
|
|
4
|
+
import android.webkit.WebResourceResponse;
|
|
5
|
+
import android.webkit.WebView;
|
|
6
|
+
import com.getcapacitor.Bridge;
|
|
7
|
+
import com.getcapacitor.BridgeWebViewClient;
|
|
8
|
+
import java.io.ByteArrayInputStream;
|
|
9
|
+
import java.io.File;
|
|
10
|
+
import java.io.IOException;
|
|
11
|
+
import java.util.Arrays;
|
|
12
|
+
import java.util.Collections;
|
|
13
|
+
import java.util.Objects;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Keeps page script from reading the app's files through its local server, which opens any file a request's path
|
|
17
|
+
* names (AndroidProtocolHandler.java:66-87): the WebView's cookie database, with the session token in plain text,
|
|
18
|
+
* lives below the app's data folder. Refused, with an empty 403:
|
|
19
|
+
* - the file and content routes, `/_capacitor_file_` and `/_capacitor_content_`, which Protobase never uses;
|
|
20
|
+
* - a `..` segment in the decoded path the server opens;
|
|
21
|
+
* - while the server serves files from a folder (the live updater's bundle), a path whose file is not inside the
|
|
22
|
+
* live updater's bundles folder, wherever that folder came from.
|
|
23
|
+
* The decoded path is the one the server reads (WebViewLocalServer.java:236-239,686-689). The server's folder is read
|
|
24
|
+
* again after it answered, so a switch between the check and the read is refused too.
|
|
25
|
+
*/
|
|
26
|
+
final class GuardedWebViewClient extends BridgeWebViewClient {
|
|
27
|
+
|
|
28
|
+
private final Bridge bridge;
|
|
29
|
+
private final File bundles;
|
|
30
|
+
|
|
31
|
+
GuardedWebViewClient(Bridge bridge, File bundles) {
|
|
32
|
+
super(bridge);
|
|
33
|
+
this.bridge = bridge;
|
|
34
|
+
this.bundles = bundles;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@Override
|
|
38
|
+
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
|
|
39
|
+
String decoded = request.getUrl().getPath();
|
|
40
|
+
String path = decoded == null ? "" : decoded;
|
|
41
|
+
String basePath = bridge.getServerBasePath();
|
|
42
|
+
if (refused(path, basePath)) return forbidden();
|
|
43
|
+
WebResourceResponse response = super.shouldInterceptRequest(view, request);
|
|
44
|
+
// A stream left open here is the garbage collector's to close: it only happens to a request racing a switch.
|
|
45
|
+
if (!Objects.equals(basePath, bridge.getServerBasePath())) return forbidden();
|
|
46
|
+
return response;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private boolean refused(String path, String basePath) {
|
|
50
|
+
if (path.startsWith(Bridge.CAPACITOR_FILE_START) || path.startsWith(Bridge.CAPACITOR_CONTENT_START)) return true;
|
|
51
|
+
if (Arrays.asList(path.split("/")).contains("..")) return true;
|
|
52
|
+
if (basePath == null || !new File(basePath).isAbsolute()) return false;
|
|
53
|
+
return !inside(new File(basePath + path));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Compares whole path components of the canonical paths, so a sibling such as `<bundles>_x` is not inside. One
|
|
57
|
+
// that cannot be made canonical is refused.
|
|
58
|
+
private boolean inside(File file) {
|
|
59
|
+
String canonical;
|
|
60
|
+
String root;
|
|
61
|
+
try {
|
|
62
|
+
canonical = file.getCanonicalPath();
|
|
63
|
+
root = bundles.getCanonicalPath();
|
|
64
|
+
} catch (IOException e) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
return canonical.equals(root) || canonical.startsWith(root + File.separator);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private static WebResourceResponse forbidden() {
|
|
71
|
+
return new WebResourceResponse("text/plain", "utf-8", 403, "Forbidden", Collections.emptyMap(), new ByteArrayInputStream(new byte[0]));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
package net.protobase.mobile;
|
|
2
|
+
|
|
3
|
+
import com.getcapacitor.Plugin;
|
|
4
|
+
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
5
|
+
import java.io.File;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Puts GuardedWebViewClient in front of the app's local server. It has no methods: it only acts when it loads, which
|
|
9
|
+
* is before the first page, since Capacitor 8.5.3 loads plugins before it installs the bridge's WebViewClient on the
|
|
10
|
+
* WebView (Bridge.java:231-233,281).
|
|
11
|
+
*/
|
|
12
|
+
@CapacitorPlugin(name = "ProtobaseLocalServerGuard")
|
|
13
|
+
public class LocalServerGuard extends Plugin {
|
|
14
|
+
|
|
15
|
+
// The folder the live updater keeps its bundles in, a fixed name below the app's files (LiveUpdate.java:114).
|
|
16
|
+
private static final String LIVE_UPDATE_BUNDLES = "_capacitor_live_update_bundles";
|
|
17
|
+
|
|
18
|
+
@Override
|
|
19
|
+
public void load() {
|
|
20
|
+
getBridge().setWebViewClient(new GuardedWebViewClient(getBridge(), new File(getContext().getFilesDir(), LIVE_UPDATE_BUNDLES)));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
package net.protobase.mobile;
|
|
2
|
+
|
|
3
|
+
import com.getcapacitor.JSObject;
|
|
4
|
+
import com.getcapacitor.Plugin;
|
|
5
|
+
import com.getcapacitor.PluginCall;
|
|
6
|
+
import com.getcapacitor.PluginMethod;
|
|
7
|
+
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
8
|
+
import com.getcapacitor.plugin.util.HttpRequestHandler;
|
|
9
|
+
import java.util.ArrayList;
|
|
10
|
+
import java.util.Iterator;
|
|
11
|
+
import java.util.List;
|
|
12
|
+
import java.util.Locale;
|
|
13
|
+
import java.util.concurrent.ExecutorService;
|
|
14
|
+
import java.util.concurrent.Executors;
|
|
15
|
+
import org.json.JSONObject;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The shell's sign-in requests, through native HTTP and the device's cookie jar, to `<apiOrigin>/api/auth/` only. It
|
|
19
|
+
* names the Capacitor origin of the app, which the server trusts for sign-in, and hands page script no Set-Cookie: the
|
|
20
|
+
* session token stays in the jar. `apiOrigin` and `origin` come from capacitor.config.json, which the CLI writes and
|
|
21
|
+
* ships outside the app's web part, so neither page script nor a live update can change them.
|
|
22
|
+
*/
|
|
23
|
+
@CapacitorPlugin(name = "ProtobaseAuth")
|
|
24
|
+
public class ProtobaseAuth extends Plugin {
|
|
25
|
+
|
|
26
|
+
private final ExecutorService executor = Executors.newCachedThreadPool();
|
|
27
|
+
|
|
28
|
+
@Override
|
|
29
|
+
protected void handleOnDestroy() {
|
|
30
|
+
super.handleOnDestroy();
|
|
31
|
+
executor.shutdownNow();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@PluginMethod
|
|
35
|
+
public void request(PluginCall call) {
|
|
36
|
+
String apiOrigin = getConfig().getString("apiOrigin");
|
|
37
|
+
String origin = getConfig().getString("origin");
|
|
38
|
+
if (apiOrigin == null || origin == null) {
|
|
39
|
+
call.reject("ProtobaseAuth has no apiOrigin or origin in its config");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
String url = call.getString("url");
|
|
43
|
+
if (url == null || !AuthUrl.allowed(url, apiOrigin)) {
|
|
44
|
+
call.reject("ProtobaseAuth calls " + apiOrigin + AuthUrl.AUTH_BASE_PATH + "/ only");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
String method = call.getString("method", "GET").toUpperCase(Locale.ROOT);
|
|
48
|
+
// All Better Auth serves.
|
|
49
|
+
if (!method.equals("GET") && !method.equals("POST")) {
|
|
50
|
+
call.reject("ProtobaseAuth sends GET or POST only, not " + method);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
Object data = call.getData().opt("data");
|
|
54
|
+
if (data != null && !(data instanceof String)) {
|
|
55
|
+
call.reject("ProtobaseAuth sends a text body only");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
JSObject headers = AuthHeaders.allowed(call.getObject("headers"));
|
|
59
|
+
headers.put("Origin", origin);
|
|
60
|
+
replaceOptions(call.getData(), url, method, headers, (String) data);
|
|
61
|
+
if (executor.isShutdown()) {
|
|
62
|
+
call.reject("ProtobaseAuth was shut down");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
executor.submit(() -> send(call, method));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private void send(PluginCall call, String method) {
|
|
69
|
+
// Every failure answers the call, as CapacitorHttp's does: a task's exception would otherwise leave it unanswered.
|
|
70
|
+
try {
|
|
71
|
+
JSObject response = HttpRequestHandler.request(call, method, getBridge());
|
|
72
|
+
removeCookieHeaders(response.optJSONObject("headers"));
|
|
73
|
+
call.resolve(response);
|
|
74
|
+
} catch (Exception e) {
|
|
75
|
+
call.reject(e.getLocalizedMessage(), e.getClass().getSimpleName(), e);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// HttpRequestHandler reads its options from the call: only the checked ones are left, so page script cannot add
|
|
80
|
+
// params, a file body (`dataType`), timeouts or redirects.
|
|
81
|
+
private static void replaceOptions(JSObject options, String url, String method, JSObject headers, String data) {
|
|
82
|
+
for (String key : keysOf(options)) options.remove(key);
|
|
83
|
+
options.put("url", url);
|
|
84
|
+
options.put("method", method);
|
|
85
|
+
options.put("headers", headers);
|
|
86
|
+
if (data != null) options.put("data", data);
|
|
87
|
+
options.put("responseType", "text");
|
|
88
|
+
options.put("disableRedirects", true);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private static void removeCookieHeaders(JSONObject headers) {
|
|
92
|
+
if (headers == null) return;
|
|
93
|
+
for (String name : keysOf(headers)) {
|
|
94
|
+
if (name.equalsIgnoreCase("set-cookie") || name.equalsIgnoreCase("set-cookie2")) headers.remove(name);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Collected before any is removed, which the iterator does not allow.
|
|
99
|
+
private static List<String> keysOf(JSONObject object) {
|
|
100
|
+
List<String> keys = new ArrayList<>();
|
|
101
|
+
Iterator<String> names = object.keys();
|
|
102
|
+
while (names.hasNext()) keys.add(names.next());
|
|
103
|
+
return keys;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
package net.protobase.mobile;
|
|
2
|
+
|
|
3
|
+
import com.getcapacitor.PluginCall;
|
|
4
|
+
|
|
5
|
+
/** How the stand-ins answer page script that calls a stock plugin they replace. */
|
|
6
|
+
final class Refusal {
|
|
7
|
+
|
|
8
|
+
private Refusal() {}
|
|
9
|
+
|
|
10
|
+
static void reject(PluginCall call, String plugin) {
|
|
11
|
+
call.reject(plugin + " is not available to page script in a Protobase app");
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
package net.protobase.mobile;
|
|
2
|
+
|
|
3
|
+
import com.getcapacitor.Plugin;
|
|
4
|
+
import com.getcapacitor.PluginCall;
|
|
5
|
+
import com.getcapacitor.PluginMethod;
|
|
6
|
+
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Takes the place of Capacitor's CapacitorCookies, which lets page script write and delete any cookie in the device's
|
|
10
|
+
* jar: plant another session or clear the user's. Replaced by name as RefusedHttp is. The stock plugin's load() has
|
|
11
|
+
* already installed the cookie handler native HTTP keeps the session with (CapacitorCookies.java:21-28).
|
|
12
|
+
*/
|
|
13
|
+
@CapacitorPlugin(name = "CapacitorCookies")
|
|
14
|
+
public class RefusedCookies extends Plugin {
|
|
15
|
+
|
|
16
|
+
@Override
|
|
17
|
+
public void load() {
|
|
18
|
+
getBridge().getWebView().removeJavascriptInterface("CapacitorCookiesAndroidInterface");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@PluginMethod
|
|
22
|
+
public void getCookies(PluginCall call) {
|
|
23
|
+
Refusal.reject(call, "CapacitorCookies");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@PluginMethod
|
|
27
|
+
public void setCookie(PluginCall call) {
|
|
28
|
+
Refusal.reject(call, "CapacitorCookies");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@PluginMethod
|
|
32
|
+
public void deleteCookie(PluginCall call) {
|
|
33
|
+
Refusal.reject(call, "CapacitorCookies");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@PluginMethod
|
|
37
|
+
public void clearCookies(PluginCall call) {
|
|
38
|
+
Refusal.reject(call, "CapacitorCookies");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@PluginMethod
|
|
42
|
+
public void clearAllCookies(PluginCall call) {
|
|
43
|
+
Refusal.reject(call, "CapacitorCookies");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
package net.protobase.mobile;
|
|
2
|
+
|
|
3
|
+
import com.getcapacitor.Plugin;
|
|
4
|
+
import com.getcapacitor.PluginCall;
|
|
5
|
+
import com.getcapacitor.PluginMethod;
|
|
6
|
+
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Takes the place of Capacitor's CapacitorHttp, whose answers hand page script every response header, the session's
|
|
10
|
+
* Set-Cookie included, for any request it makes with the device's cookie jar. Capacitor 8.5.3 registers its core
|
|
11
|
+
* plugins first and an app's plugins after them under the same map, keyed by name only (Bridge.java:660-668,711), so
|
|
12
|
+
* this one replaces it. Native code keeps HttpRequestHandler, which ProtobaseAuth uses.
|
|
13
|
+
*/
|
|
14
|
+
@CapacitorPlugin(name = "CapacitorHttp")
|
|
15
|
+
public class RefusedHttp extends Plugin {
|
|
16
|
+
|
|
17
|
+
@Override
|
|
18
|
+
public void load() {
|
|
19
|
+
// Added by the stock plugin's load(), which already ran; plugins load before the first page.
|
|
20
|
+
getBridge().getWebView().removeJavascriptInterface("CapacitorHttpAndroidInterface");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@PluginMethod
|
|
24
|
+
public void request(PluginCall call) {
|
|
25
|
+
Refusal.reject(call, "CapacitorHttp");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@PluginMethod
|
|
29
|
+
public void get(PluginCall call) {
|
|
30
|
+
Refusal.reject(call, "CapacitorHttp");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@PluginMethod
|
|
34
|
+
public void post(PluginCall call) {
|
|
35
|
+
Refusal.reject(call, "CapacitorHttp");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@PluginMethod
|
|
39
|
+
public void put(PluginCall call) {
|
|
40
|
+
Refusal.reject(call, "CapacitorHttp");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@PluginMethod
|
|
44
|
+
public void patch(PluginCall call) {
|
|
45
|
+
Refusal.reject(call, "CapacitorHttp");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@PluginMethod
|
|
49
|
+
public void delete(PluginCall call) {
|
|
50
|
+
Refusal.reject(call, "CapacitorHttp");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
package net.protobase.mobile;
|
|
2
|
+
|
|
3
|
+
import com.getcapacitor.Plugin;
|
|
4
|
+
import com.getcapacitor.PluginCall;
|
|
5
|
+
import com.getcapacitor.PluginMethod;
|
|
6
|
+
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Takes the place of Capacitor's WebView plugin, which lets page script point the app's local server at any folder,
|
|
10
|
+
* and keep it there across launches (WebView.java:17-48). Replaced by name as RefusedHttp is. The live updater sets
|
|
11
|
+
* the server's folder on the bridge itself, not through this plugin.
|
|
12
|
+
*/
|
|
13
|
+
@CapacitorPlugin(name = "WebView")
|
|
14
|
+
public class RefusedWebView extends Plugin {
|
|
15
|
+
|
|
16
|
+
@PluginMethod
|
|
17
|
+
public void setServerAssetPath(PluginCall call) {
|
|
18
|
+
Refusal.reject(call, "WebView");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@PluginMethod
|
|
22
|
+
public void setServerBasePath(PluginCall call) {
|
|
23
|
+
Refusal.reject(call, "WebView");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@PluginMethod
|
|
27
|
+
public void getServerBasePath(PluginCall call) {
|
|
28
|
+
Refusal.reject(call, "WebView");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@PluginMethod
|
|
32
|
+
public void persistServerBasePath(PluginCall call) {
|
|
33
|
+
Refusal.reject(call, "WebView");
|
|
34
|
+
}
|
|
35
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protobase/mobile",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "The Capacitor that protobase mobile builds a Protobase app's iOS and Android projects with, at fixed versions; installed by the CLI on first use.",
|
|
3
|
+
"version": "0.45.0",
|
|
4
|
+
"description": "The Capacitor that protobase mobile builds a Protobase app's iOS and Android projects with, at fixed versions, and the native plugin that keeps page script away from the session; installed by the CLI on first use.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -14,8 +14,15 @@
|
|
|
14
14
|
"node": ">=22.12.0"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
|
-
"LICENSE"
|
|
17
|
+
"LICENSE",
|
|
18
|
+
"android/src/main/",
|
|
19
|
+
"android/build.gradle"
|
|
18
20
|
],
|
|
21
|
+
"capacitor": {
|
|
22
|
+
"android": {
|
|
23
|
+
"src": "android"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
19
26
|
"publishConfig": {
|
|
20
27
|
"access": "public"
|
|
21
28
|
},
|