feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,13 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
export * from '../../src/private/specs/modules/NativeWebSocketModule';
import NativeWebSocketModule from '../../src/private/specs/modules/NativeWebSocketModule';
export default NativeWebSocketModule;

View File

@@ -0,0 +1,37 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTDefines.h>
#if RCT_DEV // Only supported in dev mode
@class RCTReconnectingWebSocket;
@protocol RCTReconnectingWebSocketDelegate
- (void)reconnectingWebSocketDidOpen:(RCTReconnectingWebSocket *)webSocket;
- (void)reconnectingWebSocket:(RCTReconnectingWebSocket *)webSocket didReceiveMessage:(id)message;
/** Sent when the socket has closed due to error or clean shutdown. An automatic reconnect will start shortly. */
- (void)reconnectingWebSocketDidClose:(RCTReconnectingWebSocket *)webSocket;
@end
@interface RCTReconnectingWebSocket : NSObject
/** Delegate will be messaged on the given queue (required). */
- (instancetype)initWithURL:(NSURL *)url queue:(dispatch_queue_t)queue;
@property (nonatomic, weak) id<RCTReconnectingWebSocketDelegate> delegate;
- (void)send:(id)data;
- (void)start;
- (void)stop;
- (instancetype)initWithURL:(NSURL *)url __deprecated_msg("Use initWithURL:queue: instead");
/** @brief Must be set before -start to have effect */
@property (nonatomic, strong) dispatch_queue_t delegateDispatchQueue __deprecated_msg("Use initWithURL:queue: instead");
@end
#endif

View File

@@ -0,0 +1,109 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTReconnectingWebSocket.h>
#import <React/RCTConvert.h>
#import <React/RCTDefines.h>
#import <SocketRocket/SRWebSocket.h>
#if RCT_DEV // Only supported in dev mode
@interface RCTReconnectingWebSocket () <SRWebSocketDelegate>
@end
@implementation RCTReconnectingWebSocket {
NSURL *_url;
SRWebSocket *_socket;
BOOL _stopped;
}
- (instancetype)initWithURL:(NSURL *)url queue:(dispatch_queue_t)queue
{
if (self = [super init]) {
_url = url;
_delegateDispatchQueue = queue;
}
return self;
}
- (instancetype)initWithURL:(NSURL *)url
{
return [self initWithURL:url queue:dispatch_get_main_queue()];
}
- (void)send:(id)data
{
[_socket sendData:data error:nil];
}
- (void)start
{
[self stop];
_stopped = NO;
_socket = [[SRWebSocket alloc] initWithURL:_url];
_socket.delegate = self;
[_socket setDelegateDispatchQueue:_delegateDispatchQueue];
[_socket open];
}
- (void)stop
{
_stopped = YES;
_socket.delegate = nil;
[_socket closeWithCode:1000 reason:@"Invalidated"];
_socket = nil;
}
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message
{
[_delegate reconnectingWebSocket:self didReceiveMessage:message];
}
- (void)reconnect
{
if (_stopped) {
return;
}
__weak SRWebSocket *socket = _socket;
__weak __typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf start];
if (!socket) {
[weakSelf reconnect];
}
});
}
- (void)webSocketDidOpen:(SRWebSocket *)webSocket
{
[_delegate reconnectingWebSocketDidOpen:self];
}
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error
{
[_delegate reconnectingWebSocketDidClose:self];
if ([error code] != ECONNREFUSED) {
[self reconnect];
}
}
- (void)webSocket:(SRWebSocket *)webSocket
didCloseWithCode:(NSInteger)code
reason:(NSString *)reason
wasClean:(BOOL)wasClean
{
[_delegate reconnectingWebSocketDidClose:self];
[self reconnect];
}
@end
#endif

View File

