"@react-native-async-storage/async-storage": "2.2.0",
"@react-navigation/native": "^7.1.28",
"@react-navigation/native-stack": "^7.13.0",
+ "buffer": "^6.0.3",
"expo": "~54.0.33",
"expo-crypto": "~15.0.8",
"expo-notifications": "~0.32.16",
}
},
"node_modules/buffer": {
- "version": "5.7.1",
- "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
- "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [
{
"type": "github",
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
- "ieee754": "^1.1.13"
+ "ieee754": "^1.2.1"
}
},
"node_modules/buffer-from": {
"node": ">=10"
}
},
+ "node_modules/whatwg-url-without-unicode/node_modules/buffer": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"@react-native-async-storage/async-storage": "2.2.0",
"@react-navigation/native": "^7.1.28",
"@react-navigation/native-stack": "^7.13.0",
+ "buffer": "^6.0.3",
"expo": "~54.0.33",
"expo-crypto": "~15.0.8",
"expo-notifications": "~0.32.16",
/**
* env.ts
* ------
- * 1 seul point de config réseau.
- *
- * IMPORTANT :
- * - REST = https://wallette.duckdns.org/api
- * - Socket.IO = https://wallette.duckdns.org (PAS /api)
- *
- * Expo = __DEV__ => on peut forcer PROD pour tester le serveur déployé.
+ * Source unique de config réseau.
*/
-// ✅ PROD (duckdns)
-const PROD_GATEWAY = "https://wallette.duckdns.org";
+const DEV_LAN_IP = "192.168.129.121";
+const DEV_GATEWAY = `http://${DEV_LAN_IP}:3000`;
-// ✅ DEV (pour test Expo sur tel) : force PROD
-// Si tu veux revenir au local plus tard : remets ton IP LAN + http://IP:3000
-const DEV_GATEWAY = "https://wallette.duckdns.org";
+const PROD_GATEWAY = "https://wallette.duckdns.org";
export const GATEWAY_BASE_URL = __DEV__ ? DEV_GATEWAY : PROD_GATEWAY;
-
-// REST (via gateway)
export const API_BASE_URL = `${GATEWAY_BASE_URL}/api`;
-
-// Socket.IO (via gateway) -> RACINE (pas /api)
export const SERVER_URL = GATEWAY_BASE_URL;
export const ENV_MODE = __DEV__ ? "DEV" : "PROD";
-export const PLATFORM = Platform.OS;
\ No newline at end of file
+export const PLATFORM = Platform.OS;
+
+/**
+ * Basic Auth (protection serveur / NGINX)
+ * --------------------------------------
+ * Sert à passer la barrière d’accès (HTTP 401).
+ *
+ * ⚠️ Oui, c’est “dans l’app”. Pour un projet étudiant, c’est acceptable.
+ * Si vous voulez mieux : variables EAS (secrets) plus tard.
+ */
+export const BASIC_AUTH_USER = "Thibaud";
+export const BASIC_AUTH_PASS = "ThibaudLCC";
\ No newline at end of file
const filteredSorted = useMemo(() => {
const base =
- filter === "ALL" ? items : items.filter((a) => (a.alertLevel ?? "INFO") === filter);
+ filter === "ALL" ? items : items.filter((a) => ((a.alertLevel ?? a.criticality ?? "INFO") === filter));
return [...base].sort((a, b) => {
const r = severityRank(b.alertLevel) - severityRank(a.alertLevel);
</View>
<View style={[ui.badge, { backgroundColor: `${lColor}22`, marginTop: 0 }]}>
- <Text style={[ui.badgeText, { color: lColor }]}>{item.alertLevel ?? "INFO"}</Text>
+ <Text style={[ui.badgeText, { color: lColor }]}>{item.alertLevel ?? item.criticality ?? "INFO"}</Text>
</View>
</View>
import { API_BASE_URL } from "../../config/env";
+import { BASIC_AUTH_USER, BASIC_AUTH_PASS } from "../../config/env";
+import { Buffer } from "buffer";
/**
* HTTP helper (fetch)
* -------------------
- * Compatible avec 2 formats :
- * A) "wrap" : { ok:true, data: ... } / { ok:false, error:{message} }
- * B) "raw" : { ... } / Alert[] etc.
+ * - Ajoute Basic Auth si configuré (pour passer la barrière 401)
+ * - Compatible avec 2 formats :
+ * A) wrap : { ok:true, data: ... } / { ok:false, error:{message} }
+ * B) raw : { ... } / [...]
*/
+function getBasicAuthHeader(): string | null {
+ const u = (BASIC_AUTH_USER ?? "").trim();
+ const p = (BASIC_AUTH_PASS ?? "").trim();
+ if (!u || !p) return null;
+
+ const token = Buffer.from(`${u}:${p}`, "utf8").toString("base64");
+ return `Basic ${token}`;
+}
+
async function parseJsonSafe(res: Response) {
const text = await res.text();
try {
return json as T;
}
+function buildHeaders(extra?: Record<string, string>) {
+ const headers: Record<string, string> = {
+ Accept: "application/json",
+ ...(extra ?? {}),
+ };
+
+ const auth = getBasicAuthHeader();
+ if (auth) headers.Authorization = auth;
+
+ return headers;
+}
+
export async function apiGet<T>(path: string): Promise<T> {
const res = await fetch(buildUrl(path), {
method: "GET",
- headers: { Accept: "application/json" },
+ headers: buildHeaders(),
});
const json = await parseJsonSafe(res);
export async function apiPost<T>(path: string, body: unknown): Promise<T> {
const res = await fetch(buildUrl(path), {
method: "POST",
- headers: {
- Accept: "application/json",
- "Content-Type": "application/json",
- },
+ headers: buildHeaders({ "Content-Type": "application/json" }),
body: JSON.stringify(body),
});
+/**
+ * Alert.ts
+ * --------
+ * Format d'alerte utilisé par :
+ * - Socket.IO (event "alert")
+ * - REST : GET /api/alerts/events?userId=...&limit=...
+ *
+ * Le backend peut envoyer :
+ * - alertLevel (ancien)
+ * - OU criticality (nouveau BotService)
+ *
+ * On accepte les deux.
+ */
+
export type AlertLevel = "CRITICAL" | "WARNING" | "INFO";
export type AlertAction = "BUY" | "SELL" | "HOLD" | "STOP_LOSS";
export interface Alert {
action?: AlertAction;
pair?: string;
- confidence?: number;
+ confidence?: number; // 0..1
reason?: string;
- // compat web
+ // compat: certains front/back utilisent 'message'
message?: string;
+ // ancien nom
alertLevel?: AlertLevel;
- timestamp?: number;
+
+ // nouveau nom (BotService)
+ criticality?: AlertLevel;
+
+ timestamp?: number; // ms
price?: number;
}
\ No newline at end of file