Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ dependencies {
implementation "androidx.credentials:credentials:1.5.0"
implementation "androidx.credentials:credentials-play-services-auth:1.5.0"

// play install referrer — deferred deep linking (InstallReferrerPlugin.java)
implementation "com.android.installreferrer:installreferrer:2.2"

testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
Expand Down
2 changes: 2 additions & 0 deletions android/app/capacitor.build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-app')
implementation project(':capacitor-browser')
implementation project(':capacitor-camera')
implementation project(':capacitor-clipboard')
implementation project(':capacitor-haptics')
implementation project(':capacitor-keyboard')
implementation project(':capacitor-preferences')
implementation project(':capacitor-splash-screen')
implementation project(':capacitor-status-bar')
implementation project(':capgo-capacitor-crisp')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package me.peanut.wallet;

import android.os.Handler;
import android.os.Looper;

import com.android.installreferrer.api.InstallReferrerClient;
import com.android.installreferrer.api.InstallReferrerStateListener;
import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;

import java.util.concurrent.atomic.AtomicBoolean;

/**
* exposes the play install referrer to JS for deferred deep linking: the web
* store-bounce appends ?referrer=<payload> to the play url, and after install
* the JS side (src/utils/deferred-link.ts) reads it back here exactly once.
* always resolves — {referrer: null} on any failure or after a 5s timeout
* (the referrer service can bind without ever calling back, and the JS side
* awaits this before finishing app init) — so the promise can never hang.
*/
@CapacitorPlugin(name = "InstallReferrer")
public class InstallReferrerPlugin extends Plugin {

private static final long TIMEOUT_MS = 5000;

@PluginMethod
public void getReferrer(PluginCall call) {
final InstallReferrerClient client = InstallReferrerClient.newBuilder(getContext()).build();
final AtomicBoolean resolved = new AtomicBoolean(false);
final Handler timeoutHandler = new Handler(Looper.getMainLooper());
final Runnable timeout = () -> resolveOnce(call, client, resolved, null);
timeoutHandler.postDelayed(timeout, TIMEOUT_MS);

try {
client.startConnection(new InstallReferrerStateListener() {
@Override
public void onInstallReferrerSetupFinished(int responseCode) {
timeoutHandler.removeCallbacks(timeout);
String referrer = null;
try {
if (responseCode == InstallReferrerClient.InstallReferrerResponse.OK) {
referrer = client.getInstallReferrer().getInstallReferrer();
}
// else: SERVICE_UNAVAILABLE / FEATURE_NOT_SUPPORTED / DEVELOPER_ERROR
} catch (Exception ignored) {}
resolveOnce(call, client, resolved, referrer);
}

@Override
public void onInstallReferrerServiceDisconnected() {
// one-shot read; resolve null instead of leaving the call pending
timeoutHandler.removeCallbacks(timeout);
resolveOnce(call, client, resolved, null);
}
});
} catch (Exception e) {
timeoutHandler.removeCallbacks(timeout);
resolveOnce(call, client, resolved, null);
}
}

private void resolveOnce(PluginCall call, InstallReferrerClient client, AtomicBoolean resolved, String referrer) {
if (!resolved.compareAndSet(false, true)) return;
try {
client.endConnection();
} catch (Exception ignored) {}
JSObject ret = new JSObject();
ret.put("referrer", referrer);
call.resolve(ret);
}
}
2 changes: 2 additions & 0 deletions android/app/src/main/java/me/peanut/wallet/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public class MainActivity extends BridgeActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
// app-local plugin, not auto-discovered — must register before super.onCreate
registerPlugin(InstallReferrerPlugin.class);
super.onCreate(savedInstanceState);

Bridge bridge = this.getBridge();
Expand Down
6 changes: 6 additions & 0 deletions android/capacitor.settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ project(':capacitor-app').projectDir = new File('../node_modules/.pnpm/@capacito
include ':capacitor-browser'
project(':capacitor-browser').projectDir = new File('../node_modules/.pnpm/@capacitor+browser@8.0.3_@capacitor+core@8.2.0/node_modules/@capacitor/browser/android')

include ':capacitor-camera'
project(':capacitor-camera').projectDir = new File('../node_modules/.pnpm/@capacitor+camera@8.2.0_@capacitor+core@8.2.0/node_modules/@capacitor/camera/android')

include ':capacitor-clipboard'
project(':capacitor-clipboard').projectDir = new File('../node_modules/.pnpm/@capacitor+clipboard@8.0.1_@capacitor+core@8.2.0/node_modules/@capacitor/clipboard/android')

Expand All @@ -17,6 +20,9 @@ project(':capacitor-haptics').projectDir = new File('../node_modules/.pnpm/@capa
include ':capacitor-keyboard'
project(':capacitor-keyboard').projectDir = new File('../node_modules/.pnpm/@capacitor+keyboard@8.0.3_@capacitor+core@8.2.0/node_modules/@capacitor/keyboard/android')

include ':capacitor-preferences'
project(':capacitor-preferences').projectDir = new File('../node_modules/.pnpm/@capacitor+preferences@8.0.1_@capacitor+core@8.2.0/node_modules/@capacitor/preferences/android')

include ':capacitor-splash-screen'
project(':capacitor-splash-screen').projectDir = new File('../node_modules/.pnpm/@capacitor+splash-screen@8.0.1_@capacitor+core@8.2.0/node_modules/@capacitor/splash-screen/android')

Expand Down
20 changes: 19 additions & 1 deletion ios/App/App/ClipboardDetectPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,28 @@ public class ClipboardDetectPlugin: CAPPlugin, CAPBridgedPlugin {
public let identifier = "ClipboardDetectPlugin"
public let jsName = "ClipboardDetect"
public let pluginMethods: [CAPPluginMethod] = [
CAPPluginMethod(name: "hasStrings", returnType: CAPPluginReturnPromise)
CAPPluginMethod(name: "hasStrings", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "hasProbableWebUrl", returnType: CAPPluginReturnPromise)
]

@objc func hasStrings(_ call: CAPPluginCall) {
call.resolve(["value": UIPasteboard.general.hasStrings])
}

/*
* detectPatterns is also prompt-free: it reports pattern confidence
* without exposing content. Gates the deferred-deep-link clipboard read
* so only a probable web URL (the hand-off shape) triggers the real,
* prompt-raising read.
*/
@objc func hasProbableWebUrl(_ call: CAPPluginCall) {
UIPasteboard.general.detectPatterns(for: [\.probableWebURL]) { result in
switch result {
case .success(let patterns):
call.resolve(["value": patterns.contains(\.probableWebURL)])
case .failure:
call.resolve(["value": false])
}
}
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"@capacitor/haptics": "^8.0.2",
"@capacitor/ios": "8.2.0",
"@capacitor/keyboard": "^8.0.3",
"@capacitor/preferences": "^8.0.1",
"@capacitor/splash-screen": "^8.0.1",
"@capacitor/status-bar": "^8.0.2",
"@capgo/capacitor-crisp": "^8.0.27",
Expand Down
Loading
Loading