@passlock/client 0.9.22 → 0.9.24

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 (156) hide show
  1. package/README.md +10 -5
  2. package/README.template.md +130 -0
  3. package/dist/authentication/authenticate.d.ts +16 -16
  4. package/dist/authentication/authenticate.fixture.d.ts +21 -7
  5. package/dist/authentication/authenticate.fixture.js +7 -5
  6. package/dist/authentication/authenticate.fixture.js.map +1 -1
  7. package/dist/authentication/authenticate.js +19 -8
  8. package/dist/authentication/authenticate.js.map +1 -1
  9. package/dist/capabilities/capabilities.d.ts +9 -5
  10. package/dist/capabilities/capabilities.js +11 -2
  11. package/dist/capabilities/capabilities.js.map +1 -1
  12. package/dist/connection/connection.d.ts +11 -7
  13. package/dist/connection/connection.fixture.d.ts +2 -2
  14. package/dist/connection/connection.fixture.js +2 -1
  15. package/dist/connection/connection.fixture.js.map +1 -1
  16. package/dist/connection/connection.js +12 -3
  17. package/dist/connection/connection.js.map +1 -1
  18. package/dist/effect.d.ts +23 -46
  19. package/dist/effect.js +55 -51
  20. package/dist/effect.js.map +1 -1
  21. package/dist/email/email.d.ts +42 -12
  22. package/dist/email/email.fixture.d.ts +19 -5
  23. package/dist/email/email.fixture.js +5 -4
  24. package/dist/email/email.fixture.js.map +1 -1
  25. package/dist/email/email.js +44 -8
  26. package/dist/email/email.js.map +1 -1
  27. package/dist/event/event.d.ts +4 -2
  28. package/dist/event/event.js +4 -1
  29. package/dist/event/event.js.map +1 -1
  30. package/dist/index.d.ts +105 -27
  31. package/dist/index.js +101 -50
  32. package/dist/index.js.map +1 -1
  33. package/dist/logging/eventLogger.d.ts +13 -1
  34. package/dist/logging/eventLogger.js +14 -1
  35. package/dist/logging/eventLogger.js.map +1 -1
  36. package/dist/registration/register.d.ts +19 -22
  37. package/dist/registration/register.fixture.d.ts +20 -6
  38. package/dist/registration/register.fixture.js +14 -7
  39. package/dist/registration/register.fixture.js.map +1 -1
  40. package/dist/registration/register.js +18 -9
  41. package/dist/registration/register.js.map +1 -1
  42. package/dist/rpc/authentication.d.ts +1 -2
  43. package/dist/rpc/authentication.js +2 -1
  44. package/dist/rpc/authentication.js.map +1 -1
  45. package/dist/rpc/client.d.ts +5 -2
  46. package/dist/rpc/client.js +12 -3
  47. package/dist/rpc/client.js.map +1 -1
  48. package/dist/rpc/config.d.ts +0 -1
  49. package/dist/rpc/connection.d.ts +1 -2
  50. package/dist/rpc/connection.js +2 -1
  51. package/dist/rpc/connection.js.map +1 -1
  52. package/dist/rpc/registration.d.ts +1 -2
  53. package/dist/rpc/registration.js +2 -1
  54. package/dist/rpc/registration.js.map +1 -1
  55. package/dist/rpc/social.d.ts +1 -2
  56. package/dist/rpc/social.js +2 -1
  57. package/dist/rpc/social.js.map +1 -1
  58. package/dist/rpc/user.d.ts +1 -2
  59. package/dist/rpc/user.js +2 -1
  60. package/dist/rpc/user.js.map +1 -1
  61. package/dist/social/social.d.ts +17 -24
  62. package/dist/social/social.fixture.d.ts +22 -10
  63. package/dist/social/social.fixture.js +8 -14
  64. package/dist/social/social.fixture.js.map +1 -1
  65. package/dist/social/social.js +15 -11
  66. package/dist/social/social.js.map +1 -1
  67. package/dist/storage/storage.d.ts +41 -13
  68. package/dist/storage/storage.fixture.d.ts +2 -2
  69. package/dist/storage/storage.fixture.js +2 -2
  70. package/dist/storage/storage.fixture.js.map +1 -1
  71. package/dist/storage/storage.js +52 -15
  72. package/dist/storage/storage.js.map +1 -1
  73. package/dist/test/fixtures.d.ts +2 -3
  74. package/dist/test/fixtures.js +20 -5
  75. package/dist/test/fixtures.js.map +1 -1
  76. package/dist/user/user.d.ts +9 -6
  77. package/dist/user/user.fixture.d.ts +2 -2
  78. package/dist/user/user.fixture.js +9 -5
  79. package/dist/user/user.fixture.js.map +1 -1
  80. package/dist/user/user.js +12 -3
  81. package/dist/user/user.js.map +1 -1
  82. package/dist/version.d.ts +1 -2
  83. package/dist/version.js +1 -1
  84. package/dist/version.js.map +1 -1
  85. package/package.json +38 -30
  86. package/src/authentication/authenticate.fixture.ts +10 -7
  87. package/src/authentication/authenticate.test.ts +61 -18
  88. package/src/authentication/authenticate.ts +37 -33
  89. package/src/capabilities/capabilities.ts +11 -9
  90. package/src/connection/connection.fixture.ts +4 -1
  91. package/src/connection/connection.test.ts +4 -3
  92. package/src/connection/connection.ts +10 -8
  93. package/src/effect.ts +129 -134
  94. package/src/email/email.fixture.ts +7 -4
  95. package/src/email/email.test.ts +6 -5
  96. package/src/email/email.ts +27 -17
  97. package/src/event/event.node.test.ts +1 -0
  98. package/src/event/event.test.ts +1 -0
  99. package/src/event/event.ts +2 -1
  100. package/src/index.ts +235 -173
  101. package/src/logging/eventLogger.test.ts +2 -1
  102. package/src/logging/eventLogger.ts +3 -3
  103. package/src/registration/register.fixture.ts +16 -8
  104. package/src/registration/register.test.ts +16 -10
  105. package/src/registration/register.ts +37 -35
  106. package/src/rpc/authentication.ts +43 -0
  107. package/src/rpc/client.ts +174 -0
  108. package/src/rpc/config.ts +18 -0
  109. package/src/rpc/connection.ts +30 -0
  110. package/src/rpc/registration.ts +41 -0
  111. package/src/rpc/social.ts +45 -0
  112. package/src/rpc/user.ts +57 -0
  113. package/src/social/social.fixture.ts +12 -18
  114. package/src/social/social.test.ts +16 -30
  115. package/src/social/social.ts +22 -47
  116. package/src/storage/storage.fixture.ts +4 -4
  117. package/src/storage/storage.test.ts +29 -19
  118. package/src/storage/storage.ts +37 -36
  119. package/src/test/fixtures.ts +23 -6
  120. package/src/user/user.fixture.ts +19 -7
  121. package/src/user/user.test.ts +3 -5
  122. package/src/user/user.ts +16 -10
  123. package/src/version.ts +1 -0
  124. package/dist/authentication/authenticate.d.ts.map +0 -1
  125. package/dist/authentication/authenticate.fixture.d.ts.map +0 -1
  126. package/dist/capabilities/capabilities.d.ts.map +0 -1
  127. package/dist/config.d.ts +0 -18
  128. package/dist/config.d.ts.map +0 -1
  129. package/dist/config.js +0 -20
  130. package/dist/config.js.map +0 -1
  131. package/dist/connection/connection.d.ts.map +0 -1
  132. package/dist/connection/connection.fixture.d.ts.map +0 -1
  133. package/dist/effect.d.ts.map +0 -1
  134. package/dist/email/email.d.ts.map +0 -1
  135. package/dist/email/email.fixture.d.ts.map +0 -1
  136. package/dist/event/event.d.ts.map +0 -1
  137. package/dist/index.d.ts.map +0 -1
  138. package/dist/logging/eventLogger.d.ts.map +0 -1
  139. package/dist/registration/register.d.ts.map +0 -1
  140. package/dist/registration/register.fixture.d.ts.map +0 -1
  141. package/dist/rpc/authentication.d.ts.map +0 -1
  142. package/dist/rpc/client.d.ts.map +0 -1
  143. package/dist/rpc/config.d.ts.map +0 -1
  144. package/dist/rpc/connection.d.ts.map +0 -1
  145. package/dist/rpc/registration.d.ts.map +0 -1
  146. package/dist/rpc/social.d.ts.map +0 -1
  147. package/dist/rpc/user.d.ts.map +0 -1
  148. package/dist/social/social.d.ts.map +0 -1
  149. package/dist/social/social.fixture.d.ts.map +0 -1
  150. package/dist/storage/storage.d.ts.map +0 -1
  151. package/dist/storage/storage.fixture.d.ts.map +0 -1
  152. package/dist/test/fixtures.d.ts.map +0 -1
  153. package/dist/user/user.d.ts.map +0 -1
  154. package/dist/user/user.fixture.d.ts.map +0 -1
  155. package/dist/version.d.ts.map +0 -1
  156. package/src/config.ts +0 -42
