@@ -46,6 +46,7 @@ import {
4646import {
4747 deleteSlackWebhookUrlFromKeychain ,
4848 deleteTelegramBotTokenFromKeychain ,
49+ getTelegramBotTokenMigrationRef ,
4950 isSlackWebhookUrlKeychainRef ,
5051 isTelegramBotTokenKeychainRef ,
5152 resolveSlackWebhookUrl ,
@@ -83,7 +84,7 @@ async function redactSecretsFromManagedLogs(secrets: string[]) {
8384 }
8485}
8586
86- function redactPersistedTelegramBotToken (
87+ export function redactPersistedTelegramBotToken (
8788 client : ReturnType < typeof getLoopndrollDatabase > [ "client" ] ,
8889 plaintextBotToken : string ,
8990 botTokenRef : string ,
@@ -156,6 +157,28 @@ function redactPersistedTelegramBotToken(
156157 } ) ( ) ;
157158}
158159
160+ function deleteTelegramBotTokenFromKeychainIfUnused (
161+ db : ReturnType < typeof getLoopndrollDatabase > [ "db" ] ,
162+ botTokenOrRef : string | null | undefined ,
163+ ) {
164+ if ( typeof botTokenOrRef !== "string" || ! isTelegramBotTokenKeychainRef ( botTokenOrRef ) ) {
165+ return ;
166+ }
167+ const botTokenRef = botTokenOrRef . trim ( ) ;
168+
169+ const remainingRef = db
170+ . select ( { id : notifications . id } )
171+ . from ( notifications )
172+ . where ( and ( eq ( notifications . channel , "telegram" ) , eq ( notifications . botToken , botTokenRef ) ) )
173+ . limit ( 1 )
174+ . get ( ) ;
175+ if ( remainingRef ) {
176+ return ;
177+ }
178+
179+ deleteTelegramBotTokenFromKeychain ( botTokenRef ) ;
180+ }
181+
159182export async function saveDefaultPrompt ( defaultPrompt : string ) {
160183 const paths = getLoopndrollPaths ( ) ;
161184 const { db } = getLoopndrollDatabase ( paths . databasePath ) ;
@@ -262,9 +285,8 @@ export async function updateLoopNotification(notification: UpdateLoopNotificatio
262285 if ( notification . channel === "slack" ) {
263286 const previousWebhookUrl =
264287 currentNotification . channel === "slack" ? currentNotification . webhookUrl : "" ;
265- if ( currentNotification . channel === "telegram" ) {
266- deleteTelegramBotTokenFromKeychain ( currentNotification . botToken ) ;
267- }
288+ const previousBotToken =
289+ currentNotification . channel === "telegram" ? currentNotification . botToken : "" ;
268290 const webhookUrlRef = isSlackWebhookUrlKeychainRef ( notification . webhookUrl )
269291 ? notification . webhookUrl . trim ( )
270292 : storeSlackWebhookUrlInKeychain ( notification . id , notification . webhookUrl ) ;
@@ -282,6 +304,7 @@ export async function updateLoopNotification(notification: UpdateLoopNotificatio
282304 } )
283305 . where ( eq ( notifications . id , notification . id ) )
284306 . run ( ) ;
307+ deleteTelegramBotTokenFromKeychainIfUnused ( db , previousBotToken ) ;
285308 } else {
286309 const previousBotToken =
287310 currentNotification . channel === "telegram" ? currentNotification . botToken : "" ;
@@ -308,6 +331,7 @@ export async function updateLoopNotification(notification: UpdateLoopNotificatio
308331 } )
309332 . where ( eq ( notifications . id , notification . id ) )
310333 . run ( ) ;
334+ deleteTelegramBotTokenFromKeychainIfUnused ( db , previousBotToken ) ;
311335 }
312336
313337 return loadSnapshot ( paths ) ;
@@ -364,7 +388,7 @@ export async function deleteLoopNotification(notificationId: string) {
364388 . where ( eq ( settings . globalNotificationId , notificationId ) )
365389 . run ( ) ;
366390 } ) ;
367- deleteTelegramBotTokenFromKeychain ( existingNotification ?. botToken ) ;
391+ deleteTelegramBotTokenFromKeychainIfUnused ( db , existingNotification ?. botToken ) ;
368392 deleteSlackWebhookUrlFromKeychain ( existingNotification ?. webhookUrl ) ;
369393
370394 return loadSnapshot ( paths ) ;
@@ -378,6 +402,7 @@ export async function migrateNotificationSecretsToKeychain() {
378402 . from ( notifications )
379403 . orderBy ( asc ( notifications . createdAt ) , asc ( notifications . id ) )
380404 . all ( ) ;
405+ const migratedTelegramBotTokenRefs = new Map < string , string > ( ) ;
381406
382407 for ( const row of existingNotificationRows ) {
383408 const currentNotification = mapNotificationRow ( row ) ;
@@ -414,7 +439,16 @@ export async function migrateNotificationSecretsToKeychain() {
414439 continue ;
415440 }
416441
417- const botTokenRef = storeTelegramBotTokenInKeychain ( currentNotification . id , botToken ) ;
442+ const botTokenMigration = getTelegramBotTokenMigrationRef (
443+ currentNotification . id ,
444+ botToken ,
445+ migratedTelegramBotTokenRefs ,
446+ ) ;
447+ let botTokenRef = botTokenMigration . ref ;
448+ if ( botTokenMigration . shouldStore ) {
449+ botTokenRef = storeTelegramBotTokenInKeychain ( currentNotification . id , botToken ) ;
450+ migratedTelegramBotTokenRefs . set ( botToken , botTokenRef ) ;
451+ }
418452 redactPersistedTelegramBotToken ( client , botToken , botTokenRef ) ;
419453 await redactSecretsFromManagedLogs ( [ botToken ] ) ;
420454 db . update ( notifications )
0 commit comments