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

feat(app-check, android): Implement app check token change listener #7309

Merged
merged 2 commits into from
Sep 4, 2023
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,19 @@
import io.invertase.firebase.common.ReactNativeFirebaseMeta;
import io.invertase.firebase.common.ReactNativeFirebaseModule;
import io.invertase.firebase.common.ReactNativeFirebasePreferences;
import io.invertase.firebase.common.ReactNativeFirebaseEvent;
import io.invertase.firebase.common.ReactNativeFirebaseEventEmitter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class ReactNativeFirebaseAppCheckModule extends ReactNativeFirebaseModule {
private static final String TAG = "AppCheck";
private static final String LOGTAG = "RNFBAppCheck";
private static final String KEY_APPCHECK_TOKEN_REFRESH_ENABLED = "app_check_token_auto_refresh";

private static HashMap<String, FirebaseAppCheck.AppCheckListener> mAppCheckListeners = new HashMap<>();

ReactNativeFirebaseAppCheckProviderFactory providerFactory =
new ReactNativeFirebaseAppCheckProviderFactory();

Expand Down Expand Up @@ -90,6 +98,24 @@ private boolean isAppDebuggable() throws Exception {
firebaseAppCheck.setTokenAutoRefreshEnabled(isAppCheckTokenRefreshEnabled());
}

@Override
public void onCatalystInstanceDestroy() {
super.onCatalystInstanceDestroy();
Log.d(TAG, "instance-destroyed");

Iterator appCheckListenerIterator = mAppCheckListeners.entrySet().iterator();

while (appCheckListenerIterator.hasNext()) {
Map.Entry pair = (Map.Entry) appCheckListenerIterator.next();
String appName = (String) pair.getKey();
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
FirebaseAppCheck firebaseAppCheck = FirebaseAppCheck.getInstance(firebaseApp);
FirebaseAppCheck.AppCheckListener mAppCheckListener = (FirebaseAppCheck.AppCheckListener) pair.getValue();
firebaseAppCheck.removeAppCheckListener(mAppCheckListener);
appCheckListenerIterator.remove();
}
}

@ReactMethod
public void configureProvider(
String appName, String providerName, String debugToken, Promise promise) {
Expand Down Expand Up @@ -177,4 +203,45 @@ public void getToken(String appName, boolean forceRefresh, Promise promise) {
}
});
}

/** Add a new token change listener - if one doesn't exist already */
@ReactMethod
public void addAppCheckListener(final String appName) {
Log.d(TAG, "addAppCheckListener " + appName);

FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
FirebaseAppCheck firebaseAppCheck = FirebaseAppCheck.getInstance(firebaseApp);

if (mAppCheckListeners.get(appName) == null) {
FirebaseAppCheck.AppCheckListener newAppCheckListener = appCheckToken -> {
WritableMap eventBody = Arguments.createMap();
eventBody.putString("appName", appName); // for js side distribution
eventBody.putString("token", appCheckToken.getToken());
eventBody.putDouble("expireTimeMillis", appCheckToken.getExpireTimeMillis());

ReactNativeFirebaseEventEmitter emitter = ReactNativeFirebaseEventEmitter.getSharedInstance();
ReactNativeFirebaseEvent event = new ReactNativeFirebaseEvent("appCheck_token_changed", eventBody, appName);
emitter.sendEvent(event);
};

firebaseAppCheck.addAppCheckListener(newAppCheckListener);
mAppCheckListeners.put(appName, newAppCheckListener);
}
}

/** Removes the current token change listener */
@ReactMethod
public void removeAppCheckListener(String appName) {
Log.d(TAG, "removeAppCheckListener " + appName);

FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
FirebaseAppCheck firebaseAppCheck = FirebaseAppCheck.getInstance(firebaseApp);

FirebaseAppCheck.AppCheckListener mAppCheckListener = mAppCheckListeners.get(appName);

if (mAppCheckListener != null) {
firebaseAppCheck.removeAppCheckListener(mAppCheckListener);
mAppCheckListeners.remove(appName);
}
}
}
37 changes: 28 additions & 9 deletions packages/app-check/lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,18 @@ export namespace FirebaseAppCheckTypes {
isTokenAutoRefreshEnabled?: boolean;
}

export type NextFn<T> = (value: T) => void;
export type ErrorFn = (error: Error) => void;
export type CompleteFn = () => void;

export interface Observer<T> {
next: NextFn<T>;
error: ErrorFn;
complete: CompleteFn;
}

export type PartialObserver<T> = Partial<Observer<T>>;