@@ -1,9 +1,11 @@
1
- import { Duplicate, InternalBrowserError } from '@passlock/shared/dist/error/error.js'
2
- import { RegistrationClient } from '@passlock/shared/dist/rpc/registration.js'
3
1
  import { Effect as E, Layer as L, Layer, LogLevel, Logger, pipe } from 'effect'
4
2
  import { describe, expect, test, vi } from 'vitest'
5
3
  import { mock } from 'vitest-mock-extended'
4
+
5
+ import { Duplicate, InternalBrowserError } from '@passlock/shared/dist/error/error.js'
6
+
6
7
  import * as Fixture from './register.fixture.js'
8
+ import { RegistrationClient } from '../rpc/registration.js'
7
9
  import { CreateCredential, RegistrationService, RegistrationServiceLive } from './register.js'
8
10
 
9
11
  describe('register should', () => {
@@ -70,7 +72,9 @@ describe('register should', () => {
70
72
  yield* _(service.registerPasskey(Fixture.registrationRequest))
71
73
 
72
74
  const rpcClient = yield* _(RegistrationClient)
73
- expect(rpcClient.verifyRegistrationCredential).toHaveBeenCalledWith(Fixture.rpcVerificationReq)
75
+ expect(rpcClient.verifyRegistrationCredential).toHaveBeenCalledWith(
76
+ Fixture.rpcVerificationReq,
77
+ )
74
78
  })
75
79
 
76
80
  const rpcClientTest = L.effect(
@@ -114,7 +118,9 @@ describe('register should', () => {
114
118
  E.sync(() => {
115
119
  const rpcMock = mock<RegistrationClient['Type']>()
116
120
 
117
- rpcMock.getRegistrationOptions.mockReturnValue(E.fail(new Duplicate({ message: 'User already exists' })))
121
+ rpcMock.getRegistrationOptions.mockReturnValue(
122
+ E.fail(new Duplicate({ message: 'User already exists' })),
123
+ )
118
124
 
119
125
  return rpcMock
120
126
  }),
@@ -147,11 +153,11 @@ describe('register should', () => {
147
153
  const createTest = L.effect(
148
154
  CreateCredential,
149
155
  E.sync(() => {
150
- const createTest = vi.fn()
156
+ const createCredential = vi.fn()
151
157
 
152
- createTest.mockReturnValue(E.fail(new Duplicate({ message: 'boom!' })))
158
+ createCredential.mockReturnValue(E.fail(new Duplicate({ message: 'boom!' })))
153
159
 
154
- return createTest
160
+ return { createCredential }
155
161
  }),
156
162
  )
157
163
 
@@ -185,11 +191,11 @@ describe('register should', () => {
185
191
  const createTest = L.effect(
186
192
  CreateCredential,
187
193
  E.sync(() => {
188
- const createTest = vi.fn()
194
+ const createCredential = vi.fn()
189
195
 
190
- createTest.mockReturnValue(E.fail(new InternalBrowserError({ message: 'boom!' })))
196
+ createCredential.mockReturnValue(E.fail(new InternalBrowserError({ message: 'boom!' })))
191
197
 
192
- return createTest
198
+ return { createCredential }
193
199
  }),
194
200
  )
195
201
 
@@ -2,41 +2,37 @@
2
2
  * User & passkey registration effects
3
3
  */
4
4
  import {
5
- parseCreationOptionsFromJSON,
6
5
  type CredentialCreationOptionsJSON,
6
+ parseCreationOptionsFromJSON,
7
7
  } from '@github/webauthn-json/browser-ponyfill'
8
- import type { NotSupported } from '@passlock/shared/dist/error/error.js'
9
- import { Duplicate, InternalBrowserError } from '@passlock/shared/dist/error/error.js'
10
- import type { OptionsErrors, VerificationErrors } from '@passlock/shared/dist/rpc/registration.js'
11
- import { OptionsReq, RegistrationClient, VerificationReq } from '@passlock/shared/dist/rpc/registration.js'
12
- import { VerifyEmail } from '@passlock/shared/dist/schema/email.js'
13
- import type {
14
- RegistrationCredential,
15
- UserVerification,
16
- } from '@passlock/shared/dist/schema/passkey.js'
17
- import { Principal } from '@passlock/shared/dist/schema/principal.js'
18
8
  import { Context, Effect as E, Layer, flow, pipe } from 'effect'
9
+
10
+ import type { Duplicate, NotSupported } from '@passlock/shared/dist/error/error.js'
11
+ import { InternalBrowserError } from '@passlock/shared/dist/error/error.js'
12
+ import type { OptionsErrors, VerificationErrors } from '@passlock/shared/dist/rpc/registration.js'
13
+ import { OptionsReq, VerificationReq } from '@passlock/shared/dist/rpc/registration.js'
14
+ import type { RegistrationCredential } from '@passlock/shared/dist/schema/passkey.js'
15
+ import type { Principal } from '@passlock/shared/dist/schema/principal.js'
16
+
19
17
  import { Capabilities } from '../capabilities/capabilities.js'
18
+ import { RegistrationClient } from '../rpc/registration.js'
20
19
  import { StorageService } from '../storage/storage.js'
21
- import { UserService } from '../user/user.js'
20
+ import type { UserService } from '../user/user.js'
22
21
 
23
22
  /* Requests */
24
23
 
25
- export type RegistrationRequest = {
26
- email: string
27
- givenName: string
28
- familyName: string
29
- userVerification?: UserVerification
30
- verifyEmail?: VerifyEmail
31
- }
24
+ export type RegistrationRequest = OptionsReq
32
25
 
33
26
  /* Dependencies */
34
27
 
35
- export type CreateCredential = (
36
- request: CredentialCreationOptions,
37
- ) => E.Effect<RegistrationCredential, InternalBrowserError | Duplicate>
38
-
39
- export const CreateCredential = Context.GenericTag<CreateCredential>('@services/Create')
28
+ export class CreateCredential extends Context.Tag('@services/CreateCredential')<
29
+ CreateCredential,
30
+ {
31
+ createCredential: (
32
+ request: CredentialCreationOptions,
33
+ ) => E.Effect<RegistrationCredential, InternalBrowserError | Duplicate>
34
+ }
35
+ >() {}
40
36
 
41
37
  /* Errors */
42
38
 
@@ -44,13 +40,12 @@ export type RegistrationErrors = NotSupported | OptionsErrors | VerificationErro
44
40
 
45
41
  /* Service */
46
42
 
47
- export type RegistrationService = {
48
- registerPasskey: (request: RegistrationRequest) => E.Effect<Principal, RegistrationErrors>
49
- }
50
-
51
- export const RegistrationService = Context.GenericTag<RegistrationService>(
52
- '@services/RegistrationService',
53
- )
43
+ export class RegistrationService extends Context.Tag('@services/RegistrationService')<
44
+ RegistrationService,
45
+ {
46
+ registerPasskey: (request: RegistrationRequest) => E.Effect<Principal, RegistrationErrors>
47
+ }
48
+ >() {}
54
49
 
55
50
  /* Utilities */
56
51
 
@@ -94,7 +89,12 @@ const verifyCredential = (request: VerificationReq) => {
94
89
 
95
90
  /* Effects */
96
91
 
97
- type Dependencies = Capabilities | CreateCredential | StorageService | UserService | RegistrationClient
92
+ type Dependencies =
93
+ | Capabilities
94
+ | CreateCredential
95
+ | StorageService
96
+ | UserService
97
+ | RegistrationClient
98
98
 
99
99
  export const registerPasskey = (
100
100
  request: RegistrationRequest,
@@ -105,10 +105,10 @@ export const registerPasskey = (
105
105
  yield* _(capabilities.passkeySupport)
106
106
 
107
107
  yield* _(E.logInfo('Fetching registration options from Passlock'))
108
- const { options, session } = yield* _(fetchOptions(new OptionsReq(request)))
108
+ const { options, session } = yield* fetchOptions(new OptionsReq(request))
109
109
 
110
110
  yield* _(E.logInfo('Building new credential'))
111
- const createCredential = yield* _(CreateCredential)
111
+ const { createCredential } = yield* _(CreateCredential)
112
112
  const credential = yield* _(createCredential(options))
113
113
 
114
114
  yield* _(E.logInfo('Storing credential public key in Passlock'))
@@ -145,7 +145,9 @@ export const RegistrationServiceLive = Layer.effect(
145
145
  RegistrationService,
146
146
  E.gen(function* (_) {
147
147
  const context = yield* _(
148
- E.context<CreateCredential | RegistrationClient | Capabilities | StorageService | UserService>(),
148
+ E.context<
149
+ CreateCredential | RegistrationClient | Capabilities | StorageService | UserService
150
+ >(),
149
151
  )
150
152
 
151
153
  return RegistrationService.of({
@@ -0,0 +1,43 @@
1
+ import { Context, Effect as E, Layer } from 'effect'
2
+
3
+ import {
4
+ type AuthenticationService,
5
+ OPTIONS_ENDPOINT,
6
+ OptionsErrors,
7
+ OptionsReq,
8
+ OptionsRes,
9
+ VERIFY_ENDPOINT,
10
+ VerificationErrors,
11
+ VerificationReq,
12
+ VerificationRes,
13
+ } from '@passlock/shared/dist/rpc/authentication.js'
14
+
15
+ import { Dispatcher, makePostRequest } from './client.js'
16
+
17
+ /* Client */
18
+
19
+ export class AuthenticationClient extends Context.Tag('@passkey/auth/client')<
20
+ AuthenticationClient,
21
+ AuthenticationService
22
+ >() {}
23
+
24
+ export const AuthenticationClientLive = Layer.effect(
25
+ AuthenticationClient,
26
+ E.gen(function* (_) {
27
+ const dispatcher = yield* _(Dispatcher)
28
+
29
+ const optionsResolver = makePostRequest(OptionsReq, OptionsRes, OptionsErrors, dispatcher)
30
+
31
+ const verifyResolver = makePostRequest(
32
+ VerificationReq,
33
+ VerificationRes,
34
+ VerificationErrors,
35
+ dispatcher,
36
+ )
37
+
38
+ return {
39
+ getAuthenticationOptions: req => optionsResolver(OPTIONS_ENDPOINT, req),
40
+ verifyAuthenticationCredential: req => verifyResolver(VERIFY_ENDPOINT, req),
41
+ }
42
+ }),
43
+ )
@@ -0,0 +1,174 @@
1
+ import * as S from '@effect/schema/Schema'
2
+ import { Context, Effect as E, Layer, pipe } from 'effect'
3
+
4
+ import { NetworkError } from '@passlock/shared/dist/error/error.js'
5
+
6
+ import { PASSLOCK_CLIENT_VERSION } from '../version.js'
7
+ import { RetrySchedule, RpcConfig } from './config.js'
8
+
9
+ export type DispatcherResponse = {
10
+ status: number
11
+ body: object
12
+ }
13
+
14
+ /** To send the JSON to the backend */
15
+ export class Dispatcher extends Context.Tag('@rpc/Dispatcher')<
16
+ Dispatcher,
17
+ {
18
+ get: (path: string) => E.Effect<DispatcherResponse, NetworkError>
19
+ post: (path: string, body: string) => E.Effect<DispatcherResponse, NetworkError>
20
+ }
21
+ >() {}
22
+
23
+ /** Fires off client requests using fetch */
24
+ /** TODO: Write tests */
25
+ /** TODO: Evaluate platform/http client (if now stable) */
26
+ export const DispatcherLive = Layer.effect(
27
+ Dispatcher,
28
+ E.gen(function* (_) {
29
+ const { schedule } = yield* _(RetrySchedule)
30
+ const { tenancyId, clientId, endpoint: maybeEndpoint } = yield* _(RpcConfig)
31
+
32
+ const parseJson = (res: Response, url: string) =>
33
+ E.tryPromise({
34
+ try: () => res.json() as Promise<unknown>,
35
+ catch: e =>
36
+ new NetworkError({
37
+ message: 'Unable to extract json response from ' + url,
38
+ detail: String(e),
39
+ }),
40
+ })
41
+
42
+ // 400 errors are reflected in the RPC response error channel
43
+ // so in network terms they're still "ok"
44
+ const assertNo500s = (res: Response, url: string) => {
45
+ if (res.status >= 500) {
46
+ return E.fail(
47
+ new NetworkError({
48
+ message: 'Received 500 response code from ' + url,
49
+ }),
50
+ )
51
+ } else return E.void
52
+ }
53
+
54
+ const parseJsonObject = (json: unknown) => {
55
+ return typeof json === 'object' && json !== null
56
+ ? E.succeed(json)
57
+ : E.fail(
58
+ new NetworkError({
59
+ message: `Expected JSON object to be returned from RPC endpoint, actual ${typeof json}`,
60
+ }),
61
+ )
62
+ }
63
+
64
+ const buildUrl = (_path: string) => {
65
+ const endpoint = maybeEndpoint || 'https://api.passlock.dev'
66
+ // drop leading /
67
+ const path = _path.replace(/^\//, '')
68
+ return `${endpoint}/${tenancyId}/${path}`
69
+ }
70
+
71
+ return {
72
+ get: (path: string) => {
73
+ const effect = E.gen(function* (_) {
74
+ const headers = {
75
+ 'Accept': 'application/json',
76
+ 'X-CLIENT-ID': clientId,
77
+ 'X-PASSLOCK-CLIENT-VERSION': PASSLOCK_CLIENT_VERSION,
78
+ }
79
+
80
+ const url = buildUrl(path)
81
+
82
+ const res = yield* _(
83
+ E.tryPromise({
84
+ try: () => fetch(url, { method: 'GET', headers }),
85
+ catch: e =>
86
+ new NetworkError({ message: 'Unable to fetch from ' + url, detail: String(e) }),
87
+ }),
88
+ )
89
+
90
+ const json = yield* _(parseJson(res, url))
91
+ yield* _(assertNo500s(res, url))
92
+ const jsonObject = yield* _(parseJsonObject(json))
93
+
94
+ return { status: res.status, body: jsonObject }
95
+ })
96
+
97
+ return E.retry(effect, { schedule })
98
+ },
99
+
100
+ post: (_path: string, body: string) => {
101
+ const effect = E.gen(function* (_) {
102
+ const headers = {
103
+ 'Content-Type': 'application/json',
104
+ 'Accept': 'application/json',
105
+ 'X-CLIENT-ID': clientId,
106
+ 'X-PASSLOCK-CLIENT-VERSION': PASSLOCK_CLIENT_VERSION,
107
+ }
108
+
109
+ // drop leading /
110
+ const url = buildUrl(_path)
111
+
112
+ const res = yield* _(
113
+ E.tryPromise({
114
+ try: () => fetch(url, { method: 'POST', headers, body }),
115
+ catch: e =>
116
+ new NetworkError({ message: 'Unable to fetch from ' + url, detail: String(e) }),
117
+ }),
118
+ )
119
+
120
+ const json = yield* _(parseJson(res, url))
121
+ yield* _(assertNo500s(res, url))
122
+ const jsonObject = yield* _(parseJsonObject(json))
123
+
124
+ return { status: res.status, body: jsonObject }
125
+ })
126
+
127
+ return E.retry(effect, { schedule })
128
+ },
129
+ }
130
+ }),
131
+ )
132
+
133
+ export const makeGetRequest =
134
+ <Res, ResEnc, Err, ErrEnc>(
135
+ responseSchema: S.Schema<Res, ResEnc, never>,
136
+ errorSchema: S.Schema<Err, ErrEnc, never>,
137
+ dispatcher: Dispatcher['Type'],
138
+ ) =>
139
+ (path: string) =>
140
+ pipe(
141
+ dispatcher.get(path),
142
+ E.flatMap(res => {
143
+ if (res.status === 200) return S.decodeUnknown(responseSchema)(res.body)
144
+ return pipe(
145
+ S.decodeUnknown(errorSchema)(res.body),
146
+ E.flatMap(err => E.fail(err)),
147
+ )
148
+ }),
149
+ E.catchTag('ParseError', e => E.die(e)),
150
+ E.catchTag('NetworkError', e => E.die(e)),
151
+ )
152
+
153
+ export const makePostRequest =
154
+ <Req, ReqEnc, Res, ResEnc, Err, ErrEnc>(
155
+ requestSchema: S.Schema<Req, ReqEnc, never>,
156
+ responseSchema: S.Schema<Res, ResEnc, never>,
157
+ errorSchema: S.Schema<Err, ErrEnc, never>,
158
+ dispatcher: Dispatcher['Type'],
159
+ ) =>
160
+ (path: string, request: Req) => {
161
+ return pipe(
162
+ S.encode(requestSchema)(request),
163
+ E.flatMap(request => dispatcher.post(path, JSON.stringify(request))),
164
+ E.flatMap(res => {
165
+ if (res.status === 200) return S.decodeUnknown(responseSchema)(res.body)
166
+ return pipe(
167
+ S.decodeUnknown(errorSchema)(res.body),
168
+ E.flatMap(err => E.fail(err)),
169
+ )
170
+ }),
171
+ E.catchTag('ParseError', e => E.die(e)),
172
+ E.catchTag('NetworkError', e => E.die(e)),
173
+ )
174
+ }
@@ -0,0 +1,18 @@
1
+ import type { Schedule } from 'effect'
2
+ import { Context } from 'effect'
3
+
4
+ export class RpcConfig extends Context.Tag('@rpc/RpcConfig')<
5
+ RpcConfig,
6
+ {
7
+ endpoint?: string
8
+ tenancyId: string
9
+ clientId: string
10
+ }
11
+ >() {}
12
+
13
+ export class RetrySchedule extends Context.Tag('@rpc/RetrySchedule')<
14
+ RetrySchedule,
15
+ {
16
+ schedule: Schedule.Schedule<unknown>
17
+ }
18
+ >() {}
@@ -0,0 +1,30 @@
1
+ import * as S from '@effect/schema/Schema'
2
+ import { Context, Effect as E, Layer } from 'effect'
3
+
4
+ import {
5
+ CONNECT_ENDPOINT,
6
+ ConnectRes,
7
+ type ConnectionService,
8
+ } from '@passlock/shared/dist/rpc/connection.js'
9
+
10
+ import { Dispatcher, makeGetRequest } from './client.js'
11
+
12
+ /* Client */
13
+
14
+ export class ConnectionClient extends Context.Tag('@connection/client')<
15
+ ConnectionClient,
16
+ ConnectionService
17
+ >() {}
18
+
19
+ export const ConnectionClientLive = Layer.effect(
20
+ ConnectionClient,
21
+ E.gen(function* (_) {
22
+ const dispatcher = yield* _(Dispatcher)
23
+
24
+ const preConnectResolver = makeGetRequest(ConnectRes, S.Never, dispatcher)
25
+
26
+ return {
27
+ preConnect: () => preConnectResolver(CONNECT_ENDPOINT),
28
+ }
29
+ }),
30
+ )
@@ -0,0 +1,41 @@
1
+ import { Context, Effect as E, Layer } from 'effect'
2
+
3
+ import {
4
+ OptionsErrors,
5
+ OptionsReq,
6
+ OptionsRes,
7
+ type RegistrationService,
8
+ VerificationErrors,
9
+ VerificationReq,
10
+ VerificationRes,
11
+ } from '@passlock/shared/dist/rpc/registration.js'
12
+
13
+ import { Dispatcher, makePostRequest } from './client.js'
14
+
15
+ /* Client */
16
+
17
+ export class RegistrationClient extends Context.Tag('@passkey/register/client')<
18
+ RegistrationClient,
19
+ RegistrationService
20
+ >() {}
21
+
22
+ export const RegistrationClientLive = Layer.effect(
23
+ RegistrationClient,
24
+ E.gen(function* (_) {
25
+ const dispatcher = yield* _(Dispatcher)
26
+
27
+ const optionsResolver = makePostRequest(OptionsReq, OptionsRes, OptionsErrors, dispatcher)
28
+
29
+ const verifyResolver = makePostRequest(
30
+ VerificationReq,
31
+ VerificationRes,
32
+ VerificationErrors,
33
+ dispatcher,
34
+ )
35
+
36
+ return {
37
+ getRegistrationOptions: req => optionsResolver('/passkey/register/options', req),
38
+ verifyRegistrationCredential: req => verifyResolver('/passkey/register/verify', req),
39
+ }
40
+ }),
41
+ )
@@ -0,0 +1,45 @@
1
+ import { Context, Effect as E, Layer } from 'effect'
2
+
3
+ import {
4
+ AuthOidcErrors,
5
+ AuthOidcReq,
6
+ PrincipalRes,
7
+ RegisterOidcErrors,
8
+ RegisterOidcReq,
9
+ type SocialService,
10
+ } from '@passlock/shared/dist/rpc/social.js'
11
+
12
+ import { Dispatcher, makePostRequest } from './client.js'
13
+
14
+ /* Client */
15
+
16
+ export const OIDC_REGISTER_ENDPOINT = '/social/oidc/register'
17
+ export const OIDC_AUTH_ENDPOINT = '/social/oidc/auth'
18
+
19
+ export class SocialClient extends Context.Tag('@social/client')<SocialClient, SocialService>() {}
20
+
21
+ export const SocialClientLive = Layer.effect(
22
+ SocialClient,
23
+ E.gen(function* (_) {
24
+ const dispatcher = yield* _(Dispatcher)
25
+
26
+ const registerResolver = makePostRequest(
27
+ RegisterOidcReq,
28
+ PrincipalRes,
29
+ RegisterOidcErrors,
30
+ dispatcher,
31
+ )
32
+
33
+ const authenticateResolver = makePostRequest(
34
+ AuthOidcReq,
35
+ PrincipalRes,
36
+ AuthOidcErrors,
37
+ dispatcher,
38
+ )
39
+
40
+ return {
41
+ registerOidc: req => registerResolver(OIDC_REGISTER_ENDPOINT, req),
42
+ authenticateOidc: req => authenticateResolver(OIDC_AUTH_ENDPOINT, req),
43
+ }
44
+ }),
45
+ )
@@ -0,0 +1,57 @@
1
+ import * as S from '@effect/schema/Schema'
2
+ import { Context, Effect as E, Layer } from 'effect'
3
+
4
+ import {
5
+ IsExistingUserReq,
6
+ IsExistingUserRes,
7
+ RESEND_EMAIL_ENDPOINT,
8
+ ResendEmailErrors,
9
+ ResendEmailReq,
10
+ ResendEmailRes,
11
+ USER_STATUS_ENDPOINT,
12
+ type UserService,
13
+ VERIFY_EMAIL_ENDPOINT,
14
+ VerifyEmailErrors,
15
+ VerifyEmailReq,
16
+ VerifyEmailRes,
17
+ } from '@passlock/shared/dist/rpc/user.js'
18
+
19
+ import { Dispatcher, makePostRequest } from './client.js'
20
+
21
+ /* Client */
22
+
23
+ export class UserClient extends Context.Tag('@user/client')<UserClient, UserService>() {}
24
+
25
+ export const UserClientLive = Layer.effect(
26
+ UserClient,
27
+ E.gen(function* (_) {
28
+ const dispatcher = yield* _(Dispatcher)
29
+
30
+ const isExistingUserResolver = makePostRequest(
31
+ IsExistingUserReq,
32
+ IsExistingUserRes,
33
+ S.Never,
34
+ dispatcher,
35
+ )
36
+
37
+ const verifyEmailResolver = makePostRequest(
38
+ VerifyEmailReq,
39
+ VerifyEmailRes,
40
+ VerifyEmailErrors,
41
+ dispatcher,
42
+ )
43
+
44
+ const resendEmailResolver = makePostRequest(
45
+ ResendEmailReq,
46
+ ResendEmailRes,
47
+ ResendEmailErrors,
48
+ dispatcher,
49
+ )
50
+
51
+ return {
52
+ isExistingUser: req => isExistingUserResolver(USER_STATUS_ENDPOINT, req),
53
+ verifyEmail: req => verifyEmailResolver(VERIFY_EMAIL_ENDPOINT, req),
54
+ resendVerificationEmail: req => resendEmailResolver(RESEND_EMAIL_ENDPOINT, req),
55
+ }
56
+ }),
57
+ )
@@ -1,8 +1,10 @@
1
+ import { Effect as E, Layer as L, Option as O } from 'effect'
2
+
1
3
  import * as Shared from '@passlock/shared/dist/rpc/social.js'
2
- import { SocialClient } from '@passlock/shared/dist/rpc/social.js'
3
- import { Effect as E, Layer as L } from 'effect'
4
+
4
5
  import * as Fixtures from '../test/fixtures.js'
5
- import type { AuthenticateOidcReq, RegisterOidcReq } from './social.js'
6
+ import { SocialClient } from '../rpc/social.js'
7
+ import type { AuthenticateOidcReq } from './social.js'
6
8
 
7
9
  export const session = 'session'
8
10
  export const token = 'token'
@@ -10,30 +12,22 @@ export const code = 'code'
10
12
  export const authType = 'passkey'
11
13
  export const expireAt = Date.now() + 10000
12
14
 
13
- export const registerOidcReq: RegisterOidcReq = {
15
+ export const registerOidcReq = new Shared.RegisterOidcReq({
14
16
  provider: 'google',
15
17
  idToken: 'google-token',
16
18
  nonce: 'nonce',
17
- givenName: 'john',
18
- familyName: 'doe'
19
- }
19
+ givenName: O.some('john'),
20
+ familyName: O.some('doe'),
21
+ })
20
22
 
21
- export const authOidcReq: AuthenticateOidcReq = {
23
+ export const authOidcReq: AuthenticateOidcReq = new Shared.AuthOidcReq({
22
24
  provider: 'google',
23
25
  idToken: 'google-token',
24
- nonce: 'nonce'
25
- }
26
-
27
- export const rpcRegisterReq = new Shared.RegisterOidcReq({
28
- ...registerOidcReq,
29
- ...(registerOidcReq.givenName ? { givenName: registerOidcReq.givenName } : {}),
30
- ...(registerOidcReq.familyName ? { familyName: registerOidcReq.familyName } : {})
26
+ nonce: 'nonce',
31
27
  })
32
28
 
33
29
  export const rpcRegisterRes = new Shared.PrincipalRes({ principal: Fixtures.principal })
34
30
 
35
- export const rpcAuthenticateReq = new Shared.AuthOidcReq({ ...authOidcReq })
36
-
37
31
  export const rpcAuthenticateRes = new Shared.PrincipalRes({ principal: Fixtures.principal })
38
32
 
39
33
  export const rpcClientTest = L.succeed(
@@ -41,7 +35,7 @@ export const rpcClientTest = L.succeed(
41
35
  SocialClient.of({
42
36
  registerOidc: () => E.fail(Fixtures.notImplemented),
43
37
  authenticateOidc: () => E.fail(Fixtures.notImplemented),
44
- })
38
+ }),
45
39
  )
46
40
 
47
41
  export const principal = Fixtures.principal