nocal-auth-sdk 0.1.8 → 0.2.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.
@@ -1,10 +1,30 @@
1
1
  export interface NocalAuthOptions {
2
2
  authorizeUrl?: string;
3
+ clientId?: string;
4
+ redirectUri?: string;
5
+ scope?: string;
6
+ state?: string;
3
7
  [key: string]: any;
4
8
  }
9
+ export interface NocalPopupResult {
10
+ success: boolean;
11
+ data?: Record<string, any>;
12
+ error?: string;
13
+ message?: string;
14
+ }
15
+ export interface LoginWithPopupOptions {
16
+ url?: string;
17
+ popupName?: string;
18
+ width?: number;
19
+ height?: number;
20
+ features?: string;
21
+ }
5
22
  export declare class NocalAuth {
6
23
  private authorizeUrl;
7
- [key: string]: any;
24
+ private options;
8
25
  constructor(options?: NocalAuthOptions);
9
- loginWithPopup(): Promise<unknown>;
26
+ static notifyPopupResult(result: NocalPopupResult, targetWindow?: Window | null): boolean;
27
+ getAuthorizeUrl(): string;
28
+ static loginWithPopup(options?: LoginWithPopupOptions): Promise<unknown>;
29
+ loginWithPopup(options?: LoginWithPopupOptions): Promise<unknown>;
10
30
  }
package/dist/NocalAuth.js CHANGED
@@ -1,19 +1,61 @@
1
1
  export class NocalAuth {
2
2
  constructor(options = {}) {
3
- this.authorizeUrl = options.authorizeUrl || "";
3
+ this.options = options;
4
+ this.authorizeUrl =
5
+ options.authorizeUrl || "https://auth.nocal.vn/oauth/authorize";
4
6
  }
5
- loginWithPopup() {
6
- // Thêm check an toàn nếu chạy ở môi trường SSR (Next.js/Nuxt) không có window
7
+ static notifyPopupResult(result, targetWindow = typeof window !== "undefined"
8
+ ? window.opener
9
+ : null) {
10
+ if (typeof window === "undefined")
11
+ return false;
12
+ const opener = targetWindow ?? window.opener;
13
+ if (!opener)
14
+ return false;
15
+ const payload = {
16
+ type: "nocal_auth_result",
17
+ success: result.success,
18
+ ...(result.success
19
+ ? { data: result.data ?? {} }
20
+ : {
21
+ error: result.error || "AUTH_FAILED",
22
+ message: result.message || result.error || "AUTH_FAILED",
23
+ }),
24
+ };
25
+ opener.postMessage(payload, "*");
26
+ return true;
27
+ }
28
+ getAuthorizeUrl() {
29
+ const url = new URL(this.authorizeUrl || "https://auth.nocal.vn/oauth/authorize");
30
+ const params = {
31
+ client_id: this.options.clientId,
32
+ redirect_uri: this.options.redirectUri,
33
+ scope: this.options.scope,
34
+ state: this.options.state,
35
+ ...this.options,
36
+ };
37
+ Object.entries(params).forEach(([key, value]) => {
38
+ if (value === undefined || value === null || value === "")
39
+ return;
40
+ if (key === "authorizeUrl")
41
+ return;
42
+ url.searchParams.set(key, String(value));
43
+ });
44
+ return url.toString();
45
+ }
46
+ static loginWithPopup(options = {}) {
7
47
  if (typeof window === "undefined") {
8
48
  return Promise.reject(new Error("loginWithPopup() can only be used in a browser environment"));
9
49
  }
50
+ const url = options.url || "https://auth.nocal.vn/oauth/authorize";
51
+ const width = options.width ?? 500;
52
+ const height = options.height ?? 700;
53
+ const left = window.screenX + (window.innerWidth - width) / 2;
54
+ const top = window.screenY + (window.innerHeight - height) / 2;
55
+ const features = options.features ??
56
+ `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes`;
10
57
  return new Promise((resolve, reject) => {
11
- const width = 500;
12
- const height = 700;
13
- const left = window.screenX + (window.innerWidth - width) / 2;
14
- const top = window.screenY + (window.innerHeight - height) / 2;
15
- const features = `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes`;
16
- const popup = window.open(this.authorizeUrl, "nocal_auth_popup", features);
58
+ const popup = window.open(url, options.popupName ?? "nocal_auth_popup", features);
17
59
  if (!popup)
18
60
  return reject(new Error("POPUP_BLOCKED"));
19
61
  popup.focus();
@@ -33,10 +75,10 @@ export class NocalAuth {
33
75
  return;
34
76
  cleanup();
35
77
  if (payload.success) {
36
- resolve(payload.data);
78
+ resolve(payload.data ?? {});
37
79
  }
38
80
  else {
39
- reject(new Error(payload.error || "AUTH_FAILED"));
81
+ reject(new Error(payload.error || payload.message || "AUTH_FAILED"));
40
82
  }
41
83
  };
42
84
  window.addEventListener("message", handleMessage);
@@ -47,10 +89,16 @@ export class NocalAuth {
47
89
  reject(new Error("POPUP_CLOSED"));
48
90
  }
49
91
  }
50
- catch (e) {
51
- // Bỏ qua lỗi Cross-Origin khi popup chuyển hướng
92
+ catch {
93
+ // Ignore cross-origin checks while popup is redirecting.
52
94
  }
53
95
  }, 500);
54
96
  });
55
97
  }