@@ -0,0 +1,297 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
import type {BlobData} from '../Blob/BlobTypes';
import type {EventSubscription} from '../vendor/emitter/EventEmitter';
import Blob from '../Blob/Blob';
import BlobManager from '../Blob/BlobManager';
import NativeEventEmitter from '../EventEmitter/NativeEventEmitter';
import binaryToBase64 from '../Utilities/binaryToBase64';
import Platform from '../Utilities/Platform';
import NativeWebSocketModule from './NativeWebSocketModule';
import WebSocketEvent from './WebSocketEvent';
import base64 from 'base64-js';
import EventTarget from 'event-target-shim';
import invariant from 'invariant';
type ArrayBufferView =
| Int8Array
| Uint8Array
| Uint8ClampedArray
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| Float32Array
| Float64Array
| DataView;
type BinaryType = 'blob' | 'arraybuffer';
const CONNECTING = 0;
const OPEN = 1;
const CLOSING = 2;
const CLOSED = 3;
const CLOSE_NORMAL = 1000;
// Abnormal closure where no code is provided in a control frame
// https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.5
const CLOSE_ABNORMAL = 1006;
const WEBSOCKET_EVENTS = ['close', 'error', 'message', 'open'];
let nextWebSocketId = 0;
type WebSocketEventDefinitions = {
websocketOpen: [{id: number, protocol: string}],
websocketClosed: [{id: number, code: number, reason: string}],
websocketMessage: [
| {type: 'binary', id: number, data: string}
| {type: 'text', id: number, data: string}
| {type: 'blob', id: number, data: BlobData},
],
websocketFailed: [{id: number, message: string}],
};
/**
* Browser-compatible WebSockets implementation.
*
* See https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
* See https://github.com/websockets/ws
*/
class WebSocket extends (EventTarget(...WEBSOCKET_EVENTS): any) {
static CONNECTING: number = CONNECTING;
static OPEN: number = OPEN;
static CLOSING: number = CLOSING;
static CLOSED: number = CLOSED;
CONNECTING: number = CONNECTING;
OPEN: number = OPEN;
CLOSING: number = CLOSING;
CLOSED: number = CLOSED;
_socketId: number;
_eventEmitter: NativeEventEmitter<WebSocketEventDefinitions>;
_subscriptions: Array<EventSubscription>;
_binaryType: ?BinaryType;
onclose: ?Function;
onerror: ?Function;
onmessage: ?Function;
onopen: ?Function;
bufferedAmount: number;
extension: ?string;
protocol: ?string;
readyState: number = CONNECTING;
url: ?string;
constructor(
url: string,
protocols: ?string | ?Array<string>,
options: ?{headers?: {origin?: string, ...}, ...},
) {
super();
this.url = url;
if (typeof protocols === 'string') {
protocols = [protocols];
}
const {headers = {}, ...unrecognized} = options || {};
// Preserve deprecated backwards compatibility for the 'origin' option
// $FlowFixMe[prop-missing]
if (unrecognized && typeof unrecognized.origin === 'string') {
console.warn(
'Specifying `origin` as a WebSocket connection option is deprecated. Include it under `headers` instead.',
);
/* $FlowFixMe[prop-missing] (>=0.54.0 site=react_native_fb,react_native_
* oss) This comment suppresses an error found when Flow v0.54 was
* deployed. To see the error delete this comment and run Flow. */
headers.origin = unrecognized.origin;
/* $FlowFixMe[prop-missing] (>=0.54.0 site=react_native_fb,react_native_
* oss) This comment suppresses an error found when Flow v0.54 was
* deployed. To see the error delete this comment and run Flow. */
delete unrecognized.origin;
}
// Warn about and discard anything else
if (Object.keys(unrecognized).length > 0) {
console.warn(
'Unrecognized WebSocket connection option(s) `' +
Object.keys(unrecognized).join('`, `') +
'`. ' +
'Did you mean to put these under `headers`?',
);
}
if (!Array.isArray(protocols)) {
protocols = null;
}
this._eventEmitter = new NativeEventEmitter(
// T88715063: NativeEventEmitter only used this parameter on iOS. Now it uses it on all platforms, so this code was modified automatically to preserve its behavior
// If you want to use the native module on other platforms, please remove this condition and test its behavior
Platform.OS !== 'ios' ? null : NativeWebSocketModule,
);
this._socketId = nextWebSocketId++;
this._registerEvents();
NativeWebSocketModule.connect(url, protocols, {headers}, this._socketId);
}
get binaryType(): ?BinaryType {
return this._binaryType;
}
set binaryType(binaryType: BinaryType): void {
if (binaryType !== 'blob' && binaryType !== 'arraybuffer') {
throw new Error("binaryType must be either 'blob' or 'arraybuffer'");
}
if (this._binaryType === 'blob' || binaryType === 'blob') {
invariant(
BlobManager.isAvailable,
'Native module BlobModule is required for blob support',
);
if (binaryType === 'blob') {
BlobManager.addWebSocketHandler(this._socketId);
} else {
BlobManager.removeWebSocketHandler(this._socketId);
}
}
this._binaryType = binaryType;
}
close(code?: number, reason?: string): void {
if (this.readyState === this.CLOSING || this.readyState === this.CLOSED) {
return;
}
this.readyState = this.CLOSING;
this._close(code, reason);
}
send(data: string | ArrayBuffer | ArrayBufferView | Blob): void {
if (this.readyState === this.CONNECTING) {
throw new Error('INVALID_STATE_ERR');
}
if (data instanceof Blob) {
invariant(
BlobManager.isAvailable,
'Native module BlobModule is required for blob support',
);
BlobManager.sendOverSocket(data, this._socketId);
return;
}
if (typeof data === 'string') {
NativeWebSocketModule.send(data, this._socketId);
return;
}
if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
NativeWebSocketModule.sendBinary(binaryToBase64(data), this._socketId);
return;
}
throw new Error('Unsupported data type');
}
ping(): void {
if (this.readyState === this.CONNECTING) {
throw new Error('INVALID_STATE_ERR');
}
NativeWebSocketModule.ping(this._socketId);
}
_close(code?: number, reason?: string): void {
// See https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
const statusCode = typeof code === 'number' ? code : CLOSE_NORMAL;
const closeReason = typeof reason === 'string' ? reason : '';
NativeWebSocketModule.close(statusCode, closeReason, this._socketId);
if (BlobManager.isAvailable && this._binaryType === 'blob') {
BlobManager.removeWebSocketHandler(this._socketId);
}
}
_unregisterEvents(): void {
this._subscriptions.forEach(e => e.remove());
this._subscriptions = [];
}
_registerEvents(): void {
this._subscriptions = [
this._eventEmitter.addListener('websocketMessage', ev => {
if (ev.id !== this._socketId) {
return;
}
let data: Blob | BlobData | ArrayBuffer | string = ev.data;
switch (ev.type) {
case 'binary':
data = base64.toByteArray(ev.data).buffer;
break;
case 'blob':
data = BlobManager.createFromOptions(ev.data);
break;
}
this.dispatchEvent(new WebSocketEvent('message', {data}));
}),
this._eventEmitter.addListener('websocketOpen', ev => {
if (ev.id !== this._socketId) {
return;
}
this.readyState = this.OPEN;
this.protocol = ev.protocol;
this.dispatchEvent(new WebSocketEvent('open'));
}),
this._eventEmitter.addListener('websocketClosed', ev => {
if (ev.id !== this._socketId) {
return;
}
this.readyState = this.CLOSED;
this.dispatchEvent(
new WebSocketEvent('close', {
code: ev.code,
reason: ev.reason,
// TODO: missing `wasClean` (exposed on iOS as `clean` but missing on Android)
}),
);
this._unregisterEvents();
this.close();
}),
this._eventEmitter.addListener('websocketFailed', ev => {
if (ev.id !== this._socketId) {
return;
}
this.readyState = this.CLOSED;
this.dispatchEvent(
new WebSocketEvent('error', {
message: ev.message,
}),
);
this.dispatchEvent(
new WebSocketEvent('close', {
code: CLOSE_ABNORMAL,
reason: ev.message,
// TODO: Expose `wasClean`
}),
);
this._unregisterEvents();
this.close();
}),
];
}
}
module.exports = WebSocket;

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
/**
* Event object passed to the `onopen`, `onclose`, `onmessage`, `onerror`
* callbacks of `WebSocket`.
*
* The `type` property is "open", "close", "message", "error" respectively.
*
* In case of "message", the `data` property contains the incoming data.
*/
class WebSocketEvent {
constructor(type, eventInitDict) {
this.type = type.toString();
Object.assign(this, eventInitDict);
}
}
module.exports = WebSocketEvent;

