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 @@
*.d.ts

View File

@@ -0,0 +1,20 @@
env:
browser: true
commonjs: true
es6: false
extends: 'eslint:recommended'
parserOptions:
ecmaVersion: 5
rules:
indent:
- warn
- 2
linebreak-style:
- warn
- unix
quotes:
- warn
- single
semi:
- warn
- always

View File

@@ -0,0 +1,24 @@
{
"include": [
"**/*.js"
],
"exclude": [
"coverage/**",
"test/**",
"test{,-*}.js",
"**/*.test.js",
"**/__tests__/**",
"**/node_modules/**",
"**/*.spec.js"
],
"reporter": [
"lcov",
"text"
],
"check-coverage": true,
"per-file": false,
"statements": 100,
"branches": 100,
"functions": 100,
"lines": 100
}

View File

@@ -0,0 +1,3 @@
language: node_js
node_js:
- "10"

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Jon K. Bernhardsen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,127 @@
# fetch-retry
Adds retry functionality to the `Fetch` API.
It wraps any `Fetch` API package (eg: [isomorphic-fetch](https://github.com/matthew-andrews/isomorphic-fetch), [cross-fetch](https://github.com/lquixada/cross-fetch), [isomorphic-unfetch](https://github.com/developit/unfetch) and etc.) and retries requests that fail due to network issues. It can also be configured to retry requests on specific HTTP status codes.
[![Build Status](https://travis-ci.org/jonbern/fetch-retry.svg?branch=master)](https://travis-ci.org/jonbern/fetch-retry)
## npm package
```javascript
npm install fetch-retry --save
```
## Example
`fetch-retry` is used the same way as `fetch`, but also accepts `retries`, `retryDelay`, and `retryOn` on the `options` object.
These properties are optional, and unless different defaults have been specified when requiring `fetch-retry`, these will default to 3 retries, with a 1000ms retry delay, and to only retry on network errors.
```javascript
require('es6-promise').polyfill();
var originalFetch = require('isomorphic-fetch');
var fetch = require('fetch-retry')(originalFetch);
```
```javascript
fetch(url, {
retries: 3,
retryDelay: 1000
})
.then(function(response) {
return response.json();
})
.then(function(json) {
// do something with the result
console.log(json);
});
```
or passing your own defaults:
```javascript
var originalFetch = require('isomorphic-fetch');
var fetch = require('fetch-retry')(originalFetch, {
retries: 5,
retryDelay: 800
});
```
> `fetch-retry` uses promises and requires you to polyfill the Promise API in order to support Internet Explorer.
## Example: Exponential backoff
The default behavior of `fetch-retry` is to wait a fixed amount of time between attempts, but it is also possible to customize this by passing a function as the `retryDelay` option. The function is supplied three arguments: `attempt` (starting at 0), `error` (in case of a network error), and `response`. It must return a number indicating the delay.
```javascript
fetch(url, {
retryDelay: function(attempt, error, response) {
return Math.pow(2, attempt) * 1000; // 1000, 2000, 4000
}
}).then(function(response) {
return response.json();
}).then(function(json) {
// do something with the result
console.log(json);
});
```
## Example: Retry on 503 (Service Unavailable)
The default behavior of `fetch-retry` is to only retry requests on network related issues, but it is also possible to configure it to retry on specific HTTP status codes. This is done by using the `retryOn` property, which expects an array of HTTP status codes.
```javascript
fetch(url, {
retryOn: [503]
})
.then(function(response) {
return response.json();
})
.then(function(json) {
// do something with the result
console.log(json);
});
```
## Example: Retry custom behavior
The `retryOn` option may also be specified as a function, in which case it will be supplied three arguments: `attempt` (starting at 0), `error` (in case of a network error), and `response`. Return a truthy value from this function in order to trigger a retry, any falsy value will result in the call to fetch either resolving (in case the last attempt resulted in a response), or rejecting (in case the last attempt resulted in an error).
```javascript
fetch(url, {
retryOn: function(attempt, error, response) {
// retry on any network error, or 4xx or 5xx status codes
if (error !== null || response.status >= 400) {
console.log(`retrying, attempt number ${attempt + 1}`);
return true;
}
})
.then(function(response) {
return response.json();
}).then(function(json) {
// do something with the result
console.log(json);
});
```
## Example: Retry custom behavior with async
The `retryOn` option may also be used with async and await for calling asyncronous functions:
```javascript
fetch(url, {
retryOn: async function(attempt, error, response) {
if (attempt > 3) return false;
if (error !== null) {
var json = await response.json();
if (json.property !== undefined) {
return true;
}
}
})
.then(function(response) {
return response.json();
}).then(function(json) {
// do something with the result
console.log(json);
});
```

View File

@@ -0,0 +1,26 @@
/// <reference lib="dom" />
declare module 'fetch-retry' {
const _fetch: typeof fetch;
type RequestDelayFunction = ((
attempt: number,
error: Error | null,
response: Response | null
) => number);
type RequestRetryOnFunction = ((
attempt: number,
error: Error | null,
response: Response | null
) => boolean | Promise<boolean>);
interface IRequestInitWithRetry extends RequestInit {
retries?: number;
retryDelay?: number | RequestDelayFunction;
retryOn?: number[] | RequestRetryOnFunction;
}
function fetchBuilder(fetch: typeof _fetch, defaults?: object): ((input: RequestInfo, init?: IRequestInitWithRetry) => Promise<Response>);
export = fetchBuilder;
}

View File

@@ -0,0 +1,137 @@
'use strict';
module.exports = function (fetch, defaults) {
defaults = defaults || {};
if (typeof fetch !== 'function') {
throw new ArgumentError('fetch must be a function');
}
if (typeof defaults !== 'object') {
throw new ArgumentError('defaults must be an object');
}
if (defaults.retries !== undefined && !isPositiveInteger(defaults.retries)) {
throw new ArgumentError('retries must be a positive integer');
}
if (defaults.retryDelay !== undefined && !isPositiveInteger(defaults.retryDelay) && typeof defaults.retryDelay !== 'function') {
throw new ArgumentError('retryDelay must be a positive integer or a function returning a positive integer');
}
if (defaults.retryOn !== undefined && !Array.isArray(defaults.retryOn) && typeof defaults.retryOn !== 'function') {
throw new ArgumentError('retryOn property expects an array or function');
}
var baseDefaults = {
retries: 3,
retryDelay: 1000,
retryOn: [],
};
defaults = Object.assign(baseDefaults, defaults);
return function fetchRetry(input, init) {
var retries = defaults.retries;
var retryDelay = defaults.retryDelay;
var retryOn = defaults.retryOn;
if (init && init.retries !== undefined) {
if (isPositiveInteger(init.retries)) {
retries = init.retries;
} else {
throw new ArgumentError('retries must be a positive integer');
}
}
if (init && init.retryDelay !== undefined) {
if (isPositiveInteger(init.retryDelay) || (typeof init.retryDelay === 'function')) {
retryDelay = init.retryDelay;
} else {
throw new ArgumentError('retryDelay must be a positive integer or a function returning a positive integer');
}
}
if (init && init.retryOn) {
if (Array.isArray(init.retryOn) || (typeof init.retryOn === 'function')) {
retryOn = init.retryOn;
} else {
throw new ArgumentError('retryOn property expects an array or function');
}
}
// eslint-disable-next-line no-undef
return new Promise(function (resolve, reject) {
var wrappedFetch = function (attempt) {
fetch(input, init)
.then(function (response) {
if (Array.isArray(retryOn) && retryOn.indexOf(response.status) === -1) {
resolve(response);
} else if (typeof retryOn === 'function') {
try {
// eslint-disable-next-line no-undef
return Promise.resolve(retryOn(attempt, null, response))
.then(function (retryOnResponse) {
if(retryOnResponse) {
retry(attempt, null, response);
} else {
resolve(response);
}
}).catch(reject);
} catch (error) {
reject(error);
}
} else {
if (attempt < retries) {
retry(attempt, null, response);
} else {
resolve(response);
}
}
})
.catch(function (error) {
if (typeof retryOn === 'function') {
try {
// eslint-disable-next-line no-undef
Promise.resolve(retryOn(attempt, error, null))
.then(function (retryOnResponse) {
if(retryOnResponse) {
retry(attempt, error, null);
} else {
reject(error);
}
})
.catch(function(error) {
reject(error);
});
} catch(error) {
reject(error);
}
} else if (attempt < retries) {
retry(attempt, error, null);
} else {
reject(error);
}
});
};
function retry(attempt, error, response) {
var delay = (typeof retryDelay === 'function') ?
retryDelay(attempt, error, response) : retryDelay;
setTimeout(function () {
wrappedFetch(++attempt);
}, delay);
}
wrappedFetch(0);
});
};
};
function isPositiveInteger(value) {
return Number.isInteger(value) && value >= 0;
}
function ArgumentError(message) {
this.name = 'ArgumentError';
this.message = message;
}

View File

@@ -0,0 +1,35 @@
{
"name": "fetch-retry",
"version": "4.1.1",
"description": "Extend any fetch library with retry functionality",
"repository": "https://github.com/jonbern/fetch-retry.git",
"main": "index.js",
"types": "index.d.ts",
"scripts": {
"lint": "eslint index.js ./test/**/*.js",
"test": "nyc mocha test/**/**.js && npm run lint",
"integration-test": "mocha test/integration/"
},
"keywords": [
"fetch",
"retry",
"http",
"retry",
"Fetch API"
],
"author": "Jon K. Bernhardsen",
"license": "MIT",
"dependencies": {},
"devDependencies": {
"body-parser": "^1.19.0",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"eslint": "^6.8.0",
"expectations": "^0.7.1",
"express": "^4.17.1",
"isomorphic-fetch": "^3.0.0",
"mocha": "^8.0.1",
"nyc": "^14.1.1",
"sinon": "^6.3.5"
}
}

View File

@@ -0,0 +1,7 @@
env:
node: true
mocha: true
parserOptions:
ecmaVersion: 10
rules:
no-console: 0

View File

@@ -0,0 +1,194 @@
'use strict';
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
chai.should();
const childProcess = require('child_process');
const fetch = require('isomorphic-fetch');
const fetchRetry = require('../../')(fetch);
describe('fetch-retry integration tests', () => {
const baseUrl = 'http://localhost:3000/mock';
before(() => {
const process = childProcess.fork('./test/integration/mock-api/index.js');
process.on('error', err => {
console.log(err);
});
});
after(() => {
return fetchRetry(baseUrl + '/stop', {
method: 'POST'
});
});
const setupResponses = (responses) => {
return fetchRetry(baseUrl, {
method: 'POST',
body: JSON.stringify(responses),
headers: {
'content-type': 'application/json'
}
});
};
const getCallCount = () => {
return fetchRetry(`${baseUrl}/calls`)
.then(response => {
return response.text();
})
.then(text => {
return Number.parseInt(text);
});
};
[200, 503, 404].forEach(statusCode => {
describe('when endpoint returns ' + statusCode, () => {
before(() => {
return setupResponses([statusCode]);
});
it('does not retry request', () => {
return fetchRetry(baseUrl)
.then(getCallCount)
.should.eventually.equal(1);
});
});
});
describe('when configured to retry on a specific HTTP code', () => {
describe('and it never succeeds', () => {
const retryOn = [503];
beforeEach(() => {
return setupResponses([503, 503, 503, 503]);
});
it('retries the request #retries times', () => {
const init = {
retries: 3,
retryDelay: 100,
retryOn
};
const expectedCallCount = init.retries + 1;
return fetchRetry(baseUrl, init)
.then(getCallCount)
.should.eventually.equal(expectedCallCount);
});
it('eventually resolves the promise with the response of the last request', () => {
const init = {
retries: 3,
retryDelay: 100,
retryOn
};
const expectedResponse = {
status: 503,
ok: false
};
return fetchRetry(baseUrl, init)
.then(response => {
return {
status: response.status,
ok: response.ok
};
})
.should.become(expectedResponse);
});
});
describe('and it eventually succeeds', () => {
const retryOnStatus = 503;
const responses = [503, 503, 200];
const requestsToRetry = responses
.filter(response => response === retryOnStatus)
.length;
beforeEach(() => {
return setupResponses(responses);
});
it('retries the request up to #retries times', () => {
const init = {
retries: 3,
retryDelay: 100,
retryOn: [retryOnStatus]
};
const expectedCallCount = requestsToRetry + 1;
return fetchRetry(baseUrl, init)
.then(getCallCount)
.should.eventually.equal(expectedCallCount);
});
it('eventually resolves the promise with the received response of the last request', () => {
const init = {
retries: 3,
retryDelay: 100,
retryOn: [retryOnStatus]
};
const expectedResponse = {
status: 200,
ok: true
};
return fetchRetry(baseUrl, init)
.then(response => {
return {
status: response.status,
ok: response.ok
};
})
.should.become(expectedResponse);
});
});
});
describe('when configured to retry on a set of HTTP codes', () => {
describe('and it never succeeds', () => {
const retryOn = [503, 404];
beforeEach(() => {
return setupResponses([503, 404, 404, 503]);
});
it('retries the request #retries times', () => {
const init = {
retries: 3,
retryDelay: 100,
retryOn
};
const expectedCallCount = init.retries + 1;
return fetchRetry(baseUrl, init)
.then(getCallCount)
.should.eventually.equal(expectedCallCount);
});
});
});
});

View File

@@ -0,0 +1,42 @@
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
let server;
let calls = 0;
let responses = [];
app.post('/mock', (req, res) => {
responses = req.body;
calls = 0;
res.status(200).send(responses);
});
app.get('/mock', (req, res) => {
if (responses.length === 0) {
res.status(501).send('Mock API is not configured to return anything');
} else {
calls++;
res.status(responses.shift()).send();
}
});
app.get('/mock/calls', (req, res) => {
res.status(200).send(calls.toString());
});
app.post('/mock/stop', (req, res) => {
res.status(200).send();
server.close();
});
server = app.listen(3000);

File diff suppressed because it is too large Load Diff