@straitlink/capacitor 0.1.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/Package.swift +1 -1
- package/README.md +17 -2
- package/android/build.gradle +1 -1
- package/android/src/main/java/dev/strait/capacitor/StraitPlugin.java +106 -0
- package/dist/index.d.ts +1 -1
- package/dist/strait.d.ts +54 -0
- package/dist/strait.js +2 -0
- package/ios/Sources/StraitPlugin/StraitPlugin.swift +44 -0
- package/package.json +1 -1
package/Package.swift
CHANGED
|
@@ -10,7 +10,7 @@ let package = Package(
|
|
|
10
10
|
],
|
|
11
11
|
dependencies: [
|
|
12
12
|
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0"),
|
|
13
|
-
.package(url: "https://github.com/strait-link/strait-sdk-swift.git", from: "0.9.
|
|
13
|
+
.package(url: "https://github.com/strait-link/strait-sdk-swift.git", from: "0.9.4"),
|
|
14
14
|
],
|
|
15
15
|
targets: [
|
|
16
16
|
.target(
|
package/README.md
CHANGED
|
@@ -92,6 +92,19 @@ The app opens on the link's screen and the console prints
|
|
|
92
92
|
`Strait link: direct app_link true /p/42` (your path). The open shows up in the dashboard's
|
|
93
93
|
Analytics within a minute. On a fresh install the first launch prints a `deferred` event instead.
|
|
94
94
|
|
|
95
|
+
### Check your setup
|
|
96
|
+
|
|
97
|
+
In a debug build, after `start()`:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
const report = await strait.doctor();
|
|
101
|
+
for (const f of report.findings) console.log(f.severity, f.id, f.title, f.fix);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**You should see** `ok key.ok`, `ok app.listed` and, on Android, `ok cert.listed`. Anything marked
|
|
105
|
+
`error` comes with the fix, for example `cert.missing`: "Add AB:CD:… in Dashboard → Apps & stores.
|
|
106
|
+
Debug, release and Play App Signing keys each have their own fingerprint."
|
|
107
|
+
|
|
95
108
|
## Platform features
|
|
96
109
|
|
|
97
110
|
| Feature | Status | Notes |
|
|
@@ -118,6 +131,8 @@ Analytics within a minute. On a fresh install the first launch prints a `deferre
|
|
|
118
131
|
| `trackEvent(name, { value, currency, linkId, clickId })` | Conversion or revenue event, placed on the tap of the last attributed open (7 days). Resolves `true` when accepted. |
|
|
119
132
|
| `pendingOpenReports()` / `flushOpenReports()` | Open reports saved while offline (sent again on start and resume). |
|
|
120
133
|
| `deviceClass()` | `phone` or `tablet` (iPad / 600 dp and up), as sent with open reports. |
|
|
134
|
+
| `openStoreSheet(url, options?)` | The app store inside your app for one of your links (another app to install), deep link kept through the install. Options: `androidPackage`, `listing`, `inline`, `appStoreId`, `providerToken`, `customProductPageId`, `style`. Resolves `{ opened, method, clickId, linkId, referrer, reason }`. |
|
|
135
|
+
| `doctor()` | Setup check for debug builds (key, endpoint, this app listed in your link domain's files, Android signing certificate). Resolves `{ ok, findings }`. |
|
|
121
136
|
| `stop()` | Stops delivering events to this object. |
|
|
122
137
|
|
|
123
138
|
`LinkEvent`: `id, kind (direct|deferred), route (app_link|custom_scheme|install_referrer|fingerprint|clipboard),
|
|
@@ -130,11 +145,11 @@ The plugin is a thin layer. All link logic (resolving short links, the once-per-
|
|
|
130
145
|
check, open reports and their retry queue, app-state labels) runs in the native SDKs, which share
|
|
131
146
|
golden test vectors with the Strait engine:
|
|
132
147
|
|
|
133
|
-
- **Android:** `strait-sdk-android` **v0.8.
|
|
148
|
+
- **Android:** `strait-sdk-android` **v0.8.3** from JitPack. The plugin adds JitPack to your Gradle
|
|
134
149
|
repositories, limited to the `com.github.strait-link` group, so nothing else is ever fetched from
|
|
135
150
|
it. It reads the Play Install Referrer (`com.android.installreferrer:installreferrer:2.2`), the launch
|
|
136
151
|
intent and `onNewIntent`, and keeps state in SharedPreferences (`strait`).
|
|
137
|
-
- **iOS:** `StraitSDK` **0.9.
|
|
152
|
+
- **iOS:** `StraitSDK` **0.9.4+** through Swift Package Manager. Links come from Capacitor's
|
|
138
153
|
`capacitorOpenURL` / `capacitorOpenUniversalLink` notifications; state lives in `UserDefaults`.
|
|
139
154
|
|
|
140
155
|
What gets recorded, the privacy details and the match methods are the same as the native SDKs:
|
package/android/build.gradle
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
package dev.strait.capacitor;
|
|
2
2
|
|
|
3
3
|
import android.content.Context;
|
|
4
|
+
import android.content.ActivityNotFoundException;
|
|
4
5
|
import android.content.Intent;
|
|
6
|
+
import android.net.Uri;
|
|
5
7
|
import android.content.SharedPreferences;
|
|
6
8
|
import android.content.pm.ApplicationInfo;
|
|
9
|
+
import android.content.pm.PackageInfo;
|
|
10
|
+
import android.content.pm.PackageManager;
|
|
11
|
+
import android.content.pm.Signature;
|
|
12
|
+
import android.os.Build;
|
|
13
|
+
import android.util.Log;
|
|
7
14
|
import android.util.DisplayMetrics;
|
|
8
15
|
|
|
9
16
|
import com.android.installreferrer.api.InstallReferrerClient;
|
|
@@ -15,6 +22,7 @@ import com.getcapacitor.PluginCall;
|
|
|
15
22
|
import com.getcapacitor.PluginMethod;
|
|
16
23
|
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
17
24
|
|
|
25
|
+
import java.security.MessageDigest;
|
|
18
26
|
import java.util.ArrayList;
|
|
19
27
|
import java.util.List;
|
|
20
28
|
import java.util.Locale;
|
|
@@ -28,9 +36,14 @@ import java.util.concurrent.TimeUnit;
|
|
|
28
36
|
import dev.strait.sdk.AppLifecycleState;
|
|
29
37
|
import dev.strait.sdk.CoreKt;
|
|
30
38
|
import dev.strait.sdk.DeviceFields;
|
|
39
|
+
import dev.strait.sdk.DoctorFinding;
|
|
40
|
+
import dev.strait.sdk.DoctorReport;
|
|
31
41
|
import dev.strait.sdk.KeyValueStore;
|
|
32
42
|
import dev.strait.sdk.LinkEvent;
|
|
33
43
|
import dev.strait.sdk.LinkStart;
|
|
44
|
+
import dev.strait.sdk.StoreLauncher;
|
|
45
|
+
import dev.strait.sdk.StoreSheetOptions;
|
|
46
|
+
import dev.strait.sdk.StoreSheetResult;
|
|
34
47
|
import dev.strait.sdk.StraitClient;
|
|
35
48
|
import dev.strait.sdk.StraitConfig;
|
|
36
49
|
import dev.strait.sdk.UrlConnectionTransport;
|
|
@@ -149,6 +162,99 @@ public class StraitPlugin extends Plugin {
|
|
|
149
162
|
call.resolve(r);
|
|
150
163
|
}
|
|
151
164
|
|
|
165
|
+
/** B23: setup check (debug builds). Sends this app's package and signing certificate(s). */
|
|
166
|
+
@PluginMethod
|
|
167
|
+
public void doctor(PluginCall call) {
|
|
168
|
+
StraitClient c = ready(call);
|
|
169
|
+
if (c == null) return;
|
|
170
|
+
Context ctx = getContext();
|
|
171
|
+
String pkg = ctx.getPackageName();
|
|
172
|
+
List<String> certs = signingCerts(ctx);
|
|
173
|
+
executor.execute(() -> {
|
|
174
|
+
DoctorReport r = c.doctor(pkg, certs);
|
|
175
|
+
JSArray list = new JSArray();
|
|
176
|
+
for (DoctorFinding f : r.getFindings()) {
|
|
177
|
+
JSObject o = new JSObject();
|
|
178
|
+
o.put("id", f.getId());
|
|
179
|
+
o.put("severity", f.getSeverity());
|
|
180
|
+
o.put("title", f.getTitle());
|
|
181
|
+
o.put("fix", f.getFix());
|
|
182
|
+
list.put(o);
|
|
183
|
+
Log.i("StraitDoctor", f.getSeverity() + " " + f.getId() + ": " + f.getTitle() + (f.getFix().isEmpty() ? "" : " → " + f.getFix()));
|
|
184
|
+
}
|
|
185
|
+
JSObject out = new JSObject();
|
|
186
|
+
out.put("ok", r.getOk());
|
|
187
|
+
out.put("findings", list);
|
|
188
|
+
call.resolve(out);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Store sheet: Google Play inside the app for one of the workspace's links, deep link kept. */
|
|
193
|
+
@PluginMethod
|
|
194
|
+
public void openStoreSheet(PluginCall call) {
|
|
195
|
+
StraitClient c = ready(call);
|
|
196
|
+
if (c == null) return;
|
|
197
|
+
String url = call.getString("url", "");
|
|
198
|
+
if (url == null || url.isEmpty()) {
|
|
199
|
+
call.reject("Strait: openStoreSheet needs a url");
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
Context ctx = getContext();
|
|
203
|
+
StoreSheetOptions opts = new StoreSheetOptions(
|
|
204
|
+
call.getString("androidPackage"), ctx.getPackageName(), call.getString("listing"), call.getBoolean("inline", true));
|
|
205
|
+
StoreLauncher launcher = s -> {
|
|
206
|
+
Intent intent = new Intent(s.getAction(), Uri.parse(s.getData()));
|
|
207
|
+
if (s.getPackageName() != null) intent.setPackage(s.getPackageName());
|
|
208
|
+
for (Map.Entry<String, Object> kv : s.getExtras().entrySet()) {
|
|
209
|
+
if (kv.getValue() instanceof Boolean) intent.putExtra(kv.getKey(), (Boolean) kv.getValue());
|
|
210
|
+
else if (kv.getValue() instanceof String) intent.putExtra(kv.getKey(), (String) kv.getValue());
|
|
211
|
+
}
|
|
212
|
+
if (getActivity() == null) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
213
|
+
try {
|
|
214
|
+
(getActivity() != null ? getActivity() : ctx).startActivity(intent);
|
|
215
|
+
return true;
|
|
216
|
+
} catch (ActivityNotFoundException e) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
executor.execute(() -> {
|
|
221
|
+
StoreSheetResult r = c.openStoreSheet(url, launcher, opts);
|
|
222
|
+
JSObject o = new JSObject();
|
|
223
|
+
o.put("opened", r.getOpened());
|
|
224
|
+
o.put("method", r.getMethod());
|
|
225
|
+
putIf(o, "clickId", r.getClickId());
|
|
226
|
+
putIf(o, "linkId", r.getLinkId());
|
|
227
|
+
putIf(o, "referrer", r.getReferrer());
|
|
228
|
+
putIf(o, "reason", r.getReason());
|
|
229
|
+
call.resolve(o);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** SHA-256 of this build's signing certificate(s), "AB:CD:…" (what assetlinks.json lists). */
|
|
234
|
+
@SuppressWarnings("deprecation")
|
|
235
|
+
private static List<String> signingCerts(Context ctx) {
|
|
236
|
+
List<String> out = new ArrayList<>();
|
|
237
|
+
try {
|
|
238
|
+
PackageManager pm = ctx.getPackageManager();
|
|
239
|
+
Signature[] sigs;
|
|
240
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
241
|
+
PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_SIGNING_CERTIFICATES);
|
|
242
|
+
sigs = pi.signingInfo == null ? new Signature[0]
|
|
243
|
+
: pi.signingInfo.hasMultipleSigners() ? pi.signingInfo.getApkContentsSigners() : pi.signingInfo.getSigningCertificateHistory();
|
|
244
|
+
} else {
|
|
245
|
+
sigs = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_SIGNATURES).signatures;
|
|
246
|
+
}
|
|
247
|
+
for (Signature sig : sigs) {
|
|
248
|
+
byte[] d = MessageDigest.getInstance("SHA-256").digest(sig.toByteArray());
|
|
249
|
+
StringBuilder b = new StringBuilder();
|
|
250
|
+
for (int i = 0; i < d.length; i++) b.append(i == 0 ? "" : ":").append(String.format("%02X", d[i]));
|
|
251
|
+
out.add(b.toString());
|
|
252
|
+
}
|
|
253
|
+
} catch (Exception ignored) {
|
|
254
|
+
}
|
|
255
|
+
return out;
|
|
256
|
+
}
|
|
257
|
+
|
|
152
258
|
@Override
|
|
153
259
|
protected void handleOnNewIntent(Intent intent) {
|
|
154
260
|
super.handleOnNewIntent(intent);
|
package/dist/index.d.ts
CHANGED
|
@@ -6,4 +6,4 @@ export declare const StraitNative: StraitNativePlugin;
|
|
|
6
6
|
* Android and iOS only; on the web every call resolves a safe default (use @straitlink/web there).
|
|
7
7
|
*/
|
|
8
8
|
export declare function createStrait(config: CreateStraitConfig, native?: StraitNativePlugin): Strait;
|
|
9
|
-
export type { CreateStraitConfig, DeviceClass, LinkEvent, LinkStart, Strait, StraitNativePlugin, TrackEventExtra, } from './strait.js';
|
|
9
|
+
export type { CreateStraitConfig, DeviceClass, DoctorFinding, DoctorReport, LinkEvent, LinkStart, Strait, StoreSheetOptions, StoreSheetResult, StraitNativePlugin, TrackEventExtra, } from './strait.js';
|
package/dist/strait.d.ts
CHANGED
|
@@ -34,6 +34,45 @@ export interface LinkStart {
|
|
|
34
34
|
at: number;
|
|
35
35
|
}
|
|
36
36
|
export type DeviceClass = 'phone' | 'tablet';
|
|
37
|
+
/** One setup check (contract B23, the same ids and wording in every Strait SDK). */
|
|
38
|
+
export interface DoctorFinding {
|
|
39
|
+
id: string;
|
|
40
|
+
severity: 'ok' | 'warn' | 'error';
|
|
41
|
+
title: string;
|
|
42
|
+
/** What to do; empty for `ok`. */
|
|
43
|
+
fix: string;
|
|
44
|
+
}
|
|
45
|
+
/** Store sheet options (same names as @straitlink/react-native). */
|
|
46
|
+
export interface StoreSheetOptions {
|
|
47
|
+
/** Android: the app to install. Default: the workspace's Android package from Strait. */
|
|
48
|
+
androidPackage?: string;
|
|
49
|
+
/** Android: a Play custom store listing name. */
|
|
50
|
+
listing?: string;
|
|
51
|
+
/** Android: false skips Play's inline half-sheet. Default true. */
|
|
52
|
+
inline?: boolean;
|
|
53
|
+
/** iPhone: the app to install. Default: the workspace's App Store ID from Strait. */
|
|
54
|
+
appStoreId?: string;
|
|
55
|
+
providerToken?: string;
|
|
56
|
+
customProductPageId?: string;
|
|
57
|
+
/** iPhone: the full product page (default) or a small card at the bottom. */
|
|
58
|
+
style?: 'product_page' | 'overlay';
|
|
59
|
+
}
|
|
60
|
+
export interface StoreSheetResult {
|
|
61
|
+
opened: boolean;
|
|
62
|
+
/** inline_install | market | web | product_page | overlay | none */
|
|
63
|
+
method: string;
|
|
64
|
+
clickId?: string;
|
|
65
|
+
linkId?: string;
|
|
66
|
+
/** Android: the Play Install Referrer sent. */
|
|
67
|
+
referrer?: string;
|
|
68
|
+
/** not_found, expired, offline, no_package, no_app_store_id, not_shown, unavailable… */
|
|
69
|
+
reason?: string;
|
|
70
|
+
}
|
|
71
|
+
export interface DoctorReport {
|
|
72
|
+
/** No `error` finding. */
|
|
73
|
+
ok: boolean;
|
|
74
|
+
findings: DoctorFinding[];
|
|
75
|
+
}
|
|
37
76
|
export interface CreateStraitConfig {
|
|
38
77
|
/** Your workspace's publishable key (`st_pub_live_…`), Dashboard → Get started. Never the secret key. */
|
|
39
78
|
publishableKey: string;
|
|
@@ -68,6 +107,10 @@ export interface StraitNativePlugin {
|
|
|
68
107
|
deviceClass(): Promise<{
|
|
69
108
|
deviceClass?: DeviceClass;
|
|
70
109
|
}>;
|
|
110
|
+
doctor(): Promise<DoctorReport>;
|
|
111
|
+
openStoreSheet(options: {
|
|
112
|
+
url: string;
|
|
113
|
+
} & StoreSheetOptions): Promise<StoreSheetResult>;
|
|
71
114
|
addListener(event: 'link', cb: (e: LinkEvent) => void): Promise<PluginListenerHandle>;
|
|
72
115
|
addListener(event: 'linkStart', cb: (s: LinkStart) => void): Promise<PluginListenerHandle>;
|
|
73
116
|
}
|
|
@@ -88,6 +131,17 @@ export interface Strait {
|
|
|
88
131
|
flushOpenReports(): Promise<void>;
|
|
89
132
|
/** What the app runs on, as sent with open reports (B14): iPad / sw600dp = tablet. */
|
|
90
133
|
deviceClass(): Promise<DeviceClass | undefined>;
|
|
134
|
+
/**
|
|
135
|
+
* Setup check for debug builds (B23): key, endpoint, and whether your link domain's
|
|
136
|
+
* assetlinks.json / apple-app-site-association lists this app (Android: also this build's
|
|
137
|
+
* signing certificate). Call after start(). Never throws.
|
|
138
|
+
*/
|
|
139
|
+
doctor(): Promise<DoctorReport>;
|
|
140
|
+
/**
|
|
141
|
+
* Store sheet: open the app store inside your app for one of your short links (an app you
|
|
142
|
+
* want people to install), keeping the deep link through the install. Never throws.
|
|
143
|
+
*/
|
|
144
|
+
openStoreSheet(url: string, options?: StoreSheetOptions): Promise<StoreSheetResult>;
|
|
91
145
|
/** Stop delivering events to this object. */
|
|
92
146
|
stop(): void;
|
|
93
147
|
}
|
package/dist/strait.js
CHANGED
|
@@ -59,6 +59,8 @@ export function createStrait(config, native) {
|
|
|
59
59
|
pendingOpenReports: () => safe(async () => (await native.pendingOpenReports()).count ?? 0, 0),
|
|
60
60
|
flushOpenReports: () => safe(() => native.flushOpenReports(), undefined),
|
|
61
61
|
deviceClass: () => safe(async () => (await native.deviceClass()).deviceClass, undefined),
|
|
62
|
+
doctor: () => safe(() => native.doctor(), { ok: false, findings: [] }),
|
|
63
|
+
openStoreSheet: (url, options = {}) => safe(() => native.openStoreSheet({ url, ...options }), { opened: false, method: 'none', reason: 'unavailable' }),
|
|
62
64
|
stop() {
|
|
63
65
|
linkCbs.clear();
|
|
64
66
|
startCbs.clear();
|
|
@@ -17,6 +17,8 @@ public class StraitPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
17
17
|
CAPPluginMethod(name: "pendingOpenReports", returnType: CAPPluginReturnPromise),
|
|
18
18
|
CAPPluginMethod(name: "flushOpenReports", returnType: CAPPluginReturnPromise),
|
|
19
19
|
CAPPluginMethod(name: "deviceClass", returnType: CAPPluginReturnPromise),
|
|
20
|
+
CAPPluginMethod(name: "doctor", returnType: CAPPluginReturnPromise),
|
|
21
|
+
CAPPluginMethod(name: "openStoreSheet", returnType: CAPPluginReturnPromise),
|
|
20
22
|
]
|
|
21
23
|
|
|
22
24
|
private var links: StraitLinks?
|
|
@@ -98,6 +100,48 @@ public class StraitPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
98
100
|
}
|
|
99
101
|
}
|
|
100
102
|
|
|
103
|
+
/// B23: setup check (debug builds) for this app's bundle ID.
|
|
104
|
+
@objc func doctor(_ call: CAPPluginCall) {
|
|
105
|
+
guard let l = ready(call) else { return }
|
|
106
|
+
l.doctor(bundleId: Bundle.main.bundleIdentifier) { r in
|
|
107
|
+
for f in r.findings { print("[Strait doctor] \(f.severity) \(f.id): \(f.title)\(f.fix.isEmpty ? "" : " → \(f.fix)")") }
|
|
108
|
+
call.resolve([
|
|
109
|
+
"ok": r.ok,
|
|
110
|
+
"findings": r.findings.map { ["id": $0.id, "severity": $0.severity, "title": $0.title, "fix": $0.fix] },
|
|
111
|
+
])
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// Store sheet: the App Store inside the app for one of the workspace's links, deep link kept.
|
|
116
|
+
@objc func openStoreSheet(_ call: CAPPluginCall) {
|
|
117
|
+
guard let l = ready(call) else { return }
|
|
118
|
+
guard let url = call.getString("url"), !url.isEmpty else {
|
|
119
|
+
call.reject("Strait: openStoreSheet needs a url")
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
DispatchQueue.main.async { [weak self] in
|
|
123
|
+
guard let vc = self?.bridge?.viewController else {
|
|
124
|
+
call.resolve(["opened": false, "method": "none", "reason": "no_view"])
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
let options = StoreSheetOptions(
|
|
128
|
+
appStoreId: call.getString("appStoreId"),
|
|
129
|
+
providerToken: call.getString("providerToken"),
|
|
130
|
+
customProductPageId: call.getString("customProductPageId"),
|
|
131
|
+
style: StoreSheetStyle(rawValue: call.getString("style") ?? "") ?? .productPage
|
|
132
|
+
)
|
|
133
|
+
let presenter = SystemStoreSheetPresenter(from: vc)
|
|
134
|
+
l.openStoreSheet(url: url, options: options, presenter: presenter) { r in
|
|
135
|
+
_ = presenter // keep the presenter (the store sheet's delegate) alive until done
|
|
136
|
+
var o: [String: Any] = ["opened": r.opened, "method": r.method]
|
|
137
|
+
if let v = r.clickId { o["clickId"] = v }
|
|
138
|
+
if let v = r.linkId { o["linkId"] = v }
|
|
139
|
+
if let v = r.reason { o["reason"] = v }
|
|
140
|
+
call.resolve(o)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
101
145
|
private func ready(_ call: CAPPluginCall) -> StraitLinks? {
|
|
102
146
|
guard let l = links else {
|
|
103
147
|
call.reject("Strait: call start() first")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@straitlink/capacitor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Deep linking for Capacitor and Ionic apps: direct links (App Links, Universal Links, custom schemes), deferred links that survive the install and link-open analytics. Wraps the Strait Android and iOS SDKs.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"capacitor",
|