apns2 icon indicating copy to clipboard operation
apns2 copied to clipboard

TooManyProviderTokenUpdates

Open naveents opened this issue 1 year ago • 4 comments

Hello, First of all, thank you for the APNS2 library. It works flawlessly in most cases. However, I am facing one issue with it. When sending concurrent requests to the following class using APNS2, I often receive the error 'TooManyProviderTokenUpdates.' Can you review my code?

import { ApnsClient, Notification } from 'apns2';
import fs from 'fs';
import path from 'path';
import { IPushNotification } from './sendPushService';

class ApnNotifyService {
    private static instances: Map<string, ApnNotifyService> = new Map();
    private readonly signingKeyPath: string;
    private readonly defaultTopic: string;
    private client!: ApnsClient;
    private tokenExpirationTimestamp: number;
    private isRefreshing: boolean = false;

    private constructor(private appTypeName: string) {
        this.signingKeyPath = path.join(__dirname, '../../certificates/myApp.p8');
        this.defaultTopic = (appTypeName === 'myBasicApp') ? 'com.bundle.myBasicApp' : 'com.bundle.myStandardApp';
        this.tokenExpirationTimestamp = 0;
        this.createClient();
    }

    public static getInstance(appTypeName: string): ApnNotifyService {
        if (!this.instances.has(appTypeName)) {
            this.instances.set(appTypeName, new ApnNotifyService(appTypeName));
        }
        return this.instances.get(appTypeName)!;
    }

    private createClient() {
        this.client = new ApnsClient({
            team: '<teamid>',
            keyId: '<keyid>',
            signingKey: fs.readFileSync(this.signingKeyPath),
            defaultTopic: this.defaultTopic,
            requestTimeout: 0,
            pingInterval: 5000,
        });
        this.tokenExpirationTimestamp = Date.now() + (2 * 60 * 60 * 1000); // 2 hours
        console.log('Configured APN client for topic:', this.defaultTopic);
    }

    private async refreshClient() {
        if (Date.now() >= this.tokenExpirationTimestamp && !this.isRefreshing) {
            this.isRefreshing = true;
            console.log('Refreshing APN client...');
            try {
                this.createClient();
            } catch (error) {
                console.error('Error refreshing APNs client:', error);
            } finally {
                this.isRefreshing = false;
            }
        }
    }

    private async ensureValidClient() {
        if (Date.now() >= this.tokenExpirationTimestamp) {
            await this.refreshClient();
        }
    }

    async sendNotification(notification: IPushNotification) {
        await this.ensureValidClient();

        const notificationBody = new Notification(notification.token, {
            alert: {
                title: notification.title,
                body: notification.body,
            },
            badge: 4,
            data: notification.data || {},
        });

        try {
            const result = await this.client.send(notificationBody);
            return { status: true, data: result, error: null };
        } catch (error: any) {
            console.error('APNs send error:', error);

            if (error && error.reason === 'DeviceTokenNotForTopic') {
                console.error(`DeviceTokenNotForTopic: ${error.reason}`);
            } else if (error && error.reason === 'BadDeviceToken') {
                console.error(`BadDeviceToken: ${error.reason}`);
            } else {
                console.error(`Unknown error: ${error.reason}`);
            }
            return {
                status: false,
                data: null,
                error: { reason: error.reason ?? error.toString(), statusCode: error.statusCode ?? '' },
            };
        }
    }
}

export default ApnNotifyService;

As you can see i am regenerating the token every 2 hours. What may be the reason for getting TooManyProviderTokenUpdates?

naveents avatar Aug 06 '24 14:08 naveents

You're exporting ApnNotifyService which means you are probably creating a bunch of instances of this class which you should not do. You should only create a single instance of the ApnsClient from apns2 and just it let it manage the token. apns2 already manages creating new tokens so im not sure why you are trying to do it yourself.

AndrewBarba avatar Aug 08 '24 22:08 AndrewBarba

@AndrewBarba Thanks.

naveents avatar Aug 14 '24 07:08 naveents

@AndrewBarba - I have one more question. Does it support multiple team ids? I mean if i have two different apps - Can i instantiate one ApnsClient for App 1 (team id :1) and another for App 2 (team id:2) ?

naveents avatar Aug 15 '24 07:08 naveents

Yes absolutely. Creating one instance per team is what you want. The tokens will be managed inside each instance and as long as you don't keep creating those instances over and over it will work the way you want.

AndrewBarba avatar Aug 15 '24 13:08 AndrewBarba