entropic-bond 1.48.1 → 1.50.0
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/.github/workflows/release.yml +26 -0
- package/CHANGELOG.md +1151 -0
- package/docs/.nojekyll +1 -0
- package/docs/README.md +94 -0
- package/docs/classes/Auth.md +391 -0
- package/docs/classes/AuthMock.md +278 -0
- package/docs/classes/AuthService.md +188 -0
- package/docs/classes/CloudFunctions.md +123 -0
- package/docs/classes/CloudFunctionsMock.md +97 -0
- package/docs/classes/CloudStorage.md +215 -0
- package/docs/classes/DataSource.md +248 -0
- package/docs/classes/EntropicComponent.md +666 -0
- package/docs/classes/JsonDataSource.md +328 -0
- package/docs/classes/MockCloudStorage.md +279 -0
- package/docs/classes/Model.md +274 -0
- package/docs/classes/Observable.md +120 -0
- package/docs/classes/Persistent.md +420 -0
- package/docs/classes/ServerAuth.md +211 -0
- package/docs/classes/ServerAuthMock.md +176 -0
- package/docs/classes/ServerAuthService.md +130 -0
- package/docs/classes/Store.md +218 -0
- package/docs/classes/StoredFile.md +636 -0
- package/docs/enums/StoredFileEvent.md +41 -0
- package/docs/interfaces/AuthError.md +30 -0
- package/docs/interfaces/CloudFunctionsService.md +69 -0
- package/docs/interfaces/Collection.md +13 -0
- package/docs/interfaces/CustomCredentials.md +7 -0
- package/docs/interfaces/DocumentReference.md +49 -0
- package/docs/interfaces/JsonRawData.md +7 -0
- package/docs/interfaces/SignData.md +63 -0
- package/docs/interfaces/StoreParams.md +52 -0
- package/docs/interfaces/StoredFileChange.md +41 -0
- package/docs/interfaces/UploadControl.md +90 -0
- package/docs/interfaces/UserCredentials.md +113 -0
- package/docs/interfaces/Values.md +17 -0
- package/docs/modules.md +1273 -0
- package/package.json +23 -19
- package/src/auth/auth-mock.spec.ts +168 -0
- package/src/auth/auth-mock.ts +129 -0
- package/src/auth/auth.ts +185 -0
- package/src/auth/user-auth-types.ts +21 -0
- package/src/cloud-functions/cloud-functions-mock.spec.ts +136 -0
- package/src/cloud-functions/cloud-functions-mock.ts +23 -0
- package/src/cloud-functions/cloud-functions.ts +83 -0
- package/src/cloud-storage/cloud-storage.spec.ts +207 -0
- package/src/cloud-storage/cloud-storage.ts +60 -0
- package/src/cloud-storage/mock-cloud-storage.ts +72 -0
- package/src/cloud-storage/stored-file.ts +102 -0
- package/src/index.ts +19 -0
- package/src/observable/observable.spec.ts +105 -0
- package/src/observable/observable.ts +67 -0
- package/src/persistent/entropic-component.spec.ts +143 -0
- package/src/persistent/entropic-component.ts +135 -0
- package/src/persistent/persistent.spec.ts +828 -0
- package/src/persistent/persistent.ts +650 -0
- package/src/server-auth/server-auth-mock.spec.ts +53 -0
- package/src/server-auth/server-auth-mock.ts +45 -0
- package/src/server-auth/server-auth.ts +49 -0
- package/src/store/data-source.ts +186 -0
- package/src/store/json-data-source.spec.ts +100 -0
- package/src/store/json-data-source.ts +256 -0
- package/src/store/mocks/mock-data.json +155 -0
- package/src/store/mocks/test-user.ts +122 -0
- package/src/store/model.spec.ts +659 -0
- package/src/store/model.ts +462 -0
- package/src/store/store.spec.ts +30 -0
- package/src/store/store.ts +113 -0
- package/src/types/utility-types.spec.ts +117 -0
- package/src/types/utility-types.ts +116 -0
- package/src/utils/test-utils/test-person.ts +44 -0
- package/src/utils/utils.spec.ts +95 -0
- package/{lib/utils/utils.d.ts → src/utils/utils.ts} +34 -10
- package/tsconfig-build.json +7 -0
- package/tsconfig-cjs.json +9 -0
- package/tsconfig.json +33 -0
- package/vite.config.ts +22 -0
- package/lib/auth/auth-mock.d.ts +0 -21
- package/lib/auth/auth-mock.js +0 -108
- package/lib/auth/auth-mock.js.map +0 -1
- package/lib/auth/auth.d.ts +0 -129
- package/lib/auth/auth.js +0 -146
- package/lib/auth/auth.js.map +0 -1
- package/lib/auth/user-auth-types.d.ts +0 -19
- package/lib/auth/user-auth-types.js +0 -3
- package/lib/auth/user-auth-types.js.map +0 -1
- package/lib/cloud-functions/cloud-functions-mock.d.ts +0 -11
- package/lib/cloud-functions/cloud-functions-mock.js +0 -19
- package/lib/cloud-functions/cloud-functions-mock.js.map +0 -1
- package/lib/cloud-functions/cloud-functions.d.ts +0 -19
- package/lib/cloud-functions/cloud-functions.js +0 -64
- package/lib/cloud-functions/cloud-functions.js.map +0 -1
- package/lib/cloud-storage/cloud-storage.d.ts +0 -24
- package/lib/cloud-storage/cloud-storage.js +0 -37
- package/lib/cloud-storage/cloud-storage.js.map +0 -1
- package/lib/cloud-storage/mock-cloud-storage.d.ts +0 -20
- package/lib/cloud-storage/mock-cloud-storage.js +0 -68
- package/lib/cloud-storage/mock-cloud-storage.js.map +0 -1
- package/lib/cloud-storage/stored-file.d.ts +0 -39
- package/lib/cloud-storage/stored-file.js +0 -106
- package/lib/cloud-storage/stored-file.js.map +0 -1
- package/lib/index.d.ts +0 -19
- package/lib/index.js +0 -36
- package/lib/index.js.map +0 -1
- package/lib/observable/observable.d.ts +0 -52
- package/lib/observable/observable.js +0 -66
- package/lib/observable/observable.js.map +0 -1
- package/lib/persistent/entropic-component.d.ts +0 -76
- package/lib/persistent/entropic-component.js +0 -109
- package/lib/persistent/entropic-component.js.map +0 -1
- package/lib/persistent/persistent.d.ts +0 -281
- package/lib/persistent/persistent.js +0 -539
- package/lib/persistent/persistent.js.map +0 -1
- package/lib/server-auth/server-auth-mock.d.ts +0 -12
- package/lib/server-auth/server-auth-mock.js +0 -39
- package/lib/server-auth/server-auth-mock.js.map +0 -1
- package/lib/server-auth/server-auth.d.ts +0 -24
- package/lib/server-auth/server-auth.js +0 -36
- package/lib/server-auth/server-auth.js.map +0 -1
- package/lib/store/data-source.d.ts +0 -137
- package/lib/store/data-source.js +0 -62
- package/lib/store/data-source.js.map +0 -1
- package/lib/store/json-data-source.d.ts +0 -68
- package/lib/store/json-data-source.js +0 -199
- package/lib/store/json-data-source.js.map +0 -1
- package/lib/store/mocks/test-user.d.ts +0 -49
- package/lib/store/mocks/test-user.js +0 -135
- package/lib/store/mocks/test-user.js.map +0 -1
- package/lib/store/model.d.ts +0 -238
- package/lib/store/model.js +0 -417
- package/lib/store/model.js.map +0 -1
- package/lib/store/store.d.ts +0 -62
- package/lib/store/store.js +0 -102
- package/lib/store/store.js.map +0 -1
- package/lib/types/utility-types.d.ts +0 -45
- package/lib/types/utility-types.js +0 -3
- package/lib/types/utility-types.js.map +0 -1
- package/lib/utils/test-utils/test-person.d.ts +0 -33
- package/lib/utils/test-utils/test-person.js +0 -25
- package/lib/utils/test-utils/test-person.js.map +0 -1
- package/lib/utils/utils.js +0 -76
- package/lib/utils/utils.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "entropic-bond",
|
|
3
|
-
"
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "1.50.0",
|
|
5
|
+
"description": "Tidy up your messy components",
|
|
6
|
+
"main": "./lib/entropic-bond.umd.cjs",
|
|
7
|
+
"module": "./lib/entropic-bond.js",
|
|
8
|
+
"types": "./lib/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./lib/index.js",
|
|
12
|
+
"require": "./lib/entropic-bond.umd.cjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
4
15
|
"publishConfig": {
|
|
5
16
|
"access": "public",
|
|
6
17
|
"branches": [
|
|
@@ -16,19 +27,10 @@
|
|
|
16
27
|
"@semantic-release/github"
|
|
17
28
|
]
|
|
18
29
|
},
|
|
19
|
-
"description": "Tidy up your messy components",
|
|
20
|
-
"main": "lib/index.js",
|
|
21
|
-
"types": "lib/index.d.ts",
|
|
22
|
-
"files": [
|
|
23
|
-
"lib"
|
|
24
|
-
],
|
|
25
30
|
"scripts": {
|
|
26
31
|
"test": "vitest",
|
|
27
|
-
"build": "
|
|
28
|
-
"prepare": "npm run build"
|
|
29
|
-
"build-ts": "cp -r ./src/ ./lib",
|
|
30
|
-
"build-cjs": "tsc -p tsconfig-cjs.json",
|
|
31
|
-
"build-next": "tsc -p tsconfig.json"
|
|
32
|
+
"build": "tsc -p tsconfig-build.json && vite build",
|
|
33
|
+
"prepare": "npm run build"
|
|
32
34
|
},
|
|
33
35
|
"repository": {
|
|
34
36
|
"type": "git",
|
|
@@ -50,14 +52,16 @@
|
|
|
50
52
|
"@semantic-release/changelog": "^6.0.3",
|
|
51
53
|
"@semantic-release/git": "^10.0.1",
|
|
52
54
|
"@type-challenges/utils": "^0.1.1",
|
|
53
|
-
"@types/
|
|
55
|
+
"@types/node": "^20.11.16",
|
|
56
|
+
"@types/uuid": "^9.0.8",
|
|
54
57
|
"git-branch-is": "^4.0.0",
|
|
55
|
-
"husky": "^
|
|
56
|
-
"semantic-release": "^
|
|
57
|
-
"typedoc": "^0.25.
|
|
58
|
-
"typedoc-plugin-markdown": "^3.
|
|
59
|
-
"typescript": "^5.
|
|
60
|
-
"
|
|
58
|
+
"husky": "^9.0.10",
|
|
59
|
+
"semantic-release": "^23.0.0",
|
|
60
|
+
"typedoc": "^0.25.7",
|
|
61
|
+
"typedoc-plugin-markdown": "^3.17.1",
|
|
62
|
+
"typescript": "^5.3.3",
|
|
63
|
+
"vite-plugin-dts": "^3.7.2",
|
|
64
|
+
"vitest": "^1.2.2"
|
|
61
65
|
},
|
|
62
66
|
"dependencies": {
|
|
63
67
|
"uuid": "^9.0.1"
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { Auth } from './auth'
|
|
2
|
+
import { AuthMock } from './auth-mock'
|
|
3
|
+
import { UserCredentials } from './user-auth-types'
|
|
4
|
+
|
|
5
|
+
interface CustomCredentials {
|
|
6
|
+
role: string
|
|
7
|
+
customer: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe( 'Auth Mock', ()=>{
|
|
11
|
+
let authChangeSpy = vi.fn()
|
|
12
|
+
let mockAuthService: AuthMock
|
|
13
|
+
const fakeUseCredentials = {
|
|
14
|
+
email: 'fakeUser@test.com',
|
|
15
|
+
emailVerified: true,
|
|
16
|
+
creationDate: new Date( '2017-01-01' ).getDate(),
|
|
17
|
+
lastLogin: new Date( '2017-01-03' ).getDate(),
|
|
18
|
+
customData: {
|
|
19
|
+
role: 'testRole',
|
|
20
|
+
customer: 'testCustomer'
|
|
21
|
+
},
|
|
22
|
+
id: 'fakeUser',
|
|
23
|
+
} as UserCredentials<CustomCredentials>
|
|
24
|
+
|
|
25
|
+
beforeEach(()=>{
|
|
26
|
+
Auth.useAuthService( mockAuthService = new AuthMock() )
|
|
27
|
+
Auth.instance.onAuthStateChange( authChangeSpy )
|
|
28
|
+
|
|
29
|
+
mockAuthService.fakeRegisteredUser( fakeUseCredentials )
|
|
30
|
+
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it( 'should throw if AuthService not set', ()=>{
|
|
34
|
+
Auth.useAuthService( undefined! )
|
|
35
|
+
expect(
|
|
36
|
+
()=>Auth.instance
|
|
37
|
+
).toThrow( Auth.error.shouldBeRegistered )
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
it( 'should emulate sign-up', async ()=>{
|
|
42
|
+
const userCredentials = await Auth.instance.signUp({
|
|
43
|
+
authProvider: 'google',
|
|
44
|
+
email: 'test@test.com',
|
|
45
|
+
password: 'password'
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
expect( userCredentials.email ).toEqual( 'test@test.com' )
|
|
49
|
+
expect( authChangeSpy ).toHaveBeenCalledWith( userCredentials )
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it( 'should emulate failed sign-up', async ()=>{
|
|
53
|
+
try {
|
|
54
|
+
var userCredentials = await Auth.instance.signUp({
|
|
55
|
+
authProvider: 'email',
|
|
56
|
+
email: 'test@test.com',
|
|
57
|
+
password: 'fail'
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
catch {}
|
|
61
|
+
|
|
62
|
+
expect( userCredentials! ).toBeUndefined()
|
|
63
|
+
expect( authChangeSpy ).toHaveBeenCalledWith( undefined )
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it( 'should login with fake registered user', async ()=>{
|
|
67
|
+
const userCredentials = await Auth.instance.login({
|
|
68
|
+
email: 'fakeUser@test.com',
|
|
69
|
+
password: 'password',
|
|
70
|
+
authProvider: 'google'
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
const modUserCredentials = {
|
|
74
|
+
...fakeUseCredentials,
|
|
75
|
+
id: fakeUseCredentials.id
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
expect( userCredentials ).toEqual( modUserCredentials )
|
|
79
|
+
expect( authChangeSpy ).toHaveBeenCalledWith( modUserCredentials )
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it( 'should fail login with email auth provider if does not match fake user credentials', async ()=>{
|
|
83
|
+
try {
|
|
84
|
+
var userCredentials = await Auth.instance.login({
|
|
85
|
+
email: 'test@test.com',
|
|
86
|
+
password: 'password',
|
|
87
|
+
authProvider: 'email'
|
|
88
|
+
})
|
|
89
|
+
} catch {}
|
|
90
|
+
|
|
91
|
+
expect( userCredentials! ).toEqual( undefined )
|
|
92
|
+
expect( authChangeSpy ).toHaveBeenCalledWith( undefined )
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it( 'should NOT fail login with non email auth provider even if does not match fake user credentials', async ()=>{
|
|
96
|
+
const userCredentials = await Auth.instance.login({
|
|
97
|
+
email: 'test@test.com',
|
|
98
|
+
password: 'password',
|
|
99
|
+
authProvider: 'google'
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
expect( userCredentials.email ).toEqual( 'test@test.com' )
|
|
103
|
+
expect( authChangeSpy ).toHaveBeenCalledWith( undefined )
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it( 'should logout', async ()=>{
|
|
107
|
+
await Auth.instance.logout()
|
|
108
|
+
|
|
109
|
+
expect( authChangeSpy ).toHaveBeenCalledWith( undefined )
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it( 'should throw if email does not exists in resetEmailPassword', async ()=>{
|
|
113
|
+
return expect(
|
|
114
|
+
Auth.instance.resetEmailPassword( 'non-existing-email@test.com' )
|
|
115
|
+
).rejects.toEqual( expect.objectContaining({ code: 'userNotFound' }) )
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it( 'should retrieve custom credentials', async ()=>{
|
|
119
|
+
const userCredentials = await Auth.instance.login<CustomCredentials>({
|
|
120
|
+
email: 'fakeUser@test.com',
|
|
121
|
+
password: 'password',
|
|
122
|
+
authProvider: 'google'
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
expect( userCredentials.customData?.role ).toEqual( 'testRole' )
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it( 'should throw if login and not email in email auth provider', ()=>{
|
|
129
|
+
return expect(
|
|
130
|
+
Auth.instance.login({
|
|
131
|
+
authProvider: 'email',
|
|
132
|
+
email: '',
|
|
133
|
+
password: '****'
|
|
134
|
+
})
|
|
135
|
+
).rejects.toEqual( expect.objectContaining({ code: 'missingEmail' }))
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it( 'should throw if login and not password in email auth provider', ()=>{
|
|
139
|
+
return expect(
|
|
140
|
+
Auth.instance.login({
|
|
141
|
+
authProvider: 'email',
|
|
142
|
+
email: 'test@test.com',
|
|
143
|
+
password: ''
|
|
144
|
+
})
|
|
145
|
+
).rejects.toEqual( expect.objectContaining({ code: 'missingPassword' }))
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it( 'should throw if signup and not email in email auth provider', ()=>{
|
|
149
|
+
return expect(
|
|
150
|
+
Auth.instance.signUp({
|
|
151
|
+
authProvider: 'email',
|
|
152
|
+
email: '',
|
|
153
|
+
password: '****'
|
|
154
|
+
})
|
|
155
|
+
).rejects.toEqual( expect.objectContaining({ code: 'missingEmail' }))
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it( 'should throw if signup and not password in email auth provider', ()=>{
|
|
159
|
+
return expect(
|
|
160
|
+
Auth.instance.signUp({
|
|
161
|
+
authProvider: 'email',
|
|
162
|
+
email: 'test@test.com',
|
|
163
|
+
password: ''
|
|
164
|
+
})
|
|
165
|
+
).rejects.toEqual( expect.objectContaining({ code: 'missingPassword' }))
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
})
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Collection } from '../types/utility-types'
|
|
2
|
+
import { AuthService, RejectedCallback, ResovedCallback } from "./auth"
|
|
3
|
+
import { UserCredentials, SignData, AuthProvider } from "./user-auth-types"
|
|
4
|
+
|
|
5
|
+
export class AuthMock extends AuthService {
|
|
6
|
+
|
|
7
|
+
signUp<T extends {}>( signData: SignData ): Promise<UserCredentials<T>> {
|
|
8
|
+
const { verificationLink, email, password, authProvider } = signData
|
|
9
|
+
|
|
10
|
+
const promise = new Promise<UserCredentials<T>>( async ( resolve: ResovedCallback<T>, reject: RejectedCallback ) => {
|
|
11
|
+
if ( authProvider === 'email' ) {
|
|
12
|
+
if ( !email ) reject({ code: 'missingEmail', message: 'missingEmail' })
|
|
13
|
+
if ( !password ) reject({ code: 'missingPassword', message: 'missingPassword' })
|
|
14
|
+
}
|
|
15
|
+
if ( password !== 'fail' && email !== 'fail' ) {
|
|
16
|
+
this._loggedUser = this.userCredentials<T>( signData )
|
|
17
|
+
this._fakeRegisteredUsers[ this._loggedUser.id ] = this._loggedUser
|
|
18
|
+
resolve( this._loggedUser as UserCredentials<T> )
|
|
19
|
+
this.notifyChange?.( this._loggedUser )
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
reject({ code: 'userNotFound', message: verificationLink || 'Test auth error' })
|
|
23
|
+
this.notifyChange?.( undefined )
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
this.pendingPromises.push( promise )
|
|
27
|
+
return promise
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
login<T extends {}>( signData: SignData ): Promise<UserCredentials<T>> {
|
|
31
|
+
const fakeUser = Object.values( this._fakeRegisteredUsers ).find(
|
|
32
|
+
user => user.email === signData.email
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
if ( signData.authProvider === 'email' && !fakeUser && signData.email) {
|
|
36
|
+
signData.email = 'fail'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return this.signUp( signData )
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
onAuthStateChange<T extends {}>( onChange: ( userCredentials: UserCredentials<T> )=>void ) {
|
|
43
|
+
this.notifyChange = onChange
|
|
44
|
+
this.notifyChange( this._loggedUser )
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async logout(): Promise<void> {
|
|
48
|
+
const promise = new Promise<void>( resolve => {
|
|
49
|
+
this._loggedUser = undefined
|
|
50
|
+
resolve()
|
|
51
|
+
this.notifyChange?.( undefined )
|
|
52
|
+
})
|
|
53
|
+
this.pendingPromises.push( promise )
|
|
54
|
+
return promise
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
resetEmailPassword( email: string ) {
|
|
58
|
+
const fakeUserExists = Object.values( this._fakeRegisteredUsers ).find(
|
|
59
|
+
user => user.email === email
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
if ( fakeUserExists ) return Promise.resolve()
|
|
63
|
+
else return Promise.reject({ code: 'userNotFound', message: 'Test auth error' })
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
resendVerificationEmail( email: string, _password: string, _verificationLink: string ) {
|
|
67
|
+
const fakeUserExists = Object.values( this._fakeRegisteredUsers ).find(
|
|
68
|
+
user => user.email === email
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
if ( fakeUserExists ) return Promise.resolve()
|
|
72
|
+
else return Promise.reject({ code: 'userNotFound', message: 'Test auth error' })
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
override refreshToken(): Promise<void> {
|
|
76
|
+
return Promise.resolve()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
linkAdditionalProvider( provider: AuthProvider ): Promise<unknown> {
|
|
80
|
+
throw new Error('Not implemented.')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
unlinkProvider( provider: AuthProvider ): Promise<unknown> {
|
|
84
|
+
throw new Error('Not implemented.')
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async flush() {
|
|
88
|
+
await Promise.all(this.pendingPromises)
|
|
89
|
+
this.pendingPromises = []
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
fakeRegisteredUser<T extends {}>( userCredentials: UserCredentials<T> ) {
|
|
93
|
+
if ( this._fakeRegisteredUsers[ userCredentials.id ] ) throw new Error( `User with id ${ userCredentials.id } already exists in fake user list`)
|
|
94
|
+
this._fakeRegisteredUsers[ userCredentials.id ] = userCredentials
|
|
95
|
+
return this
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
get fakeRegisteredUsers() {
|
|
99
|
+
return this._fakeRegisteredUsers
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private userCredentials<T extends {}>( signData: SignData ): UserCredentials<T> {
|
|
103
|
+
const fakeUser = Object.values( this._fakeRegisteredUsers ).find(
|
|
104
|
+
user => user.email === signData.email
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
if ( fakeUser ) {
|
|
108
|
+
return { ...fakeUser as UserCredentials<T> }
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
return ({
|
|
112
|
+
id: signData.authProvider || `testUID${ signData.email? '-' + signData.email : '' }`,
|
|
113
|
+
email: signData.email || 'testEmail',
|
|
114
|
+
name: signData.authProvider || `testName${ signData.email? ' ' + signData.email : '' }` ,
|
|
115
|
+
phoneNumber: 'testPhone',
|
|
116
|
+
customData: {
|
|
117
|
+
role: 'test'
|
|
118
|
+
} as unknown as T,
|
|
119
|
+
lastLogin: 0,
|
|
120
|
+
creationDate: 0
|
|
121
|
+
} as UserCredentials<T>)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private pendingPromises: Promise<any>[] = []
|
|
126
|
+
private _loggedUser: UserCredentials<{}> | undefined
|
|
127
|
+
private notifyChange: (( userCredentials: UserCredentials<{}> | undefined ) => void ) | undefined
|
|
128
|
+
private _fakeRegisteredUsers: Collection<UserCredentials<{}>> = {}
|
|
129
|
+
}
|
package/src/auth/auth.ts
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { Observable } from '../observable/observable'
|
|
2
|
+
import { AuthProvider, SignData, UserCredentials } from "./user-auth-types"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The AuthService class is an abstract class that defines the interface of an authentication service.
|
|
6
|
+
* You should derive from this class to implement your own authentication service.
|
|
7
|
+
*/
|
|
8
|
+
export abstract class AuthService {
|
|
9
|
+
abstract signUp<T extends {}>( signData: SignData ): Promise<UserCredentials<T>>
|
|
10
|
+
abstract login<T extends {}>( signData: SignData ): Promise<UserCredentials<T>>
|
|
11
|
+
abstract logout(): Promise<void>
|
|
12
|
+
abstract resetEmailPassword( email: string ): Promise<void>
|
|
13
|
+
abstract refreshToken(): Promise<void>
|
|
14
|
+
abstract linkAdditionalProvider( provider: AuthProvider ): Promise<unknown>
|
|
15
|
+
abstract unlinkProvider( provider: AuthProvider ): Promise<unknown>
|
|
16
|
+
abstract onAuthStateChange<T extends {}>( onChange: (userCredentials: UserCredentials<T> | undefined) => void ): void
|
|
17
|
+
abstract resendVerificationEmail( email: string, password: string, verificationLink: string ): Promise<void>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type AuthErrorCode = 'wrongPassword' | 'popupClosedByUser' | 'userNotFound' | 'invalidEmail' | 'missingPassword' | 'missingEmail'
|
|
21
|
+
|
|
22
|
+
export interface AuthError {
|
|
23
|
+
code: AuthErrorCode
|
|
24
|
+
message: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Types the callback to accept a user credentials object
|
|
29
|
+
*/
|
|
30
|
+
export type ResovedCallback<T extends {}> = ( credentials: UserCredentials<T> ) => void
|
|
31
|
+
export type RejectedCallback = ( reason: AuthError ) => void
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The Auth class is a singleton that provides a unified interface to the authentication service.
|
|
35
|
+
* You should register an authentication service by using `Auth.useAuthService`
|
|
36
|
+
* method before using the Auth class.
|
|
37
|
+
*/
|
|
38
|
+
export class Auth extends AuthService {
|
|
39
|
+
static error = { shouldBeRegistered: 'You should register an auth service before using Auth.' }
|
|
40
|
+
|
|
41
|
+
protected constructor() {
|
|
42
|
+
super()
|
|
43
|
+
if (!Auth._authService ) throw ( new Error( Auth.error.shouldBeRegistered ) )
|
|
44
|
+
Auth._authService.onAuthStateChange(
|
|
45
|
+
userCredentials => this.authStateChanged( userCredentials )
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Registers an authentication service to be used by the Auth class.
|
|
51
|
+
* You need to register an authentication service before using the Auth class.
|
|
52
|
+
* @param authService the authentication service to be used by the Auth class
|
|
53
|
+
*/
|
|
54
|
+
static useAuthService( authService: AuthService ) {
|
|
55
|
+
if ( Auth._authService != authService ) {
|
|
56
|
+
Auth._authService = authService
|
|
57
|
+
this._instance = undefined
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The instance of the Auth class
|
|
63
|
+
* @returns the authentication service
|
|
64
|
+
*/
|
|
65
|
+
static get instance() {
|
|
66
|
+
return this._instance || (this._instance = new this() )
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Signs up a new user
|
|
71
|
+
* @param singData the data to be used to sign up the user
|
|
72
|
+
* @returns a promise that resolves to the user credentials
|
|
73
|
+
* @example
|
|
74
|
+
* // Sign up a new user with email and password
|
|
75
|
+
* Auth.instance.signUp({ authProvider: 'email', email: 'john@test.com', password: '123456' })
|
|
76
|
+
* // Sign up a new user with a Google account
|
|
77
|
+
* Auth.instance.signUp({ authProvider: 'google'})
|
|
78
|
+
*/
|
|
79
|
+
signUp<T extends {}>( singData: SignData ): Promise<UserCredentials<T>> {
|
|
80
|
+
return Auth._authService.signUp( singData )
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Logs in an existing user
|
|
85
|
+
* @param singData the data to be used to log in the user
|
|
86
|
+
* @returns a promise that resolves to the user credentials
|
|
87
|
+
* @example
|
|
88
|
+
* // Log in an existing user with email and password
|
|
89
|
+
* Auth.instance.login({ authProvider: 'email', email: 'john@test.com', password: '123456' })
|
|
90
|
+
* // Log in an existing user with a Google account
|
|
91
|
+
* Auth.instance.login({ authProvider: 'google'})
|
|
92
|
+
*/
|
|
93
|
+
login<T extends {}>( singData: SignData ): Promise<UserCredentials<T>> {
|
|
94
|
+
return Auth._authService.login( singData )
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Logs out the current user
|
|
99
|
+
* @returns a promise that resolves when the user is logged out
|
|
100
|
+
*/
|
|
101
|
+
logout(): Promise<void> {
|
|
102
|
+
return Auth._authService.logout()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Resets the password associated with the email.
|
|
107
|
+
* @param email the email address of the user to reset the password
|
|
108
|
+
* @returns a promise that resolves when the process is done
|
|
109
|
+
*/
|
|
110
|
+
resetEmailPassword( email: string ) {
|
|
111
|
+
return Auth._authService.resetEmailPassword( email )
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Resends the email verification to the user.
|
|
116
|
+
* @returns a promise that resolves when the process is done
|
|
117
|
+
*/
|
|
118
|
+
override resendVerificationEmail( email: string, password: string, verificationLink: string ): Promise<void> {
|
|
119
|
+
return Auth._authService.resendVerificationEmail( email, password, verificationLink )
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
override refreshToken(): Promise<void> {
|
|
123
|
+
return Auth._authService.refreshToken()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Adds a listener to be called when the authentication state changes.
|
|
128
|
+
* @param onChange the listener to be called when the authentication state changes.
|
|
129
|
+
* The listener is called with the user credentials as a parameter.
|
|
130
|
+
* If the user is logged out, the listener is called with `undefined` as a parameter.
|
|
131
|
+
* @returns a function to remove the listener
|
|
132
|
+
* @example
|
|
133
|
+
* // Add a listener to be called when the authentication state changes
|
|
134
|
+
* const removeListener = Auth.instance.onAuthStateChange( userCredentials => {
|
|
135
|
+
* if ( userCredentials ) {
|
|
136
|
+
* // The user is logged in
|
|
137
|
+
* } else {
|
|
138
|
+
* // The user is logged out
|
|
139
|
+
* }
|
|
140
|
+
* })
|
|
141
|
+
*/
|
|
142
|
+
onAuthStateChange<T extends {}>( onChange: ( userCredentials: UserCredentials<T> )=>void ) {
|
|
143
|
+
return this._onAuthStateChange.subscribe( onChange )
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Removes a listener that was added by `onAuthStateChange` method.
|
|
148
|
+
* @param onChange the listener to be removed
|
|
149
|
+
*/
|
|
150
|
+
removeAuthStateChange<T extends {}>( onChange: ( userCredentials: UserCredentials<T> )=>void ) {
|
|
151
|
+
this._onAuthStateChange.unsubscribe( onChange )
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Links an additional authentication provider to the authenticated user.
|
|
156
|
+
* @param provider the provider to be linked
|
|
157
|
+
* @returns a promise that resolves when the process is done
|
|
158
|
+
* @example
|
|
159
|
+
* // Link a Google account to the auth service
|
|
160
|
+
* Auth.instance.linkAdditionalProvider({ authProvider: 'google' })
|
|
161
|
+
*/
|
|
162
|
+
linkAdditionalProvider( provider: AuthProvider ): Promise<unknown> {
|
|
163
|
+
return Auth._authService.linkAdditionalProvider( provider )
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Unlinks an authentication provider from the authenticated user.
|
|
168
|
+
* @param provider the provider to be unlinked
|
|
169
|
+
* @returns a promise that resolves when the process is done
|
|
170
|
+
* @example
|
|
171
|
+
* // Unlink the Google account from the auth service
|
|
172
|
+
* Auth.instance.unlinkProvider({ authProvider: 'google' })
|
|
173
|
+
*/
|
|
174
|
+
unlinkProvider( provider: AuthProvider ): Promise<unknown> {
|
|
175
|
+
return Auth._authService.unlinkProvider( provider )
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private authStateChanged( userCredentials: UserCredentials<{}> | undefined ) {
|
|
179
|
+
this._onAuthStateChange.notify( userCredentials )
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private static _instance: Auth | undefined = undefined
|
|
183
|
+
private static _authService: AuthService
|
|
184
|
+
private _onAuthStateChange: Observable<UserCredentials<{}>> = new Observable<UserCredentials<{}>>()
|
|
185
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface UserCredentials<T extends {} = {}> {
|
|
2
|
+
id: string
|
|
3
|
+
email: string
|
|
4
|
+
name?: string
|
|
5
|
+
pictureUrl?: string
|
|
6
|
+
phoneNumber?: string
|
|
7
|
+
emailVerified?: boolean
|
|
8
|
+
customData?: T
|
|
9
|
+
lastLogin?: number
|
|
10
|
+
creationDate?: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type AuthProvider = 'email' | 'facebook' | 'google' | 'twitter'
|
|
14
|
+
|
|
15
|
+
export interface SignData {
|
|
16
|
+
authProvider: AuthProvider
|
|
17
|
+
email?: string
|
|
18
|
+
password?: string
|
|
19
|
+
name?: string
|
|
20
|
+
verificationLink?: string
|
|
21
|
+
}
|