websockets + chat app (django)
This commit is contained in:
@@ -3,8 +3,7 @@ import axios from "axios";
|
||||
// --- ENV CONFIG ---
|
||||
const API_BASE_URL =
|
||||
import.meta.env.VITE_API_BASE_URL || "http://localhost:8000";
|
||||
const REFRESH_URL =
|
||||
import.meta.env.VITE_API_REFRESH_URL || "/api/token/refresh/";
|
||||
|
||||
const LOGIN_PATH = import.meta.env.VITE_LOGIN_PATH || "/login";
|
||||
|
||||
|
||||
@@ -30,6 +29,7 @@ function notifyError(detail: ApiErrorDetail) {
|
||||
function onError(handler: ApiErrorHandler) {
|
||||
const wrapped = handler as EventListener;
|
||||
window.addEventListener(ERROR_EVENT, wrapped as EventListener);
|
||||
|
||||
return () => window.removeEventListener(ERROR_EVENT, wrapped);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ function onError(handler: ApiErrorHandler) {
|
||||
function createAxios(baseURL: string): any {
|
||||
const instance = axios.create({
|
||||
baseURL,
|
||||
withCredentials: true, // <-- always true
|
||||
withCredentials: true, // cookies
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
@@ -51,12 +51,14 @@ function createAxios(baseURL: string): any {
|
||||
const apiPublic = createAxios(API_BASE_URL);
|
||||
const apiAuth = createAxios(API_BASE_URL);
|
||||
|
||||
|
||||
// --- REQUEST INTERCEPTOR (PUBLIC) ---
|
||||
// Ensure no Authorization header is ever sent by the public client
|
||||
apiPublic.interceptors.request.use(function (config: any) {
|
||||
if (config?.headers && (config.headers as any).Authorization) {
|
||||
delete (config.headers as any).Authorization;
|
||||
}
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
@@ -64,6 +66,7 @@ apiPublic.interceptors.request.use(function (config: any) {
|
||||
// Do not attach Authorization header; rely on cookies set by Django.
|
||||
apiAuth.interceptors.request.use(function (config: any) {
|
||||
(config as any)._retryCount = (config as any)._retryCount || 0;
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
@@ -76,16 +79,18 @@ apiAuth.interceptors.response.use(
|
||||
async function (error: any) {
|
||||
if (!error.response) {
|
||||
alert("Backend connection is unavailable. Please check your network.");
|
||||
|
||||
notifyError({
|
||||
message: "Network error or backend unavailable",
|
||||
url: error.config?.url,
|
||||
});
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
const status = error.response.status;
|
||||
if (status === 401) {
|
||||
clearTokens(); // optional: clear cookies client-side
|
||||
ClearTokens();
|
||||
window.location.assign(LOGIN_PATH);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
@@ -130,35 +135,37 @@ apiPublic.interceptors.response.use(
|
||||
}
|
||||
);
|
||||
|
||||
// --- TOKEN HELPERS (NO-OPS) ---
|
||||
// Django sets/rotates cookies server-side. Keep API surface to avoid breaking imports.
|
||||
function setTokens(_access?: string, _refresh?: string) {
|
||||
// no-op: cookies are managed by Django
|
||||
}
|
||||
function clearTokens() {
|
||||
// optional: try to clear auth cookies client-side; server should also clear on logout
|
||||
|
||||
|
||||
|
||||
function Logout() {
|
||||
try {
|
||||
document.cookie = "access_token=; Max-Age=0; path=/";
|
||||
document.cookie = "refresh_token=; Max-Age=0; path=/";
|
||||
} catch {
|
||||
// ignore
|
||||
const LogOutResponse = apiAuth.post("/api/logout/");
|
||||
|
||||
if (LogOutResponse.body.detail != "Logout successful") {
|
||||
throw new Error("Logout failed");
|
||||
}
|
||||
|
||||
ClearTokens();
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error during logout:", error);
|
||||
}
|
||||
}
|
||||
function getAccessToken(): string | null {
|
||||
// no Authorization header is used; rely purely on cookies
|
||||
return null;
|
||||
|
||||
function ClearTokens(){
|
||||
document.cookie = "access_token=; Max-Age=0; path=/";
|
||||
document.cookie = "refresh_token=; Max-Age=0; path=/";
|
||||
}
|
||||
|
||||
|
||||
// --- EXPORT DEFAULT API WRAPPER ---
|
||||
const Client = {
|
||||
// Axios instances
|
||||
auth: apiAuth,
|
||||
public: apiPublic,
|
||||
|
||||
// Token helpers (kept for compatibility; now no-ops)
|
||||
setTokens,
|
||||
clearTokens,
|
||||
getAccessToken,
|
||||
Logout,
|
||||
|
||||
// Error subscription
|
||||
onError,
|
||||
|
||||
82
frontend/src/api/models/User.ts
Normal file
82
frontend/src/api/models/User.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
// frontend/src/api/model/user.js
|
||||
// User API model for searching users by username
|
||||
// Structure matches other model files (see order.js for reference)
|
||||
|
||||
import Client from '../Client';
|
||||
|
||||
const API_BASE_URL = "/account/users";
|
||||
|
||||
const userAPI = {
|
||||
/**
|
||||
* Get current authenticated user
|
||||
* @returns {Promise<User>}
|
||||
*/
|
||||
async getCurrentUser() {
|
||||
const response = await Client.auth.get(`${API_BASE_URL}/me/`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all users
|
||||
* @returns {Promise<Array<User>>}
|
||||
*/
|
||||
async getUsers(params: Object) {
|
||||
const response = await Client.auth.get(`${API_BASE_URL}/`, { params });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a single user by ID
|
||||
* @param {number|string} id
|
||||
* @returns {Promise<User>}
|
||||
*/
|
||||
async getUser(id: number) {
|
||||
const response = await Client.auth.get(`${API_BASE_URL}/${id}/`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update a user by ID
|
||||
* @param {number|string} id
|
||||
* @param {Object} data
|
||||
* @returns {Promise<User>}
|
||||
*/
|
||||
async updateUser(id: number, data: Object) {
|
||||
const response = await Client.auth.patch(`${API_BASE_URL}/${id}/`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a user by ID
|
||||
* @param {number|string} id
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async deleteUser(id: number) {
|
||||
const response = await Client.auth.delete(`${API_BASE_URL}/${id}/`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new user
|
||||
* @param {Object} data
|
||||
* @returns {Promise<User>}
|
||||
*/
|
||||
async createUser(data: Object) {
|
||||
const response = await Client.auth.post(`${API_BASE_URL}/`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Search users by username (partial match)
|
||||
* @param {Object} params - { username: string }
|
||||
* @returns {Promise<Array<User>>}
|
||||
*/
|
||||
async searchUsers(params: { username: string }) {
|
||||
// Adjust the endpoint as needed for your backend
|
||||
const response = await Client.auth.get(`${API_BASE_URL}/`, { params });
|
||||
console.log("User search response:", response.data);
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
|
||||
export default userAPI;
|
||||
19
frontend/src/api/websockets/WebSocketClient.ts
Normal file
19
frontend/src/api/websockets/WebSocketClient.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
const wsUri = "ws://127.0.0.1/";
|
||||
|
||||
const websocket = new WebSocket(wsUri);
|
||||
|
||||
websocket.onopen = function (event) {
|
||||
console.log("WebSocket is open now.", event);
|
||||
};
|
||||
|
||||
websocket.onmessage = function (event) {
|
||||
console.log("WebSocket message received:", event.data);
|
||||
};
|
||||
|
||||
websocket.onclose = function (event) {
|
||||
console.log("WebSocket is closed now.", event.reason);
|
||||
};
|
||||
|
||||
websocket.onerror = function (event) {
|
||||
console.error("WebSocket error observed:", event);
|
||||
};
|
||||
Reference in New Issue
Block a user