1- import { initializeApp } from 'firebase/app' ;
1+ import { FirebaseApp , initializeApp } from 'firebase/app' ;
22import {
33 deleteDoc ,
44 doc ,
55 getDoc ,
66 getFirestore ,
77 setDoc ,
8+ Timestamp ,
9+ Firestore
810} from 'firebase/firestore' ;
911import { env } from '~/env.mjs' ;
1012
@@ -25,16 +27,29 @@ export interface AuthProps {
2527 user : User ;
2628}
2729
28- const { FIRE_API_KEY , FIRE_DOMAIN , FIRE_PROJECT_ID } = env ;
30+ export interface ClientTokenData {
31+ token : string ;
32+ expiresAt : Timestamp ;
33+ }
34+
35+ let app : FirebaseApp ;
36+ let db : Firestore ;
37+
38+ function getDb ( ) {
39+ if ( ! db ) {
40+ const { FIRE_API_KEY , FIRE_DOMAIN , FIRE_PROJECT_ID } = env ;
2941
30- const firebaseConfig = {
31- apiKey : FIRE_API_KEY ,
32- authDomain : FIRE_DOMAIN ,
33- projectId : FIRE_PROJECT_ID ,
34- } ;
42+ const firebaseConfig = {
43+ apiKey : FIRE_API_KEY ,
44+ authDomain : FIRE_DOMAIN ,
45+ projectId : FIRE_PROJECT_ID ,
46+ } ;
3547
36- const app = initializeApp ( firebaseConfig ) ;
37- const db = getFirestore ( app ) ;
48+ app = initializeApp ( firebaseConfig ) ;
49+ db = getFirestore ( app ) ;
50+ }
51+ return db ;
52+ }
3853
3954// Firestore data management functions
4055
@@ -43,7 +58,7 @@ export async function setUser(user: User) {
4358 if ( ! user ) return Promise . resolve ( ) ;
4459
4560 const { email, id, username } = user ;
46- const ref = doc ( db , 'users' , String ( id ) ) ;
61+ const ref = doc ( getDb ( ) , 'users' , String ( id ) ) ;
4762 const data : UserData = { email } ;
4863
4964 if ( username ) {
@@ -64,7 +79,7 @@ export async function setStore(props: AuthProps) {
6479 if ( ! accessToken || ! scope ) return null ;
6580
6681 const storeHash = context ?. split ( '/' ) [ 1 ] || '' ;
67- const ref = doc ( db , 'store' , storeHash ) ;
82+ const ref = doc ( getDb ( ) , 'store' , storeHash ) ;
6883 const data = { accessToken, adminId : id , scope } ;
6984
7085 await setDoc ( ref , data ) ;
@@ -82,26 +97,66 @@ export async function setStoreUser(session: AuthProps) {
8297
8398 const storeHash = context . split ( '/' ) [ 1 ] ;
8499 const documentId = `${ userId } _${ storeHash } ` ;
85- const ref = doc ( db , 'storeUsers' , documentId ) ;
100+ const ref = doc ( getDb ( ) , 'storeUsers' , documentId ) ;
86101
87102 await setDoc ( ref , { storeHash } ) ;
88103}
89104
90105export async function deleteUser ( storeHash : string , user : User ) {
91106 const docId = `${ user . id } _${ storeHash } ` ;
92- const ref = doc ( db , 'storeUsers' , docId ) ;
107+ const ref = doc ( getDb ( ) , 'storeUsers' , docId ) ;
93108
94109 await deleteDoc ( ref ) ;
95110}
96111
97112export async function getStoreToken ( storeHash : string ) : Promise < string | null > {
98113 if ( ! storeHash ) return null ;
99- const storeDoc = await getDoc ( doc ( db , 'store' , storeHash ) ) ;
114+ const storeDoc = await getDoc ( doc ( getDb ( ) , 'store' , storeHash ) ) ;
100115
101116 return storeDoc . data ( ) ?. accessToken ;
102117}
103118
104119export async function deleteStore ( storeHash : string ) {
105- const ref = doc ( db , 'store' , storeHash ) ;
120+ const ref = doc ( getDb ( ) , 'store' , storeHash ) ;
106121 await deleteDoc ( ref ) ;
107122}
123+
124+ export async function saveClientToken ( clientToken : string ) : Promise < string > {
125+ if ( ! clientToken ) {
126+ throw new Error ( 'A clientToken is required to create an exchange token.' ) ;
127+ }
128+
129+ const exchangeToken = crypto . randomUUID ( ) ;
130+ const expiresAt = Timestamp . fromMillis ( Date . now ( ) + 120 * 1000 ) ; // 2 minutes from now
131+
132+ const ref = doc ( getDb ( ) , 'exchangeTokens' , exchangeToken ) ;
133+ const data : Omit < ClientTokenData , 'expiresAt' > & { expiresAt : Timestamp } = {
134+ token : clientToken ,
135+ expiresAt
136+ } ;
137+
138+ await setDoc ( ref , data ) ;
139+
140+ return exchangeToken ;
141+ }
142+
143+ export async function getClientTokenMaybeAndDelete ( exchangeToken : string ) : Promise < string | false > {
144+ const ref = doc ( getDb ( ) , 'exchangeTokens' , exchangeToken ) ;
145+ const docSnap = await getDoc ( ref ) ;
146+
147+ await deleteDoc ( ref ) ;
148+
149+ if ( ! docSnap . exists ( ) ) {
150+ return false ;
151+ }
152+
153+ const data = docSnap . data ( ) as ClientTokenData ;
154+ const now = Timestamp . now ( ) ;
155+
156+ if ( now > data . expiresAt ) {
157+ console . warn ( 'Exchange token expired:' , exchangeToken ) ;
158+ return false ;
159+ }
160+
161+ return data . token ;
162+ }
0 commit comments