How to resolve React Native Firebase Error (messaging().getToken()), related to react-native-device-info

Serdar Mustafa picture Serdar Mustafa · May 14, 2020 · Viewed 7.7k times · Source

I am in the process of migrating react-native-device-info from v2 to v3 and I have a breaking change. Prior to updating the package, everything was working fine. The error appears on all emulators and real devices.

The error I get is:

NativeFirebaseError: [messaging/unregistered] You must be registered for remote notifications before calling get token, see messaging().registerForRemoteNotifications() or requestPermission().

Env Info:

   System:
    OS: macOS 10.15.4
    CPU: (8) x64 Intel(R) Core(TM) i7-6920HQ CPU @ 2.90GHz
    Memory: 6.44 GB / 16.00 GB
    Shell: 5.7.1 - /bin/zsh
  Binaries:
    Node: 12.14.0 - ~/.nvm/versions/node/v12.14.0/bin/node
    Yarn: 1.22.4 - /usr/local/bin/yarn
    npm: 6.13.4 - ~/.nvm/versions/node/v12.14.0/bin/npm
  SDKs:
    iOS SDK:
      Platforms: iOS 13.4, DriverKit 19.0, macOS 10.15, tvOS 13.4, watchOS 6.2
    Android SDK:
      API Levels: 28, 29
      Build Tools: 28.0.3, 29.0.3
      System Images: android-28 | Google Play Intel x86 Atom
  IDEs:
    Android Studio: 3.6 AI-192.7142.36.36.6241897
    Xcode: 11.4.1/11E503a - /usr/bin/xcodebuild
  npmPackages:
    react: 16.12.0 => 16.12.0 
    react-native: 0.61.5 => 0.61.5 
  npmGlobalPackages:
    create-react-native-app: 2.0.2
    react-native-cli: 2.0.1
    react-native-debugger-open: 0.3.24

My code:

export interface LoginParams {
    token?: string;
    useProduct?: string;
    useUser?: string;
    ssoToken?: string;
}

// TODO: Should be switched out with tv-ui-common once the whole app starts using Product model.
interface AuthenticationResponse {
    noCacheToken: string;
    products: ProductData[];
    currentUserId: number;
}
interface ProductsResponse {
    currentProductCode: string;
    products: ProductData[];
}

const getInitialIsLoggedIn = () => false;
const getInitialAuthenticating = () => false;
const getInitialProducts = () => [];
const getInitialSelectedProduct = () => null;
const getInitialCurrentUserId = () => null;

export default class AuthenticationStore {
    private static async authenticate({ token, useProduct, useUser, ssoToken }: LoginParams) {
        const params = new URLSearchParams();
        params.append('ui', UI);
        params.append('uuid', DeviceInfo.getUniqueId());
        params.append('model', DeviceInfo.getSystemName());
        params.append('hwVersion', DeviceInfo.getModel());
        params.append('swVersion', DeviceInfo.getSystemVersion());
        params.append('uiVersion', getUiVersion());
        if (useProduct) {
            params.append('useProduct', useProduct);
        }
        if (useUser) {
            params.append('useUser', useUser);
        }

        try {
            const notificationToken = await messaging().getToken();
            params.append('pushToken', notificationToken);
            console.log(notificationToken, 'notificationToken');
        } catch (error) {
            // iPhone Simulator does not give a token
            console.log("error", error);
        }

        try {
            // If we have token, meaning user authenticated via TV pairing
            if (token) {
                params.append('token', token);
                return await Api.post<AuthenticationResponse>(
                    'authentication/token',
                    params.toString(),
                    undefined,
                    '3.0',
                );
            }
            if (ssoToken) {
                params.append('ssoToken', ssoToken);
            }
            return await Api.post<AuthenticationResponse>(
                'authentication/sso',
                params.toString(),
                undefined,
                '3.0',
            );
        } catch (error) {
            console.log('err', error);
            if (error.response.status === 423) {
                throw new ProductNotAvailableError(error.response.data.products);
            } else {
                throw error;
            }
        }
    }

