Skip to content

Commit 6c16968

Browse files
Merge pull request #90 from geturbackend/feat/sdk-v0.2.0-upgrade
Feat/sdk v0.2.0 upgrade
2 parents 037dfa5 + 5fb59f4 commit 6c16968

15 files changed

Lines changed: 691 additions & 139 deletions

File tree

AGENTS.md

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -158,11 +158,3 @@ For Jest in this repo, `--runInBand` is often safer in constrained Windows envir
158158
- Do not make social auth depend on dashboard login cookies; it belongs to project public auth.
159159
- When touching docs, update both repo docs and in-dashboard docs if the user-facing flow changes.
160160

161-
## Current version: v0.8.0
162-
Social auth (GitHub + Google) shipped. Next: v0.9.0 — Webhooks + BYOK Resend mail.
163-
164-
## Planned for v0.9.0 (already done)
165-
- Webhook system: per-project config, HMAC-SHA256, retry, delivery logs
166-
- BYOK Resend mail key: project-level Resend API key, custom domain mail
167-
- Follow same encryption pattern as authProviders for storing Resend key
168-
- Webhook model: separate MongoDB collection (not embedded in Project)

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "urbackend-monorepo",
3-
"version": "0.8.0",
3+
"version": "0.9.0",
44
"private": true,
55
"workspaces": [
66
"apps/*",

sdks/urbackend-sdk/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@urbackend/sdk",
3-
"version": "0.1.1",
3+
"version": "0.2.0",
44
"description": "Official TypeScript SDK for urBackend BaaS",
55
"main": "./dist/index.cjs",
66
"module": "./dist/index.mjs",

sdks/urbackend-sdk/src/client.ts

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,27 @@ import { UrBackendError, parseApiError } from './errors';
33
import { AuthModule } from './modules/auth';
44
import { DatabaseModule } from './modules/database';
55
import { StorageModule } from './modules/storage';
6+
import { SchemaModule } from './modules/schema';
7+
import { MailModule } from './modules/mail';
68

79
export class UrBackendClient {
810
private apiKey: string;
911
private baseUrl: string;
1012
private _auth?: AuthModule;
1113
private _db?: DatabaseModule;
1214
private _storage?: StorageModule;
15+
private _schema?: SchemaModule;
16+
private _mail?: MailModule;
1317
private headers: Record<string, string>;
1418

1519
constructor(config: UrBackendConfig) {
1620
this.apiKey = config.apiKey;
1721
this.baseUrl = config.baseUrl || 'https://api.ub.bitbros.in';
1822
this.headers = config.headers || {};
1923

20-
if (typeof window !== 'undefined') {
24+
if (typeof window !== 'undefined' && this.apiKey.startsWith('sk_live_')) {
2125
console.warn(
22-
'⚠️ urbackend-sdk: Avoid exposing your SK-API key in client-side code(instead use pk_live key). This can lead to unauthorized access to your account and data.',
26+
'⚠️ urbackend-sdk: Avoid exposing your Secret Key (sk_live_...) in client-side code. This can lead to unauthorized access to your account and data. Use your Publishable Key (pk_live_...) instead.',
2327
);
2428
}
2529
}
@@ -45,6 +49,28 @@ export class UrBackendClient {
4549
return this._storage;
4650
}
4751

52+
get schema(): SchemaModule {
53+
if (!this._schema) {
54+
this._schema = new SchemaModule(this);
55+
}
56+
return this._schema;
57+
}
58+
59+
get mail(): MailModule {
60+
if (!this._mail) {
61+
this._mail = new MailModule(this);
62+
}
63+
return this._mail;
64+
}
65+
66+
public getBaseUrl(): string {
67+
return this.baseUrl;
68+
}
69+
70+
public getApiKey(): string {
71+
return this.apiKey;
72+
}
73+
4874
/**
4975
* Internal request handler
5076
*/
@@ -56,14 +82,19 @@ export class UrBackendClient {
5682
const url = `${this.baseUrl}${path}`;
5783
const headers: Record<string, string> = {
5884
'x-api-key': this.apiKey,
59-
'User-Agent': `urbackend-sdk-js/0.1.1`,
85+
'User-Agent': `urbackend-sdk-js/0.2.0`,
6086
...this.headers,
6187
};
6288

6389
if (options.token) {
6490
headers['Authorization'] = `Bearer ${options.token}`;
6591
}
6692

93+
// Merge custom headers from options if provided
94+
if (options.headers) {
95+
Object.assign(headers, options.headers);
96+
}
97+
6798
let requestBody: BodyInit | undefined;
6899

69100
if (options.isMultipart) {
@@ -79,6 +110,7 @@ export class UrBackendClient {
79110
method,
80111
headers,
81112
body: requestBody,
113+
credentials: options.credentials,
82114
});
83115

84116
if (!response.ok) {
@@ -89,7 +121,11 @@ export class UrBackendClient {
89121
if (contentType && contentType.includes('application/json')) {
90122
const json = await response.json();
91123
// The API returns { data, success, message }
92-
return json.data !== undefined ? json.data : json;
124+
// If data is present, return it. If success/message are present but no data, return the whole object (for exchange/logout etc)
125+
if (json.data !== undefined) {
126+
return json.data;
127+
}
128+
return json;
93129
}
94130

95131
return (await response.text()) as unknown as T;

sdks/urbackend-sdk/src/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import { UrBackendClient } from './client';
22
import { UrBackendConfig } from './types';
3+
import { AuthModule } from './modules/auth';
4+
import { DatabaseModule } from './modules/database';
5+
import { StorageModule } from './modules/storage';
6+
import { SchemaModule } from './modules/schema';
7+
import { MailModule } from './modules/mail';
38

49
export * from './types';
510
export * from './errors';
6-
export { UrBackendClient };
11+
export { UrBackendClient, AuthModule, DatabaseModule, StorageModule, SchemaModule, MailModule };
712

813
/**
914
* Factory function to create a new urBackend client

sdks/urbackend-sdk/src/modules/auth.ts

Lines changed: 167 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
import { UrBackendClient } from '../client';
2-
import { AuthUser, AuthResponse, SignUpPayload, LoginPayload } from '../types';
2+
import {
3+
AuthUser,
4+
AuthResponse,
5+
SignUpPayload,
6+
LoginPayload,
7+
UpdateProfilePayload,
8+
ChangePasswordPayload,
9+
VerifyEmailPayload,
10+
ResendOtpPayload,
11+
RequestPasswordResetPayload,
12+
ResetPasswordPayload,
13+
SocialExchangePayload,
14+
SocialExchangeResponse,
15+
RequestOptions,
16+
} from '../types';
317
import { AuthError } from '../errors';
418

519
export class AuthModule {
@@ -21,7 +35,15 @@ export class AuthModule {
2135
const response = await this.client.request<AuthResponse>('POST', '/api/userAuth/login', {
2236
body: payload,
2337
});
24-
this.sessionToken = response.token;
38+
39+
this.sessionToken = response.accessToken || response.token;
40+
41+
if (!response.accessToken && response.token) {
42+
console.warn(
43+
'urbackend-sdk: The server returned "token" which is deprecated. Please update your backend to return "accessToken".',
44+
);
45+
}
46+
2547
return response;
2648
}
2749

@@ -32,16 +54,156 @@ export class AuthModule {
3254
const activeToken = token || this.sessionToken;
3355

3456
if (!activeToken) {
35-
throw new AuthError('Authentication token is required for /me endpoint', 401, '/api/userAuth/me');
57+
throw new AuthError(
58+
'Authentication token is required for /me endpoint',
59+
401,
60+
'/api/userAuth/me',
61+
);
3662
}
3763

3864
return this.client.request<AuthUser>('GET', '/api/userAuth/me', { token: activeToken });
3965
}
4066

4167
/**
42-
* Clear the local session token
68+
* Update the current authenticated user's profile
69+
*/
70+
public async updateProfile(payload: UpdateProfilePayload, token?: string): Promise<{ message: string }> {
71+
const activeToken = token || this.sessionToken;
72+
if (!activeToken) {
73+
throw new AuthError('Authentication token is required to update profile', 401, '/api/userAuth/update-profile');
74+
}
75+
return this.client.request<{ message: string }>('PUT', '/api/userAuth/update-profile', {
76+
body: payload,
77+
token: activeToken,
78+
});
79+
}
80+
81+
/**
82+
* Change the current authenticated user's password
83+
*/
84+
public async changePassword(payload: ChangePasswordPayload, token?: string): Promise<{ message: string }> {
85+
const activeToken = token || this.sessionToken;
86+
if (!activeToken) {
87+
throw new AuthError('Authentication token is required to change password', 401, '/api/userAuth/change-password');
88+
}
89+
return this.client.request<{ message: string }>('PUT', '/api/userAuth/change-password', {
90+
body: payload,
91+
token: activeToken,
92+
});
93+
}
94+
95+
/**
96+
* Verify user email with OTP
97+
*/
98+
public async verifyEmail(payload: VerifyEmailPayload): Promise<{ message: string }> {
99+
return this.client.request<{ message: string }>('POST', '/api/userAuth/verify-email', {
100+
body: payload,
101+
});
102+
}
103+
104+
/**
105+
* Resend verification OTP
106+
*/
107+
public async resendVerificationOtp(payload: ResendOtpPayload): Promise<{ message: string }> {
108+
return this.client.request<{ message: string }>('POST', '/api/userAuth/resend-verification-otp', {
109+
body: payload,
110+
});
111+
}
112+
113+
/**
114+
* Request password reset OTP
115+
*/
116+
public async requestPasswordReset(payload: RequestPasswordResetPayload): Promise<{ message: string }> {
117+
return this.client.request<{ message: string }>('POST', '/api/userAuth/request-password-reset', {
118+
body: payload,
119+
});
120+
}
121+
122+
/**
123+
* Reset user password with OTP
43124
*/
44-
public logout(): void {
125+
public async resetPassword(payload: ResetPasswordPayload): Promise<{ message: string }> {
126+
return this.client.request<{ message: string }>('POST', '/api/userAuth/reset-password', {
127+
body: payload,
128+
});
129+
}
130+
131+
/**
132+
* Get public-safe profile by username
133+
*/
134+
public async publicProfile(username: string): Promise<AuthUser> {
135+
return this.client.request<AuthUser>('GET', `/api/userAuth/public/${username}`);
136+
}
137+
138+
/**
139+
* Refresh the access token
140+
* @param refreshToken Optional refresh token for header mode. If omitted, uses cookie mode.
141+
*/
142+
public async refreshToken(refreshToken?: string): Promise<AuthResponse> {
143+
const options: RequestOptions = {};
144+
if (refreshToken) {
145+
options.headers = { 'x-refresh-token': refreshToken, 'x-refresh-token-mode': 'header' };
146+
} else {
147+
options.credentials = 'include';
148+
}
149+
150+
const response = await this.client.request<AuthResponse>('POST', '/api/userAuth/refresh-token', options);
151+
this.sessionToken = response.accessToken || response.token;
152+
return response;
153+
}
154+
155+
/**
156+
* Returns the start URL for social authentication.
157+
* Redirect the user's browser to this URL to begin the flow.
158+
*/
159+
public socialStart(provider: 'github' | 'google'): string {
160+
return `${this.client.getBaseUrl()}/api/userAuth/social/${provider}/start?key=${this.client.getApiKey()}`;
161+
}
162+
163+
/**
164+
* Exchange social auth rtCode for a refresh token
165+
*/
166+
public async socialExchange(payload: SocialExchangePayload): Promise<SocialExchangeResponse> {
167+
return this.client.request<SocialExchangeResponse>('POST', '/api/userAuth/social/exchange', {
168+
body: payload,
169+
});
170+
}
171+
172+
/**
173+
* Revoke the current session and clear local state
174+
*/
175+
public async logout(token?: string): Promise<{ success: boolean; message: string }> {
176+
const activeToken = token || this.sessionToken;
177+
let result = { success: true, message: 'Logged out locally' };
178+
179+
if (activeToken) {
180+
try {
181+
result = await this.client.request<{ success: boolean; message: string }>(
182+
'POST',
183+
'/api/userAuth/logout',
184+
{ token: activeToken, credentials: 'include' },
185+
);
186+
} catch (e) {
187+
// Silently fail if server logout fails, we still want to clear local state
188+
console.warn('urbackend-sdk: Server logout failed', e);
189+
}
190+
}
191+
45192
this.sessionToken = undefined;
193+
return result;
194+
}
195+
196+
/**
197+
* Manually set the session token (e.g. after social auth exchange)
198+
*/
199+
public setToken(token: string): void {
200+
this.sessionToken = token;
201+
}
202+
203+
/**
204+
* Get the current stored session token
205+
*/
206+
public getToken(): string | undefined {
207+
return this.sessionToken;
46208
}
47209
}

0 commit comments

Comments
 (0)