export interface ReactNativeFirebaseAppCheckProviderOptions {
/**
* debug token to use, if any. Defaults to undefined, pre-configure tokens in firebase web console if needed
Expand Down Expand Up @@ -165,6 +177,10 @@ export namespace FirebaseAppCheckTypes {
*/
readonly expireTimeMillis: number;
}
/**
* The result return from `onTokenChanged`
*/
export type AppCheckListenerResult = AppCheckToken & { readonly appName: string };

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface Statics {
Expand Down Expand Up @@ -258,12 +274,9 @@ export namespace FirebaseAppCheckTypes {
*
* @returns A function that unsubscribes this listener.
*/
// TODO there is a great deal of Observer / PartialObserver typing to carry-in
// onTokenChanged(observer: PartialObserver<AppCheckTokenResult>): () => void;
onTokenChanged(observer: PartialObserver<AppCheckListenerResult>): () => void;

/**
* TODO implement token listener for android.
*
* Registers a listener to changes in the token state. There can be more
* than one listener registered at the same time for one or more
* App Check instances. The listeners call back on the UI thread whenever
Expand All @@ -272,13 +285,19 @@ export namespace FirebaseAppCheckTypes {
* Token listeners do not exist in the native SDK for iOS, no token change events will be emitted on that platform.
* This is not yet implemented on Android, no token change events will be emitted until implemented.
*
* NOTE: Although an `onError` callback can be provided, it will
* never be called, Android sdk code doesn't provide handling for onError function
*
* NOTE: Although an `onCompletion` callback can be provided, it will
* never be called because the token stream is never-ending.
*
* @returns A function that unsubscribes this listener.
*/
// onTokenChanged(
// onNext: (tokenResult: AppCheckTokenResult) => void,
// onError?: (error: Error) => void,
// onCompletion?: () => void,
// ): () => void;
onTokenChanged(
onNext: (tokenResult: AppCheckListenerResult) => void,
onError?: (error: Error) => void,
onCompletion?: () => void,
): () => void;
}
}

Expand Down
45 changes: 41 additions & 4 deletions packages/app-check/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@
const nativeModuleName = 'RNFBAppCheckModule';

class FirebaseAppCheckModule extends FirebaseModule {
constructor(...args) {
super(...args);

this.emitter.addListener(this.eventNameForApp('appCheck_token_changed'), event => {
this.emitter.emit(this.eventNameForApp('onAppCheckTokenChanged'), event);

Check warning on line 47 in packages/app-check/lib/index.js

View check run for this annotation

Codecov / codecov/patch

packages/app-check/lib/index.js#L47

Added line #L47 was not covered by tests
});

this._listenerCount = 0;
}

getIsTokenRefreshEnabledDefault() {
// no default to start
isTokenAutoRefreshEnabled = undefined;
Expand Down Expand Up @@ -131,13 +141,40 @@
}
}

onTokenChanged() {
_parseListener(listenerOrObserver) {

Check warning on line 144 in packages/app-check/lib/index.js

View check run for this annotation

Codecov / codecov/patch

packages/app-check/lib/index.js#L144

Added line #L144 was not covered by tests
return typeof listenerOrObserver === 'object'
? listenerOrObserver.next.bind(listenerOrObserver)
: listenerOrObserver;

Check warning on line 147 in packages/app-check/lib/index.js

View check run for this annotation

Codecov / codecov/patch

packages/app-check/lib/index.js#L146-L147

Added lines #L146 - L147 were not covered by tests
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
onTokenChanged(onNextOrObserver, onError, onCompletion) {

Check warning on line 151 in packages/app-check/lib/index.js

View check run for this annotation

Codecov / codecov/patch

packages/app-check/lib/index.js#L151

Added line #L151 was not covered by tests
// iOS does not provide any native listening feature
if (isIOS) {
// eslint-disable-next-line no-console
console.warn('onTokenChanged is not implemented on IOS, only for Android');

Check warning on line 155 in packages/app-check/lib/index.js

View check run for this annotation

Codecov / codecov/patch

packages/app-check/lib/index.js#L155

Added line #L155 was not covered by tests
return () => {};
}
// TODO unimplemented on Android
return () => {};
const nextFn = this._parseListener(onNextOrObserver);

Check warning on line 158 in packages/app-check/lib/index.js

View check run for this annotation

Codecov / codecov/patch

packages/app-check/lib/index.js#L158

Added line #L158 was not covered by tests
// let errorFn = function () { };
// if (onNextOrObserver.error != null) {
// errorFn = onNextOrObserver.error.bind(onNextOrObserver);
// }
// else if (onError) {
// errorFn = onError;
// }
const subscription = this.emitter.addListener(

Check warning on line 166 in packages/app-check/lib/index.js

View check run for this annotation

Codecov / codecov/patch

packages/app-check/lib/index.js#L166

Added line #L166 was not covered by tests
this.eventNameForApp('onAppCheckTokenChanged'),
nextFn,
);
if (this._listenerCount === 0) this.native.addAppCheckListener();

this._listenerCount++;
return () => {
subscription.remove();
this._listenerCount--;

Check warning on line 175 in packages/app-check/lib/index.js

View check run for this annotation

Codecov / codecov/patch

packages/app-check/lib/index.js#L172-L175

Added lines #L172 - L175 were not covered by tests
if (this._listenerCount === 0) this.native.removeAppCheckListener();
};
}
}

Expand All @@ -151,7 +188,7 @@
version,
namespace,
nativeModuleName,
nativeEvents: false, // TODO implement ['appcheck-token-changed'],
nativeEvents: ['appCheck_token_changed'],
hasMultiAppSupport: true,
hasCustomUrlOrRegionSupport: false,
ModuleClass: FirebaseAppCheckModule,
Expand Down
Loading