Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support changing apiKey in session callback #1855

Merged
10 commits merged into from
Jul 4, 2023
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## TBD

### Enhancements

* Added `Session.apiKey` so that it can be changed in an `OnSessionCallback`
[#1855](https://github.com/bugsnag/bugsnag-android/pull/1855)

### Bug fixes

* Prevent rare app crash while migrating old `SharedPreferences` data from older versions of `bugsnag-android`
Expand Down
2 changes: 2 additions & 0 deletions bugsnag-android-core/api/bugsnag-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -573,11 +573,13 @@ public abstract interface class com/bugsnag/android/Plugin {
}

public final class com/bugsnag/android/Session : com/bugsnag/android/JsonStream$Streamable, com/bugsnag/android/UserAware {
public fun getApiKey ()Ljava/lang/String;
public fun getApp ()Lcom/bugsnag/android/App;
public fun getDevice ()Lcom/bugsnag/android/Device;
public fun getId ()Ljava/lang/String;
public fun getStartedAt ()Ljava/util/Date;
public fun getUser ()Lcom/bugsnag/android/User;
public fun setApiKey (Ljava/lang/String;)V
public fun setId (Ljava/lang/String;)V
public fun setStartedAt (Ljava/util/Date;)V
public fun setUser (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ static Client generateClient() {

static Session generateSession() {
return new Session("test", new Date(), new User(), false,
new Notifier(), NoopLogger.INSTANCE);
new Notifier(), NoopLogger.INSTANCE, "TEST APIKEY");
}

static Event generateEvent() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ void writeLegacyFile(Session session) throws IOException {
*/
@Test
public void testSessionFromFile() throws Exception {
Session payload = new Session(file, new Notifier(), NoopLogger.INSTANCE);
Session payload = new Session(file, new Notifier(), NoopLogger.INSTANCE, "TEST APIKEY");
payload.setApp(generateApp());
payload.setDevice(generateDevice());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public void testSessionFromFile() throws Exception {
session.toStream(stream);
out.flush();

Session payload = new Session(file, new Notifier(), NoopLogger.INSTANCE);
Session payload = new Session(file, new Notifier(), NoopLogger.INSTANCE, "TEST APIKEY");
JSONObject obj = BugsnagTestUtils.streamableToJson(payload);
JSONObject rootNode = obj.getJSONArray("sessions").getJSONObject(0);
assertNotNull(rootNode);
Expand All @@ -71,7 +71,15 @@ public void testSessionFromFile() throws Exception {

@Test
public void testAutoCapturedOverride() throws Exception {
session = new Session("id", new Date(), null, false, new Notifier(), NoopLogger.INSTANCE);
session = new Session(
"id",
new Date(),
null,
false,
new Notifier(),
NoopLogger.INSTANCE,
"TEST APIKEY"
);
assertFalse(session.isAutoCaptured());
session.setAutoCaptured(true);
assertTrue(session.isAutoCaptured());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ internal class BugsnagEventMapper(
// populate session
val sessionMap = map["session"] as? Map<String, Any?>
sessionMap?.let {
event.session = Session(it, logger)
event.session = Session(it, logger, apiKey)
}

// populate threads
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,19 @@ public final class Session implements JsonStream.Streamable, UserAware {
private final AtomicBoolean tracked = new AtomicBoolean(false);
final AtomicBoolean isPaused = new AtomicBoolean(false);

private String apiKey;

static Session copySession(Session session) {
Session copy = new Session(session.id, session.startedAt, session.user,
session.unhandledCount.get(), session.handledCount.get(), session.notifier,
session.logger);
session.logger, session.getApiKey());
copy.tracked.set(session.tracked.get());
copy.autoCaptured.set(session.isAutoCaptured());
return copy;
}

Session(Map<String, Object> map, Logger logger) {
this(null, null, logger);
Session(Map<String, Object> map, Logger logger, String apiKey) {
this(null, null, logger, apiKey);
setId((String) map.get("id"));

String timestamp = (String) map.get("startedAt");
Expand All @@ -61,25 +63,28 @@ static Session copySession(Session session) {
}

Session(String id, Date startedAt, User user, boolean autoCaptured,
Notifier notifier, Logger logger) {
this(null, notifier, logger);
Notifier notifier, Logger logger, String apiKey) {
this(null, notifier, logger, apiKey);
this.id = id;
this.startedAt = new Date(startedAt.getTime());
this.user = user;
this.autoCaptured.set(autoCaptured);
this.apiKey = apiKey;
}

Session(String id, Date startedAt, User user, int unhandledCount, int handledCount,
Notifier notifier, Logger logger) {
this(id, startedAt, user, false, notifier, logger);
Notifier notifier, Logger logger, String apiKey) {
this(id, startedAt, user, false, notifier, logger, apiKey);
this.unhandledCount.set(unhandledCount);
this.handledCount.set(handledCount);
this.tracked.set(true);
this.apiKey = apiKey;
}

Session(File file, Notifier notifier, Logger logger) {
Session(File file, Notifier notifier, Logger logger, String apiKey) {
this.file = file;
this.logger = logger;
this.apiKey = SessionFilenameInfo.findApiKeyInFilename(file, apiKey);
if (notifier != null) {
Notifier copy = new Notifier(notifier.getName(),
notifier.getVersion(), notifier.getUrl());
Expand Down Expand Up @@ -261,4 +266,25 @@ void serializeSessionInfo(@NonNull JsonStream writer) throws IOException {
writer.name("user").value(user);
writer.endObject();
}

/**
* The API key used for session sent to Bugsnag. Even though the API key is set when Bugsnag
* is initialized, you may choose to send certain sessions to a different Bugsnag project.
*/
public void setApiKey(@NonNull String apiKey) {
if (apiKey != null) {
this.apiKey = apiKey;
} else {
logNull("apiKey");
}
}

/**
* The API key used for session sent to Bugsnag. Even though the API key is set when Bugsnag
* is initialized, you may choose to send certain sessions to a different Bugsnag project.
*/
@NonNull
public String getApiKey() {
return apiKey;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.bugsnag.android

import com.bugsnag.android.internal.ImmutableConfig
import java.io.File
import java.util.UUID

Expand All @@ -11,12 +12,13 @@ import java.util.UUID
* timestamp - to sort error reports by time of capture
*/
internal data class SessionFilenameInfo(
var apiKey: String,
val timestamp: Long,
val uuid: String,
val uuid: String
) {

fun encode(): String {
return toFilename(timestamp, uuid)
return toFilename(apiKey, timestamp, uuid)
}

internal companion object {
Expand All @@ -27,29 +29,63 @@ internal data class SessionFilenameInfo(
* Generates a filename for the session in the format
* "[UUID][timestamp]_v2.json"
*/
fun toFilename(timestamp: Long, uuid: String): String {
return "${uuid}${timestamp}_v2.json"
fun toFilename(apiKey: String, timestamp: Long, uuid: String): String {
return "${apiKey}_${uuid}${timestamp}_v3.json"
}

@JvmStatic
fun defaultFilename(): String {
return toFilename(System.currentTimeMillis(), UUID.randomUUID().toString())
fun defaultFilename(
obj: Any,
config: ImmutableConfig
): SessionFilenameInfo {
val sanitizedApiKey = when (obj) {
is Session -> obj.apiKey
else -> config.apiKey
}

return SessionFilenameInfo(
sanitizedApiKey,
System.currentTimeMillis(),
UUID.randomUUID().toString()
)
}

fun fromFile(file: File): SessionFilenameInfo {
fun fromFile(file: File, defaultApiKey: String): SessionFilenameInfo {
return SessionFilenameInfo(
findApiKeyInFilename(file, defaultApiKey),
findTimestampInFilename(file),
findUuidInFilename(file)
)
}

private fun findUuidInFilename(file: File): String {
return file.name.substring(0, uuidLength - 1)
@JvmStatic
fun findUuidInFilename(file: File): String {
var fileName = file.name
if (isFileV3(file)) {
fileName = file.name.substringAfter('_')
}
return fileName.takeIf { it.length >= uuidLength }?.take(uuidLength) ?: ""
}

@JvmStatic
fun findTimestampInFilename(file: File): Long {
return file.name.substring(uuidLength, file.name.indexOf("_")).toLongOrNull() ?: -1
var fileName = file.name
if (isFileV3(file)) {
fileName = file.name.substringAfter('_')
}
return fileName.drop(findUuidInFilename(file).length)
.substringBefore('_')
This conversation was marked as resolved.
Show resolved Hide resolved
.toLongOrNull() ?: -1
}

@JvmStatic
fun findApiKeyInFilename(file: File?, defaultApiKey: String): String {
if (file == null || !isFileV3(file)) {
return defaultApiKey
}
return file.name.substringBefore('_').takeUnless { it.isEmpty() } ?: defaultApiKey
}

internal fun isFileV3(file: File): Boolean = file.name.endsWith("_v3.json")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
class SessionStore extends FileStore {

private final ImmutableConfig config;
static final Comparator<File> SESSION_COMPARATOR = new Comparator<File>() {
@Override
public int compare(File lhs, File rhs) {
Expand All @@ -43,12 +44,15 @@ public int compare(File lhs, File rhs) {
SESSION_COMPARATOR,
logger,
delegate);
this.config = config;
}

@NonNull
@Override
String getFilename(Object object) {
return SessionFilenameInfo.defaultFilename();
SessionFilenameInfo sessionInfo
= SessionFilenameInfo.defaultFilename(object, config);
return sessionInfo.encode();
}

public boolean isTooOld(File file) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,10 @@ Session startNewSession(@NonNull Date date, @Nullable User user,
return null;
}
String id = UUID.randomUUID().toString();
Session session = new Session(id, date, user, autoCaptured, client.getNotifier(), logger);
Session session = new Session(
id, date, user, autoCaptured,
client.getNotifier(), logger, configuration.getApiKey()
);
if (trackSessionIfNeeded(session)) {
return session;
} else {
Expand Down Expand Up @@ -157,7 +160,7 @@ Session registerExistingSession(@Nullable Date date, @Nullable String sessionId,
Session session = null;
if (date != null && sessionId != null) {
session = new Session(sessionId, date, user, unhandledCount, handledCount,
client.getNotifier(), logger);
client.getNotifier(), logger, configuration.getApiKey());
notifySessionStartObserver(session);
} else {
updateState(StateEvent.PauseSession.INSTANCE);
Expand Down Expand Up @@ -256,7 +259,9 @@ void flushStoredSessions() {

void flushStoredSession(File storedFile) {
logger.d("SessionTracker#flushStoredSession() - attempting delivery");
Session payload = new Session(storedFile, client.getNotifier(), logger);
Session payload = new Session(
storedFile, client.getNotifier(), logger, configuration.getApiKey()
);

if (!payload.isV2Payload()) { // collect data here
payload.setApp(client.getAppDataCollector().generateApp());
Expand Down Expand Up @@ -330,7 +335,7 @@ void deliverInMemorySession(Session session) {
}

DeliveryStatus deliverSessionPayload(Session payload) {
DeliveryParams params = configuration.getSessionApiDeliveryParams();
DeliveryParams params = configuration.getSessionApiDeliveryParams(payload);
Delivery delivery = configuration.getDelivery();
return delivery.deliver(payload, params);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.bugsnag.android.EventPayload
import com.bugsnag.android.Logger
import com.bugsnag.android.ManifestConfigLoader.Companion.BUILD_UUID
import com.bugsnag.android.NoopLogger
import com.bugsnag.android.Session
import com.bugsnag.android.Telemetry
import com.bugsnag.android.ThreadSendPolicy
import com.bugsnag.android.errorApiHeaders
Expand Down Expand Up @@ -65,8 +66,8 @@ data class ImmutableConfig(
DeliveryParams(endpoints.notify, errorApiHeaders(payload))

@JvmName("getSessionApiDeliveryParams")
internal fun getSessionApiDeliveryParams() =
DeliveryParams(endpoints.sessions, sessionApiHeaders(apiKey))
internal fun getSessionApiDeliveryParams(session: Session) =
DeliveryParams(endpoints.sessions, sessionApiHeaders(session.apiKey))

/**
* Returns whether the given throwable should be discarded
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ static EventPayload generateEventPayload(ImmutableConfig config) {

static Session generateSession() {
return new Session("test", new Date(), new User(), false,
new Notifier(), NoopLogger.INSTANCE);
new Notifier(), NoopLogger.INSTANCE, "BUGSNAG_API_KEY");
}

static Event generateEvent() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ internal class DeliveryDelegateTest {
@Mock
lateinit var eventStore: EventStore

private val apiKey = "BUGSNAG_API_KEY"
private val notifier = Notifier()
val config = generateImmutableConfig()
val callbackState = CallbackState()
Expand All @@ -40,7 +41,7 @@ internal class DeliveryDelegateTest {
notifier,
BackgroundTaskService()
)
event.session = Session("123", Date(), User(null, null, null), false, notifier, NoopLogger)
event.session = Session("123", Date(), User(null, null, null), false, notifier, NoopLogger, apiKey)
}

@Test
Expand All @@ -59,6 +60,8 @@ internal class DeliveryDelegateTest {
// check session count incremented
assertEquals(1, event.session!!.unhandledCount)
assertEquals(0, event.session!!.handledCount)

assertEquals("BUGSNAG_API_KEY", event.session!!.apiKey)
}

@Test
Expand All @@ -67,7 +70,7 @@ internal class DeliveryDelegateTest {
SeverityReason.REASON_HANDLED_EXCEPTION
)
val event = Event(RuntimeException("Whoops!"), config, state, NoopLogger)
event.session = Session("123", Date(), User(null, null, null), false, notifier, NoopLogger)
event.session = Session("123", Date(), User(null, null, null), false, notifier, NoopLogger, apiKey)

var msg: StateEvent.NotifyHandled? = null
deliveryDelegate.addObserver(
Expand Down