    @observable public isLoggedIn = getInitialIsLoggedIn();
    @observable public authenticating = getInitialAuthenticating();
    @observable public products: ProductData[] = getInitialProducts();
    @observable public selectedProduct: ProductData | null = getInitialSelectedProduct();
    @observable public currentUserId: number | null = getInitialCurrentUserId();

    constructor() {
        this.registerPushNotificationToken = this.registerPushNotificationToken.bind(this);
        this.loadProducts = this.loadProducts.bind(this);
    }

    @action public reset() {
        this.isLoggedIn = getInitialIsLoggedIn();
        this.authenticating = getInitialAuthenticating();
        this.products = getInitialProducts();
        this.selectedProduct = getInitialSelectedProduct();
        this.currentUserId = getInitialCurrentUserId();
    }

    public async registerPushNotificationToken(token: string) {
        if (this.isLoggedIn) {
            await Api.post('push-token-update', { token }, undefined, '3.0');
        }
    }

    public async login(params: LoginParams = {}): Promise<boolean> {
        this.setAuthenticating(true);

        try {
            const response = await AuthenticationStore.authenticate(params);
            setNoCacheToken(response.data.noCacheToken);

            const products = response.data.products || [];

            this.setProducts(products);
            if (response.status === 202) {
                if (products.length === 1) {
                    const [product] = products;
                    return this.login({ useProduct: product.code });
                }
                if (products.length > 1) {
                    throw new ProductNotSelectedError();
                }
            }
            this.setSelectedProduct(products[0]);

            this.setAuthenticating(false, true, response.data.currentUserId);
            analytics().setUserId(response.data.currentUserId.toString());
        } catch (error) {
            this.setAuthenticating(false, false);

            if (error.response && error.response.status === 401) {
                if (error.response.data.errorCode === 'E_UNAUTHORIZED') {
                    // Silently fail error if user was not authenticated while doing sso request.
                    // We do not need to handle that 'error'. It's common case for cookies
                    // to expire.
                    return false;
                }
                throw new NoProductsError();
            }
            if (
                (error.response && error.response.status === 400)
                || error instanceof ProductNotSelectedError
            ) {
                throw error;
            } else if (error instanceof ProductNotAvailableError) {
                // Product is suspended or in debt and thus not usable

                const selectedProduct = error.products.find(
                    product => product.code === params.useProduct,
                );
                this.setProducts(error.products, selectedProduct);
                throw error;
            } else {
                // Something went wrong with authentication on server side.
                throw new Error(`Unexpected authentication error: ${error}`);
            }
        }

        return this.isLoggedIn;
    }

    public async loadProducts() {
        try {
            // Logged in version of fetching products
            const { data: { products, currentProductCode } } = await Api.get<ProductsResponse>('products');
            const selectedProduct = products.find(product => product.code === currentProductCode);
            this.setProducts(products, selectedProduct);
        } catch (error) {
            if (error.response.status === 401) {
                // Logged out version of fetching products
                try {
                    const response = await AuthenticationStore.authenticate({});
                    const products = response.data.products || [];
                    this.setProducts(products);
                } catch (loginError) {
                    // TODO: Handle error
                }
            }
            // TODO: Handle failed request on loading products
        }
    }

    @action public setAuthenticating(
        authenticating: boolean = false,
        isLoggedIn: boolean = this.isLoggedIn,
        currentUserId: number | null = this.currentUserId,
    ) {
        this.authenticating = authenticating;
        this.isLoggedIn = isLoggedIn;
        this.currentUserId = this.isLoggedIn ? currentUserId : null;
    }

    @action private setProducts(
        products: ProductData[],
        selectedProduct: ProductData | null = null,
    ) {
        this.products = products;
        this.setSelectedProduct(selectedProduct);
    }

    @action private setSelectedProduct(product: ProductData | null = null) {
        this.selectedProduct = product;
    }
}

I have dug around online but can't seem to find anything at all similar, does anyone have any ideas what I can try?

Answer

Evan Miller picture Evan Miller · May 14, 2020

The error says that you need to request the users permission for notifications before calling getToken. Have you tried this? https://rnfirebase.io/messaging/usage