View File

@@ -0,0 +1,229 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import NativeEventEmitter from '../EventEmitter/NativeEventEmitter';
import Platform from '../Utilities/Platform';
import NativeWebSocketModule from './NativeWebSocketModule';
import base64 from 'base64-js';
const originalRCTWebSocketConnect = NativeWebSocketModule.connect;
const originalRCTWebSocketSend = NativeWebSocketModule.send;
const originalRCTWebSocketSendBinary = NativeWebSocketModule.sendBinary;
const originalRCTWebSocketClose = NativeWebSocketModule.close;
let eventEmitter;
let subscriptions;
let closeCallback;
let sendCallback;
let connectCallback;
let onOpenCallback;
let onMessageCallback;
let onErrorCallback;
let onCloseCallback;
let isInterceptorEnabled = false;
/**
* A network interceptor which monkey-patches RCTWebSocketModule methods
* to gather all websocket network requests/responses, in order to show
* their information in the React Native inspector development tool.
*/
const WebSocketInterceptor = {
/**
* Invoked when RCTWebSocketModule.close(...) is called.
*/
setCloseCallback(callback) {
closeCallback = callback;
},
/**
* Invoked when RCTWebSocketModule.send(...) or sendBinary(...) is called.
*/
setSendCallback(callback) {
sendCallback = callback;
},
/**
* Invoked when RCTWebSocketModule.connect(...) is called.
*/
setConnectCallback(callback) {
connectCallback = callback;
},
/**
* Invoked when event "websocketOpen" happens.
*/
setOnOpenCallback(callback) {
onOpenCallback = callback;
},
/**
* Invoked when event "websocketMessage" happens.
*/
setOnMessageCallback(callback) {
onMessageCallback = callback;
},
/**
* Invoked when event "websocketFailed" happens.
*/
setOnErrorCallback(callback) {
onErrorCallback = callback;
},
/**
* Invoked when event "websocketClosed" happens.
*/
setOnCloseCallback(callback) {
onCloseCallback = callback;
},
isInterceptorEnabled() {
return isInterceptorEnabled;
},
_unregisterEvents() {
subscriptions.forEach(e => e.remove());
subscriptions = [];
},
/**
* Add listeners to the RCTWebSocketModule events to intercept them.
*/
_registerEvents() {
subscriptions = [
eventEmitter.addListener('websocketMessage', ev => {
if (onMessageCallback) {
onMessageCallback(
ev.id,
ev.type === 'binary'
? WebSocketInterceptor._arrayBufferToString(ev.data)
: ev.data,
);
}
}),
eventEmitter.addListener('websocketOpen', ev => {
if (onOpenCallback) {
onOpenCallback(ev.id);
}
}),
eventEmitter.addListener('websocketClosed', ev => {
if (onCloseCallback) {
onCloseCallback(ev.id, {code: ev.code, reason: ev.reason});
}
}),
eventEmitter.addListener('websocketFailed', ev => {
if (onErrorCallback) {
onErrorCallback(ev.id, {message: ev.message});
}
}),
];
},
enableInterception() {
if (isInterceptorEnabled) {
return;
}
eventEmitter = new NativeEventEmitter(
// T88715063: NativeEventEmitter only used this parameter on iOS. Now it uses it on all platforms, so this code was modified automatically to preserve its behavior
// If you want to use the native module on other platforms, please remove this condition and test its behavior
Platform.OS !== 'ios' ? null : NativeWebSocketModule,
);
WebSocketInterceptor._registerEvents();
// Override `connect` method for all RCTWebSocketModule requests
// to intercept the request url, protocols, options and socketId,
// then pass them through the `connectCallback`.
NativeWebSocketModule.connect = function (
url,
protocols,
options,
socketId,
) {
if (connectCallback) {
connectCallback(url, protocols, options, socketId);
}
originalRCTWebSocketConnect.apply(this, arguments);
};
// Override `send` method for all RCTWebSocketModule requests to intercept
// the data sent, then pass them through the `sendCallback`.
NativeWebSocketModule.send = function (data, socketId) {
if (sendCallback) {
sendCallback(data, socketId);
}
originalRCTWebSocketSend.apply(this, arguments);
};
// Override `sendBinary` method for all RCTWebSocketModule requests to
// intercept the data sent, then pass them through the `sendCallback`.
NativeWebSocketModule.sendBinary = function (data, socketId) {
if (sendCallback) {
sendCallback(WebSocketInterceptor._arrayBufferToString(data), socketId);
}
originalRCTWebSocketSendBinary.apply(this, arguments);
};
// Override `close` method for all RCTWebSocketModule requests to intercept
// the close information, then pass them through the `closeCallback`.
NativeWebSocketModule.close = function () {
if (closeCallback) {
if (arguments.length === 3) {
closeCallback(arguments[0], arguments[1], arguments[2]);
} else {
closeCallback(null, null, arguments[0]);
}
}
originalRCTWebSocketClose.apply(this, arguments);
};
isInterceptorEnabled = true;
},
_arrayBufferToString(data) {
const value = base64.toByteArray(data).buffer;
if (value === undefined || value === null) {
return '(no value)';
}
if (
typeof ArrayBuffer !== 'undefined' &&
typeof Uint8Array !== 'undefined' &&
value instanceof ArrayBuffer
) {
return `ArrayBuffer {${String(Array.from(new Uint8Array(value)))}}`;
}
return value;
},
// Unpatch RCTWebSocketModule methods and remove the callbacks.
disableInterception() {
if (!isInterceptorEnabled) {
return;
}
isInterceptorEnabled = false;
NativeWebSocketModule.send = originalRCTWebSocketSend;
NativeWebSocketModule.sendBinary = originalRCTWebSocketSendBinary;
NativeWebSocketModule.close = originalRCTWebSocketClose;
NativeWebSocketModule.connect = originalRCTWebSocketConnect;
connectCallback = null;
closeCallback = null;
sendCallback = null;
onOpenCallback = null;
onMessageCallback = null;
onCloseCallback = null;
onErrorCallback = null;
WebSocketInterceptor._unregisterEvents();
},
};
module.exports = WebSocketInterceptor;

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
// Jest fatals for the following statement (minimal repro case)
//
// exports.something = Symbol;
//
// Until it is fixed, mocking the entire node module makes the
// problem go away.
'use strict';
function EventTarget() {
// Support both EventTarget and EventTarget(...)
// as a super class, just like the original module does.
if (arguments.length > 0) {
return EventTarget;
}
}
module.exports = EventTarget;