nocal-auth-sdk 0.2.1 → 0.2.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/README.md ADDED
@@ -0,0 +1,182 @@
1
+ # Nocal Auth SDK
2
+
3
+ Client-side SDK để tích hợp đăng nhập bằng Nocal theo flow popup OAuth 2.0 với PKCE.
4
+
5
+ SDK này giúp bạn mở popup đăng nhập, trao đổi authorization code với auth server, lấy thông tin user và lưu session vào localStorage để tiếp tục sử dụng trong frontend.
6
+
7
+ ## Tính năng
8
+
9
+ - Đăng nhập popup không cần redirect trang
10
+ - Hỗ trợ OAuth 2.0 + PKCE
11
+ - Tự động lưu session/user vào localStorage
12
+ - Lấy thông tin người dùng sau khi xác thực
13
+ - Tích hợp với Supabase Auth-compatible endpoint mặc định
14
+
15
+ ## Cài đặt
16
+
17
+ ```bash
18
+ npm install nocal-auth-sdk
19
+ ```
20
+
21
+ Hoặc nếu đang dùng trong repo local:
22
+
23
+ ```bash
24
+ npm install
25
+ npm run build
26
+ ```
27
+
28
+ ## Quick start
29
+
30
+ ```ts
31
+ import { NocalAuthSDK } from "nocal-auth-sdk";
32
+
33
+ const auth = new NocalAuthSDK({
34
+ clientId: "your-client-id",
35
+ redirectUri: "https://your-app.com/auth/callback",
36
+ authServerUrl: "https://your-auth-server.com",
37
+ });
38
+
39
+ async function login() {
40
+ try {
41
+ const { user, session } = await auth.signInWithNocal();
42
+ console.log("User:", user);
43
+ console.log("Session:", session);
44
+ } catch (error) {
45
+ console.error("Login failed:", error);
46
+ }
47
+ }
48
+ ```
49
+
50
+ ## Ví dụ dùng trong button click
51
+
52
+ ```ts
53
+ const loginButton = document.getElementById("login-btn");
54
+
55
+ loginButton?.addEventListener("click", async () => {
56
+ try {
57
+ const { user, session } = await auth.signInWithNocal({
58
+ state: "custom-state-value",
59
+ });
60
+
61
+ console.log("Đăng nhập thành công:", user.email || user.phone);
62
+ console.log("Access token:", session.access_token);
63
+ } catch (err) {
64
+ console.error("Lỗi đăng nhập:", err);
65
+ }
66
+ });
67
+ ```
68
+
69
+ ## Cấu hình
70
+
71
+ ```ts
72
+ interface NocalClientConfig {
73
+ clientId: string;
74
+ redirectUri: string;
75
+ authServerUrl?: string;
76
+ storageKey?: string;
77
+ }
78
+ ```
79
+
80
+ ### Giải thích các field
81
+
82
+ - `clientId`: ID client OAuth đã đăng ký với Nocal
83
+ - `redirectUri`: URL callback sau khi auth server redirect về
84
+ - `authServerUrl`: base URL của auth server, mặc định là:
85
+ `https://hldymiruucsqqzicnwdv.supabase.co`
86
+ - `storageKey`: chưa được dùng trong phiên bản hiện tại, session được lưu theo key mặc định `nocal_auth_session`
87
+
88
+ ## API
89
+
90
+ ### `new NocalAuthSDK(config)`
91
+
92
+ Khởi tạo instance SDK.
93
+
94
+ ### `signInWithNocal(req?)`
95
+
96
+ Mở popup OAuth và trả về:
97
+
98
+ ```ts
99
+ {
100
+ user: NocalUser;
101
+ session: NocalSession;
102
+ }
103
+ ```
104
+
105
+ #### Tham số `req`
106
+
107
+ ```ts
108
+ interface OAuthLoginReq {
109
+ state?: string;
110
+ provider?: string;
111
+ redirectTo?: string;
112
+ }
113
+ ```
114
+
115
+ ### `exchangeCodeForToken(code, codeVerifier)`
116
+
117
+ Đổi authorization code lấy access token và refresh token.
118
+
119
+ ## Session và User
120
+
121
+ SDK sẽ lưu session và thông tin user vào `localStorage` với key:
122
+
123
+ ```ts
124
+ nocal_auth_session;
125
+ ```
126
+
127
+ Khi khởi tạo instance, nếu có giá trị trong localStorage thì SDK sẽ tự động restore session hiện tại.
128
+
129
+ ## Luồng xử lý OAuth
130
+
131
+ 1. Tạo `state` và `codeVerifier`
132
+ 2. Tạo `code_challenge` bằng PKCE (`S256`)
133
+ 3. Mở popup tới `/auth/v1/oauth/authorize`
134
+ 4. Lắng nghe message từ auth server
135
+ 5. Nhận `code`
136
+ 6. Gọi `/auth/v1/oauth/token` để trao đổi token
137
+ 7. Gọi `/auth/v1/oauth/userinfo` để lấy thông tin user
138
+ 8. Lưu vào `localStorage` và resolve Promise
139
+
140
+ ## Lưu ý quan trọng
141
+
142
+ - Trình duyệt phải cho phép popup mở được
143
+ - `redirectUri` phải khớp với config đã đăng ký trên server
144
+ - Nếu popup bị chặn, SDK ném lỗi `POPUP_BLOCKED`
145
+ - Nếu người dùng đóng popup, SDK ném lỗi `POPUP_CLOSED_BY_USER`
146
+
147
+ ## Xử lý lỗi phổ biến
148
+
149
+ - `POPUP_BLOCKED`: popup bị chặn bởi trình duyệt
150
+ - `OAUTH_STATE_MISMATCH`: `state` không khớp
151
+ - `MISSING_AUTHORIZATION_CODE`: không nhận được `code`
152
+ - `FAILED_TO_FETCH_USERINFO`: không lấy được thông tin user
153
+
154
+ ## Mẫu type
155
+
156
+ ```ts
157
+ interface NocalUser {
158
+ sub: string;
159
+ email?: string;
160
+ phone?: string;
161
+ email_confirmed?: boolean;
162
+ phone_confirmed?: boolean;
163
+ user_metadata?: Record<string, unknown>;
164
+ [key: string]: unknown;
165
+ }
166
+
167
+ interface NocalSession {
168
+ access_token: string;
169
+ refresh_token: string;
170
+ token_type: string;
171
+ expires_in: number;
172
+ expires_at?: number;
173
+ }
174
+ ```
175
+
176
+ ## License
177
+
178
+ MIT
179
+
180
+ ## Author
181
+
182
+ Nocal Auth SDK
package/dist/index.d.ts CHANGED
@@ -13,7 +13,7 @@ export declare class NocalAuthSDK {
13
13
  user: NocalUser;
14
14
  session: NocalSession;
15
15
  }>;