98
+ loginWithPopup(options = {}) {
99
+ return NocalAuth.loginWithPopup({
100
+ ...options,
101
+ url: options.url || this.getAuthorizeUrl(),
102
+ });
103
+ }
56
104
  }
@@ -0,0 +1,46 @@
1
+ import * as _supabase_auth_js from '@supabase/auth-js';
2
+ import { User, Session } from '@supabase/supabase-js';
3
+ export { Session, User } from '@supabase/supabase-js';
4
+
5
+ interface NocalClientConfig {
6
+ clientId: string;
7
+ redirectUri: string;
8
+ }
9
+ type NocalOAuthProvider = "google" | "facebook" | "github" | "apple";
10
+ interface OAuthLoginReq {
11
+ provider?: NocalOAuthProvider;
12
+ redirectTo?: string;
13
+ }
14
+
15
+ declare class NocalAuthSDK {
16
+ private supabase;
17
+ private clientId;
18
+ private redirectUri;
19
+ constructor(config: NocalClientConfig);
20
+ /**
21
+ * Mở Popup UI Đăng nhập/Đăng ký tập trung của Nocal
22
+ */
23
+ signInWithNocal(req?: OAuthLoginReq): Promise<User>;
24
+ /**
25
+ * Lấy thông tin User hiện tại
26
+ */
27
+ getUser(): Promise<User | null>;
28
+ /**
29
+ * Lấy Session / Access Token hiện tại
30
+ */
31
+ getSession(): Promise<Session | null>;
32
+ /**
33
+ * Đăng xuất khỏi hệ thống
34
+ */
35
+ signOut(): Promise<void>;
36
+ /**
37
+ * Lắng nghe sự kiện đăng nhập / đăng xuất
38
+ */
39
+ onAuthStateChange(callback: (event: string, session: Session | null) => void): {
40
+ data: {
41
+ subscription: _supabase_auth_js.Subscription;
42
+ };
43
+ };
44
+ }
45
+
46
+ export { NocalAuthSDK, type NocalClientConfig, type NocalOAuthProvider, type OAuthLoginReq };
package/dist/index.d.ts CHANGED
@@ -1,2 +1,22 @@
1
- export { NocalAuth } from "./NocalAuth";
2
- export type { NocalAuthOptions } from "./NocalAuth";
1
+ import { NocalClientConfig, OAuthLoginReq, NocalUser, NocalSession } from "./types";
2
+ export declare class NocalAuthSDK {
3
+ private clientId;
4
+ private redirectUri;
5
+ private authServerUrl;
6
+ private currentSession;
7
+ private currentUser;
8
+ constructor(config: NocalClientConfig);
9
+ /**
10
+ * Mở Popup UI Đăng nhập & Ủy quyền OAuth 2.0 với PKCE
11
+ */
12
+ signInWithNocal(req?: OAuthLoginReq): Promise<{
13
+ user: NocalUser;
14
+ session: NocalSession;
15
+ }>;
16
+ private exchangeCodeForToken;
17
+ private fetchUserInfo;
18
+ getUser(): Promise<NocalUser | null>;
19
+ getSession(): Promise<NocalSession | null>;
20
+ signOut(): Promise<void>;
21
+ }
22
+ export * from "./types";
package/dist/index.js CHANGED
@@ -1 +1,171 @@
1
- export { NocalAuth } from "./NocalAuth";
1
+ const DEFAULT_AUTH_SERVER_URL = "http://localhost:3001";
2
+ // Helper for PKCE crypto
3
+ function base64URLEncode(buffer) {
4
+ let binary = "";
5
+ const bytes = new Uint8Array(buffer);
6
+ for (let i = 0; i < bytes.byteLength; i++) {
7
+ binary += String.fromCharCode(bytes[i]);
8
+ }
9
+ return btoa(binary)
10
+ .replace(/\+/g, "-")
11
+ .replace(/\//g, "_")
12
+ .replace(/=+$/, "");
13
+ }
14
+ async function generateCodeChallenge(verifier) {
15
+ const encoder = new TextEncoder();
16
+ const data = encoder.encode(verifier);
17
+ const digest = await crypto.subtle.digest("SHA-256", data);
18
+ return base64URLEncode(new Uint8Array(digest));
19
+ }
20
+ function generateRandomString(length = 32) {
21
+ const array = new Uint8Array(length);
22
+ crypto.getRandomValues(array);
23
+ return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
24
+ }
25
+ export class NocalAuthSDK {
26
+ constructor(config) {
27
+ this.currentSession = null;
28
+ this.currentUser = null;
29
+ this.clientId = config.clientId;
30
+ this.redirectUri = config.redirectUri;
31
+ this.authServerUrl = config.authServerUrl || DEFAULT_AUTH_SERVER_URL;
32
+ // Load session from localStorage if available
33
+ if (typeof window !== "undefined") {
34
+ try {
35
+ const stored = localStorage.getItem("nocal_auth_session");
36
+ if (stored) {
37
+ const parsed = JSON.parse(stored);
38
+ this.currentSession = parsed.session;
39
+ this.currentUser = parsed.user;
40
+ }
41
+ }
42
+ catch (e) {
43
+ // ignore
44
+ }
45
+ }
46
+ }
47
+ /**
48
+ * Mở Popup UI Đăng nhập & Ủy quyền OAuth 2.0 với PKCE
49
+ */
50
+ async signInWithNocal(req) {
51
+ const state = req?.state || generateRandomString(16);
52
+ const codeVerifier = generateRandomString(32);
53
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
54
+ const authUrl = new URL(`${this.authServerUrl}/oauth/authorize`);
55
+ authUrl.searchParams.set("client_id", this.clientId);
56
+ authUrl.searchParams.set("redirect_uri", this.redirectUri);
57
+ authUrl.searchParams.set("response_type", "code");
58
+ authUrl.searchParams.set("state", state);
59
+ authUrl.searchParams.set("code_challenge", codeChallenge);
60
+ authUrl.searchParams.set("code_challenge_method", "S256");
61
+ const width = 480;
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;
65
+ const popup = window.open(authUrl.toString(), "NocalAuthPopup", `width=${width},height=${height},left=${left},top=${top},status=yes,scrollbars=yes`);
66
+ if (!popup) {
67
+ throw new Error("POPUP_BLOCKED");
68
+ }
69
+ return new Promise((resolve, reject) => {
70
+ const allowedOrigin = new URL(this.authServerUrl).origin;
71
+ const messageListener = async (event) => {
72
+ if (event.origin !== allowedOrigin)
73
+ return;
74
+ const data = event.data;
75
+ if (!data || (data.type !== "NOCAL_OAUTH_CODE" && data.type !== "NOCAL_OAUTH_ERROR")) {
76
+ return;
77
+ }
78
+ window.removeEventListener("message", messageListener);
79
+ clearInterval(checkClosedTimer);
80
+ if (data.type === "NOCAL_OAUTH_ERROR" || data.error) {
81
+ popup.close();
82
+ reject(new Error(data.error || "OAUTH_ERROR"));
83
+ return;
84
+ }
85
+ if (data.state && data.state !== state) {
86
+ popup.close();
87
+ reject(new Error("OAUTH_STATE_MISMATCH"));
88
+ return;
89
+ }
90
+ const code = data.code;
91
+ if (!code) {
92
+ popup.close();
93
+ reject(new Error("MISSING_AUTHORIZATION_CODE"));
94
+ return;
95
+ }
96
+ try {
97
+ popup.close();
98
+ // Đổi code lấy token
99
+ const session = await this.exchangeCodeForToken(code, codeVerifier);
100
+ const user = await this.fetchUserInfo(session.access_token);
101
+ this.currentSession = session;
102
+ this.currentUser = user;
103
+ if (typeof window !== "undefined") {
104
+ localStorage.setItem("nocal_auth_session", JSON.stringify({ session, user }));
105
+ }
106
+ resolve({ user, session });
107
+ }
108
+ catch (err) {
109
+ reject(err);
110
+ }
111
+ };
112
+ window.addEventListener("message", messageListener);
113
+ const checkClosedTimer = setInterval(() => {
114
+ try {
115
+ if (popup.closed) {
116
+ clearInterval(checkClosedTimer);
117
+ window.removeEventListener("message", messageListener);
118
+ reject(new Error("POPUP_CLOSED_BY_USER"));
119
+ }
120
+ }
121
+ catch (e) {
122
+ // ignore
123
+ }
124
+ }, 500);
125
+ });
126
+ }
127
+ async exchangeCodeForToken(code, codeVerifier) {
128
+ const res = await fetch(`${this.authServerUrl}/api/oauth/token`, {
129
+ method: "POST",
130
+ headers: { "Content-Type": "application/json" },
131
+ body: JSON.stringify({
132
+ grant_type: "authorization_code",
133
+ code,
134
+ client_id: this.clientId,
135
+ redirect_uri: this.redirectUri,
136
+ code_verifier: codeVerifier,
137
+ }),
138
+ });
139
+ const data = await res.json();
140
+ if (!res.ok) {
141
+ throw new Error(data.error_description || data.error || "FAILED_TO_EXCHANGE_TOKEN");
142
+ }
143
+ return data;
144
+ }
145
+ async fetchUserInfo(accessToken) {
146
+ const res = await fetch(`${this.authServerUrl}/api/oauth/userinfo`, {
147
+ headers: {
148
+ Authorization: `Bearer ${accessToken}`,
149
+ },
150
+ });
151
+ const data = await res.json();
152
+ if (!res.ok) {
153
+ throw new Error(data.error_description || data.error || "FAILED_TO_FETCH_USERINFO");
154
+ }
155
+ return data;
156
+ }
157
+ async getUser() {
158
+ return this.currentUser;
159
+ }
160
+ async getSession() {
161
+ return this.currentSession;
162
+ }
163
+ async signOut() {
164
+ this.currentSession = null;
165
+ this.currentUser = null;
166
+ if (typeof window !== "undefined") {
167
+ localStorage.removeItem("nocal_auth_session");
168
+ }
169
+ }
170
+ }
171
+ export * from "./types";
package/dist/index.mjs ADDED
@@ -0,0 +1,110 @@
1
+ // src/index.ts
2
+ import {
3
+ createClient
4
+ } from "@supabase/supabase-js";
5
+ var NOCAL_AUTH_SERVER_URL = "https://hldymiruucsqqzicnwdv.supabase.co";
6
+ var NOCAL_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImhsZHltaXJ1dWNzcXF6aWNud2R2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODk2MTQ2NzAsImV4cCI6MjEwNTE5MDY3MH0.dLaPWdgTV8mRVToNT1XLg1EUjHvyvRRK2qc0DQAu8YI";
7
+ var NocalAuthSDK = class {
8
+ constructor(config) {
9
+ this.clientId = config.clientId;
10
+ this.redirectUri = config.redirectUri;
11
+ this.supabase = createClient(NOCAL_AUTH_SERVER_URL, NOCAL_ANON_KEY, {
12
+ auth: {
13
+ autoRefreshToken: true,
14
+ persistSession: true,
15
+ detectSessionInUrl: true
16
+ }
17
+ });
18
+ }
19
+ /**
20
+ * Mở Popup UI Đăng nhập/Đăng ký tập trung của Nocal
21
+ */
22
+ async signInWithNocal(req) {
23
+ const targetRedirect = req?.redirectTo || this.redirectUri;
24
+ const provider = req?.provider || "google";
25
+ const { data, error } = await this.supabase.auth.signInWithOAuth({
26
+ provider,
27
+ options: {
28
+ redirectTo: targetRedirect,
29
+ skipBrowserRedirect: true,
30
+ // Lấy URL trả về, không nhảy trang tab chính
31
+ queryParams: {
32
+ client_id: this.clientId
33
+ // Gắn Client ID để Auth Server nhận diện App
34
+ }
35
+ }
36
+ });
37
+ if (error) throw error;
38
+ if (!data?.url) throw new Error("CANNOT_GENERATE_AUTH_URL");
39
+ const width = 480;
40
+ const height = 680;
41
+ const left = typeof window !== "undefined" ? window.screenX + (window.outerWidth - width) / 2 : 0;
42
+ const top = typeof window !== "undefined" ? window.screenY + (window.outerHeight - height) / 2 : 0;
43
+ const popup = window.open(
44
+ data.url,
45
+ "NocalAuthPopup",
46
+ `width=${width},height=${height},left=${left},top=${top},status=yes,scrollbars=yes`
47
+ );
48
+ if (!popup) {
49
+ throw new Error("POPUP_BLOCKED");
50
+ }
51
+ return new Promise((resolve, reject) => {
52
+ const isPopupClosed = () => {
53
+ try {
54
+ return popup.closed;
55
+ } catch (e) {
56
+ return false;
57
+ }
58
+ };
59
+ const timer = setInterval(async () => {
60
+ if (isPopupClosed()) {
61
+ clearInterval(timer);
62
+ const { data: sessionData } = await this.supabase.auth.getSession();
63
+ if (sessionData?.session?.user) {
64
+ resolve(sessionData.session.user);
65
+ } else {
66
+ reject(new Error("LOGIN_CANCELLED_OR_FAILED"));
67
+ }
68
+ }
69
+ }, 500);
70
+ });
71
+ }
72
+ /**
73
+ * Lấy thông tin User hiện tại
74
+ */
75
+ async getUser() {
76
+ const {
77
+ data: { user },
78
+ error
79
+ } = await this.supabase.auth.getUser();
80
+ if (error) throw error;
81
+ return user;
82
+ }
83
+ /**
84
+ * Lấy Session / Access Token hiện tại
85
+ */
86
+ async getSession() {
87
+ const {
88
+ data: { session },
89
+ error
90
+ } = await this.supabase.auth.getSession();
91
+ if (error) throw error;
92
+ return session;
93
+ }
94
+ /**
95
+ * Đăng xuất khỏi hệ thống
96
+ */
97
+ async signOut() {
98
+ const { error } = await this.supabase.auth.signOut();
99
+ if (error) throw error;
100
+ }
101
+ /**
102
+ * Lắng nghe sự kiện đăng nhập / đăng xuất
103
+ */
104
+ onAuthStateChange(callback) {
105
+ return this.supabase.auth.onAuthStateChange(callback);
106
+ }
107
+ };
108
+ export {
109
+ NocalAuthSDK
110
+ };
@@ -0,0 +1,23 @@
1
+ export interface NocalClientConfig {
2
+ clientId: string;
3
+ redirectUri: string;
4
+ authServerUrl?: string;
5
+ }
6
+ export interface OAuthLoginReq {
7
+ state?: string;
8
+ }
9
+ export interface NocalUser {
10
+ sub: string;
11
+ email?: string;
12
+ phone?: string;
13
+ email_confirmed?: boolean;
14
+ phone_confirmed?: boolean;
15
+ user_metadata?: Record<string, any>;
16
+ [key: string]: any;
17
+ }
18
+ export interface NocalSession {
19
+ access_token: string;
20
+ refresh_token: string;
21
+ token_type: string;
22
+ expires_in: number;
23
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nocal-auth-sdk",
3
- "version": "0.1.8",
3
+ "version": "0.2.0",
4
4
  "description": "Nocal Auth - client-side SDK for Login with Nocal (popup flow)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",