@sentroy-co/client-sdk 2.13.8 → 2.14.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 (59) hide show
  1. package/README.md +24 -5
  2. package/dist/auth/admin/index.d.ts +111 -10
  3. package/dist/auth/admin/index.d.ts.map +1 -1
  4. package/dist/auth/admin/index.js +125 -20
  5. package/dist/auth/admin/index.js.map +1 -1
  6. package/dist/auth/client.d.ts +127 -1
  7. package/dist/auth/client.d.ts.map +1 -1
  8. package/dist/auth/client.js +361 -3
  9. package/dist/auth/client.js.map +1 -1
  10. package/dist/auth/index.d.ts +1 -1
  11. package/dist/auth/index.d.ts.map +1 -1
  12. package/dist/auth/index.js.map +1 -1
  13. package/dist/auth/react/index.d.ts +63 -4
  14. package/dist/auth/react/index.d.ts.map +1 -1
  15. package/dist/auth/react/index.js +180 -1
  16. package/dist/auth/react/index.js.map +1 -1
  17. package/dist/auth/types.d.ts +55 -0
  18. package/dist/auth/types.d.ts.map +1 -1
  19. package/dist/cli/ai.d.ts +35 -0
  20. package/dist/cli/ai.d.ts.map +1 -0
  21. package/dist/cli/ai.js +399 -0
  22. package/dist/cli/ai.js.map +1 -0
  23. package/dist/cli/args.d.ts +62 -0
  24. package/dist/cli/args.d.ts.map +1 -0
  25. package/dist/cli/args.js +199 -0
  26. package/dist/cli/args.js.map +1 -0
  27. package/dist/cli/env.d.ts.map +1 -1
  28. package/dist/cli/env.js +8 -2
  29. package/dist/cli/env.js.map +1 -1
  30. package/dist/cli/format.d.ts +37 -0
  31. package/dist/cli/format.d.ts.map +1 -0
  32. package/dist/cli/format.js +129 -0
  33. package/dist/cli/format.js.map +1 -0
  34. package/dist/cli/index.d.ts +8 -2
  35. package/dist/cli/index.d.ts.map +1 -1
  36. package/dist/cli/index.js +128 -25
  37. package/dist/cli/index.js.map +1 -1
  38. package/dist/cli/mail.d.ts +25 -0
  39. package/dist/cli/mail.d.ts.map +1 -0
  40. package/dist/cli/mail.js +253 -0
  41. package/dist/cli/mail.js.map +1 -0
  42. package/dist/cli/storage.d.ts +28 -0
  43. package/dist/cli/storage.d.ts.map +1 -0
  44. package/dist/cli/storage.js +189 -0
  45. package/dist/cli/storage.js.map +1 -0
  46. package/package.json +8 -2
  47. package/skill/SKILL.md +542 -0
  48. package/src/auth/admin/index.ts +227 -31
  49. package/src/auth/client.ts +438 -4
  50. package/src/auth/index.ts +9 -0
  51. package/src/auth/react/index.tsx +255 -4
  52. package/src/auth/types.ts +66 -0
  53. package/src/cli/ai.ts +440 -0
  54. package/src/cli/args.ts +225 -0
  55. package/src/cli/env.ts +10 -2
  56. package/src/cli/format.ts +147 -0
  57. package/src/cli/index.ts +147 -25
  58. package/src/cli/mail.ts +363 -0
  59. package/src/cli/storage.ts +307 -0
@@ -2,6 +2,7 @@
2
2
 
