Skip to content

Commit

Permalink
[android][ios][home] persist session info in one location on clients (e…
Browse files Browse the repository at this point in the history
…xpo#2521)

[android][ios][home][jest-expo] persist session info in one location on clients
  • Loading branch information
esamelson authored Nov 8, 2018
1 parent 6fb8ef7 commit 8a20a96
Show file tree
Hide file tree
Showing 14 changed files with 300 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import host.exp.exponent.network.ExponentNetwork;
import host.exp.exponent.storage.ExponentSharedPreferences;
import host.exp.exponent.utils.ColorParser;
import host.exp.expoview.Exponent;
import host.exp.expoview.R;
import expolib_v1.okhttp3.Request;

Expand Down Expand Up @@ -209,7 +210,11 @@ public void fetchManifest(final String manifestUrl, final ManifestListener liste
String httpManifestUrl = uriBuilder.build().toString();

// Fetch manifest
Request.Builder requestBuilder = ExponentUrls.addExponentHeadersToManifestUrl(httpManifestUrl, manifestUrl.equals(Constants.INITIAL_URL));
Request.Builder requestBuilder = ExponentUrls.addExponentHeadersToManifestUrl(
httpManifestUrl,
manifestUrl.equals(Constants.INITIAL_URL),
mExponentSharedPreferences.getSessionSecret()
);
requestBuilder.header("Exponent-Accept-Signature", "true");
requestBuilder.header("Expo-JSON-Error", "true");
requestBuilder.cacheControl(CacheControl.FORCE_NETWORK);
Expand Down Expand Up @@ -290,7 +295,11 @@ public boolean fetchCachedManifest(final String manifestUrl, final ManifestListe
}

// Fetch manifest
Request.Builder requestBuilder = ExponentUrls.addExponentHeadersToManifestUrl(httpManifestUrl, manifestUrl.equals(Constants.INITIAL_URL));
Request.Builder requestBuilder = ExponentUrls.addExponentHeadersToManifestUrl(
httpManifestUrl,
manifestUrl.equals(Constants.INITIAL_URL),
mExponentSharedPreferences.getSessionSecret()
);
requestBuilder.header("Exponent-Accept-Signature", "true");
requestBuilder.header("Expo-JSON-Error", "true");

Expand Down Expand Up @@ -404,7 +413,11 @@ public void onCachedResponse(ExpoResponse response, boolean isEmbedded) {
public void fetchEmbeddedManifest(final String manifestUrl, final ManifestListener listener) {
String httpManifestUrl = httpManifestUrlBuilder(manifestUrl).build().toString();

Request.Builder requestBuilder = ExponentUrls.addExponentHeadersToManifestUrl(httpManifestUrl, manifestUrl.equals(Constants.INITIAL_URL));
Request.Builder requestBuilder = ExponentUrls.addExponentHeadersToManifestUrl(
httpManifestUrl,
manifestUrl.equals(Constants.INITIAL_URL),
mExponentSharedPreferences.getSessionSecret()
);
requestBuilder.header("Exponent-Accept-Signature", "true");
requestBuilder.header("Expo-JSON-Error", "true");
String finalUri = requestBuilder.build().url().toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public String toString() {
formattedMessage = "This experience requires a newer version of the Expo client - please download the latest version from the Play Store.";
break;
case "EXPERIENCE_NOT_VIEWABLE":
formattedMessage = "The experience you requested is not viewable by you. You will need to log in or ask the owner to grant you access.";
formattedMessage = rawMessage; // From server: The experience you requested is not viewable by you. You will need to log in or ask the owner to grant you access.
break;
case "USER_SNACK_NOT_FOUND":
case "SNACK_NOT_FOUND":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@

public class ExponentUrls {

private static String sSessionSecret;

private static final List<String> HTTPS_HOSTS = new ArrayList<>();
static {
HTTPS_HOSTS.add("exp.host");
Expand All @@ -31,10 +29,6 @@ private static boolean isHttpsHost(final String host) {
return false;
}

public static void setSessionSecret(String sessionSecret) {
sSessionSecret = sessionSecret;
}

public static String toHttp(final String rawUrl) {
if (rawUrl.startsWith("http")) {
return rawUrl;
Expand All @@ -59,7 +53,7 @@ public static Request.Builder addExponentHeadersToUrl(String urlString) {
return builder;
}

public static Request.Builder addExponentHeadersToManifestUrl(String urlString, boolean isShellAppManifest) {
public static Request.Builder addExponentHeadersToManifestUrl(String urlString, boolean isShellAppManifest, String sessionSecret) {
Request.Builder builder = addExponentHeadersToUrl(urlString)
.header("Accept", "application/expo+json,application/json");

Expand All @@ -79,8 +73,8 @@ public static Request.Builder addExponentHeadersToManifestUrl(String urlString,

builder.header("Expo-Api-Version", "1")
.header("Expo-Client-Environment", clientEnvironment);
if (sSessionSecret != null) {
builder.header("Expo-Session", sSessionSecret);
if (sessionSecret != null) {
builder.header("Expo-Session", sessionSecret);
}

return builder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import android.app.Activity;
import android.support.annotation.Nullable;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
Expand All @@ -14,7 +15,8 @@
import com.facebook.react.common.MapBuilder;
import com.facebook.react.modules.core.DeviceEventManagerModule;

import java.io.IOException;
import org.json.JSONObject;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
Expand All @@ -28,16 +30,10 @@
import host.exp.exponent.experience.ExperienceActivity;
import host.exp.exponent.kernel.ExponentKernelModuleInterface;
import host.exp.exponent.kernel.ExponentKernelModuleProvider;
import host.exp.exponent.kernel.ExponentUrls;
import host.exp.exponent.kernel.Kernel;
import host.exp.exponent.network.ExpoHttpCallback;
import host.exp.exponent.network.ExpoResponse;
import host.exp.exponent.network.ExponentNetwork;
import host.exp.exponent.storage.ExponentSharedPreferences;
import expolib_v1.okhttp3.Call;
import expolib_v1.okhttp3.Callback;
import expolib_v1.okhttp3.Request;
import expolib_v1.okhttp3.Response;
import host.exp.exponent.utils.JSONBundleConverter;

public class ExponentKernelModule extends ReactContextBaseJavaModule implements ExponentKernelModuleInterface {

Expand Down Expand Up @@ -112,13 +108,39 @@ public void consumeEventQueue() {
}

@ReactMethod
public void setSessionSecret(String sessionSecret) {
ExponentUrls.setSessionSecret(sessionSecret);
public void getSessionAsync(Promise promise) {
String sessionString = mExponentSharedPreferences.getString(ExponentSharedPreferences.EXPO_AUTH_SESSION);
try {
JSONObject sessionJsonObject = new JSONObject(sessionString);
WritableMap session = Arguments.fromBundle(JSONBundleConverter.JSONToBundle(sessionJsonObject));
promise.resolve(session);
} catch (Exception e) {
promise.resolve(null);
EXL.e(TAG, e);
}
}

@ReactMethod
public void removeSessionSecret() {
ExponentUrls.setSessionSecret(null);
public void setSessionAsync(ReadableMap session, Promise promise) {
try {
JSONObject sessionJsonObject = new JSONObject(session.toHashMap());
mExponentSharedPreferences.updateSession(sessionJsonObject);
promise.resolve(null);
} catch (Exception e) {
promise.reject("ERR_SESSION_NOT_SAVED", "Could not save session secret", e);
EXL.e(TAG, e);
}
}

@ReactMethod
public void removeSessionAsync(Promise promise) {
try {
mExponentSharedPreferences.removeSession();
promise.resolve(null);
} catch (Exception e) {
promise.reject("ERR_SESSION_NOT_REMOVED", "Could not remove session secret", e);
EXL.e(TAG, e);
}
}

@ReactMethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ public static class ManifestAndBundleUrl {
public static final String SHOULD_NOT_USE_KERNEL_CACHE = "should_not_use_kernel_cache";
public static final String KERNEL_REVISION_ID = "kernel_revision_id";
public static final String SAFE_MANIFEST_KEY = "safe_manifest";
public static final String EXPO_AUTH_SESSION = "expo_auth_session";
public static final String EXPO_AUTH_SESSION_SECRET_KEY = "sessionSecret";

// Metadata
public static final String EXPERIENCE_METADATA_PREFIX = "experience_metadata_";
Expand Down Expand Up @@ -136,6 +138,28 @@ public String getOrCreateUUID() {
return uuid;
}

public void updateSession(JSONObject session) {
setString(EXPO_AUTH_SESSION, session.toString());
}

public void removeSession() {
setString(EXPO_AUTH_SESSION, null);
}

public String getSessionSecret() {
String sessionString = getString(EXPO_AUTH_SESSION);
if (sessionString == null) {
return null;
}
try {
JSONObject session = new JSONObject(sessionString);
return session.getString(EXPO_AUTH_SESSION_SECRET_KEY);
} catch (Exception e) {
EXL.e(TAG, e);
return null;
}
}

public void updateManifest(String manifestUrl, JSONObject manifest, String bundleUrl) {
try {
JSONObject parentObject = new JSONObject();
Expand Down
31 changes: 20 additions & 11 deletions home/storage/LocalStorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,29 @@ async function updateSettingsAsync(updatedSettings) {
}

async function getSessionAsync() {
let results = await AsyncStorage.getItem(Keys.Session);

try {
let session = JSON.parse(results);
return session;
} catch (e) {
return null;
let results = await ExponentKernel.getSessionAsync();
if (!results) {
// NOTE(2018-11-8): we are migrating to storing all session keys
// using the Kernel module instead of AsyncStorage, but we need to
// continue to check the old location for a little while
// until all clients in use have migrated over
results = await AsyncStorage.getItem(Keys.Session);
if (results) {
try {
results = JSON.parse(results);
await saveSessionAsync(results);
await AsyncStorage.removeItem(Keys.Session);
} catch (e) {
return null;
}
}
}

return results;
}

async function saveSessionAsync(session) {
ExponentKernel.setSessionSecret(session.sessionSecret);
return AsyncStorage.setItem(Keys.Session, JSON.stringify(session));
return ExponentKernel.setSessionAsync(session);
}

async function getHistoryAsync() {
Expand Down Expand Up @@ -89,8 +99,7 @@ async function removeAuthTokensAsync() {
}

async function removeSessionAsync() {
ExponentKernel.removeSessionSecret();
return AsyncStorage.removeItem(Keys.Session);
return ExponentKernel.removeSessionAsync();
}

async function clearAllAsync() {
Expand Down
6 changes: 6 additions & 0 deletions ios/Exponent.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
0779EE2620977C9E00C2D71B /* AIRGoogleMapOverlay.m in Sources */ = {isa = PBXBuildFile; fileRef = 0779EE2120977C9D00C2D71B /* AIRGoogleMapOverlay.m */; };
0779EE2720977C9E00C2D71B /* AIRGoogleMapOverlayManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0779EE2320977C9D00C2D71B /* AIRGoogleMapOverlayManager.m */; };
0779EE2820977C9E00C2D71B /* AIRDummyView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0779EE2420977C9E00C2D71B /* AIRDummyView.m */; };
0799CFDA21839B520039E97C /* EXSession.m in Sources */ = {isa = PBXBuildFile; fileRef = 0799CFD921839B520039E97C /* EXSession.m */; };
07E26F301FFFFB55004667A1 /* EXCalendarConverter.m in Sources */ = {isa = PBXBuildFile; fileRef = 07E26F2D1FFFFB55004667A1 /* EXCalendarConverter.m */; };
07E26F311FFFFB55004667A1 /* EXCalendar.m in Sources */ = {isa = PBXBuildFile; fileRef = 07E26F2E1FFFFB55004667A1 /* EXCalendar.m */; };
08FCA2B1B8F4B2420E552303 /* libPods-Tests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FDC01B7874B93745E639DFA6 /* libPods-Tests.a */; };
Expand Down Expand Up @@ -347,6 +348,8 @@
0779EE2320977C9D00C2D71B /* AIRGoogleMapOverlayManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AIRGoogleMapOverlayManager.m; sourceTree = "<group>"; };
0779EE2420977C9E00C2D71B /* AIRDummyView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AIRDummyView.m; sourceTree = "<group>"; };
0779EE2520977C9E00C2D71B /* AIRGoogleMapOverlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AIRGoogleMapOverlay.h; sourceTree = "<group>"; };
0799CFD821839B470039E97C /* EXSession.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EXSession.h; sourceTree = "<group>"; };
0799CFD921839B520039E97C /* EXSession.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EXSession.m; sourceTree = "<group>"; };
07E26F2C1FFFFB55004667A1 /* EXCalendar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EXCalendar.h; sourceTree = "<group>"; };
07E26F2D1FFFFB55004667A1 /* EXCalendarConverter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EXCalendarConverter.m; sourceTree = "<group>"; };
07E26F2E1FFFFB55004667A1 /* EXCalendar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EXCalendar.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1511,6 +1514,8 @@
B5AC39961E95A90B00540AA7 /* EXKernelDevKeyCommands.m */,
B5AC39991E95A90B00540AA7 /* EXKernelDevMotionHandler.h */,
B5AC399A1E95A90B00540AA7 /* EXKernelDevMotionHandler.m */,
0799CFD821839B470039E97C /* EXSession.h */,
0799CFD921839B520039E97C /* EXSession.m */,
);
path = DevSupport;
sourceTree = "<group>";
Expand Down Expand Up @@ -2611,6 +2616,7 @@
B5AC39AA1E95A90B00540AA7 /* EXKernelDevKeyCommands.m in Sources */,
B5FB74311FF6DADB001C764B /* EXVideoManager.m in Sources */,
BB50E88D20B9C0D3003C752D /* RNRootViewGestureRecognizer.m in Sources */,
0799CFDA21839B520039E97C /* EXSession.m in Sources */,
3159BB3621806E24002D2A81 /* RNSVGLine.m in Sources */,
BB5BD32920AEFCB4007E02FC /* REAOperatorNode.m in Sources */,
B54D2913205307380019411A /* EXAppLoadingCancelView.m in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ - (NSError *)_formatError:(NSError *)error
} else if ([errorCode isEqualToString:@"NO_COMPATIBLE_EXPERIENCE_FOUND"]){
formattedMessage = rawMessage; // No compatible experience found at ${originalUrl}. Only ${currentSdkVersions} are supported.
} else if ([errorCode isEqualToString:@"EXPERIENCE_NOT_VIEWABLE"]) {
formattedMessage = [NSString stringWithFormat:@"The experience you requested is not viewable by you. You will need to log in or ask the owner to grant you access."];
formattedMessage = rawMessage; // From server: The experience you requested is not viewable by you. You will need to log in or ask the owner to grant you access.
} else if ([errorCode isEqualToString:@"USER_SNACK_NOT_FOUND"] || [errorCode isEqualToString:@"SNACK_NOT_FOUND"]) {
formattedMessage = [NSString stringWithFormat:@"No snack found at %@.", self.originalUrl];
} else if ([errorCode isEqualToString:@"SNACK_RUNTIME_NOT_RELEASE"]) {
Expand Down
3 changes: 2 additions & 1 deletion ios/Exponent/Kernel/AppLoader/EXFileDownloader.m
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#import "EXEnvironment.h"
#import "EXFileDownloader.h"
#import "EXSession.h"
#import "EXVersions.h"
#import "EXKernelUtil.h"

Expand Down Expand Up @@ -104,7 +105,7 @@ - (void)setHTTPHeaderFields:(NSMutableURLRequest *)request
[request setValue:@"1" forHTTPHeaderField:@"Expo-Api-Version"];
[request setValue:clientEnvironment forHTTPHeaderField:@"Expo-Client-Environment"];

NSString *sessionSecret = [EXEnvironment sharedEnvironment].sessionSecret;
NSString *sessionSecret = [[EXSession sharedInstance] sessionSecret];
if (sessionSecret) {
[request setValue:sessionSecret forHTTPHeaderField:@"Expo-Session"];
}
Expand Down
34 changes: 30 additions & 4 deletions ios/Exponent/Kernel/DevSupport/EXHomeModule.m
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#import "EXEnvironment.h"
#import "EXHomeModule.h"
#import "EXSession.h"
#import "EXUnversioned.h"

#import <React/RCTEventDispatcher.h>
Expand Down Expand Up @@ -170,14 +171,39 @@ - (void)dispatchJSEvent:(NSString *)eventName body:(NSDictionary *)eventBody onS
}
}

RCT_EXPORT_METHOD(setSessionSecret:(NSString *)sessionSecret)
RCT_REMAP_METHOD(getSessionAsync,
getSessionAsync:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSDictionary *session = [[EXSession sharedInstance] session];
resolve(session);
}

RCT_REMAP_METHOD(setSessionAsync,
setSessionAsync:(NSDictionary *)session
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
[EXEnvironment sharedEnvironment].sessionSecret = sessionSecret;
NSError *error;
BOOL success = [[EXSession sharedInstance] saveSessionToKeychain:session error:&error];
if (success) {
resolve(nil);
} else {
reject(@"ERR_SESSION_NOT_SAVED", @"Could not save session", error);
}
}

RCT_EXPORT_METHOD(removeSessionSecret)
RCT_REMAP_METHOD(removeSessionAsync,
removeSessionAsync:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
[EXEnvironment sharedEnvironment].sessionSecret = nil;
NSError *error;
BOOL success = [[EXSession sharedInstance] deleteSessionFromKeychainWithError:&error];
if (success) {
resolve(nil);
} else {
reject(@"ERR_SESSION_NOT_REMOVED", @"Could not remove session", error);
}
}

RCT_EXPORT_METHOD(addDevMenu)
Expand Down
18 changes: 18 additions & 0 deletions ios/Exponent/Kernel/DevSupport/EXSession.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright 2015-present 650 Industries. All rights reserved.

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface EXSession : NSObject

+ (instancetype)sharedInstance;

- (NSDictionary * _Nullable)session;
- (NSString * _Nullable)sessionSecret;
- (BOOL)saveSessionToKeychain:(NSDictionary *)session error:(NSError **)error;
- (BOOL)deleteSessionFromKeychainWithError:(NSError **)error;

@end

NS_ASSUME_NONNULL_END
Loading

0 comments on commit 8a20a96

Please sign in to comment.