@taruvi/sdk 1.0.2 → 1.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taruvi/sdk",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Taruvi SDK",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
package/src/client.ts CHANGED
@@ -33,8 +33,11 @@ export class Client {
33
33
  }
34
34
 
35
35
  /**
36
- * Extracts access_token and refresh_token from URL hash and stores them in localStorage.
37
- * This handles OAuth callback URLs like: #access_token=xxx&refresh_token=xxx
36
+ * Extracts authentication tokens from URL hash and stores them using TokenClient.
37
+ * This handles Web UI Flow callback URLs like:
38
+ * #session_token=xxx&access_token=yyy&refresh_token=zzz&expires_in=172800&token_type=Bearer
39
+ *
40
+ * After successful extraction, the URL hash is cleared to prevent token exposure.
38
41
  */
39
42
  private extractTokensFromUrl(): void {
40
43
  if (typeof window === "undefined" || typeof localStorage === "undefined") {
@@ -47,15 +50,38 @@ export class Client {
47
50
  }
48
51
 
49
52
  const params = new URLSearchParams(hash.substring(1))
53
+ const sessionToken = params.get("session_token")
50
54
  const accessToken = params.get("access_token")
51
55
  const refreshToken = params.get("refresh_token")
56
+ const expiresIn = params.get("expires_in")
57
+ const tokenType = params.get("token_type")
52
58
 
53
- if (accessToken) {
54
- localStorage.setItem("jwt", accessToken)
59
+ // Only proceed if we have the required tokens
60
+ if (!accessToken || !refreshToken) {
61
+ return
62
+ }
63
+
64
+ // Store tokens using TokenClient
65
+ const tokens: any = {
66
+ accessToken,
67
+ refreshToken,
68
+ tokenType: tokenType || "Bearer"
69
+ }
70
+
71
+ if (sessionToken) {
72
+ tokens.sessionToken = sessionToken
73
+ }
74
+
75
+ if (expiresIn) {
76
+ tokens.expiresIn = parseInt(expiresIn, 10)
55
77
  }
56
78
 
57
- if (refreshToken) {
58
- localStorage.setItem("refresh_token", refreshToken)
79
+ this._tokenClient.setTokens(tokens)
80
+
81
+ // Clear hash from URL without reloading page
82
+ if (window.history && window.history.replaceState) {
83
+ const urlWithoutHash = window.location.pathname + window.location.search
84
+ window.history.replaceState(null, "", urlWithoutHash)
59
85
  }
60
86
  }
61
87
 
package/src/index.ts CHANGED
@@ -12,9 +12,10 @@ export { Secrets } from "./lib/Secrets/SecretsClient.js"
12
12
 
13
13
  // Export types
14
14
  export type { TaruviConfig, StorageFilters, DatabaseFilters } from "./types.js"
15
+ export type { AuthTokens } from "./lib-internal/token/TokenClient.js"
15
16
  export type { UserCreateRequest, UserCreateResponse as UserResponse, UserDataResponse } from "./lib/user/types.js"
16
17
  export type { FunctionRequest, FunctionResponse, FunctionInvocation } from "./lib/Function/types.js"
17
18
  export type { DatabaseRequest, DatabaseResponse } from "./lib/Database/types.js"
18
19
  export type { StorageRequest, StorageUpdateRequest, StorageResponse } from "./lib/Storage/types.js"
19
- export type { SettingsRequest, SettingsResponse, SettingsMetadataResponse } from "./lib/Settings/types.js"
20
+ export type { SettingsResponse } from "./lib/Settings/types.js"
20
21
  export type { SecretRequest, SecretResponse } from "./lib/Secrets/types.js"
@@ -1,5 +1,5 @@
1
1
  import type { Client } from "../../client.js";
2
- import { DatabaseRoutes } from "../../lib-internal/routes/DatabaseRoutes.js";
2
+ import { DatabaseRoutes, type DatabaseRouteKey } from "../../lib-internal/routes/DatabaseRoutes.js";
3
3
  import { HttpMethod } from "../../lib-internal/http/types.js";
4
4
  import type { TaruviConfig, DatabaseFilters } from "../../types.js";
5
5
  import type { UrlParams } from "./types.js";
@@ -44,12 +44,21 @@ export class Database {
44
44
  }
45
45
 
46
46
  private buildRoute(): string {
47
- return DatabaseRoutes.baseUrl(this.config.appSlug) + Object.keys(this.urlParams).reduce((acc, key) => {
48
- if (this.urlParams[key] && DatabaseRoutes[key]) {
49
- acc += DatabaseRoutes[key](this.urlParams[key])
50
- }
51
- return acc
52
- }, "") + "/" + buildQueryString(this.filters)
47
+ return (
48
+ DatabaseRoutes.baseUrl(this.config.appSlug) +
49
+ (Object.keys(this.urlParams) as DatabaseRouteKey[]).reduce((acc, key) => {
50
+ const value = this.urlParams[key]
51
+ const routeBuilder = DatabaseRoutes[key]
52
+
53
+ if (value && routeBuilder) {
54
+ acc += routeBuilder(value)
55
+ }
56
+
57
+ return acc
58
+ }, "") +
59
+ "/" +
60
+ buildQueryString(this.filters)
61
+ )
53
62
  }
54
63
 
55
64
  async execute() {
@@ -1,6 +1,6 @@
1
1
  import type { Client } from "../../client.js";
2
2
  import type { BucketFileUpload, BucketUrlParams } from "./types.js";
3
- import { StorageRoutes } from "../../lib-internal/routes/StorageRoutes.js";
3
+ import { StorageRoutes, type StorageRouteKey } from "../../lib-internal/routes/StorageRoutes.js";
4
4
  import type { TaruviConfig, StorageFilters } from "../../types.js";
5
5
  import { HttpMethod } from "../../lib-internal/http/types.js";
6
6
  import { buildQueryString } from "../../utils/utils.js";
@@ -55,17 +55,32 @@ export class Storage {
55
55
  }, HttpMethod.POST, formData)
56
56
  }
57
57
 
58
-
59
58
  private buildRoute(): string {
60
- return StorageRoutes.baseUrl(this.config.appSlug, this.urlParams.bucket) + Object.keys(this.urlParams).reduce((acc, key) => {
61
- if (this.urlParams[key] && StorageRoutes[key]) {
62
- acc += StorageRoutes[key](this.urlParams[key])
63
- }
64
- return acc
65
- }, "") + "/" + buildQueryString(this.filters as Record<string, unknown>)
59
+ return (
60
+ StorageRoutes.baseUrl(this.config.appSlug, this.urlParams.bucket!) +
61
+ (Object.keys(this.urlParams) as StorageRouteKey[]).reduce((acc, key) => {
62
+ const value = this.urlParams[key as keyof BucketUrlParams]
63
+
64
+ if (!value) return acc
65
+
66
+ if (key === 'path' && typeof value === 'string') {
67
+ acc += StorageRoutes.path(value)
68
+ }
69
+
70
+ if ((key === 'upload' || key === 'delete') && typeof StorageRoutes[key] === 'function') {
71
+ acc += (StorageRoutes[key] as () => string)()
72
+ }
73
+
74
+ return acc
75
+ }, '') +
76
+ '/' +
77
+ buildQueryString(this.filters as Record<string, unknown>)
78
+ )
66
79
  }
67
80
 
68
- async execute(): Promise<T> {
81
+
82
+
83
+ async execute(): Promise<any> {
69
84
  const url = this.buildRoute()
70
85
  const operation = this.operation || HttpMethod.GET
71
86
 
@@ -1,8 +1,15 @@
1
1
  import type { Client } from "../../client.js";
2
- import { User } from "../user/UserClient.js";
3
- import { Settings } from "../Settings/SettingsClient.js";
4
2
 
5
- // handles user auth not dev auth
3
+ /**
4
+ * Auth Client - Handles user authentication using Web UI Flow
5
+ * Implements cross-domain authentication with redirect-based token delivery
6
+ *
7
+ * Flow:
8
+ * 1. User calls login() → Redirects to backend /accounts/login/
9
+ * 2. User authenticates on backend
10
+ * 3. Backend redirects back with tokens in URL hash
11
+ * 4. Client extracts and stores tokens automatically
12
+ */
6
13
  export class Auth {
7
14
  private client: Client
8
15
 
@@ -10,55 +17,201 @@ export class Auth {
10
17
  this.client = client
11
18
  }
12
19
 
13
- async authenticateUser() {
14
- // const myHeaders = new Headers();
15
- // myHeaders.append("sec-ch-ua-platform", "\"Linux\"");
16
- // myHeaders.append("Referer", "http://localhost:5173/");
17
- // myHeaders.append("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36");
18
- // myHeaders.append("sec-ch-ua", "\"Chromium\";v=\"142\", \"Google Chrome\";v=\"142\", \"Not_A Brand\";v=\"99\"");
19
- // myHeaders.append("Content-Type", "application/json");
20
- // myHeaders.append("sec-ch-ua-mobile", "?0");
21
-
22
- // const raw = JSON.stringify({
23
- // "password": "admin123",
24
- // "email": "admin@example.com"
25
- // });
26
-
27
- // const requestOptions: RequestInit = {
28
- // method: "POST",
29
- // headers: myHeaders,
30
- // body: raw,
31
- // redirect: "follow"
32
- // };
33
-
34
- // await fetch("https://test-api.taruvi.cloud/api/v1/auth/login", requestOptions)
35
- // .then((response) => response.json())
36
- // .then((result) => {
37
- // localStorage.setItem("jwt", result.meta.accesstoken)
38
- // })
39
- // .catch((error) => console.error(error));
20
+ /**
21
+ * Redirect to login page (Web UI Flow)
22
+ * @param callbackUrl - URL to redirect to after successful login (defaults to current page)
23
+ */
24
+ login(callbackUrl?: string): void {
25
+ if (typeof window === "undefined") {
26
+ console.error("login() can only be called in browser environment")
27
+ return
28
+ }
29
+
30
+ const config = this.client.getConfig()
31
+ const callback = callbackUrl || window.location.origin + window.location.pathname
32
+
33
+ // Redirect to /accounts/login/ with redirect_to parameter
34
+ const loginUrl = `${config.baseUrl}/accounts/login/?redirect_to=${encodeURIComponent(callback)}`
35
+
36
+ // Optional: Store state before redirecting
37
+ if (typeof sessionStorage !== "undefined") {
38
+ sessionStorage.setItem("auth_state", JSON.stringify({
39
+ returnTo: window.location.pathname,
40
+ timestamp: Date.now()
41
+ }))
42
+ }
43
+
44
+ window.location.href = loginUrl
45
+ }
46
+
47
+ /**
48
+ * Redirect to signup page (Web UI Flow)
49
+ * @param callbackUrl - URL to redirect to after successful signup (defaults to current page)
50
+ */
51
+ signup(callbackUrl?: string): void {
52
+ if (typeof window === "undefined") {
53
+ console.error("signup() can only be called in browser environment")
54
+ return
55
+ }
56
+
57
+ const config = this.client.getConfig()
58
+ const callback = callbackUrl || window.location.origin + window.location.pathname
59
+
60
+ // Redirect to /accounts/signup/ with redirect_to parameter
61
+ const signupUrl = `${config.baseUrl}/accounts/signup/?redirect_to=${encodeURIComponent(callback)}`
62
+
63
+ // Optional: Store state before redirecting
64
+ if (typeof sessionStorage !== "undefined") {
65
+ sessionStorage.setItem("auth_state", JSON.stringify({
66
+ returnTo: window.location.pathname,
67
+ timestamp: Date.now()
68
+ }))
69
+ }
70
+
71
+ window.location.href = signupUrl
72
+ }
73
+
74
+ /**
75
+ * Logout user and redirect to logout page
76
+ * @param callbackUrl - URL to redirect to after logout (defaults to home page)
77
+ */
78
+ logout(callbackUrl?: string): void {
79
+ if (typeof window === "undefined") {
80
+ console.error("logout() can only be called in browser environment")
81
+ return
82
+ }
83
+
84
+ // Clear tokens immediately
85
+ this.client.tokenClient.clearTokens()
86
+
87
+ const config = this.client.getConfig()
88
+ const callback = callbackUrl || window.location.origin
89
+
90
+ // Redirect to /accounts/logout/
91
+ const logoutUrl = `${config.baseUrl}/accounts/logout/?redirect_to=${encodeURIComponent(callback)}`
92
+
93
+ window.location.href = logoutUrl
94
+ }
95
+
96
+ /**
97
+ * Check if user is authenticated
98
+ */
99
+ isAuthenticated(): boolean {
100
+ return this.client.tokenClient.isAuthenticated()
101
+ }
102
+
103
+ /**
104
+ * Get the current access token
105
+ */
106
+ getAccessToken(): string | null {
107
+ return this.client.tokenClient.getToken()
40
108
  }
41
109
 
42
- async isUserAuthenticated(): Promise<boolean> {
43
- const authValue = localStorage.getItem("jwt")
44
- return authValue ? true : false
110
+ /**
111
+ * Get the current refresh token
112
+ */
113
+ getRefreshToken(): string | null {
114
+ return this.client.tokenClient.getRefreshToken()
45
115
  }
46
116
 
47
- async redirectToLogin() {
48
- const settings = new Settings(this.client)
49
- // let { frontEndUrl } = await settings.get().execute()
50
- let frontEndUrl
51
- const currentUrl = window.location.href
52
-
53
- if (!frontEndUrl) frontEndUrl = this.client.getConfig().deskUrl
54
- window.location.href = frontEndUrl + `?redirect=${currentUrl}`
117
+ /**
118
+ * Check if the access token is expired
119
+ */
120
+ isTokenExpired(): boolean {
121
+ return this.client.tokenClient.isTokenExpired()
55
122
  }
56
123
 
57
- // TODO: Implement authentication methods
58
- // - signInWithSSO
59
- // - signInWithPassword ?
60
- // - signOut
61
- // - isUserAuthenticated
62
- // - refreshSession
63
- // - redirectToLogin
124
+ /**
125
+ * Refresh the access token using the refresh token
126
+ * ⚠️ IMPORTANT: Taruvi uses refresh token rotation
127
+ * You will receive BOTH a new access token AND a new refresh token
128
+ *
129
+ * @returns Promise with new tokens or null if refresh failed
130
+ */
131
+ async refreshAccessToken(): Promise<{ access: string; refresh: string; expires_in: number } | null> {
132
+ const refreshToken = this.client.tokenClient.getRefreshToken()
133
+
134
+ if (!refreshToken) {
135
+ console.error("No refresh token available")
136
+ return null
137
+ }
138
+
139
+ try {
140
+ const config = this.client.getConfig()
141
+ const response = await fetch(`${config.baseUrl}/api/cloud/auth/jwt/token/refresh/`, {
142
+ method: "POST",
143
+ headers: { "Content-Type": "application/json" },
144
+ body: JSON.stringify({ refresh: refreshToken })
145
+ })
146
+
147
+ if (!response.ok) {
148
+ throw new Error(`Token refresh failed: ${response.statusText}`)
149
+ }
150
+
151
+ const data = await response.json()
152
+
153
+ // Update tokens in storage (both access and refresh due to rotation)
154
+ this.client.tokenClient.updateAccessToken(data.access, data.expires_in || 172800)
155
+
156
+ if (data.refresh) {
157
+ this.client.tokenClient.updateRefreshToken(data.refresh)
158
+ }
159
+
160
+ return {
161
+ access: data.access,
162
+ refresh: data.refresh || refreshToken,
163
+ expires_in: data.expires_in || 172800
164
+ }
165
+ } catch (error) {
166
+ console.error("Failed to refresh access token:", error)
167
+
168
+ // Clear tokens and redirect to login
169
+ this.client.tokenClient.clearTokens()
170
+
171
+ if (typeof window !== "undefined") {
172
+ this.login()
173
+ }
174
+
175
+ return null
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Get current user info from access token (JWT decode)
181
+ * Note: This only decodes the token, doesn't validate signature
182
+ */
183
+ getCurrentUser(): any | null {
184
+ const accessToken = this.getAccessToken()
185
+
186
+ if (!accessToken) {
187
+ return null
188
+ }
189
+
190
+ try {
191
+ // Decode JWT (middle part is payload)
192
+ const parts = accessToken.split(".")
193
+ if (parts.length !== 3 || !parts[1]) {
194
+ throw new Error("Invalid JWT format")
195
+ }
196
+ const payload = JSON.parse(atob(parts[1]))
197
+ return payload
198
+ } catch (error) {
199
+ console.error("Failed to decode access token:", error)
200
+ return null
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Legacy method: Redirect to login using desk URL
206
+ * @deprecated Use login() instead
207
+ */
208
+ async redirectToLogin(): Promise<void> {
209
+ const config = this.client.getConfig()
210
+ const currentUrl = typeof window !== "undefined" ? window.location.href : ""
211
+
212
+ const deskUrl = config.deskUrl || config.baseUrl
213
+ if (typeof window !== "undefined") {
214
+ window.location.href = `${deskUrl}?redirect=${encodeURIComponent(currentUrl)}`
215
+ }
216
+ }
64
217
  }
@@ -2,4 +2,8 @@ export const DatabaseRoutes = {
2
2
  baseUrl: (appSlug: string) => `api/apps/${appSlug}`,
3
3
  dataTables: (tableName: string): string => `/datatables/${tableName}/data`,
4
4
  recordId: (recordId: string): string => `/${recordId}`
5
- }
5
+ }
6
+
7
+ type AllRouteKeys = keyof typeof DatabaseRoutes
8
+ export type DatabaseRouteKey = Exclude<AllRouteKeys, 'baseUrl'>
9
+ export type DatabaseUrlParams = Partial<Record<DatabaseRouteKey, string>>
@@ -4,4 +4,12 @@ export const StorageRoutes = {
4
4
  upload: () => "/batch-upload",
5
5
  delete: () => "/batch-delete"
6
6
  // bucket: (appslug: string, bucketslug: string) => `${StorageRoutesClone.baseUrl(appslug)}/${bucketslug}`
7
- }
7
+ }
8
+
9
+ export type StoragePathKey = 'path'
10
+ export type StorageFlagKey = 'upload' | 'delete'
11
+ export type StorageRouteKey = StoragePathKey | StorageFlagKey
12
+
13
+ export type BucketUrlParams = Partial<
14
+ Record<StorageRouteKey, string | true>
15
+ >