Skip to content

Commit be27ea7

Browse files
committed
fix: push notification thing
1 parent 7ce20f5 commit be27ea7

4 files changed

Lines changed: 100 additions & 14 deletions

File tree

infrastructure/evault-core/src/controllers/NotificationController.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ export class NotificationController {
1111
constructor() {
1212
this.notificationService = new NotificationService(
1313
AppDataSource.getRepository("Verification"),
14-
AppDataSource.getRepository("Notification")
14+
AppDataSource.getRepository("Notification"),
15+
AppDataSource.getRepository(DeviceToken),
1516
);
1617
this.deviceTokenService = new DeviceTokenService(
1718
AppDataSource.getRepository(DeviceToken)
@@ -76,8 +77,15 @@ export class NotificationController {
7677

7778
const token = typeof pushToken === "string" ? pushToken.trim() : undefined;
7879

80+
// Look up old tokens for this device so we can clean them from DeviceToken table
7981
if (token) {
80-
await this.deviceTokenService.register(eName, token);
82+
const existing = await AppDataSource.getRepository("Verification").findOne({
83+
where: { linkedEName: eName, deviceId },
84+
}) as { pushTokens?: string[] } | null;
85+
const oldTokens = (existing?.pushTokens ?? []).filter((t) => t !== token);
86+
for (const stale of oldTokens) {
87+
await this.deviceTokenService.unregister(eName, stale);
88+
}
8189
}
8290

8391
const verification = await this.notificationService.registerDevice({
@@ -88,6 +96,10 @@ export class NotificationController {
8896
registrationTime: new Date(),
8997
});
9098

99+
if (token) {
100+
await this.deviceTokenService.register(eName, token);
101+
}
102+
91103
res.json({
92104
success: true,
93105
message: "Device registered successfully",

infrastructure/evault-core/src/core/protocol/graphql-server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { exampleQueries } from "./examples/examples";
1313
import { typeDefs } from "./typedefs";
1414
import { VaultAccessGuard, type VaultContext } from "./vault-access-guard";
1515
import { MessageNotificationService } from "../../services/MessageNotificationService";
16+
import { DeviceToken } from "../../entities/DeviceToken";
1617
import { AppDataSource } from "../../config/database";
1718

1819
export class GraphQLServer {
@@ -54,6 +55,7 @@ export class GraphQLServer {
5455
AppDataSource.getRepository("Verification"),
5556
AppDataSource.getRepository("Notification"),
5657
this.db,
58+
AppDataSource.getRepository(DeviceToken),
5759
);
5860
}
5961
return this.messageNotificationService;

infrastructure/evault-core/src/services/MessageNotificationService.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Repository } from "typeorm";
22
import { Notification } from "../entities/Notification";
33
import { Verification } from "../entities/Verification";
4+
import { DeviceToken } from "../entities/DeviceToken";
45
import { NotificationService } from "./NotificationService";
56
import type { DbService } from "../core/db/db.service";
67
import { deserializeValue } from "../core/db/schema";
@@ -23,11 +24,13 @@ export class MessageNotificationService {
2324
constructor(
2425
verificationRepository: Repository<Verification>,
2526
notificationRepository: Repository<Notification>,
26-
db: DbService
27+
db: DbService,
28+
deviceTokenRepository?: Repository<DeviceToken>,
2729
) {
2830
this.notificationService = new NotificationService(
2931
verificationRepository,
30-
notificationRepository
32+
notificationRepository,
33+
deviceTokenRepository,
3134
);
3235
this.db = db;
3336
}

infrastructure/evault-core/src/services/NotificationService.ts

Lines changed: 79 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Repository } from "typeorm";
22
import { Verification } from "../entities/Verification";
33
import { Notification } from "../entities/Notification";
4+
import { DeviceToken } from "../entities/DeviceToken";
45

56
export interface DeviceRegistration {
67
eName: string;
@@ -22,10 +23,27 @@ export interface SendNotificationRequest {
2223
sharedSecret: string;
2324
}
2425

26+
const BAD_TOKEN_ERRORS = [
27+
"messaging/registration-token-not-valid",
28+
"messaging/invalid-registration-token",
29+
"messaging/mismatched-credential",
30+
"BadDeviceToken",
31+
"Unregistered",
32+
"DeviceTokenNotForTopic",
33+
"ExpiredProviderToken",
34+
"InvalidProviderToken",
35+
];
36+
37+
function isBadTokenError(error: unknown): boolean {
38+
const msg = error instanceof Error ? error.message : String(error);
39+
return BAD_TOKEN_ERRORS.some((e) => msg.includes(e));
40+
}
41+
2542
export class NotificationService {
2643
constructor(
2744
private verificationRepository: Repository<Verification>,
28-
private notificationRepository: Repository<Notification>
45+
private notificationRepository: Repository<Notification>,
46+
private deviceTokenRepository?: Repository<DeviceToken>,
2947
) {}
3048

3149
async registerDevice(registration: DeviceRegistration): Promise<Verification> {
@@ -56,10 +74,10 @@ export class NotificationService {
5674
if (verification) {
5775
verification.platform = registration.platform;
5876
if (token) {
59-
const existing = verification.pushTokens ?? [];
60-
if (!existing.includes(token)) {
61-
verification.pushTokens = [...existing, token];
62-
}
77+
// Replace all tokens for this device — the latest token from the
78+
// OS is the only valid one. Appending caused stale tokens to
79+
// accumulate and never get cleaned up.
80+
verification.pushTokens = [token];
6381
}
6482
verification.deviceActive = true;
6583
verification.updatedAt = new Date();
@@ -174,20 +192,71 @@ export class NotificationService {
174192

175193
const pushSucceeded = pushResults.filter(r => r.status === "fulfilled").length;
176194
const pushFailed = pushResults.filter(r => r.status === "rejected").length;
195+
196+
// Collect tokens that returned a known "bad token" error so we can purge them
197+
const badTokens: string[] = [];
198+
pushResults.forEach((r, i) => {
199+
if (r.status === "rejected") {
200+
console.error(`[NOTIF] Push failed for token index ${i}:`, r.reason);
201+
if (isBadTokenError(r.reason)) {
202+
badTokens.push(allTokens[i].token);
203+
}
204+
}
205+
});
206+
177207
if (pushFailed > 0) {
178208
console.log(`[NOTIF] Push results for ${eName}: ${pushSucceeded} sent, ${pushFailed} failed`);
179-
pushResults.forEach((r, i) => {
180-
if (r.status === "rejected") {
181-
console.error(`[NOTIF] Push failed for token index ${i}:`, r.reason);
182-
}
183-
});
184209
} else {
185210
console.log(`[NOTIF] Push sent successfully to ${pushSucceeded} token(s) for ${eName}`);
186211
}
187212

213+
// Purge bad tokens from both Verification and DeviceToken tables
214+
if (badTokens.length > 0) {
215+
console.log(`[NOTIF] Removing ${badTokens.length} bad token(s) for ${eName}`);
216+
await this.removeBadTokens(eName, badTokens);
217+
}
218+
188219
return pushSucceeded > 0 || pushFailed === 0;
189220
}
190221

222+
private async removeBadTokens(eName: string, badTokens: string[]): Promise<void> {
223+
try {
224+
// Clean Verification table
225+
const verifications = await this.verificationRepository.find({
226+
where: { linkedEName: eName },
227+
});
228+
for (const v of verifications) {
229+
const before = v.pushTokens?.length ?? 0;
230+
v.pushTokens = (v.pushTokens ?? []).filter((t) => !badTokens.includes(t));
231+
if (v.pushTokens.length !== before) {
232+
v.updatedAt = new Date();
233+
await this.verificationRepository.save(v);
234+
}
235+
}
236+
237+
// Clean DeviceToken table
238+
if (this.deviceTokenRepository) {
239+
const normalized = eName.startsWith("@") ? eName : `@${eName}`;
240+
const withoutAt = eName.replace(/^@/, "");
241+
const rows = await this.deviceTokenRepository
242+
.createQueryBuilder("dt")
243+
.where("dt.eName = :e1 OR dt.eName = :e2", { e1: normalized, e2: withoutAt })
244+
.getMany();
245+
246+
for (const row of rows) {
247+
const before = row.tokens.length;
248+
row.tokens = row.tokens.filter((t) => !badTokens.includes(t));
249+
if (row.tokens.length !== before) {
250+
row.updatedAt = new Date();
251+
await this.deviceTokenRepository.save(row);
252+
}
253+
}
254+
}
255+
} catch (err) {
256+
console.error(`[NOTIF] Failed to remove bad tokens for ${eName}:`, err);
257+
}
258+
}
259+
191260
async getUndeliveredNotifications(eName: string): Promise<Notification[]> {
192261
return await this.notificationRepository.find({
193262
where: { eName, delivered: false },

0 commit comments

Comments
 (0)