@entropic-bond/firebase 1.13.2 → 1.13.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -0
- package/lib/auth/firebase-auth.d.ts +20 -0
- package/lib/auth/firebase-auth.spec.d.ts +1 -0
- package/lib/cloud-functions/firebase-cloud-functions.d.ts +7 -0
- package/lib/cloud-functions/firebase-cloud-functions.spec.d.ts +6 -0
- package/lib/cloud-storage/firebase-cloud-storage.d.ts +10 -0
- package/lib/cloud-storage/firebase-cloud-storage.spec.d.ts +1 -0
- package/lib/entropic-bond-firebase.js +23778 -0
- package/lib/entropic-bond-firebase.js.map +1 -0
- package/lib/entropic-bond-firebase.umd.cjs +3678 -0
- package/lib/entropic-bond-firebase.umd.cjs.map +1 -0
- package/lib/firebase-helper.d.ts +38 -0
- package/lib/index.d.ts +5 -0
- package/lib/mocks/test-user.d.ts +49 -0
- package/lib/store/firebase-datasource.d.ts +19 -0
- package/lib/store/firebase-datasource.spec.d.ts +1 -0
- package/package.json +7 -3
- package/.firebaserc +0 -5
- package/.github/workflows/release.yml +0 -27
- package/CHANGELOG.md +0 -338
- package/firebase.json +0 -30
- package/firestore.indexes.json +0 -4
- package/firestore.rules +0 -8
- package/functions/package-lock.json +0 -2344
- package/functions/package.json +0 -26
- package/functions/src/index.ts +0 -33
- package/functions/tsconfig.json +0 -19
- package/src/auth/firebase-auth.spec.ts +0 -90
- package/src/auth/firebase-auth.ts +0 -212
- package/src/cloud-functions/firebase-cloud-functions.spec.ts +0 -47
- package/src/cloud-functions/firebase-cloud-functions.ts +0 -25
- package/src/cloud-storage/firebase-cloud-storage.spec.ts +0 -135
- package/src/cloud-storage/firebase-cloud-storage.ts +0 -67
- package/src/firebase-helper.ts +0 -92
- package/src/index.ts +0 -5
- package/src/mocks/mock-data.json +0 -148
- package/src/mocks/test-user.ts +0 -121
- package/src/store/firebase-datasource.spec.ts +0 -555
- package/src/store/firebase-datasource.ts +0 -146
- package/storage.rules +0 -8
- package/tsconfig-build.json +0 -7
- package/tsconfig.json +0 -30
- package/vite.config.ts +0 -23
package/functions/package.json
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "functions",
|
|
3
|
-
"scripts": {
|
|
4
|
-
"build": "tsc",
|
|
5
|
-
"build:watch": "tsc --watch",
|
|
6
|
-
"serve": "npm run build && firebase emulators:start --only functions",
|
|
7
|
-
"shell": "npm run build && firebase functions:shell",
|
|
8
|
-
"start": "npm run shell",
|
|
9
|
-
"deploy": "firebase deploy --only functions",
|
|
10
|
-
"logs": "firebase functions:log",
|
|
11
|
-
"install-build": "npm i && npm run build",
|
|
12
|
-
"postinstall": "npm run build"
|
|
13
|
-
},
|
|
14
|
-
"engines": {
|
|
15
|
-
"node": "20"
|
|
16
|
-
},
|
|
17
|
-
"main": "lib/index.js",
|
|
18
|
-
"dependencies": {
|
|
19
|
-
"firebase-admin": "^12.0.0",
|
|
20
|
-
"firebase-functions": "^4.7.0"
|
|
21
|
-
},
|
|
22
|
-
"devDependencies": {
|
|
23
|
-
"typescript": "^5.3.3"
|
|
24
|
-
},
|
|
25
|
-
"private": true
|
|
26
|
-
}
|
package/functions/src/index.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import * as _functions from 'firebase-functions'
|
|
2
|
-
import admin from 'firebase-admin'
|
|
3
|
-
import { persistent, Persistent, PersistentObject, registerPersistentClass } from 'entropic-bond'
|
|
4
|
-
|
|
5
|
-
@registerPersistentClass( 'ParamWrapper' )
|
|
6
|
-
export class ParamWrapper extends Persistent {
|
|
7
|
-
constructor( a?: string, b?: number ) {
|
|
8
|
-
super()
|
|
9
|
-
this._a = a
|
|
10
|
-
this._b = b
|
|
11
|
-
}
|
|
12
|
-
@persistent _a: string
|
|
13
|
-
@persistent _b: number
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
admin.initializeApp()
|
|
18
|
-
const functions = _functions.region('europe-west1')
|
|
19
|
-
|
|
20
|
-
export const test = functions.https.onRequest((_req, res) => {
|
|
21
|
-
res.send('Hello from Firebase!')
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
export const testCallablePersistent = functions.https.onCall(
|
|
25
|
-
( param: PersistentObject<ParamWrapper> ) => {
|
|
26
|
-
Persistent.registerFactory( 'ParamWrapper', ParamWrapper )
|
|
27
|
-
return param
|
|
28
|
-
}
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
export const testCallablePlain = functions.https.onCall(
|
|
32
|
-
( param: string ) => param.length
|
|
33
|
-
)
|
package/functions/tsconfig.json
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2021",
|
|
4
|
-
"module": "commonjs",
|
|
5
|
-
"moduleResolution": "node",
|
|
6
|
-
"esModuleInterop": true,
|
|
7
|
-
"sourceMap": true,
|
|
8
|
-
"outDir": "lib",
|
|
9
|
-
"resolveJsonModule": true,
|
|
10
|
-
"experimentalDecorators": true,
|
|
11
|
-
"lib": [
|
|
12
|
-
"ES2021", "WebWorker"
|
|
13
|
-
]
|
|
14
|
-
},
|
|
15
|
-
"compileOnSave": false,
|
|
16
|
-
"include": [
|
|
17
|
-
"src"
|
|
18
|
-
]
|
|
19
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { Auth } from 'entropic-bond'
|
|
2
|
-
import { FirebaseHelper } from '../firebase-helper'
|
|
3
|
-
import { FirebaseAuth } from './firebase-auth'
|
|
4
|
-
|
|
5
|
-
// NOTE: Firebase auth emulator requires a modification of the code to test which
|
|
6
|
-
// violates testing best practices. Therefore, this test is disabled.
|
|
7
|
-
|
|
8
|
-
describe( 'Firebase Auth', ()=>{
|
|
9
|
-
it( 'should pass', ()=>{
|
|
10
|
-
expect( true ).toBe( true )
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
// let authChangeSpy = vi.fn()
|
|
14
|
-
// FirebaseHelper.setFirebaseConfig({
|
|
15
|
-
// projectId: "demo-test",
|
|
16
|
-
// })
|
|
17
|
-
// FirebaseHelper.useEmulator({ authPort: 9099 })
|
|
18
|
-
|
|
19
|
-
// beforeEach(()=>{
|
|
20
|
-
// Auth.registerAuthService( new FirebaseAuth() )
|
|
21
|
-
// Auth.instance.onAuthStateChange( authChangeSpy )
|
|
22
|
-
// })
|
|
23
|
-
|
|
24
|
-
// it( 'should emulate sign-up', async ()=>{
|
|
25
|
-
// const userCredential = await Auth.instance.signUp({
|
|
26
|
-
// authProvider: 'google',
|
|
27
|
-
// email: 'test@test.com',
|
|
28
|
-
// password: 'password'
|
|
29
|
-
// })
|
|
30
|
-
|
|
31
|
-
// expect( userCredential.email ).toEqual( 'test@test.com' )
|
|
32
|
-
// expect( authChangeSpy ).toHaveBeenCalledWith( userCredential )
|
|
33
|
-
// })
|
|
34
|
-
|
|
35
|
-
// it( 'should emulate failed sign-up', async ()=>{
|
|
36
|
-
// try {
|
|
37
|
-
// var userCredential = await Auth.instance.signUp({
|
|
38
|
-
// authProvider: 'fail',
|
|
39
|
-
// email: 'test@test.com',
|
|
40
|
-
// password: 'password'
|
|
41
|
-
// })
|
|
42
|
-
// }
|
|
43
|
-
// catch {}
|
|
44
|
-
|
|
45
|
-
// expect( userCredential ).toBeUndefined()
|
|
46
|
-
// expect( authChangeSpy ).toHaveBeenCalledWith( undefined )
|
|
47
|
-
// })
|
|
48
|
-
|
|
49
|
-
// it( 'should login with fake registered user', async ()=>{
|
|
50
|
-
// const userCredentials = await Auth.instance.login({
|
|
51
|
-
// email: 'fakeUser@test.com',
|
|
52
|
-
// password: 'password',
|
|
53
|
-
// authProvider: 'google'
|
|
54
|
-
// })
|
|
55
|
-
|
|
56
|
-
// expect( userCredentials ).toEqual( 'fakeUseCredentials' )
|
|
57
|
-
// expect( authChangeSpy ).toHaveBeenCalledWith( 'fakeUseCredentials' )
|
|
58
|
-
// })
|
|
59
|
-
|
|
60
|
-
// it( 'should fail login with email auth provider if does not match fake user credentials', async ()=>{
|
|
61
|
-
// try {
|
|
62
|
-
// var userCredentials = await Auth.instance.login({
|
|
63
|
-
// email: 'test@test.com',
|
|
64
|
-
// password: 'password',
|
|
65
|
-
// authProvider: 'email'
|
|
66
|
-
// })
|
|
67
|
-
// } catch {}
|
|
68
|
-
|
|
69
|
-
// expect( userCredentials ).toEqual( undefined )
|
|
70
|
-
// expect( authChangeSpy ).toHaveBeenCalledWith( undefined )
|
|
71
|
-
// })
|
|
72
|
-
|
|
73
|
-
// it( 'should NOT fail login with non email auth provider even if does not match fake user credentials', async ()=>{
|
|
74
|
-
// const userCredentials = await Auth.instance.login({
|
|
75
|
-
// email: 'test@test.com',
|
|
76
|
-
// password: 'password',
|
|
77
|
-
// authProvider: 'google'
|
|
78
|
-
// })
|
|
79
|
-
|
|
80
|
-
// expect( userCredentials.email ).toEqual( 'test@test.com' )
|
|
81
|
-
// expect( authChangeSpy ).toHaveBeenCalledWith( undefined )
|
|
82
|
-
// })
|
|
83
|
-
|
|
84
|
-
// it( 'should logout', async ()=>{
|
|
85
|
-
// await Auth.instance.logout()
|
|
86
|
-
|
|
87
|
-
// expect( authChangeSpy ).toHaveBeenCalledWith( undefined )
|
|
88
|
-
// })
|
|
89
|
-
|
|
90
|
-
})
|
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
import { AuthProvider, SignData, UserCredentials, AuthService, RejectedCallback, ResovedCallback, AuthErrorCode, camelCase } from 'entropic-bond'
|
|
2
|
-
import { connectAuthEmulator, createUserWithEmailAndPassword, FacebookAuthProvider, GoogleAuthProvider, linkWithPopup, sendEmailVerification, signInAnonymously, signInWithEmailAndPassword, signInWithPopup, TwitterAuthProvider, updateProfile, unlink, User, UserCredential, sendPasswordResetEmail } from 'firebase/auth'
|
|
3
|
-
import { EmulatorConfig, FirebaseHelper } from '../firebase-helper'
|
|
4
|
-
|
|
5
|
-
interface CredentialProviders {
|
|
6
|
-
[ name: string ]: ( signData: SignData ) => Promise<UserCredential>
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const providerFactory = {
|
|
10
|
-
'twitter': () => new TwitterAuthProvider(),
|
|
11
|
-
'facebook': () => new FacebookAuthProvider(),
|
|
12
|
-
'google': () => new GoogleAuthProvider()
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export class FirebaseAuth extends AuthService {
|
|
16
|
-
constructor( emulator?: EmulatorConfig ) {
|
|
17
|
-
super()
|
|
18
|
-
if ( emulator ) FirebaseHelper.useEmulator( emulator )
|
|
19
|
-
|
|
20
|
-
if ( FirebaseHelper.emulator?.emulate ) {
|
|
21
|
-
const { host, authPort } = FirebaseHelper.emulator
|
|
22
|
-
if ( !host || !authPort ) throw new Error( `You should define a host and an auth emulator port to use the emulator` )
|
|
23
|
-
|
|
24
|
-
connectAuthEmulator( FirebaseHelper.instance.auth(), `http://${ host }:${ authPort }` )
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
this.registerCredentialProviders()
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
signUp<T extends {}>( signData: SignData ): Promise<UserCredentials<T>> {
|
|
31
|
-
const { authProvider, verificationLink } = signData
|
|
32
|
-
|
|
33
|
-
if ( authProvider.slice( 0, 5 ) === 'email' ) {
|
|
34
|
-
return new Promise<UserCredentials<T>>( async ( resolve: ResovedCallback<T>, reject: RejectedCallback ) => {
|
|
35
|
-
try {
|
|
36
|
-
const credentialFactory = this.credentialProviders[ 'email-sign-up' ]
|
|
37
|
-
if ( !credentialFactory ) throw new Error( `The provider ${ authProvider } is not registered` )
|
|
38
|
-
|
|
39
|
-
const userCredentials = await credentialFactory( signData )
|
|
40
|
-
|
|
41
|
-
if ( signData.name ) {
|
|
42
|
-
await updateProfile( userCredentials.user, {
|
|
43
|
-
displayName: signData.name
|
|
44
|
-
})
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if ( verificationLink ) {
|
|
48
|
-
await sendEmailVerification( userCredentials.user, {
|
|
49
|
-
url: verificationLink
|
|
50
|
-
})
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
resolve( await this.toUserCredentials( userCredentials.user ) )
|
|
54
|
-
}
|
|
55
|
-
catch( error ) {
|
|
56
|
-
reject({
|
|
57
|
-
code: camelCase( error.code.slice( 5 ) ) as AuthErrorCode,
|
|
58
|
-
message: error.message
|
|
59
|
-
})
|
|
60
|
-
}
|
|
61
|
-
})
|
|
62
|
-
}
|
|
63
|
-
else return this.login( signData )
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
login<T extends {}>( signData: SignData ): Promise<UserCredentials<T>> {
|
|
67
|
-
const { authProvider } = signData
|
|
68
|
-
|
|
69
|
-
return new Promise<UserCredentials<T>>( async ( resolve: ResovedCallback<T>, reject: RejectedCallback ) => {
|
|
70
|
-
try {
|
|
71
|
-
const credentialFactory = this.credentialProviders[ authProvider ]
|
|
72
|
-
if ( !credentialFactory ) throw new Error( `The provider ${ authProvider } is not registered` )
|
|
73
|
-
const userCredentials = await credentialFactory( signData )
|
|
74
|
-
resolve( await this.toUserCredentials<T>( userCredentials.user ) )
|
|
75
|
-
}
|
|
76
|
-
catch( error ) {
|
|
77
|
-
reject({
|
|
78
|
-
code: error.code === 400? 'missingPassword' : camelCase( error.code.slice( 5 )) as AuthErrorCode,
|
|
79
|
-
message: error.message
|
|
80
|
-
})
|
|
81
|
-
}
|
|
82
|
-
})
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
logout(): Promise<void> {
|
|
86
|
-
return FirebaseHelper.instance.auth().signOut()
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
resetEmailPassword( email: string ) {
|
|
90
|
-
return new Promise<void>( async ( resolve, reject ) => {
|
|
91
|
-
try {
|
|
92
|
-
await sendPasswordResetEmail( FirebaseHelper.instance.auth(), email )
|
|
93
|
-
resolve()
|
|
94
|
-
}
|
|
95
|
-
catch( error ) {
|
|
96
|
-
reject({
|
|
97
|
-
code: camelCase( error.code.slice( 5 ) ) as AuthErrorCode,
|
|
98
|
-
message: error.message
|
|
99
|
-
})
|
|
100
|
-
}
|
|
101
|
-
})
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
resendVerificationEmail( email: string, password: string, verificationLink: string ): Promise<void> {
|
|
105
|
-
return new Promise<void>( async ( resolve, reject ) => {
|
|
106
|
-
try {
|
|
107
|
-
await signInWithEmailAndPassword( FirebaseHelper.instance.auth(), email, password )
|
|
108
|
-
const user = FirebaseHelper.instance.auth().currentUser
|
|
109
|
-
if ( !user ) {
|
|
110
|
-
reject({
|
|
111
|
-
code: 'userNotFound',
|
|
112
|
-
message: `There is no logged in user`
|
|
113
|
-
})
|
|
114
|
-
return
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
await sendEmailVerification( user, {
|
|
118
|
-
url: verificationLink
|
|
119
|
-
})
|
|
120
|
-
resolve()
|
|
121
|
-
}
|
|
122
|
-
catch( error ) {
|
|
123
|
-
reject({
|
|
124
|
-
code: camelCase( error.code.slice( 5 ) ) as AuthErrorCode,
|
|
125
|
-
message: verificationLink
|
|
126
|
-
})
|
|
127
|
-
}
|
|
128
|
-
})
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
override refreshToken(): Promise<void> {
|
|
132
|
-
return FirebaseHelper.instance.auth().currentUser?.getIdToken( true ) as unknown as Promise<void>
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
onAuthStateChange<T extends {}>( onChange: (userCredentials: UserCredentials<T> | undefined) => void ) {
|
|
136
|
-
FirebaseHelper.instance.auth().onAuthStateChanged( async credentials =>{
|
|
137
|
-
onChange( credentials? await this.toUserCredentials( credentials ) : undefined )
|
|
138
|
-
})
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
linkAdditionalProvider( provider: AuthProvider ): Promise<unknown> {
|
|
142
|
-
const providerInstance = providerFactory[ provider ]()
|
|
143
|
-
const currentUser = FirebaseHelper.instance.auth().currentUser
|
|
144
|
-
if ( !currentUser ) throw new Error( `There is no logged in user` )
|
|
145
|
-
|
|
146
|
-
return linkWithPopup( currentUser, providerInstance )
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
unlinkProvider( provider: AuthProvider ): Promise<unknown> {
|
|
150
|
-
const { currentUser } = FirebaseHelper.instance.auth()
|
|
151
|
-
if ( !currentUser ) throw new Error( `There is no logged in user` )
|
|
152
|
-
|
|
153
|
-
currentUser.providerData
|
|
154
|
-
return unlink( currentUser, providerFactory[ provider ]().providerId )
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
private async toUserCredentials<T extends {}>( nativeUserCredential: User ): Promise<UserCredentials<T>> {
|
|
158
|
-
if ( !nativeUserCredential ) throw new Error( `The user in user credentials is not defined` )
|
|
159
|
-
|
|
160
|
-
const claims = ( await nativeUserCredential.getIdTokenResult() ).claims as T
|
|
161
|
-
|
|
162
|
-
return FirebaseAuth.convertCredentials<T>( nativeUserCredential, claims )
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
static convertCredentials<T extends {}>( nativeUserCredential: User, claims: T ): UserCredentials<T> {
|
|
166
|
-
return ({
|
|
167
|
-
id: nativeUserCredential.uid,
|
|
168
|
-
email: nativeUserCredential.email ?? '',
|
|
169
|
-
name: nativeUserCredential.displayName ?? undefined,
|
|
170
|
-
pictureUrl: nativeUserCredential.photoURL ?? undefined,
|
|
171
|
-
phoneNumber: nativeUserCredential.phoneNumber ?? undefined,
|
|
172
|
-
emailVerified: nativeUserCredential.emailVerified,
|
|
173
|
-
customData: {...claims},
|
|
174
|
-
lastLogin: Date.now(),
|
|
175
|
-
creationDate: nativeUserCredential.metadata.creationTime? new Date( nativeUserCredential.metadata.creationTime ).getTime() : undefined,
|
|
176
|
-
})
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
registerCredentialProvider( name: string, providerFactory: ( singData: SignData ) => Promise<UserCredential> ) {
|
|
180
|
-
this.credentialProviders[ name ] = providerFactory
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
private registerCredentialProviders() { //TODO: refactor. Not needed anymore
|
|
184
|
-
this.registerCredentialProvider( 'email-sign-up', signData => {
|
|
185
|
-
if ( !signData.email || !signData.password ) throw new Error( `Email and password are required` )
|
|
186
|
-
return createUserWithEmailAndPassword( FirebaseHelper.instance.auth(), signData.email, signData.password )
|
|
187
|
-
})
|
|
188
|
-
this.registerCredentialProvider( 'email', signData => {
|
|
189
|
-
if ( !signData.email || !signData.password ) throw new Error( `Email and password are required` )
|
|
190
|
-
return signInWithEmailAndPassword( FirebaseHelper.instance.auth(), signData.email, signData.password )
|
|
191
|
-
})
|
|
192
|
-
this.registerCredentialProvider( 'google', () => signInWithPopup(
|
|
193
|
-
FirebaseHelper.instance.auth(), new GoogleAuthProvider()
|
|
194
|
-
))
|
|
195
|
-
this.registerCredentialProvider( 'facebook', () => signInWithPopup(
|
|
196
|
-
FirebaseHelper.instance.auth(), new FacebookAuthProvider()
|
|
197
|
-
))
|
|
198
|
-
this.registerCredentialProvider( 'twitter', () => signInWithPopup(
|
|
199
|
-
FirebaseHelper.instance.auth(), new TwitterAuthProvider()
|
|
200
|
-
))
|
|
201
|
-
this.registerCredentialProvider( 'link-twitter', () => {
|
|
202
|
-
const currentUser = FirebaseHelper.instance.auth().currentUser
|
|
203
|
-
if ( !currentUser ) throw new Error( `There is no logged in user` )
|
|
204
|
-
return linkWithPopup( currentUser, new TwitterAuthProvider() )
|
|
205
|
-
})
|
|
206
|
-
this.registerCredentialProvider( 'anonymous', () => signInAnonymously(
|
|
207
|
-
FirebaseHelper.instance.auth()
|
|
208
|
-
))
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
private credentialProviders: CredentialProviders = {}
|
|
212
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { CloudFunctions, Persistent, persistent, registerPersistentClass } from 'entropic-bond'
|
|
2
|
-
import { FirebaseHelper } from '../firebase-helper'
|
|
3
|
-
import { FirebaseCloudFunctions } from './firebase-cloud-functions'
|
|
4
|
-
|
|
5
|
-
@registerPersistentClass( 'ParamWrapper' )
|
|
6
|
-
export class ParamWrapper extends Persistent {
|
|
7
|
-
constructor( a?: string, b?: number ) {
|
|
8
|
-
super()
|
|
9
|
-
this._a = a
|
|
10
|
-
this._b = b
|
|
11
|
-
}
|
|
12
|
-
@persistent _a: string | undefined
|
|
13
|
-
@persistent _b: number | undefined
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
describe( 'Cloud functions', ()=>{
|
|
17
|
-
|
|
18
|
-
beforeEach(()=>{
|
|
19
|
-
FirebaseHelper.setFirebaseConfig({
|
|
20
|
-
projectId: 'demo-test',
|
|
21
|
-
storageBucket: 'default-bucket'
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
FirebaseHelper.useEmulator()
|
|
25
|
-
CloudFunctions.useCloudFunctionsService(
|
|
26
|
-
new FirebaseCloudFunctions( 'europe-west1', { emulate: true })
|
|
27
|
-
)
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
it( 'should call cloud functions with plain types', async ()=>{
|
|
31
|
-
const testCallablePlain = CloudFunctions.instance.getFunction<string, number>( 'testCallablePlain' )
|
|
32
|
-
const result = await testCallablePlain( 'Hello' )
|
|
33
|
-
|
|
34
|
-
expect( result ).toBe( 5 )
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
it( 'should call cloud function for Persistent', async ()=>{
|
|
38
|
-
const testCallablePersistent = CloudFunctions.instance.getFunction<ParamWrapper, ParamWrapper>( 'testCallablePersistent' )
|
|
39
|
-
const paramWrapper = new ParamWrapper( 'test', 30 )
|
|
40
|
-
|
|
41
|
-
const a = paramWrapper.toObject()
|
|
42
|
-
|
|
43
|
-
const result = await testCallablePersistent( paramWrapper )
|
|
44
|
-
expect( result._a ).toEqual( 'test' )
|
|
45
|
-
expect( result._b ).toEqual( 30 )
|
|
46
|
-
})
|
|
47
|
-
})
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { CloudFunction, CloudFunctionsService } from 'entropic-bond'
|
|
2
|
-
import { connectFunctionsEmulator, httpsCallable } from 'firebase/functions'
|
|
3
|
-
import { EmulatorConfig, FirebaseHelper } from '../firebase-helper'
|
|
4
|
-
|
|
5
|
-
export class FirebaseCloudFunctions implements CloudFunctionsService {
|
|
6
|
-
constructor( region?: string, emulator?: Partial<EmulatorConfig> ) {
|
|
7
|
-
if ( region ) FirebaseHelper.setRegion( region )
|
|
8
|
-
if ( emulator ) FirebaseHelper.useEmulator( emulator )
|
|
9
|
-
|
|
10
|
-
if ( FirebaseHelper.emulator?.emulate ) {
|
|
11
|
-
const { host, functionsPort } = FirebaseHelper.emulator
|
|
12
|
-
connectFunctionsEmulator( FirebaseHelper.instance.functions(), host, functionsPort )
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
retrieveFunction<P, R>( cloudFunction: string ): CloudFunction<P, R> {
|
|
18
|
-
return httpsCallable<P,R>( FirebaseHelper.instance.functions(), cloudFunction ) as unknown as CloudFunction<P, R>
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async callFunction<P, R>( func: CloudFunction<P, R>, params: P ): Promise<R> {
|
|
22
|
-
const res = await func( params ) as any
|
|
23
|
-
return res.data
|
|
24
|
-
}
|
|
25
|
-
}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
(global as any).XMLHttpRequest = require('xhr2')
|
|
2
|
-
import { FirebaseCloudStorage } from './firebase-cloud-storage'
|
|
3
|
-
import { FirebaseHelper } from '../firebase-helper'
|
|
4
|
-
import { FirebaseDatasource } from '../store/firebase-datasource'
|
|
5
|
-
import { CloudStorage, Model, Persistent, persistent, registerPersistentClass, Store, StoredFile } from 'entropic-bond'
|
|
6
|
-
|
|
7
|
-
// Note about tests leaking. I've been checking and looks like firebase.storage
|
|
8
|
-
// methods are the responsible for the test leaking (as firebase v. 8.6.3).
|
|
9
|
-
|
|
10
|
-
class File {
|
|
11
|
-
data: Uint8Array | undefined
|
|
12
|
-
name: string | undefined
|
|
13
|
-
lastModified: any
|
|
14
|
-
size: number | undefined
|
|
15
|
-
type: any
|
|
16
|
-
arrayBuffer: any
|
|
17
|
-
slice: any
|
|
18
|
-
stream: any
|
|
19
|
-
text: any
|
|
20
|
-
}
|
|
21
|
-
global['File'] = File as any
|
|
22
|
-
|
|
23
|
-
@registerPersistentClass( 'Test' )
|
|
24
|
-
class Test extends Persistent {
|
|
25
|
-
|
|
26
|
-
get file(): StoredFile {
|
|
27
|
-
return this._file
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
@persistent private _file: StoredFile = new StoredFile()
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
describe( 'Firebase Cloud Storage', ()=>{
|
|
34
|
-
const blobData1 = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21]);
|
|
35
|
-
const blobData2 = new Uint8Array([0x6c, 0x6c, 0x6f, 0x2c, 0x48, 0x65, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21]);
|
|
36
|
-
let file: StoredFile
|
|
37
|
-
|
|
38
|
-
FirebaseHelper.setFirebaseConfig({
|
|
39
|
-
projectId: 'demo-test',
|
|
40
|
-
storageBucket: 'default-bucket'
|
|
41
|
-
})
|
|
42
|
-
FirebaseHelper.useEmulator({ firestorePort: 9080 })
|
|
43
|
-
Store.useDataSource( new FirebaseDatasource() )
|
|
44
|
-
|
|
45
|
-
beforeEach(()=>{
|
|
46
|
-
CloudStorage.useCloudStorage( new FirebaseCloudStorage() )
|
|
47
|
-
file = new StoredFile()
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
it( 'should save and get a url', async ()=>{
|
|
51
|
-
await file.save({ data:blobData1 })
|
|
52
|
-
|
|
53
|
-
expect( file.url ).toContain( file.id )
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it( 'should report metadata', async ()=>{
|
|
57
|
-
await file.save({ data: blobData1, fileName: 'test.dat' })
|
|
58
|
-
|
|
59
|
-
expect( file.originalFileName ).toEqual( 'test.dat' )
|
|
60
|
-
expect( file.provider.className ).toEqual( 'FirebaseCloudStorage' )
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
it( 'should delete file', async ()=>{
|
|
64
|
-
await file.save({ data:blobData1 })
|
|
65
|
-
|
|
66
|
-
await file.delete()
|
|
67
|
-
expect( file.url ).not.toBeDefined()
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it( 'should overwrite file on subsequent writes', async ()=>{
|
|
71
|
-
await file.save({ data:blobData1 })
|
|
72
|
-
const firstUrl = file.url
|
|
73
|
-
let resp = await fetch( file.url! )
|
|
74
|
-
expect( await resp.text() ).toEqual( 'Hello, world!')
|
|
75
|
-
|
|
76
|
-
await file.save({ data:blobData2 })
|
|
77
|
-
resp = await fetch( file.url! )
|
|
78
|
-
expect(
|
|
79
|
-
file.url?.slice( 0, file.url.indexOf('token') )
|
|
80
|
-
).toEqual(
|
|
81
|
-
firstUrl?.slice( 0, firstUrl.indexOf('token') )
|
|
82
|
-
)
|
|
83
|
-
expect( await resp.text() ).toEqual( 'llo,He world!')
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
it( 'should trigger events', async () => {
|
|
87
|
-
const cb = vi.fn()
|
|
88
|
-
|
|
89
|
-
const savePromise = file.save({ data:blobData1 })
|
|
90
|
-
file.uploadControl().onProgress( cb )
|
|
91
|
-
await savePromise
|
|
92
|
-
|
|
93
|
-
expect( cb ).toHaveBeenCalledTimes( 2 )
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
describe( 'Streaming', ()=>{
|
|
97
|
-
let model: Model<Test>
|
|
98
|
-
let testObj: Test
|
|
99
|
-
|
|
100
|
-
beforeEach(()=>{
|
|
101
|
-
testObj = new Test()
|
|
102
|
-
model = Store.getModel<Test>( testObj )
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
it( 'should load object with StoredFile', async ()=>{
|
|
106
|
-
await testObj.file.save({ data: blobData1, fileName: 'test.dat' })
|
|
107
|
-
await model.save( testObj )
|
|
108
|
-
|
|
109
|
-
const newTestObj = await model.findById( testObj.id )
|
|
110
|
-
|
|
111
|
-
expect( newTestObj?.file ).toBeInstanceOf( StoredFile )
|
|
112
|
-
expect( newTestObj?.file.url ).toContain( testObj.file.id )
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
it( 'should replace file on save after load', async ()=>{
|
|
116
|
-
const deleteSpy = vi.spyOn( testObj.file, 'delete' )
|
|
117
|
-
|
|
118
|
-
await testObj.file.save({ data: blobData1, fileName: 'test.dat' })
|
|
119
|
-
await model.save( testObj )
|
|
120
|
-
|
|
121
|
-
const newTestObj = await model.findById( testObj.id )
|
|
122
|
-
|
|
123
|
-
expect( newTestObj?.file ).toBeInstanceOf( StoredFile )
|
|
124
|
-
expect( newTestObj?.file.url ).toContain( testObj.file.id )
|
|
125
|
-
expect( deleteSpy ).not.toHaveBeenCalled()
|
|
126
|
-
|
|
127
|
-
testObj.file.setDataToStore( blobData2 )
|
|
128
|
-
await testObj.file.save()
|
|
129
|
-
|
|
130
|
-
expect( deleteSpy ).toHaveBeenCalled()
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
})
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { registerCloudStorage, CloudStorage, UploadControl, UploadProgress } from 'entropic-bond'
|
|
2
|
-
import { deleteObject, getDownloadURL, ref, uploadBytesResumable, UploadTask, connectStorageEmulator } from 'firebase/storage'
|
|
3
|
-
import { EmulatorConfig, FirebaseHelper } from '../firebase-helper'
|
|
4
|
-
|
|
5
|
-
@registerCloudStorage( 'FirebaseCloudStorage', ()=> new FirebaseCloudStorage() )
|
|
6
|
-
export class FirebaseCloudStorage extends CloudStorage {
|
|
7
|
-
constructor( emulator?: EmulatorConfig ) {
|
|
8
|
-
super()
|
|
9
|
-
if ( emulator ) FirebaseHelper.useEmulator( emulator )
|
|
10
|
-
|
|
11
|
-
if ( FirebaseHelper.emulator?.emulate ) {
|
|
12
|
-
const { host, storagePort } = FirebaseHelper.emulator
|
|
13
|
-
connectStorageEmulator( FirebaseHelper.instance.storage(), host, storagePort )
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
save( id: string, data: Blob | Uint8Array | ArrayBuffer, progress?: UploadProgress ): Promise<string> {
|
|
18
|
-
const storage = FirebaseHelper.instance.storage()
|
|
19
|
-
|
|
20
|
-
return new Promise<string>(( resolve, reject ) => {
|
|
21
|
-
this._uploadTask = uploadBytesResumable( ref( storage, id ), data )
|
|
22
|
-
|
|
23
|
-
if ( progress ) {
|
|
24
|
-
var unsubscribe = this._uploadTask.on( 'state_changed',
|
|
25
|
-
snapshot => {
|
|
26
|
-
progress( snapshot.bytesTransferred, snapshot.totalBytes )
|
|
27
|
-
},
|
|
28
|
-
null,
|
|
29
|
-
()=>unsubscribe()
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
this._uploadTask
|
|
34
|
-
.then( () => resolve( id ) )
|
|
35
|
-
.catch( error => reject( error ))
|
|
36
|
-
})
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
getUrl( reference: string ): Promise<string> {
|
|
40
|
-
if ( !reference ) return Promise.reject( 'needs a reference' )
|
|
41
|
-
|
|
42
|
-
const storage = FirebaseHelper.instance.storage()
|
|
43
|
-
return getDownloadURL( ref( storage, reference ) )
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
uploadControl(): UploadControl {
|
|
47
|
-
if ( !this._uploadTask ) throw new Error( `You should call save() before uploadControl()` )
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
cancel: ()=>this._uploadTask?.cancel(),
|
|
51
|
-
pause: ()=>this._uploadTask?.pause(),
|
|
52
|
-
resume: ()=>this._uploadTask?.resume(),
|
|
53
|
-
onProgress: ( callback )=> this._uploadTask?.on( 'state_changed', snapShot => {
|
|
54
|
-
if ( callback ) {
|
|
55
|
-
callback( snapShot.bytesTransferred, snapShot.totalBytes )
|
|
56
|
-
}
|
|
57
|
-
})
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
delete( reference: string ): Promise<void> {
|
|
62
|
-
const storage = FirebaseHelper.instance.storage()
|
|
63
|
-
return deleteObject( ref( storage, reference ) )
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
private _uploadTask: UploadTask | undefined
|
|
67
|
-
}
|