@passlock/client 0.9.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.
Files changed (147) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +42 -0
  3. package/dist/authentication/authenticate.d.ts +22 -0
  4. package/dist/authentication/authenticate.d.ts.map +1 -0
  5. package/dist/authentication/authenticate.fixture.d.ts +36 -0
  6. package/dist/authentication/authenticate.fixture.d.ts.map +1 -0
  7. package/dist/authentication/authenticate.fixture.js +52 -0
  8. package/dist/authentication/authenticate.fixture.js.map +1 -0
  9. package/dist/authentication/authenticate.js +69 -0
  10. package/dist/authentication/authenticate.js.map +1 -0
  11. package/dist/authentication/authenticate.test.d.ts +2 -0
  12. package/dist/authentication/authenticate.test.d.ts.map +1 -0
  13. package/dist/authentication/authenticate.test.js +111 -0
  14. package/dist/authentication/authenticate.test.js.map +1 -0
  15. package/dist/capabilities/capabilities.d.ts +18 -0
  16. package/dist/capabilities/capabilities.d.ts.map +1 -0
  17. package/dist/capabilities/capabilities.js +35 -0
  18. package/dist/capabilities/capabilities.js.map +1 -0
  19. package/dist/config.d.ts +22 -0
  20. package/dist/config.d.ts.map +1 -0
  21. package/dist/config.js +20 -0
  22. package/dist/config.js.map +1 -0
  23. package/dist/connection/connection.d.ts +9 -0
  24. package/dist/connection/connection.d.ts.map +1 -0
  25. package/dist/connection/connection.fixture.d.ts +12 -0
  26. package/dist/connection/connection.fixture.d.ts.map +1 -0
  27. package/dist/connection/connection.fixture.js +20 -0
  28. package/dist/connection/connection.fixture.js.map +1 -0
  29. package/dist/connection/connection.js +21 -0
  30. package/dist/connection/connection.js.map +1 -0
  31. package/dist/connection/connection.test.d.ts +2 -0
  32. package/dist/connection/connection.test.d.ts.map +1 -0
  33. package/dist/connection/connection.test.js +34 -0
  34. package/dist/connection/connection.test.js.map +1 -0
  35. package/dist/effect.d.ts +33 -0
  36. package/dist/effect.d.ts.map +1 -0
  37. package/dist/effect.js +62 -0
  38. package/dist/effect.js.map +1 -0
  39. package/dist/email/email.d.ts +37 -0
  40. package/dist/email/email.d.ts.map +1 -0
  41. package/dist/email/email.fixture.d.ts +33 -0
  42. package/dist/email/email.fixture.d.ts.map +1 -0
  43. package/dist/email/email.fixture.js +30 -0
  44. package/dist/email/email.fixture.js.map +1 -0
  45. package/dist/email/email.js +78 -0
  46. package/dist/email/email.js.map +1 -0
  47. package/dist/email/email.test.d.ts +2 -0
  48. package/dist/email/email.test.d.ts.map +1 -0
  49. package/dist/email/email.test.js +101 -0
  50. package/dist/email/email.test.js.map +1 -0
  51. package/dist/event/event.d.ts +9 -0
  52. package/dist/event/event.d.ts.map +1 -0
  53. package/dist/event/event.js +23 -0
  54. package/dist/event/event.js.map +1 -0
  55. package/dist/event/event.node.test.d.ts +2 -0
  56. package/dist/event/event.node.test.d.ts.map +1 -0
  57. package/dist/event/event.node.test.js +14 -0
  58. package/dist/event/event.node.test.js.map +1 -0
  59. package/dist/event/event.test.d.ts +2 -0
  60. package/dist/event/event.test.d.ts.map +1 -0
  61. package/dist/event/event.test.js +30 -0
  62. package/dist/event/event.test.js.map +1 -0
  63. package/dist/exit.d.ts +64 -0
  64. package/dist/exit.d.ts.map +1 -0
  65. package/dist/exit.js +106 -0
  66. package/dist/exit.js.map +1 -0
  67. package/dist/index.d.ts +110 -0
  68. package/dist/index.d.ts.map +1 -0
  69. package/dist/index.js +108 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/logging/eventLogger.d.ts +18 -0
  72. package/dist/logging/eventLogger.d.ts.map +1 -0
  73. package/dist/logging/eventLogger.js +32 -0
  74. package/dist/logging/eventLogger.js.map +1 -0
  75. package/dist/logging/eventLogger.test.d.ts +2 -0
  76. package/dist/logging/eventLogger.test.d.ts.map +1 -0
  77. package/dist/logging/eventLogger.test.js +74 -0
  78. package/dist/logging/eventLogger.test.js.map +1 -0
  79. package/dist/registration/register.d.ts +29 -0
  80. package/dist/registration/register.d.ts.map +1 -0
  81. package/dist/registration/register.fixture.d.ts +40 -0
  82. package/dist/registration/register.fixture.d.ts.map +1 -0
  83. package/dist/registration/register.fixture.js +65 -0
  84. package/dist/registration/register.fixture.js.map +1 -0
  85. package/dist/registration/register.js +84 -0
  86. package/dist/registration/register.js.map +1 -0
  87. package/dist/registration/register.test.d.ts +2 -0
  88. package/dist/registration/register.test.d.ts.map +1 -0
  89. package/dist/registration/register.test.js +122 -0
  90. package/dist/registration/register.test.js.map +1 -0
  91. package/dist/storage/storage.d.ts +53 -0
  92. package/dist/storage/storage.d.ts.map +1 -0
  93. package/dist/storage/storage.fixture.d.ts +6 -0
  94. package/dist/storage/storage.fixture.d.ts.map +1 -0
  95. package/dist/storage/storage.fixture.js +26 -0
  96. package/dist/storage/storage.fixture.js.map +1 -0
  97. package/dist/storage/storage.js +102 -0
  98. package/dist/storage/storage.js.map +1 -0
  99. package/dist/storage/storage.test.d.ts +2 -0
  100. package/dist/storage/storage.test.d.ts.map +1 -0
  101. package/dist/storage/storage.test.js +122 -0
  102. package/dist/storage/storage.test.js.map +1 -0
  103. package/dist/test/fixtures.d.ts +14 -0
  104. package/dist/test/fixtures.d.ts.map +1 -0
  105. package/dist/test/fixtures.js +39 -0
  106. package/dist/test/fixtures.js.map +1 -0
  107. package/dist/user/user.d.ts +18 -0
  108. package/dist/user/user.d.ts.map +1 -0
  109. package/dist/user/user.fixture.d.ts +8 -0
  110. package/dist/user/user.fixture.d.ts.map +1 -0
  111. package/dist/user/user.fixture.js +17 -0
  112. package/dist/user/user.fixture.js.map +1 -0
  113. package/dist/user/user.js +23 -0
  114. package/dist/user/user.js.map +1 -0
  115. package/dist/user/user.test.d.ts +2 -0
  116. package/dist/user/user.test.d.ts.map +1 -0
  117. package/dist/user/user.test.js +37 -0
  118. package/dist/user/user.test.js.map +1 -0
  119. package/package.json +87 -0
  120. package/src/authentication/authenticate.fixture.ts +72 -0
  121. package/src/authentication/authenticate.test.ts +207 -0
  122. package/src/authentication/authenticate.ts +147 -0
  123. package/src/capabilities/capabilities.ts +81 -0
  124. package/src/config.ts +43 -0
  125. package/src/connection/connection.fixture.ts +27 -0
  126. package/src/connection/connection.test.ts +61 -0
  127. package/src/connection/connection.ts +51 -0
  128. package/src/effect.ts +278 -0
  129. package/src/email/email.fixture.ts +49 -0
  130. package/src/email/email.test.ts +186 -0
  131. package/src/email/email.ts +139 -0
  132. package/src/event/event.node.test.ts +20 -0
  133. package/src/event/event.test.ts +37 -0
  134. package/src/event/event.ts +25 -0
  135. package/src/index.ts +275 -0
  136. package/src/logging/eventLogger.test.ts +102 -0
  137. package/src/logging/eventLogger.ts +35 -0
  138. package/src/registration/register.fixture.ts +94 -0
  139. package/src/registration/register.test.ts +247 -0
  140. package/src/registration/register.ts +178 -0
  141. package/src/storage/storage.fixture.ts +33 -0
  142. package/src/storage/storage.test.ts +196 -0
  143. package/src/storage/storage.ts +165 -0
  144. package/src/test/fixtures.ts +51 -0
  145. package/src/user/user.fixture.ts +23 -0
  146. package/src/user/user.test.ts +53 -0
  147. package/src/user/user.ts +50 -0
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Fire DOM events
3
+ */
4
+ import { InternalBrowserError } from '@passlock/shared/dist/error/error'
5
+ import { Effect } from 'effect'
6
+
7
+
8
+ export const DebugMessage = 'PasslogDebugMessage'
9
+
10
+ export const fireEvent = (message: string) => {
11
+ return Effect.try({
12
+ try: () => {
13
+ const evt = new CustomEvent(DebugMessage, { detail: message })
14
+ globalThis.dispatchEvent(evt)
15
+ },
16
+ catch: () => {
17
+ return new InternalBrowserError({ message: 'Unable to fire custom event' })
18
+ },
19
+ })
20
+ }
21
+
22
+ export function isPasslockEvent(event: Event): event is CustomEvent {
23
+ if (event.type !== DebugMessage) return false
24
+ return 'detail' in event
25
+ }
package/src/index.ts ADDED
@@ -0,0 +1,275 @@
1
+ import type {
2
+ BadRequest,
3
+ Disabled,
4
+ Duplicate,
5
+ Forbidden,
6
+ NotFound,
7
+ NotSupported,
8
+ Unauthorized,
9
+ } from '@passlock/shared/dist/error/error'
10
+ import { RpcConfig } from '@passlock/shared/dist/rpc/rpc'
11
+ import { Effect as E, Layer as L, Layer, Option, Runtime, Scope, pipe } from 'effect'
12
+ import { type AuthenticationRequest, AuthenticationService } from './authentication/authenticate'
13
+ import { Capabilities } from './capabilities/capabilities'
14
+ import { ConnectionService } from './connection/connection'
15
+ import { allRequirements } from './effect'
16
+ import { EmailService, type VerifyRequest } from './email/email'
17
+ import { type RegistrationRequest, RegistrationService } from './registration/register'
18
+ import { type AuthType, Storage, StorageService } from './storage/storage'
19
+ import { type Email, UserService } from './user/user'
20
+ import { ErrorCode } from '@passlock/shared/dist/error/error'
21
+ export { ErrorCode } from '@passlock/shared/dist/error/error'
22
+
23
+ export class PasslockError extends Error {
24
+ readonly _tag = 'PasslockError'
25
+ readonly code: ErrorCode
26
+
27
+ constructor(message: string, code: ErrorCode) {
28
+ super(message)
29
+ this.code = code
30
+ }
31
+
32
+ static readonly isError = (error: unknown): error is PasslockError => {
33
+ return (
34
+ typeof error === 'object' &&
35
+ error !== null &&
36
+ '_tag' in error &&
37
+ error['_tag'] === 'PasslockError'
38
+ )
39
+ }
40
+ }
41
+
42
+ type PasslockErrors =
43
+ | BadRequest
44
+ | NotSupported
45
+ | Duplicate
46
+ | Unauthorized
47
+ | Forbidden
48
+ | Disabled
49
+ | NotFound
50
+
51
+ const hasMessage = (defect: unknown): defect is { message: string } => {
52
+ return (
53
+ typeof defect === 'object' &&
54
+ defect !== null &&
55
+ 'message' in defect &&
56
+ typeof defect['message'] === 'string'
57
+ )
58
+ }
59
+
60
+ const transformErrors = <A, R>(
61
+ effect: E.Effect<A, PasslockErrors, R>,
62
+ ): E.Effect<A | PasslockError, never, R> => {
63
+ const withErrorHandling = E.catchTags(effect, {
64
+ NotSupported: e => E.succeed(new PasslockError(e.message, ErrorCode.NotSupported)),
65
+ BadRequest: e => E.succeed(new PasslockError(e.message, ErrorCode.BadRequest)),
66
+ Duplicate: e => E.succeed(new PasslockError(e.message, ErrorCode.Duplicate)),
67
+ Unauthorized: e => E.succeed(new PasslockError(e.message, ErrorCode.Unauthorized)),
68
+ Forbidden: e => E.succeed(new PasslockError(e.message, ErrorCode.Forbidden)),
69
+ Disabled: e => E.succeed(new PasslockError(e.message, ErrorCode.Disabled)),
70
+ NotFound: e => E.succeed(new PasslockError(e.message, ErrorCode.NotFound)),
71
+ })
72
+
73
+ const sandboxed = E.sandbox(withErrorHandling)
74
+
75
+ const withSandboxing = E.catchTags(sandboxed, {
76
+ Die: ({ defect }) => {
77
+ return hasMessage(defect)
78
+ ? E.succeed(new PasslockError(defect.message, ErrorCode.InternalServerError))
79
+ : E.succeed(new PasslockError('Sorry, something went wrong', ErrorCode.InternalServerError))
80
+ },
81
+
82
+ Interrupt: () => {
83
+ return E.succeed(new PasslockError('Operation aborted', ErrorCode.InternalBrowserError))
84
+ },
85
+
86
+ Sequential: errors => {
87
+ console.error(errors)
88
+ return E.succeed(new PasslockError('Sorry, something went wrong', ErrorCode.InternalServerError))
89
+ },
90
+
91
+ Parallel: errors => {
92
+ console.error(errors)
93
+ return E.succeed(new PasslockError('Sorry, something went wrong', ErrorCode.InternalServerError))
94
+ },
95
+ })
96
+
97
+ return E.unsandbox(withSandboxing)
98
+ }
99
+
100
+ type Requirements =
101
+ | UserService
102
+ | RegistrationService
103
+ | AuthenticationService
104
+ | ConnectionService
105
+ | EmailService
106
+ | StorageService
107
+ | Capabilities
108
+
109
+ export class Passlock {
110
+ private readonly runtime: Runtime.Runtime<Requirements>
111
+
112
+ constructor(config: { tenancyId: string; clientId: string; endpoint: string }) {
113
+ const rpcConfig = Layer.succeed(RpcConfig, RpcConfig.of(config))
114
+ const storage = Layer.succeed(Storage, Storage.of(globalThis.localStorage))
115
+ const allLayers = pipe(allRequirements, L.provide(rpcConfig), L.provide(storage))
116
+ const scope = E.runSync(Scope.make())
117
+ this.runtime = E.runSync(Layer.toRuntime(allLayers).pipe(Scope.extend(scope)))
118
+ }
119
+
120
+ private readonly runPromise = <A, R extends Requirements>(
121
+ effect: E.Effect<A, PasslockErrors, R>,
122
+ ) => {
123
+ return pipe(
124
+ transformErrors(effect),
125
+ E.flatMap(result => (PasslockError.isError(result) ? E.fail(result) : E.succeed(result))),
126
+ effect => Runtime.runPromise(this.runtime)(effect),
127
+ )
128
+ }
129
+
130
+ preConnect = () =>
131
+ pipe(
132
+ ConnectionService,
133
+ E.flatMap(service => service.preConnect()),
134
+ effect => Runtime.runPromise(this.runtime)(effect),
135
+ )
136
+
137
+ isPasskeySupport = () =>
138
+ pipe(
139
+ Capabilities,
140
+ E.flatMap(service => service.isPasskeySupport),
141
+ effect => Runtime.runPromise(this.runtime)(effect),
142
+ )
143
+
144
+ isExistingPasskey = (email: Email) =>
145
+ pipe(
146
+ UserService,
147
+ E.flatMap(service => service.isExistingUser(email)),
148
+ effect => this.runPromise(effect),
149
+ )
150
+
151
+ registerPasskey = (request: RegistrationRequest) =>
152
+ pipe(
153
+ RegistrationService,
154
+ E.flatMap(service => service.registerPasskey(request)),
155
+ effect => this.runPromise(effect),
156
+ )
157
+
158
+ authenticatePasskey = (request: AuthenticationRequest) =>
159
+ pipe(
160
+ AuthenticationService,
161
+ E.flatMap(service => service.authenticatePasskey(request)),
162
+ effect => this.runPromise(effect),
163
+ )
164
+
165
+ verifyEmailCode = (request: VerifyRequest) =>
166
+ pipe(
167
+ EmailService,
168
+ E.flatMap(service => service.verifyEmailCode(request)),
169
+ effect => this.runPromise(effect),
170
+ )
171
+
172
+ verifyEmailLink = () =>
173
+ pipe(
174
+ EmailService,
175
+ E.flatMap(service => service.verifyEmailLink()),
176
+ effect => this.runPromise(effect),
177
+ )
178
+
179
+ getSessionToken = (authType: AuthType) =>
180
+ pipe(
181
+ StorageService,
182
+ E.flatMap(service => service.getToken(authType).pipe(effect => E.option(effect))),
183
+ E.map(Option.getOrUndefined),
184
+ effect => Runtime.runSync(this.runtime)(effect)
185
+ )
186
+
187
+ clearExpiredTokens = () =>
188
+ pipe(
189
+ StorageService,
190
+ E.flatMap(service => service.clearExpiredTokens),
191
+ effect => Runtime.runPromise(this.runtime)(effect)
192
+ )
193
+ }
194
+
195
+ export class PasslockSafe {
196
+ private readonly runtime: Runtime.Runtime<Requirements>
197
+
198
+ constructor(config: { tenancyId: string; clientId: string; endpoint: string }) {
199
+ const rpcConfig = Layer.succeed(RpcConfig, RpcConfig.of(config))
200
+ const storage = Layer.succeed(Storage, Storage.of(globalThis.localStorage))
201
+ const allLayers = pipe(allRequirements, L.provide(rpcConfig), L.provide(storage))
202
+ const scope = E.runSync(Scope.make())
203
+ this.runtime = E.runSync(Layer.toRuntime(allLayers).pipe(Scope.extend(scope)))
204
+ }
205
+
206
+ private readonly runPromise = <A, R extends Requirements>(
207
+ effect: E.Effect<A, PasslockErrors, R>,
208
+ ) => {
209
+ return pipe(transformErrors(effect), effect => Runtime.runPromise(this.runtime)(effect))
210
+ }
211
+
212
+ isPasskeySupport = () =>
213
+ pipe(
214
+ Capabilities,
215
+ E.flatMap(service => service.isPasskeySupport),
216
+ effect => Runtime.runPromise(this.runtime)(effect),
217
+ )
218
+
219
+ preConnect = () =>
220
+ pipe(
221
+ ConnectionService,
222
+ E.flatMap(service => service.preConnect()),
223
+ effect => this.runPromise(effect),
224
+ )
225
+
226
+ isExistingPasskey = (email: Email) =>
227
+ pipe(
228
+ UserService,
229
+ E.flatMap(service => service.isExistingUser(email)),
230
+ effect => this.runPromise(effect),
231
+ )
232
+
233
+ registerPasskey = (request: RegistrationRequest) =>
234
+ pipe(
235
+ RegistrationService,
236
+ E.flatMap(service => service.registerPasskey(request)),
237
+ effect => this.runPromise(effect),
238
+ )
239
+
240
+ authenticatePasskey = (request: AuthenticationRequest = {}) =>
241
+ pipe(
242
+ AuthenticationService,
243
+ E.flatMap(service => service.authenticatePasskey(request)),
244
+ effect => this.runPromise(effect),
245
+ )
246
+
247
+ verifyEmailCode = (request: VerifyRequest) =>
248
+ pipe(
249
+ EmailService,
250
+ E.flatMap(service => service.verifyEmailCode(request)),
251
+ effect => this.runPromise(effect),
252
+ )
253
+
254
+ verifyEmailLink = () =>
255
+ pipe(
256
+ EmailService,
257
+ E.flatMap(service => service.verifyEmailLink()),
258
+ effect => this.runPromise(effect),
259
+ )
260
+
261
+ getSessionToken = (authType: AuthType) =>
262
+ pipe(
263
+ StorageService,
264
+ E.flatMap(service => service.getToken(authType).pipe(effect => E.option(effect))),
265
+ E.map(maybeToken => Option.getOrUndefined(maybeToken)),
266
+ effect => Runtime.runSync(this.runtime)(effect)
267
+ )
268
+
269
+ clearExpiredTokens = () =>
270
+ pipe(
271
+ StorageService,
272
+ E.flatMap(service => service.clearExpiredTokens),
273
+ effect => Runtime.runPromise(this.runtime)(effect)
274
+ )
275
+ }
@@ -0,0 +1,102 @@
1
+ import { Effect as E, LogLevel, Logger } from 'effect'
2
+ import { describe, expect, test, vi } from 'vitest'
3
+ import { eventLoggerLive, logRaw } from './eventLogger'
4
+
5
+ /**
6
+ * Although the core log functionality is tested alongside the logger in the @passlock/shared
7
+ * package, those tests deliberately exclude the event dispatch elements as the package
8
+ * is intended to be agnostic to the runtime environment. This client package however is
9
+ * intented to be run in the browser, so we can plugin a real event dispatcher and ensure
10
+ * it's working as expected.
11
+ */
12
+
13
+ describe('log', () => {
14
+ test('log DEBUG to the console', () => {
15
+ const logStatement = E.logDebug('hello world')
16
+
17
+ const logSpy = vi.spyOn(globalThis.console, 'log').mockImplementation(() => undefined)
18
+ const withLogLevel = logStatement.pipe(Logger.withMinimumLogLevel(LogLevel.Debug))
19
+
20
+ const effect = E.provide(withLogLevel, eventLoggerLive)
21
+ E.runSync(effect)
22
+
23
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('hello world'))
24
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('DEBUG'))
25
+ })
26
+
27
+ test('log INFO to the console', () => {
28
+ const logStatement = E.logInfo('hello world')
29
+
30
+ const logSpy = vi.spyOn(globalThis.console, 'log').mockImplementation(() => undefined)
31
+ const withLogLevel = logStatement.pipe(Logger.withMinimumLogLevel(LogLevel.Info))
32
+
33
+ const effect = E.provide(withLogLevel, eventLoggerLive)
34
+ E.runSync(effect)
35
+
36
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('hello world'))
37
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('INFO'))
38
+ })
39
+
40
+ test('log WARN to the console', () => {
41
+ const logStatement = E.logWarning('hello world')
42
+
43
+ const logSpy = vi.spyOn(globalThis.console, 'log').mockImplementation(() => undefined)
44
+ const withLogLevel = logStatement.pipe(Logger.withMinimumLogLevel(LogLevel.Warning))
45
+
46
+ const effect = E.provide(withLogLevel, eventLoggerLive)
47
+ E.runSync(effect)
48
+
49
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('hello world'))
50
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('WARN'))
51
+ })
52
+
53
+ test('log ERROR to the console', () => {
54
+ const logStatement = E.logError('hello world')
55
+
56
+ const logSpy = vi.spyOn(globalThis.console, 'log').mockImplementation(() => undefined)
57
+ const withLogLevel = logStatement.pipe(Logger.withMinimumLogLevel(LogLevel.Error))
58
+
59
+ const effect = E.provide(withLogLevel, eventLoggerLive)
60
+ E.runSync(effect)
61
+
62
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('hello world'))
63
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('ERROR'))
64
+ })
65
+
66
+ test('log raw data to the console', () => {
67
+ const logStatement = logRaw('hello world')
68
+ const logSpy = vi.spyOn(globalThis.console, 'log').mockImplementation(() => undefined)
69
+
70
+ E.runSync(logStatement)
71
+
72
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('hello world'))
73
+ })
74
+
75
+ test('fire a custom log event', () => {
76
+ const logStatement = E.logWarning('hello world')
77
+
78
+ const eventSpy = vi.spyOn(globalThis, 'dispatchEvent').mockImplementation(() => false)
79
+ const withLogLevel = logStatement.pipe(Logger.withMinimumLogLevel(LogLevel.Warning))
80
+
81
+ const effect = E.provide(withLogLevel, eventLoggerLive)
82
+ E.runSync(effect)
83
+
84
+ const expectedEvent = new CustomEvent('PasslogDebugMessage', {
85
+ detail: 'hello world',
86
+ })
87
+
88
+ expect(eventSpy).toHaveBeenCalledWith(expectedEvent)
89
+ })
90
+
91
+ test('not fire a log event for a debug message', () => {
92
+ const logStatement = E.logDebug('hello world')
93
+
94
+ const eventSpy = vi.spyOn(globalThis, 'dispatchEvent').mockImplementation(() => false)
95
+ const withDebugLevel = logStatement.pipe(Logger.withMinimumLogLevel(LogLevel.Debug))
96
+
97
+ const effect = E.provide(withDebugLevel, eventLoggerLive)
98
+ E.runSync(effect)
99
+
100
+ expect(eventSpy).not.toHaveBeenCalled()
101
+ })
102
+ })
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Logger implementation that also fires DOM events.
3
+ * This is useful to allow external code to plug into the logging
4
+ * mechanism. E.g. the Passlock demo subscribes to events to generate
5
+ * a typewriter style effect
6
+ */
7
+ import { Effect as E, LogLevel, Logger } from 'effect'
8
+
9
+ /**
10
+ * Some log messages span multiple lines/include json etc which is
11
+ * better output without being formatted by Effect's logging framework
12
+ *
13
+ * @param message
14
+ * @returns
15
+ */
16
+ export const logRaw = <T>(message: T) => {
17
+ return E.sync(() => {
18
+ console.log(message)
19
+ })
20
+ }
21
+
22
+ export const DebugMessage = 'PasslogDebugMessage'
23
+
24
+ export const eventLoggerLive = Logger.add(
25
+ Logger.make(options => {
26
+ if (typeof options.message === 'string' && options.logLevel !== LogLevel.Debug) {
27
+ try {
28
+ const evt = new CustomEvent(DebugMessage, { detail: options.message })
29
+ globalThis.dispatchEvent(evt)
30
+ } catch (e) {
31
+ globalThis.console.log('Unable to fire custom event')
32
+ }
33
+ }
34
+ }),
35
+ )
@@ -0,0 +1,94 @@
1
+ import { BadRequest } from '@passlock/shared/dist/error/error'
2
+ import {
3
+ OptionsReq,
4
+ OptionsRes,
5
+ VerificationReq,
6
+ VerificationRes,
7
+ } from '@passlock/shared/dist/rpc/registration'
8
+ import { RpcClient } from '@passlock/shared/dist/rpc/rpc'
9
+ import type { RegistrationCredential } from '@passlock/shared/dist/schema/schema'
10
+ import { Effect as E, Layer as L } from 'effect'
11
+ import { CreateCredential, type RegistrationRequest } from './register'
12
+ import * as Fixtures from '../test/fixtures'
13
+ import { UserService } from '../user/user'
14
+
15
+
16
+ export const session = 'session'
17
+ export const token = 'token'
18
+ export const code = 'code'
19
+ export const authType = 'passkey'
20
+ export const expireAt = Date.now() + 10000
21
+
22
+ export const registrationRequest: RegistrationRequest = {
23
+ email: 'jdoe@gmail.com',
24
+ firstName: 'john',
25
+ lastName: 'doe',
26
+ }
27
+
28
+ export const optionsReq = new OptionsReq(registrationRequest)
29
+
30
+ export const registrationOptions: OptionsRes = {
31
+ session,
32
+ publicKey: {
33
+ rp: {
34
+ name: 'passlock',
35
+ id: 'passlock.dev',
36
+ },
37
+ user: {
38
+ name: 'john doe',
39
+ id: 'jdoe',
40
+ displayName: 'john doe',
41
+ },
42
+ challenge: 'FKZSl_saKu5OXjLLwoq8eK3wlD8XgpGiS10SszW5RiE',
43
+ pubKeyCredParams: [],
44
+ },
45
+ }
46
+
47
+ export const optionsRes = new OptionsRes(registrationOptions)
48
+
49
+ export const credential: RegistrationCredential = {
50
+ type: 'public-key',
51
+ id: '1',
52
+ rawId: '1',
53
+ response: {
54
+ transports: [],
55
+ clientDataJSON: '',
56
+ attestationObject: '',
57
+ },
58
+ clientExtensionResults: {},
59
+ }
60
+
61
+ export const verificationReq = new VerificationReq({ session, credential })
62
+
63
+ export const verificationRes = new VerificationRes({ principal: Fixtures.principal })
64
+
65
+ export const createCredentialTest = L.succeed(
66
+ CreateCredential,
67
+ CreateCredential.of(() => E.succeed(credential)),
68
+ )
69
+
70
+ export const userServiceTest = L.succeed(
71
+ UserService,
72
+ UserService.of({
73
+ isExistingUser: () => E.succeed(false),
74
+ }),
75
+ )
76
+
77
+ export const rpcClientTest = L.succeed(
78
+ RpcClient,
79
+ RpcClient.of({
80
+ preConnect: () => E.succeed({ warmed: true }),
81
+ isExistingUser: () => E.succeed({ existingUser: true }),
82
+ verifyEmail: () => E.succeed({ verified: true }),
83
+ getRegistrationOptions: () => E.succeed(optionsRes),
84
+ verifyRegistrationCredential: () => E.succeed(verificationRes),
85
+ getAuthenticationOptions: () => E.fail(new BadRequest({ message: 'Not implemeneted' })),
86
+ verifyAuthenticationCredential: () => E.succeed({ principal: Fixtures.principal }),
87
+ }),
88
+ )
89
+
90
+ export const principal = Fixtures.principal
91
+
92
+ export const capabilitiesTest = Fixtures.capabilitiesTest
93
+
94
+ export const storageServiceTest = Fixtures.storageServiceTest