Skip to content

Commit 127ffa5

Browse files
lucas-zimermanLucas Zimermanantonis
authored
Fix Replay not being captured on Android (#1028)
* Migrate replay fixes for Android * ios implementation * fix native definition * fix tests * changelog * move func to vendor/base64converter * Update android/src/main/java/io/sentry/capacitor/SentryCapacitor.java Co-authored-by: Antonis Lilis <antonis.lilis@gmail.com> * Apply suggestions from code review Co-authored-by: Antonis Lilis <antonis.lilis@gmail.com> * missing file --------- Co-authored-by: Lucas Zimerman <lucaszimerman@Lucass-Mac-mini.local> Co-authored-by: Antonis Lilis <antonis.lilis@gmail.com>
1 parent 2d61e1a commit 127ffa5

File tree

8 files changed

+137
-62
lines changed

8 files changed

+137
-62
lines changed

CHANGELOG.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
> [migration guide](https://docs.sentry.io/platforms/javascript/guides/capacitor/migration/) first.
77
<!-- prettier-ignore-end -->
88
9+
## Unreleased
10+
11+
### Fixes
12+
13+
- Replay not being captured on Android ([#1028](https://github.com/getsentry/sentry-capacitor/pull/1028))
14+
915
## 2.4.0
1016

1117
## Feature
@@ -209,7 +215,6 @@ Sentry.init({
209215
### Dependencies
210216

211217
- Bump JavaScript SDKs from v8.55.0 to v9.0.0 ([#840](https://github.com/getsentry/sentry-capacitor/pull/840))
212-
213218
- [changelog](https://github.com/getsentry/sentry-javascript/blob/9.0.0/CHANGELOG.md#900)
214219
- [diff](https://github.com/getsentry/sentry-javascript/compare/8.55.0...9.0.0)
215220

@@ -230,7 +235,6 @@ Sentry.init({
230235
### Dependencies
231236

232237
- Bump JavaScript SDKs from v8.55.0 to v9.11.0 ([#840](https://github.com/getsentry/sentry-capacitor/pull/840), [#872](https://github.com/getsentry/sentry-capacitor/pull/872))
233-
234238
- [changelog](https://github.com/getsentry/sentry-javascript/blob/9.11.0/CHANGELOG.md)
235239
- [diff](https://github.com/getsentry/sentry-javascript/compare/8.55.0...9.11.0)
236240

@@ -276,7 +280,6 @@ Sentry.init({
276280
### Dependencies
277281

278282
- Bump JavaScript SDKs from v8.37.1 to v8.42.0 ([#730](https://github.com/getsentry/sentry-capacitor/pull/730))
279-
280283
- [changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md#8420)
281284
- [diff](https://github.com/getsentry/sentry-javascript/compare/8.37.1...8.42.0)
282285

@@ -487,12 +490,10 @@ information.
487490

488491
The release data comes from the following parameters:
489492
Android
490-
491493
- release: `PackageInfo.packageName` + `PackageInfo.versionName` + `PackageInfo.versionCode`
492494
- dist: `PackageInfo.versionCode`
493495

494496
iOS:
495-
496497
- release: `CFBundleIdentifier` + `CFBundleShortVersionString` + `CFBundleVersion`
497498
- dist: `CFBundleVersion`
498499

android/src/main/java/io/sentry/capacitor/SentryCapacitor.java

Lines changed: 24 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
import android.content.Context;
44
import android.content.pm.PackageInfo;
5-
import com.getcapacitor.JSArray;
5+
6+
import io.sentry.ILogger;
7+
import io.sentry.android.core.AndroidLogger;
8+
import io.sentry.vendor.Base64;
9+
610
import com.getcapacitor.JSObject;
711
import com.getcapacitor.Plugin;
812
import com.getcapacitor.PluginCall;
@@ -12,36 +16,31 @@
1216
import io.sentry.Breadcrumb;
1317
import io.sentry.IScope;
1418
import io.sentry.Integration;
15-
import io.sentry.ScopesAdapter;
1619
import io.sentry.Sentry;
1720
import io.sentry.SentryEvent;
1821
import io.sentry.SentryLevel;
1922
import io.sentry.UncaughtExceptionHandlerIntegration;
2023
import io.sentry.android.core.BuildConfig;
2124
import io.sentry.android.core.AnrIntegration;
25+
import io.sentry.android.core.InternalSentrySdk;
2226
import io.sentry.android.core.NdkIntegration;
2327
import io.sentry.android.core.SentryAndroid;
2428
import io.sentry.protocol.SdkVersion;
2529
import io.sentry.protocol.SentryPackage;
2630
import io.sentry.protocol.User;
27-
import java.io.File;
28-
import java.io.FileOutputStream;
2931
import java.nio.charset.StandardCharsets;
3032
import java.util.HashMap;
3133
import java.util.Iterator;
3234
import java.util.List;
3335
import java.util.Set;
34-
import java.util.UUID;
35-
import java.util.logging.Level;
36-
import java.util.logging.Logger;
3736

3837
@CapacitorPlugin
3938
public class SentryCapacitor extends Plugin {
4039

4140
private static final String NATIVE_SDK_NAME = "sentry.native.android.capacitor";
4241
private static final String ANDROID_SDK_NAME = "sentry.java.android.capacitor";
4342

44-
static final Logger logger = Logger.getLogger("capacitor-sentry");
43+
static final ILogger logger = new AndroidLogger("capacitor-sentry");
4544
private Context context;
4645
private static PackageInfo packageInfo;
4746

@@ -57,7 +56,7 @@ public void load() {
5756
String packageName = this.getContext().getPackageName();
5857
this.packageInfo = this.context.getPackageManager().getPackageInfo(packageName, 0); // Requires API 33 for deprecation change.
5958
} catch (Exception e) {
60-
logger.info("Error getting package info.");
59+
logger.log(SentryLevel.ERROR, "Error getting package info.");
6160
}
6261
}
6362

@@ -77,15 +76,14 @@ public void initNativeSdk(final PluginCall call) {
7776

7877
if (capOptions.has("debug") && capOptions.getBool("debug")) {
7978
options.setDebug(true);
80-
logger.setLevel(Level.INFO);
8179
}
8280

8381
options.setSentryClientName(sdkVersion.getName() + "/" + sdkVersion.getVersion());
8482
options.setNativeSdkName(NATIVE_SDK_NAME);
8583
options.setSdkVersion(sdkVersion);
8684

8785
String dsn = capOptions.getString("dsn") != null ? capOptions.getString("dsn") : "";
88-
logger.info(String.format("Starting with DSN: '%s'", dsn));
86+
logger.log(SentryLevel.INFO, String.format("Starting with DSN: '%s'", dsn));
8987
options.setDsn(dsn);
9088

9189
if (capOptions.has("environment") && capOptions.getString("environment") != null) {
@@ -146,7 +144,7 @@ public void initNativeSdk(final PluginCall call) {
146144

147145
options.getLogs().setEnabled(Boolean.TRUE.equals(capOptions.getBoolean("enableLogs", false)));
148146

149-
logger.info(String.format("Native Integrations '%s'", options.getIntegrations().toString()));
147+
logger.log(SentryLevel.INFO, String.format("Native Integrations '%s'", options.getIntegrations()));
150148
}
151149
);
152150

@@ -223,34 +221,21 @@ public void fetchNativeRelease(PluginCall call) {
223221

224222
@PluginMethod
225223
public void captureEnvelope(PluginCall call) {
226-
try {
227-
JSArray rawIntegers = call.getArray("envelope");
228-
byte[] bytes = new byte[rawIntegers.length()];
229-
for (int i = 0; i < bytes.length; i++) {
230-
bytes[i] = (byte) rawIntegers.getInt(i);
231-
}
232-
233-
final String outboxPath = ScopesAdapter.getInstance().getOptions().getOutboxPath();
234-
235-
if (outboxPath == null || outboxPath.isEmpty()) {
236-
logger.info("Error when writing envelope, no outbox path is present.");
237-
call.reject("Missing outboxPath");
238-
return;
239-
}
240-
241-
final File installation = new File(outboxPath, UUID.randomUUID().toString());
224+
String rawBytes = call.getString("envelope");
225+
if (rawBytes == null) {
226+
String errorMsg = "Can't send envelope due to empty data.";
227+
logger.log(SentryLevel.ERROR, errorMsg);
228+
call.reject( errorMsg);
229+
return;
230+
}
231+
byte[] bytes = Base64.decode(rawBytes, Base64.DEFAULT);
242232

243-
try (FileOutputStream out = new FileOutputStream(installation)) {
244-
out.write(bytes);
245-
logger.info("Successfully captured envelope.");
246-
} catch (Exception e) {
247-
logger.info("Error writing envelope.");
248-
call.reject(String.valueOf(e));
249-
return;
250-
}
251-
} catch (Exception e) {
252-
logger.info("Error reading envelope.");
253-
call.reject(String.valueOf(e));
233+
try {
234+
InternalSentrySdk.captureEnvelope(bytes, false);
235+
} catch (Throwable e) { // NOPMD - We don't want to crash in any case
236+
String errorMsg = "Error while capturing envelope";
237+
logger.log(SentryLevel.ERROR, errorMsg);
238+
call.reject(errorMsg);
254239
return;
255240
}
256241
call.resolve();

ios/Sources/SentryCapacitorPlugin/SentryCapacitorPlugin.swift

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,19 +102,20 @@ public class SentryCapacitorPlugin: CAPPlugin, CAPBridgedPlugin {
102102
}
103103

104104
@objc func captureEnvelope(_ call: CAPPluginCall) {
105-
guard let bytes = call.getArray("envelope", NSNumber.self) else {
105+
guard let base64Bytes = call.getString("envelope") else {
106106
print("Cannot parse the envelope data")
107107
call.reject("Envelope is null or empty")
108108
return
109109
}
110110

111-
let pointer = UnsafeMutablePointer<UInt8>.allocate(capacity: bytes.count)
112-
for (index, number) in bytes.enumerated() {
113-
// The numbers are stored as int32/64 but only the initial bits contains the number so this conversion is safe
114-
pointer[index] = UInt8(number.intValue)
111+
guard let data = Data(base64Encoded: base64Bytes) else {
112+
print("Cannot decode base64 envelope data")
113+
call.reject("Failed to decode base64 envelope")
114+
return
115115
}
116116

117-
let data = Data(buffer: UnsafeMutableBufferPointer<UInt8>(start: pointer, count: bytes.count))
117+
let pointer = UnsafeMutablePointer<UInt8>.allocate(capacity: data.count)
118+
data.copyBytes(to: pointer, count: data.count)
118119

119120
guard let envelope = PrivateSentrySDKOnly.envelope(with: data) else {
120121
call.reject("SentryCapacitor", "Failed to parse envelope from byte array.", nil)

src/definitions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export type NativeDeviceContextsResponse = {
1212

1313
export interface ISentryCapacitorPlugin {
1414
addBreadcrumb(breadcrumb: Breadcrumb): void;
15-
captureEnvelope(payload: { envelope: number[] }): PromiseLike<boolean>;
15+
captureEnvelope(payload: { envelope: string }): PromiseLike<boolean>;
1616

1717
clearBreadcrumbs(): void;
1818
closeNativeSdk(): Promise<void>;

src/vendor/fromByteArray.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/* eslint-disable */
2+
3+
// The MIT License (MIT)
4+
5+
// Copyright (c) 2014 Jameson Little
6+
7+
// Permission is hereby granted, free of charge, to any person obtaining a copy
8+
// of this software and associated documentation files (the "Software"), to deal
9+
// in the Software without restriction, including without limitation the rights
10+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
// copies of the Software, and to permit persons to whom the Software is
12+
// furnished to do so, subject to the following conditions:
13+
14+
// The above copyright notice and this permission notice shall be included in
15+
// all copies or substantial portions of the Software.
16+
17+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
// THE SOFTWARE.
24+
25+
// Adapted from https://github.dev/beatgammit/base64-js/blob/88957c9943c7e2a0f03cdf73e71d579e433627d3/index.js#L119
26+
27+
const lookup: string[] = [];
28+
29+
const code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
30+
for (let i = 0, len = code.length; i < len; ++i) {
31+
// @ts-expect-error
32+
lookup[i] = code[i];
33+
}
34+
35+
function tripletToBase64(num: number): string {
36+
// @ts-expect-error
37+
return lookup[(num >> 18) & 0x3f] + lookup[(num >> 12) & 0x3f] + lookup[(num >> 6) & 0x3f] + lookup[num & 0x3f];
38+
}
39+
40+
function encodeChunk(uint8: Uint8Array | number[], start: number, end: number): string {
41+
let tmp;
42+
const output = [];
43+
for (let i = start; i < end; i += 3) {
44+
// @ts-expect-error
45+
tmp = ((uint8[i] << 16) & 0xff0000) + ((uint8[i + 1] << 8) & 0xff00) + (uint8[i + 2] & 0xff);
46+
output.push(tripletToBase64(tmp));
47+
}
48+
return output.join('');
49+
}
50+
51+
/**
52+
* Converts a Uint8Array or Array of bytes into a string representation of base64.
53+
*/
54+
export function base64StringFromByteArray(uint8: Uint8Array | number[]): string {
55+
let tmp;
56+
const len = uint8.length;
57+
const extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes
58+
const parts = [];
59+
const maxChunkLength = 16383; // must be multiple of 3
60+
61+
// go through the array every three bytes, we'll deal with trailing stuff later
62+
for (let i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
63+
parts.push(encodeChunk(uint8, i, i + maxChunkLength > len2 ? len2 : i + maxChunkLength));
64+
}
65+
66+
// pad the end with zeros, but make sure to not forget the extra bytes
67+
if (extraBytes === 1) {
68+
tmp = uint8[len - 1];
69+
// @ts-expect-error
70+
parts.push(`${lookup[tmp >> 2] + lookup[(tmp << 4) & 0x3f]}==`);
71+
} else if (extraBytes === 2) {
72+
// @ts-expect-error
73+
tmp = (uint8[len - 2] << 8) + uint8[len - 1];
74+
// @ts-expect-error
75+
parts.push(`${lookup[tmp >> 10] + lookup[(tmp >> 4) & 0x3f] + lookup[(tmp << 2) & 0x3f]}=`);
76+
}
77+
78+
return parts.join('');
79+
}

src/wrapper.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type { CapacitorOptions } from './options';
1717
import { SentryCapacitor } from './plugin';
1818
import { convertToNormalizedObject } from './utils/normalize';
1919
import { utf8ToBytes } from './vendor';
20+
import { base64StringFromByteArray } from './vendor/fromByteArray';
2021

2122
/**
2223
* Internal interface for calling native functions
@@ -74,7 +75,7 @@ export const NATIVE = {
7475
}
7576

7677
let transportStatusCode = 200;
77-
await SentryCapacitor.captureEnvelope({ envelope: envelopeBytes }).then(
78+
await SentryCapacitor.captureEnvelope({ envelope: base64StringFromByteArray(envelopeBytes) }).then(
7879
_ => _, // We only want to know if it failed.
7980
failed => {
8081
debug.error('Failed to capture Envelope: ', failed);

test/vendor/base64Converter.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export function Base64StringToString(base64String: string): string {
2+
const binaryString = atob(base64String);
3+
const bytes = new Uint8Array(binaryString.length);
4+
for (let i = 0; i < binaryString.length; i++) {
5+
bytes[i] = binaryString.charCodeAt(i);
6+
}
7+
return new TextDecoder().decode(bytes);
8+
}
9+

0 commit comments

Comments
 (0)