16
- private exchangeCodeForToken;
16
+ exchangeCodeForToken(code: string, codeVerifier: string): Promise<NocalSession>;
17
17
  private fetchUserInfo;
18
18
  getUser(): Promise<NocalUser | null>;
19
19
  getSession(): Promise<NocalSession | null>;
package/dist/index.js CHANGED
@@ -60,8 +60,12 @@ export class NocalAuthSDK {
60
60
  authUrl.searchParams.set("code_challenge_method", "S256");
61
61
  const width = 480;
62
62
  const height = 680;
63
- const left = typeof window !== "undefined" ? window.screenX + (window.outerWidth - width) / 2 : 0;
64
- const top = typeof window !== "undefined" ? window.screenY + (window.outerHeight - height) / 2 : 0;
63
+ const left = typeof window !== "undefined"
64
+ ? window.screenX + (window.outerWidth - width) / 2
65
+ : 0;
66
+ const top = typeof window !== "undefined"
67
+ ? window.screenY + (window.outerHeight - height) / 2
68
+ : 0;
65
69
  const popup = window.open(authUrl.toString(), "NocalAuthPopup", `width=${width},height=${height},left=${left},top=${top},status=yes,scrollbars=yes`);
66
70
  if (!popup) {
67
71
  throw new Error("POPUP_BLOCKED");
@@ -72,8 +76,9 @@ export class NocalAuthSDK {
72
76
  if (event.origin !== allowedOrigin)
73
77
  return;
74
78
  const data = event.data;
75
- if (!data || (data.type !== "NOCAL_OAUTH_CODE" && data.type !== "NOCAL_OAUTH_ERROR")) {
76
- // Hoặc kiểm tra URL redirect của popup nếu Supabase native redirect trực tiếp
79
+ if (!data ||
80
+ (data.type !== "NOCAL_OAUTH_CODE" &&
81
+ data.type !== "NOCAL_OAUTH_ERROR")) {
77
82
  return;
78
83
  }
79
84
  window.removeEventListener("message", messageListener);
@@ -115,17 +120,14 @@ export class NocalAuthSDK {
115
120
  if (popup.closed) {
116
121
  clearInterval(checkClosedTimer);
117
122
  window.removeEventListener("message", messageListener);
118
- // Kiểm tra nếu popup đóng và đã có code trong redirect URL (nếu dùng redirect thuần)
119
123
  reject(new Error("POPUP_CLOSED_BY_USER"));
120
124
  }
121
125
  else {
122
- // Lắng nghe URL thay đổi nếu Supabase redirect trực tiếp về redirectUri
123
126
  try {
124
127
  const popupUrl = new URL(popup.location.href);
125
128
  if (popupUrl.origin === new URL(this.redirectUri).origin) {
126
129
  const code = popupUrl.searchParams.get("code");
127
130
  const error = popupUrl.searchParams.get("error");
128
- const returnedState = popupUrl.searchParams.get("state");
129
131
  if (code || error) {
130
132
  clearInterval(checkClosedTimer);
131
133
  window.removeEventListener("message", messageListener);
@@ -151,7 +153,7 @@ export class NocalAuthSDK {
151
153
  }
152
154
  }
153
155
  catch (e) {
154
- // Cross-origin check while on Supabase auth domain
156
+ // Cross-origin check
155
157
  }
156
158
  }
157
159
  }
@@ -162,22 +164,24 @@ export class NocalAuthSDK {
162
164
  });
163
165
  }
164
166
  async exchangeCodeForToken(code, codeVerifier) {
165
- const res = await fetch(`${this.authServerUrl}/auth/v1/oauth/token`, {
167
+ const params = new URLSearchParams();
168
+ params.set("grant_type", "authorization_code");
169
+ params.set("client_id", this.clientId);
170
+ params.set("redirect_uri", this.redirectUri);
171
+ params.set("code", code);
172
+ params.set("code_verifier", codeVerifier); // PKCE xác thực thay cho secret
173
+ const response = await fetch(`${this.authServerUrl}/auth/v1/oauth/token`, {
166
174
  method: "POST",
167
- headers: { "Content-Type": "application/json" },
168
- body: JSON.stringify({
169
- grant_type: "authorization_code",
170
- code,
171
- client_id: this.clientId,
172
- redirect_uri: this.redirectUri,
173
- code_verifier: codeVerifier,
174
- }),
175
+ headers: {
176
+ "Content-Type": "application/x-www-form-urlencoded",
177
+ },
178
+ body: params.toString(),
175
179
  });
176
- const data = await res.json();
177
- if (!res.ok) {
178
- throw new Error(data.error_description || data.error || "FAILED_TO_EXCHANGE_TOKEN");
180
+ if (!response.ok) {
181
+ const errorData = await response.json();
182
+ throw new Error(`Supabase Auth Error: ${errorData.error_description || errorData.error}`);
179
183
  }
180
- return data;
184
+ return (await response.json());
181
185
  }
182
186
  async fetchUserInfo(accessToken) {
183
187
  const res = await fetch(`${this.authServerUrl}/auth/v1/oauth/userinfo`, {
package/dist/types.d.ts CHANGED
@@ -5,6 +5,8 @@ export interface NocalClientConfig {
5
5
  }
6
6
  export interface OAuthLoginReq {
7
7
  state?: string;
8
+ provider?: string;
9
+ redirectTo?: string;
8
10
  }
9
11
  export interface NocalUser {
10
12
  sub: string;
@@ -12,12 +14,13 @@ export interface NocalUser {
12
14
  phone?: string;
13
15
  email_confirmed?: boolean;
14
16
  phone_confirmed?: boolean;
15
- user_metadata?: Record<string, any>;
16
- [key: string]: any;
17
+ user_metadata?: Record<string, unknown>;
18
+ [key: string]: unknown;
17
19
  }
18
20
  export interface NocalSession {
19
21
  access_token: string;
20
22
  refresh_token: string;
21
23
  token_type: string;
22
24
  expires_in: number;
25
+ expires_at?: number;
23
26
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nocal-auth-sdk",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Nocal Auth - client-side SDK for Login with Nocal (popup flow)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",