3
3
  import {
4
4
  createContext,
5
+ useCallback,
5
6
  useContext,
6
7
  useEffect,
7
8
  useMemo,
@@ -9,7 +10,13 @@ import {
9
10
  type ReactNode,
10
11
  } from "react"
11
12
  import { SentroyAuth, type SentroyAuthOptions } from "../client"
12
- import type { SentroyAuthUser } from "../types"
13
+ import type {
14
+ SentroyAuthUser,
15
+ SessionSummary,
16
+ ActivityEntry,
17
+ MfaStatus,
18
+ PasskeySummary,
19
+ } from "../types"
13
20
 
14
21
  /**
15
22
  * Sentroy Auth React integration.
@@ -23,29 +30,46 @@ import type { SentroyAuthUser } from "../types"
23
30
  * Provider içeride tek bir `SentroyAuth` instance tutar (mount/unmount
24
31
  * arasında stable), `onAuthStateChanged` ile React state'i senkron tutar.
25
32
  * `loading` ilk render → restore tamam mı henüz değil ayrımı için.
33
+ *
34
+ * Yeni method'lar (MFA, magic link, passkey, social, /me/*) tüm
35
+ * `auth` instance üzerinden erişilebilir — context'te kısayol olarak
36
+ * en sık kullanılanlar mevcut.
26
37
  */
27
38
 
28
39
  interface AuthContextValue {
40
+ /** Underlying SDK instance — gelişmiş senaryolarda doğrudan kullan. */
29
41
  auth: SentroyAuth
30
42
  user: SentroyAuthUser | null
31
43
  /** True iken provider ilk state'i restore etmiş değil — UI'da
32
44
  * "spinner" göster, "redirect to /login" tetikleme. */
33
45
  loading: boolean
34
- /** Convenience proxies caller `auth.signIn(...)` yerine doğrudan
35
- * `signIn(...)` kullanabilir. */
46
+ // Sık kullanılan kısayollar (proxies)
36
47
  signIn: SentroyAuth["signIn"]
37
48
  signUp: SentroyAuth["signUp"]
38
49
  signOut: SentroyAuth["signOut"]
39
50
  sendPasswordReset: SentroyAuth["sendPasswordReset"]
40
51
  verifyEmail: SentroyAuth["verifyEmail"]
52
+ verifyMfa: SentroyAuth["verifyMfa"]
53
+ sendMagicLink: SentroyAuth["sendMagicLink"]
54
+ consumeMagicLink: SentroyAuth["consumeMagicLink"]
55
+ acceptInvitation: SentroyAuth["acceptInvitation"]
56
+ socialAuthorizeUrl: SentroyAuth["socialAuthorizeUrl"]
57
+ consumeRedirectFragment: SentroyAuth["consumeRedirectFragment"]
41
58
  }
42
59
 
43
60
  const AuthContext = createContext<AuthContextValue | null>(null)
44
61
 
45
62
  export function SentroyAuthProvider({
46
63
  children,
64
+ autoConsumeFragment = true,
47
65
  ...opts
48
- }: SentroyAuthOptions & { children: ReactNode }) {
66
+ }: SentroyAuthOptions & {
67
+ children: ReactNode
68
+ /** Mount'ta `window.location.hash`'ten social-login fragment'ı
69
+ * consume et — default true. RP'nin redirectUri'sinde session
70
+ * otomatik kurulur. */
71
+ autoConsumeFragment?: boolean
72
+ }) {
49
73
  // Single instance — opts deep-compare'a girersek dependency drift'i
50
74
  // restart'a yol açar. Caller `projectSlug` değiştirmemeli runtime'da.
51
75
  const auth = useMemo(
@@ -64,6 +88,17 @@ export function SentroyAuthProvider({
64
88
  return unsubscribe
65
89
  }, [auth])
66
90
 
91
+ // Social login redirect handler — fragment varsa otomatik consume
92
+ useEffect(() => {
93
+ if (!autoConsumeFragment) return
94
+ if (typeof window === "undefined") return
95
+ if (!window.location.hash.includes("access_token=")) return
96
+ auth.consumeRedirectFragment().catch(() => {
97
+ // Fragment varsa ama consume fail ise sessizce yut — caller
98
+ // gerekirse manuel tekrar dener.
99
+ })
100
+ }, [auth, autoConsumeFragment])
101
+
67
102
  const value = useMemo<AuthContextValue>(
68
103
  () => ({
69
104
  auth,
@@ -74,6 +109,12 @@ export function SentroyAuthProvider({
74
109
  signOut: () => auth.signOut(),
75
110
  sendPasswordReset: (e) => auth.sendPasswordReset(e),
76
111
  verifyEmail: (t) => auth.verifyEmail(t),
112
+ verifyMfa: (i) => auth.verifyMfa(i),
113
+ sendMagicLink: (i) => auth.sendMagicLink(i),
114
+ consumeMagicLink: (t) => auth.consumeMagicLink(t),
115
+ acceptInvitation: (i) => auth.acceptInvitation(i),
116
+ socialAuthorizeUrl: (p, o) => auth.socialAuthorizeUrl(p, o),
117
+ consumeRedirectFragment: () => auth.consumeRedirectFragment(),
77
118
  }),
78
119
  [auth, user, loading],
79
120
  )
@@ -98,3 +139,213 @@ export function useAuth(): AuthContextValue {
98
139
  export function useUser(): SentroyAuthUser | null {
99
140
  return useAuth().user
100
141
  }
142
+
143
+ /**
144
+ * Reactive sessions hook — `/me/sessions` çağırır, refresh trigger eden
145
+ * `refresh()` döner. Manual revoke sonrası caller `refresh()` çağırır.
146
+ */
147
+ export function useSessions(): {
148
+ sessions: SessionSummary[] | null
149
+ loading: boolean
150
+ error: Error | null
151
+ refresh: () => Promise<void>
152
+ revoke: (id: string) => Promise<void>
153
+ } {
154
+ const { auth, user } = useAuth()
155
+ const [sessions, setSessions] = useState<SessionSummary[] | null>(null)
156
+ const [loading, setLoading] = useState(false)
157
+ const [error, setError] = useState<Error | null>(null)
158
+
159
+ const refresh = useCallback(async () => {
160
+ if (!user) {
161
+ setSessions(null)
162
+ return
163
+ }
164
+ setLoading(true)
165
+ setError(null)
166
+ try {
167
+ const data = await auth.listSessions()
168
+ setSessions(data)
169
+ } catch (e) {
170
+ setError(e instanceof Error ? e : new Error(String(e)))
171
+ } finally {
172
+ setLoading(false)
173
+ }
174
+ }, [auth, user])
175
+
176
+ useEffect(() => {
177
+ refresh()
178
+ }, [refresh])
179
+
180
+ const revoke = useCallback(
181
+ async (id: string) => {
182
+ await auth.revokeSession(id)
183
+ await refresh()
184
+ },
185
+ [auth, refresh],
186
+ )
187
+
188
+ return { sessions, loading, error, refresh, revoke }
189
+ }
190
+
191
+ /**
192
+ * Reactive activity log hook — `/me/activity`. RP'nin "recent activity"
193
+ * tab'ı için.
194
+ */
195
+ export function useActivity(): {
196
+ activity: ActivityEntry[] | null
197
+ loading: boolean
198
+ error: Error | null
199
+ refresh: () => Promise<void>
200
+ } {
201
+ const { auth, user } = useAuth()
202
+ const [activity, setActivity] = useState<ActivityEntry[] | null>(null)
203
+ const [loading, setLoading] = useState(false)
204
+ const [error, setError] = useState<Error | null>(null)
205
+
206
+ const refresh = useCallback(async () => {
207
+ if (!user) {
208
+ setActivity(null)
209
+ return
210
+ }
211
+ setLoading(true)
212
+ setError(null)
213
+ try {
214
+ const data = await auth.getActivity()
215
+ setActivity(data)
216
+ } catch (e) {
217
+ setError(e instanceof Error ? e : new Error(String(e)))
218
+ } finally {
219
+ setLoading(false)
220
+ }
221
+ }, [auth, user])
222
+
223
+ useEffect(() => {
224
+ refresh()
225
+ }, [refresh])
226
+
227
+ return { activity, loading, error, refresh }
228
+ }
229
+
230
+ /**
231
+ * Reactive MFA status. enroll / verify / disable wrapper'ları, status
232
+ * otomatik yeniden çekilir.
233
+ */
234
+ export function useMfa(): {
235
+ status: MfaStatus | null
236
+ loading: boolean
237
+ error: Error | null
238
+ refresh: () => Promise<void>
239
+ enrollTotp: SentroyAuth["mfa"]["enrollTotp"]
240
+ verifyTotpEnrollment: (code: string) => Promise<void>
241
+ disableTotp: (currentPassword: string) => Promise<void>
242
+ } {
243
+ const { auth, user } = useAuth()
244
+ const [status, setStatus] = useState<MfaStatus | null>(null)
245
+ const [loading, setLoading] = useState(false)
246
+ const [error, setError] = useState<Error | null>(null)
247
+
248
+ const refresh = useCallback(async () => {
249
+ if (!user) {
250
+ setStatus(null)
251
+ return
252
+ }
253
+ setLoading(true)
254
+ setError(null)
255
+ try {
256
+ const data = await auth.mfa.getStatus()
257
+ setStatus(data)
258
+ } catch (e) {
259
+ setError(e instanceof Error ? e : new Error(String(e)))
260
+ } finally {
261
+ setLoading(false)
262
+ }
263
+ }, [auth, user])
264
+
265
+ useEffect(() => {
266
+ refresh()
267
+ }, [refresh])
268
+
269
+ const verifyTotpEnrollment = useCallback(
270
+ async (code: string) => {
271
+ await auth.mfa.verifyTotpEnrollment(code)
272
+ await refresh()
273
+ },
274
+ [auth, refresh],
275
+ )
276
+
277
+ const disableTotp = useCallback(
278
+ async (currentPassword: string) => {
279
+ await auth.mfa.disableTotp(currentPassword)
280
+ await refresh()
281
+ },
282
+ [auth, refresh],
283
+ )
284
+
285
+ return {
286
+ status,
287
+ loading,
288
+ error,
289
+ refresh,
290
+ enrollTotp: () => auth.mfa.enrollTotp(),
291
+ verifyTotpEnrollment,
292
+ disableTotp,
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Reactive passkey list — list/delete/register, mutation sonrası
298
+ * otomatik refresh.
299
+ */
300
+ export function usePasskeys(): {
301
+ passkeys: PasskeySummary[] | null
302
+ loading: boolean
303
+ error: Error | null
304
+ refresh: () => Promise<void>
305
+ register: (deviceName?: string) => Promise<void>
306
+ remove: (id: string) => Promise<void>
307
+ } {
308
+ const { auth, user } = useAuth()
309
+ const [passkeys, setPasskeys] = useState<PasskeySummary[] | null>(null)
310
+ const [loading, setLoading] = useState(false)
311
+ const [error, setError] = useState<Error | null>(null)
312
+
313
+ const refresh = useCallback(async () => {
314
+ if (!user) {
315
+ setPasskeys(null)
316
+ return
317
+ }
318
+ setLoading(true)
319
+ setError(null)
320
+ try {
321
+ const data = await auth.passkey.list()
322
+ setPasskeys(data)
323
+ } catch (e) {
324
+ setError(e instanceof Error ? e : new Error(String(e)))
325
+ } finally {
326
+ setLoading(false)
327
+ }
328
+ }, [auth, user])
329
+
330
+ useEffect(() => {
331
+ refresh()
332
+ }, [refresh])
333
+
334
+ const register = useCallback(
335
+ async (deviceName?: string) => {
336
+ await auth.passkey.register(deviceName)
337
+ await refresh()
338
+ },
339
+ [auth, refresh],
340
+ )
341
+
342
+ const remove = useCallback(
343
+ async (id: string) => {
344
+ await auth.passkey.delete(id)
345
+ await refresh()
346
+ },
347
+ [auth, refresh],
348
+ )
349
+
350
+ return { passkeys, loading, error, refresh, register, remove }
351
+ }
package/src/auth/types.ts CHANGED
@@ -58,3 +58,69 @@ export class SentroyAuthError extends Error {
58
58
  this.status = status
59
59
  }
60
60
  }
61
+
62
+ /**
63
+ * Login response when MFA is enrolled — first step returns mfaRequired+
64
+ * mfaToken; second step `verifyMfa(mfaToken, code)` issues final tokens.
65
+ */
66
+ export interface MfaChallengeResponse {
67
+ mfaRequired: true
68
+ mfaToken: string
69
+ factorType: "totp"
70
+ }
71
+
72
+ /** Discriminated union — Login may resolve to tokens OR MFA challenge. */
73
+ export type LoginOutcome =
74
+ | { kind: "tokens"; data: LoginResponse }
75
+ | { kind: "mfa"; data: MfaChallengeResponse }
76
+
77
+ export interface SessionSummary {
78
+ id: string
79
+ refreshTokenPrefix: string
80
+ userAgent: string | null
81
+ ip: string | null
82
+ expiresAt: string
83
+ createdAt: string
84
+ }
85
+
86
+ export interface ActivityEntry {
87
+ id: string
88
+ action: string
89
+ ipAddress: string | null
90
+ createdAt: string
91
+ details: Record<string, unknown> | null
92
+ }
93
+
94
+ export interface MfaStatus {
95
+ enrolled: boolean
96
+ factorType?: "totp"
97
+ verifiedAt?: string | null
98
+ recoveryCodesRemaining?: number
99
+ }
100
+
101
+ export interface MfaEnrollResponse {
102
+ secret: string
103
+ otpauthUri: string
104
+ }
105
+
106
+ export interface MfaVerifyEnrollmentResponse {
107
+ enrolled: true
108
+ recoveryCodes: string[]
109
+ }
110
+
111
+ export interface PasskeySummary {
112
+ id: string
113
+ credentialIdPrefix: string
114
+ deviceName: string | null
115
+ transports: string[]
116
+ lastUsedAt: string | null
117
+ createdAt: string
118
+ }
119
+
120
+ export type SocialProvider =
121
+ | "google"
122
+ | "github"
123
+ | "facebook"
124
+ | "microsoft"
125
+ | "twitter"
126
+ | "apple"