@sincpro/mobile 0.1.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.
- package/README.md +74 -0
- package/dist/adapters/Bluetooth.adapter.d.ts +14 -0
- package/dist/adapters/Bluetooth.adapter.js +100 -0
- package/dist/adapters/Geo.adapter.d.ts +8 -0
- package/dist/adapters/Geo.adapter.js +30 -0
- package/dist/adapters/JsonSerializer.adapter.d.ts +28 -0
- package/dist/adapters/JsonSerializer.adapter.js +47 -0
- package/dist/adapters/Network.adapter.d.ts +4 -0
- package/dist/adapters/Network.adapter.js +13 -0
- package/dist/adapters/ReceiptExporter.adapter.d.ts +19 -0
- package/dist/adapters/ReceiptExporter.adapter.js +142 -0
- package/dist/adapters/repositories/database_table.repository.d.ts +13 -0
- package/dist/adapters/repositories/database_table.repository.js +29 -0
- package/dist/adapters/repositories/domain_event.repository.d.ts +32 -0
- package/dist/adapters/repositories/domain_event.repository.js +147 -0
- package/dist/adapters/repositories/domain_event_dead_letter.repository.d.ts +23 -0
- package/dist/adapters/repositories/domain_event_dead_letter.repository.js +81 -0
- package/dist/adapters/repositories/setting.repository.d.ts +10 -0
- package/dist/adapters/repositories/setting.repository.js +21 -0
- package/dist/adapters/webview.adapter.d.ts +10 -0
- package/dist/adapters/webview.adapter.js +166 -0
- package/dist/domain/connectivity/bluetooth.d.ts +19 -0
- package/dist/domain/connectivity/bluetooth.js +1 -0
- package/dist/domain/connectivity/events.d.ts +17 -0
- package/dist/domain/connectivity/events.js +17 -0
- package/dist/domain/connectivity/geo.d.ts +4 -0
- package/dist/domain/connectivity/geo.js +1 -0
- package/dist/domain/connectivity/index.d.ts +3 -0
- package/dist/domain/connectivity/index.js +3 -0
- package/dist/domain/connectivity/network.d.ts +7 -0
- package/dist/domain/connectivity/network.js +1 -0
- package/dist/domain/database/database.d.ts +16 -0
- package/dist/domain/database/database.js +1 -0
- package/dist/domain/database/index.d.ts +2 -0
- package/dist/domain/database/index.js +2 -0
- package/dist/domain/database/repository.d.ts +19 -0
- package/dist/domain/database/repository.js +1 -0
- package/dist/domain/entity/entity.d.ts +109 -0
- package/dist/domain/entity/entity.js +246 -0
- package/dist/domain/entity/entity_collection.d.ts +396 -0
- package/dist/domain/entity/entity_collection.js +824 -0
- package/dist/domain/entity/index.d.ts +3 -0
- package/dist/domain/entity/index.js +3 -0
- package/dist/domain/entity/value_object.d.ts +12 -0
- package/dist/domain/entity/value_object.js +39 -0
- package/dist/domain/event_sourcing/domain_event.d.ts +58 -0
- package/dist/domain/event_sourcing/domain_event.js +164 -0
- package/dist/domain/event_sourcing/event.d.ts +25 -0
- package/dist/domain/event_sourcing/event.js +25 -0
- package/dist/domain/event_sourcing/event_handler.d.ts +7 -0
- package/dist/domain/event_sourcing/event_handler.js +13 -0
- package/dist/domain/event_sourcing/index.d.ts +2 -0
- package/dist/domain/event_sourcing/index.js +2 -0
- package/dist/domain/events.d.ts +33 -0
- package/dist/domain/events.js +32 -0
- package/dist/domain/icon.d.ts +1 -0
- package/dist/domain/icon.js +1 -0
- package/dist/domain/index.d.ts +9 -0
- package/dist/domain/index.js +9 -0
- package/dist/domain/print/driver_registry.d.ts +4 -0
- package/dist/domain/print/driver_registry.js +29 -0
- package/dist/domain/print/events.d.ts +13 -0
- package/dist/domain/print/events.js +13 -0
- package/dist/domain/print/index.d.ts +2 -0
- package/dist/domain/print/index.js +1 -0
- package/dist/domain/print/printer.d.ts +35 -0
- package/dist/domain/print/printer.js +1 -0
- package/dist/domain/receipt.d.ts +31 -0
- package/dist/domain/receipt.js +1 -0
- package/dist/domain/repositories.d.ts +6 -0
- package/dist/domain/repositories.js +7 -0
- package/dist/domain/settings.d.ts +7 -0
- package/dist/domain/settings.js +1 -0
- package/dist/domain/webview/events.d.ts +11 -0
- package/dist/domain/webview/events.js +11 -0
- package/dist/domain/webview/index.d.ts +1 -0
- package/dist/domain/webview/index.js +1 -0
- package/dist/domain/webview/webview.d.ts +57 -0
- package/dist/domain/webview/webview.js +9 -0
- package/dist/entrypoints/cron/Cron.d.ts +13 -0
- package/dist/entrypoints/cron/Cron.js +57 -0
- package/dist/entrypoints/cron/checkNetworkStatus.cron.d.ts +3 -0
- package/dist/entrypoints/cron/checkNetworkStatus.cron.js +10 -0
- package/dist/entrypoints/db/index.d.ts +2 -0
- package/dist/entrypoints/db/index.js +2 -0
- package/dist/entrypoints/db/migrations.d.ts +10 -0
- package/dist/entrypoints/db/migrations.js +115 -0
- package/dist/entrypoints/db/repositories.d.ts +8 -0
- package/dist/entrypoints/db/repositories.js +34 -0
- package/dist/entrypoints/queue/QueueProcessor.d.ts +12 -0
- package/dist/entrypoints/queue/QueueProcessor.js +75 -0
- package/dist/entrypoints/queue/activateDomain.subscriber.d.ts +7 -0
- package/dist/entrypoints/queue/activateDomain.subscriber.js +15 -0
- package/dist/entrypoints/queue/newAppSettings.handler.d.ts +7 -0
- package/dist/entrypoints/queue/newAppSettings.handler.js +17 -0
- package/dist/entrypoints/queue/printImage.subscriber.d.ts +7 -0
- package/dist/entrypoints/queue/printImage.subscriber.js +14 -0
- package/dist/entrypoints/queue/processWebViewMessage.subscriber.d.ts +7 -0
- package/dist/entrypoints/queue/processWebViewMessage.subscriber.js +23 -0
- package/dist/entrypoints/ui/AppShell.d.ts +15 -0
- package/dist/entrypoints/ui/AppShell.js +58 -0
- package/dist/entrypoints/ui/common_provider.d.ts +35 -0
- package/dist/entrypoints/ui/common_provider.js +244 -0
- package/dist/entrypoints/ui/domain_switcher.d.ts +19 -0
- package/dist/entrypoints/ui/domain_switcher.js +22 -0
- package/dist/entrypoints/ui/theme.d.ts +15 -0
- package/dist/entrypoints/ui/theme.js +16 -0
- package/dist/exceptions.d.ts +22 -0
- package/dist/exceptions.js +60 -0
- package/dist/framework/base_module.d.ts +14 -0
- package/dist/framework/base_module.js +40 -0
- package/dist/framework/createApp.d.ts +6 -0
- package/dist/framework/createApp.js +9 -0
- package/dist/framework/domain_module.d.ts +13 -0
- package/dist/framework/domain_module.js +18 -0
- package/dist/framework/kernel.d.ts +18 -0
- package/dist/framework/kernel.js +60 -0
- package/dist/framework/orchestrator.d.ts +29 -0
- package/dist/framework/orchestrator.js +118 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +11 -0
- package/dist/infrastructure/database/connector.d.ts +13 -0
- package/dist/infrastructure/database/connector.js +165 -0
- package/dist/infrastructure/database/index.d.ts +2 -0
- package/dist/infrastructure/database/index.js +2 -0
- package/dist/infrastructure/database/mapped.d.ts +128 -0
- package/dist/infrastructure/database/mapped.js +174 -0
- package/dist/infrastructure/database/utils.d.ts +10 -0
- package/dist/infrastructure/database/utils.js +35 -0
- package/dist/infrastructure/logger.d.ts +45 -0
- package/dist/infrastructure/logger.js +125 -0
- package/dist/infrastructure/ui/ToastHost.d.ts +1 -0
- package/dist/infrastructure/ui/ToastHost.js +19 -0
- package/dist/infrastructure/ui/UIEventBus.d.ts +12 -0
- package/dist/infrastructure/ui/UIEventBus.js +65 -0
- package/dist/infrastructure/ui/errorHandler.d.ts +1 -0
- package/dist/infrastructure/ui/errorHandler.js +20 -0
- package/dist/infrastructure/ui/events.d.ts +1 -0
- package/dist/infrastructure/ui/events.js +1 -0
- package/dist/infrastructure/workers/CronWorker.d.ts +42 -0
- package/dist/infrastructure/workers/CronWorker.js +143 -0
- package/dist/infrastructure/workers/EventBus.d.ts +67 -0
- package/dist/infrastructure/workers/EventBus.js +279 -0
- package/dist/infrastructure/workers/index.d.ts +2 -0
- package/dist/infrastructure/workers/index.js +2 -0
- package/dist/services/bluetooth.service.d.ts +14 -0
- package/dist/services/bluetooth.service.js +72 -0
- package/dist/services/database_table.service.d.ts +8 -0
- package/dist/services/database_table.service.js +19 -0
- package/dist/services/dead_letter_queue.service.d.ts +38 -0
- package/dist/services/dead_letter_queue.service.js +99 -0
- package/dist/services/event.service.d.ts +7 -0
- package/dist/services/event.service.js +24 -0
- package/dist/services/network.service.d.ts +7 -0
- package/dist/services/network.service.js +43 -0
- package/dist/services/printer.service.d.ts +25 -0
- package/dist/services/printer.service.js +220 -0
- package/dist/services/webview.service.d.ts +17 -0
- package/dist/services/webview.service.js +59 -0
- package/dist/tools/utils/Initials.d.ts +1 -0
- package/dist/tools/utils/Initials.js +12 -0
- package/dist/tools/utils/collections.d.ts +120 -0
- package/dist/tools/utils/collections.js +158 -0
- package/dist/tools/utils/date.d.ts +70 -0
- package/dist/tools/utils/date.js +126 -0
- package/dist/tools/utils/maps.d.ts +4 -0
- package/dist/tools/utils/maps.js +20 -0
- package/dist/tools/utils/monetary.d.ts +3 -0
- package/dist/tools/utils/monetary.js +31 -0
- package/dist/tools/utils/quantity.d.ts +11 -0
- package/dist/tools/utils/quantity.js +44 -0
- package/dist/tools/utils/searchTools.d.ts +39 -0
- package/dist/tools/utils/searchTools.js +56 -0
- package/dist/tools/utils/serializer.d.ts +2 -0
- package/dist/tools/utils/serializer.js +44 -0
- package/dist/ui/components/atoms/DebugBanner.d.ts +2 -0
- package/dist/ui/components/atoms/DebugBanner.js +15 -0
- package/dist/ui/components/atoms/index.d.ts +1 -0
- package/dist/ui/components/atoms/index.js +1 -0
- package/dist/ui/components/molecules/BluetoothDeviceSelectorModal.d.ts +8 -0
- package/dist/ui/components/molecules/BluetoothDeviceSelectorModal.js +61 -0
- package/dist/ui/components/molecules/DeadLetterErrorBlock.d.ts +8 -0
- package/dist/ui/components/molecules/DeadLetterErrorBlock.js +14 -0
- package/dist/ui/components/molecules/EventTimelineItem.d.ts +7 -0
- package/dist/ui/components/molecules/EventTimelineItem.js +65 -0
- package/dist/ui/components/molecules/ProcessToast.d.ts +5 -0
- package/dist/ui/components/molecules/ProcessToast.js +51 -0
- package/dist/ui/components/molecules/ProcessToastProvider.d.ts +4 -0
- package/dist/ui/components/molecules/ProcessToastProvider.js +5 -0
- package/dist/ui/components/molecules/index.d.ts +5 -0
- package/dist/ui/components/molecules/index.js +5 -0
- package/dist/ui/components/organisms/BluetoothPrinterSelector.d.ts +7 -0
- package/dist/ui/components/organisms/BluetoothPrinterSelector.js +92 -0
- package/dist/ui/components/organisms/DatabaseInfoRow.d.ts +11 -0
- package/dist/ui/components/organisms/DatabaseInfoRow.js +8 -0
- package/dist/ui/components/organisms/DeadLetterQueueRow.d.ts +7 -0
- package/dist/ui/components/organisms/DeadLetterQueueRow.js +72 -0
- package/dist/ui/components/organisms/EventRow.d.ts +7 -0
- package/dist/ui/components/organisms/EventRow.js +90 -0
- package/dist/ui/components/organisms/InjectableWebView.d.ts +35 -0
- package/dist/ui/components/organisms/InjectableWebView.js +169 -0
- package/dist/ui/components/organisms/Receipt.d.ts +11 -0
- package/dist/ui/components/organisms/Receipt.js +207 -0
- package/dist/ui/components/organisms/TableInfoInfoRow.d.ts +9 -0
- package/dist/ui/components/organisms/TableInfoInfoRow.js +19 -0
- package/dist/ui/components/organisms/index.d.ts +7 -0
- package/dist/ui/components/organisms/index.js +7 -0
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.js +4 -0
- package/dist/ui/layouts/router_layouts.d.ts +17 -0
- package/dist/ui/layouts/router_layouts.js +20 -0
- package/dist/ui/screens/database/database.context.d.ts +32 -0
- package/dist/ui/screens/database/database.context.js +158 -0
- package/dist/ui/screens/database/database.json_detail.d.ts +2 -0
- package/dist/ui/screens/database/database.json_detail.js +19 -0
- package/dist/ui/screens/database/database.list.d.ts +1 -0
- package/dist/ui/screens/database/database.list.js +41 -0
- package/dist/ui/screens/database/database.table_rows.d.ts +2 -0
- package/dist/ui/screens/database/database.table_rows.js +10 -0
- package/dist/ui/screens/dead_letter_queue/dead_letter_queue.context.d.ts +34 -0
- package/dist/ui/screens/dead_letter_queue/dead_letter_queue.context.js +166 -0
- package/dist/ui/screens/dead_letter_queue/dead_letter_queue.list.d.ts +2 -0
- package/dist/ui/screens/dead_letter_queue/dead_letter_queue.list.js +24 -0
- package/dist/ui/screens/events/events.context.d.ts +25 -0
- package/dist/ui/screens/events/events.context.js +113 -0
- package/dist/ui/screens/events/events.list.d.ts +1 -0
- package/dist/ui/screens/events/events.list.js +26 -0
- package/dist/ui/screens/events/index.d.ts +1 -0
- package/dist/ui/screens/events/index.js +1 -0
- package/dist/ui/screens/index.d.ts +3 -0
- package/dist/ui/screens/index.js +3 -0
- package/package.json +125 -0
package/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# @sincpro/mobile
|
|
2
|
+
|
|
3
|
+
Core del framework móvil Sincpro (React Native / Expo): infraestructura offline-first (DB, cola/eventos, cron), patrones DDD, adapters base, composición module-driven y el `AppShell`. Consume el design system `@sincpro/mobile-ui`.
|
|
4
|
+
|
|
5
|
+
## Instalación
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx expo install @sincpro/mobile @sincpro/mobile-ui
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`@sincpro/mobile-ui` es peer dependency (lo provee la app). Para integrar Odoo: `@sincpro/mobile-odoo` (opcional).
|
|
12
|
+
|
|
13
|
+
## Instanciar una app
|
|
14
|
+
|
|
15
|
+
Una app define sus dominios como **clases que extienden `DomainModule`** y las compone con `createAppShell`:
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { createAppShell, createTheme, DomainModule } from "@sincpro/mobile";
|
|
19
|
+
import type { Subscriber } from "@sincpro/mobile/domain/subscriber";
|
|
20
|
+
|
|
21
|
+
class VentasModule extends DomainModule {
|
|
22
|
+
readonly key = "VENTAS";
|
|
23
|
+
readonly name = "Ventas";
|
|
24
|
+
|
|
25
|
+
override subscribers(): Subscriber[] {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const ventasModule = new VentasModule();
|
|
31
|
+
|
|
32
|
+
export default createAppShell({
|
|
33
|
+
theme: createTheme({ primary: "#0EA5E9" }),
|
|
34
|
+
domains: [ventasModule],
|
|
35
|
+
ui: { [ventasModule.key]: VentasScreen },
|
|
36
|
+
activeDomain: ventasModule.key,
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`createAppShell` arranca la infraestructura (migraciones, EventBus, cron, orchestrator), carga fuentes, aplica el tema (a `className` y `style`) y monta el `DomainSwitcher`.
|
|
41
|
+
|
|
42
|
+
## API pública
|
|
43
|
+
|
|
44
|
+
- `createAppShell(config)` — composition root de la app (theme, domains, ui, activeDomain, providers).
|
|
45
|
+
- `DomainModule` — clase base abstracta de un dominio (`key`, `name`, `shared?`, `repositories()`, `migrations()`, `subscribers()`, `crons()`, `persistOnReset()`).
|
|
46
|
+
- `createTheme(overrides?)` — theme tipado (defaults del framework + overrides parciales).
|
|
47
|
+
- `createApp(config)` / `Kernel` / `orchestrator` — capa baja de composición (bootstrap sin UI).
|
|
48
|
+
- `BaseModule` / `baseModule` — módulo común (COMMON) que el framework registra por defecto.
|
|
49
|
+
- `CommonProvider` / `useCommon` — estado común (debug, geo, timezone, actividad de cola/cron).
|
|
50
|
+
- `PlainLayout` / `TabNavigatorLayout` — layouts conectados a react-router.
|
|
51
|
+
- `installGlobalErrorHandler()` — handler global (errores de dominio → Alert).
|
|
52
|
+
|
|
53
|
+
## Subpaths
|
|
54
|
+
|
|
55
|
+
Las primitivas y capas se importan por subpath, p. ej.:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { DomainModule } from "@sincpro/mobile/entrypoints/app/domain_module";
|
|
59
|
+
import type { Subscriber } from "@sincpro/mobile/domain/subscriber";
|
|
60
|
+
import { setPrinterDriver } from "@sincpro/mobile/domain/print";
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Impresora (opcional)
|
|
64
|
+
|
|
65
|
+
El core es agnóstico del hardware: define el puerto `IPrinterDriver` (`@sincpro/mobile/domain/print`). La app registra un driver concreto al bootstrap:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { setPrinterDriver } from "@sincpro/mobile/domain/print";
|
|
69
|
+
setPrinterDriver(miDriver);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Desarrollo
|
|
73
|
+
|
|
74
|
+
Todo vía Makefile: `make build` (tsc → `lib/`), `make check` (lint + typecheck), `make format`, `make publish`.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BluetoothPermissionStatus, BluetoothStatus } from "../domain/connectivity";
|
|
2
|
+
declare class BluetoothAdapterImpl {
|
|
3
|
+
requestPermissions(): Promise<BluetoothPermissionStatus>;
|
|
4
|
+
hasPermissions(): Promise<BluetoothPermissionStatus>;
|
|
5
|
+
getStatus(): Promise<BluetoothStatus>;
|
|
6
|
+
isAndroid12OrHigher(): boolean;
|
|
7
|
+
getPlatform(): "android" | "ios" | "other";
|
|
8
|
+
private requestAndroidPermissions;
|
|
9
|
+
private checkAndroidPermissions;
|
|
10
|
+
private requestIOSPermissions;
|
|
11
|
+
private checkIOSPermissions;
|
|
12
|
+
}
|
|
13
|
+
export declare const BluetoothAdapter: BluetoothAdapterImpl;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { PermissionsAndroid, Platform } from "react-native";
|
|
2
|
+
const ANDROID_12_API_LEVEL = 31;
|
|
3
|
+
class BluetoothAdapterImpl {
|
|
4
|
+
async requestPermissions() {
|
|
5
|
+
if (Platform.OS === "android") {
|
|
6
|
+
return this.requestAndroidPermissions();
|
|
7
|
+
}
|
|
8
|
+
if (Platform.OS === "ios") {
|
|
9
|
+
return this.requestIOSPermissions();
|
|
10
|
+
}
|
|
11
|
+
return { scan: false, connect: false, advertise: false, isGranted: false };
|
|
12
|
+
}
|
|
13
|
+
async hasPermissions() {
|
|
14
|
+
if (Platform.OS === "android") {
|
|
15
|
+
return this.checkAndroidPermissions();
|
|
16
|
+
}
|
|
17
|
+
if (Platform.OS === "ios") {
|
|
18
|
+
return this.checkIOSPermissions();
|
|
19
|
+
}
|
|
20
|
+
return { scan: false, connect: false, advertise: false, isGranted: false };
|
|
21
|
+
}
|
|
22
|
+
async getStatus() {
|
|
23
|
+
const permissions = await this.hasPermissions();
|
|
24
|
+
const state = permissions.isGranted ? "PoweredOn" : "Unauthorized";
|
|
25
|
+
return {
|
|
26
|
+
isEnabled: permissions.isGranted,
|
|
27
|
+
state,
|
|
28
|
+
permissions,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
isAndroid12OrHigher() {
|
|
32
|
+
return Platform.OS === "android" && Platform.Version >= ANDROID_12_API_LEVEL;
|
|
33
|
+
}
|
|
34
|
+
getPlatform() {
|
|
35
|
+
if (Platform.OS === "android")
|
|
36
|
+
return "android";
|
|
37
|
+
if (Platform.OS === "ios")
|
|
38
|
+
return "ios";
|
|
39
|
+
return "other";
|
|
40
|
+
}
|
|
41
|
+
async requestAndroidPermissions() {
|
|
42
|
+
if (Platform.OS !== "android") {
|
|
43
|
+
return { scan: true, connect: true, advertise: true, isGranted: true };
|
|
44
|
+
}
|
|
45
|
+
if (Platform.Version < ANDROID_12_API_LEVEL) {
|
|
46
|
+
const locationGranted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION);
|
|
47
|
+
const isGranted = locationGranted === PermissionsAndroid.RESULTS.GRANTED;
|
|
48
|
+
return { scan: isGranted, connect: isGranted, advertise: isGranted, isGranted };
|
|
49
|
+
}
|
|
50
|
+
const results = await PermissionsAndroid.requestMultiple([
|
|
51
|
+
PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
|
|
52
|
+
PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
|
|
53
|
+
PermissionsAndroid.PERMISSIONS.BLUETOOTH_ADVERTISE,
|
|
54
|
+
]);
|
|
55
|
+
const scan = results[PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN] ===
|
|
56
|
+
PermissionsAndroid.RESULTS.GRANTED;
|
|
57
|
+
const connect = results[PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT] ===
|
|
58
|
+
PermissionsAndroid.RESULTS.GRANTED;
|
|
59
|
+
const advertise = results[PermissionsAndroid.PERMISSIONS.BLUETOOTH_ADVERTISE] ===
|
|
60
|
+
PermissionsAndroid.RESULTS.GRANTED;
|
|
61
|
+
return {
|
|
62
|
+
scan,
|
|
63
|
+
connect,
|
|
64
|
+
advertise,
|
|
65
|
+
isGranted: scan && connect,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async checkAndroidPermissions() {
|
|
69
|
+
if (Platform.OS !== "android") {
|
|
70
|
+
return { scan: true, connect: true, advertise: true, isGranted: true };
|
|
71
|
+
}
|
|
72
|
+
if (Platform.Version < ANDROID_12_API_LEVEL) {
|
|
73
|
+
const locationGranted = await PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION);
|
|
74
|
+
return {
|
|
75
|
+
scan: locationGranted,
|
|
76
|
+
connect: locationGranted,
|
|
77
|
+
advertise: locationGranted,
|
|
78
|
+
isGranted: locationGranted,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const [scan, connect, advertise] = await Promise.all([
|
|
82
|
+
PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN),
|
|
83
|
+
PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT),
|
|
84
|
+
PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.BLUETOOTH_ADVERTISE),
|
|
85
|
+
]);
|
|
86
|
+
return {
|
|
87
|
+
scan,
|
|
88
|
+
connect,
|
|
89
|
+
advertise,
|
|
90
|
+
isGranted: scan && connect,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
async requestIOSPermissions() {
|
|
94
|
+
return { scan: true, connect: true, advertise: true, isGranted: true };
|
|
95
|
+
}
|
|
96
|
+
async checkIOSPermissions() {
|
|
97
|
+
return { scan: true, connect: true, advertise: true, isGranted: true };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export const BluetoothAdapter = new BluetoothAdapterImpl();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Coordinates } from "../domain/connectivity";
|
|
2
|
+
import * as Location from "expo-location";
|
|
3
|
+
export declare const GeoAdapter: {
|
|
4
|
+
requestPermission(): Promise<boolean>;
|
|
5
|
+
hasPermission(): Promise<boolean>;
|
|
6
|
+
getCurrentLocation(): Promise<Coordinates>;
|
|
7
|
+
watchLocation(onUpdate: (coords: Coordinates) => void): Promise<Location.LocationSubscription>;
|
|
8
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as Location from "expo-location";
|
|
2
|
+
export const GeoAdapter = {
|
|
3
|
+
async requestPermission() {
|
|
4
|
+
const { status } = await Location.requestForegroundPermissionsAsync();
|
|
5
|
+
return status === Location.PermissionStatus.GRANTED;
|
|
6
|
+
},
|
|
7
|
+
async hasPermission() {
|
|
8
|
+
const { status } = await Location.getForegroundPermissionsAsync();
|
|
9
|
+
return status === Location.PermissionStatus.GRANTED;
|
|
10
|
+
},
|
|
11
|
+
async getCurrentLocation() {
|
|
12
|
+
const { coords } = await Location.getCurrentPositionAsync({});
|
|
13
|
+
return {
|
|
14
|
+
latitude: coords.latitude,
|
|
15
|
+
longitude: coords.longitude,
|
|
16
|
+
};
|
|
17
|
+
},
|
|
18
|
+
async watchLocation(onUpdate) {
|
|
19
|
+
return await Location.watchPositionAsync({
|
|
20
|
+
accuracy: Location.Accuracy.High,
|
|
21
|
+
timeInterval: 5000,
|
|
22
|
+
distanceInterval: 10,
|
|
23
|
+
}, (location) => {
|
|
24
|
+
onUpdate({
|
|
25
|
+
latitude: location.coords.latitude,
|
|
26
|
+
longitude: location.coords.longitude,
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
type JsonValue = string | number | boolean | null | JsonObject | JsonArray;
|
|
2
|
+
interface JsonObject {
|
|
3
|
+
[key: string]: JsonValue;
|
|
4
|
+
}
|
|
5
|
+
type JsonArray = JsonValue[];
|
|
6
|
+
/**
|
|
7
|
+
* Adapter for generating canonical JSON and SHA-256 hashes
|
|
8
|
+
* in a deep, deterministic manner without external dependencies.
|
|
9
|
+
*/
|
|
10
|
+
export declare const JsonSerializerAdapter: {
|
|
11
|
+
/**
|
|
12
|
+
* Normalize special types (Date, etc.) into JSON-friendly structures.
|
|
13
|
+
*/
|
|
14
|
+
cleanData<T extends JsonValue>(data: T): JsonValue;
|
|
15
|
+
/**
|
|
16
|
+
* Recursively sort object keys and serialize without extra whitespace.
|
|
17
|
+
*/
|
|
18
|
+
canonicalize(value: JsonValue): string;
|
|
19
|
+
/**
|
|
20
|
+
* Convert a RoutePlan object to its canonical JSON string.
|
|
21
|
+
*/
|
|
22
|
+
convertToCanonicalJson(jsObj: any): string;
|
|
23
|
+
/**
|
|
24
|
+
* Compute the SHA-256 hash of the canonical JSON.
|
|
25
|
+
*/
|
|
26
|
+
calculateHash(jsObj: any): Promise<string>;
|
|
27
|
+
};
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as Crypto from "expo-crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Adapter for generating canonical JSON and SHA-256 hashes
|
|
4
|
+
* in a deep, deterministic manner without external dependencies.
|
|
5
|
+
*/
|
|
6
|
+
export const JsonSerializerAdapter = {
|
|
7
|
+
/**
|
|
8
|
+
* Normalize special types (Date, etc.) into JSON-friendly structures.
|
|
9
|
+
*/
|
|
10
|
+
cleanData(data) {
|
|
11
|
+
const replacer = (_key, value) => value instanceof Date ? value.toISOString() : value;
|
|
12
|
+
return JSON.parse(JSON.stringify(data, replacer));
|
|
13
|
+
},
|
|
14
|
+
/**
|
|
15
|
+
* Recursively sort object keys and serialize without extra whitespace.
|
|
16
|
+
*/
|
|
17
|
+
canonicalize(value) {
|
|
18
|
+
if (value === null || typeof value !== "object") {
|
|
19
|
+
return JSON.stringify(value);
|
|
20
|
+
}
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
return `[${value.map((v) => this.canonicalize(v)).join(",")}]`;
|
|
23
|
+
}
|
|
24
|
+
const obj = value;
|
|
25
|
+
return ("{" +
|
|
26
|
+
Object.keys(obj)
|
|
27
|
+
.sort()
|
|
28
|
+
.filter((k) => obj[k] !== undefined)
|
|
29
|
+
.map((k) => `${JSON.stringify(k)}:${this.canonicalize(obj[k])}`)
|
|
30
|
+
.join(",") +
|
|
31
|
+
"}");
|
|
32
|
+
},
|
|
33
|
+
/**
|
|
34
|
+
* Convert a RoutePlan object to its canonical JSON string.
|
|
35
|
+
*/
|
|
36
|
+
convertToCanonicalJson(jsObj) {
|
|
37
|
+
const clean = this.cleanData(jsObj);
|
|
38
|
+
return this.canonicalize(clean);
|
|
39
|
+
},
|
|
40
|
+
/**
|
|
41
|
+
* Compute the SHA-256 hash of the canonical JSON.
|
|
42
|
+
*/
|
|
43
|
+
async calculateHash(jsObj) {
|
|
44
|
+
const canonical = this.convertToCanonicalJson(jsObj);
|
|
45
|
+
return Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, canonical);
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as Network from "expo-network";
|
|
2
|
+
export const NetworkAdapter = {
|
|
3
|
+
async getStatus() {
|
|
4
|
+
const state = await Network.getNetworkStateAsync();
|
|
5
|
+
const ip = await Network.getIpAddressAsync();
|
|
6
|
+
return {
|
|
7
|
+
isConnected: state.isConnected ?? false,
|
|
8
|
+
isInternetReachable: state.isInternetReachable ?? null,
|
|
9
|
+
type: state.type,
|
|
10
|
+
ipAddress: ip ?? null,
|
|
11
|
+
};
|
|
12
|
+
},
|
|
13
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { RefObject } from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
interface ExportOptions {
|
|
4
|
+
fileName?: string;
|
|
5
|
+
receiptWidthMm?: number;
|
|
6
|
+
quality?: number;
|
|
7
|
+
dialogTitle?: string;
|
|
8
|
+
}
|
|
9
|
+
interface ExportResult {
|
|
10
|
+
success: boolean;
|
|
11
|
+
fileUri?: string;
|
|
12
|
+
error?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const ReceiptExporterAdapter: {
|
|
15
|
+
captureAndShareAsPdf(viewRef: RefObject<View | null>, options?: ExportOptions): Promise<ExportResult>;
|
|
16
|
+
captureAsPdf(viewRef: RefObject<View | null>, options?: Omit<ExportOptions, "dialogTitle">): Promise<ExportResult>;
|
|
17
|
+
captureAsImageBase64(viewRef: RefObject<View | null>, quality?: number): Promise<string | null>;
|
|
18
|
+
};
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { File, Paths } from "expo-file-system";
|
|
2
|
+
import * as Sharing from "expo-sharing";
|
|
3
|
+
import { PDFDocument } from "pdf-lib";
|
|
4
|
+
import { Platform } from "react-native";
|
|
5
|
+
import { captureRef } from "react-native-view-shot";
|
|
6
|
+
const DEFAULT_CONFIG = {
|
|
7
|
+
RECEIPT_WIDTH_MM: 72,
|
|
8
|
+
MM_TO_POINTS: 2.83464567,
|
|
9
|
+
IMAGE_QUALITY: 0.8,
|
|
10
|
+
};
|
|
11
|
+
function sanitizeFileName(name) {
|
|
12
|
+
return name.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
13
|
+
}
|
|
14
|
+
function generateFileName(baseName, extension = "pdf") {
|
|
15
|
+
const sanitized = sanitizeFileName(baseName || "comprobante");
|
|
16
|
+
return `${sanitized}_${Date.now()}.${extension}`;
|
|
17
|
+
}
|
|
18
|
+
async function captureViewToTmpFile(viewRef, quality) {
|
|
19
|
+
const tmpUri = await captureRef(viewRef, {
|
|
20
|
+
format: "jpg",
|
|
21
|
+
quality,
|
|
22
|
+
result: "tmpfile",
|
|
23
|
+
useRenderInContext: Platform.OS === "ios",
|
|
24
|
+
});
|
|
25
|
+
return tmpUri;
|
|
26
|
+
}
|
|
27
|
+
async function readImageAsBase64(tmpUri) {
|
|
28
|
+
const tmpFile = new File(tmpUri);
|
|
29
|
+
return await tmpFile.base64();
|
|
30
|
+
}
|
|
31
|
+
async function createPdfFromJpg(jpgBase64, receiptWidthMm) {
|
|
32
|
+
const pdfDoc = await PDFDocument.create();
|
|
33
|
+
const jpgImage = await pdfDoc.embedJpg(jpgBase64);
|
|
34
|
+
const pageWidth = receiptWidthMm * DEFAULT_CONFIG.MM_TO_POINTS;
|
|
35
|
+
const scale = pageWidth / jpgImage.width;
|
|
36
|
+
const pageHeight = jpgImage.height * scale;
|
|
37
|
+
const page = pdfDoc.addPage([pageWidth, pageHeight]);
|
|
38
|
+
page.drawImage(jpgImage, {
|
|
39
|
+
x: 0,
|
|
40
|
+
y: 0,
|
|
41
|
+
width: pageWidth,
|
|
42
|
+
height: pageHeight,
|
|
43
|
+
});
|
|
44
|
+
return await pdfDoc.saveAsBase64();
|
|
45
|
+
}
|
|
46
|
+
async function savePdfToFile(pdfBase64, fileName) {
|
|
47
|
+
const file = new File(Paths.document, fileName);
|
|
48
|
+
await file.write(pdfBase64, { encoding: "base64" });
|
|
49
|
+
return file.uri;
|
|
50
|
+
}
|
|
51
|
+
async function cleanupTmpFile(tmpUri) {
|
|
52
|
+
try {
|
|
53
|
+
const tmpFile = new File(tmpUri);
|
|
54
|
+
await tmpFile.delete();
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Ignore cleanup errors
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function shareFile(fileUri, dialogTitle) {
|
|
61
|
+
await Sharing.shareAsync(fileUri, {
|
|
62
|
+
mimeType: "application/pdf",
|
|
63
|
+
dialogTitle,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async function captureViewToBase64(viewRef, quality) {
|
|
67
|
+
if (!viewRef.current)
|
|
68
|
+
return null;
|
|
69
|
+
try {
|
|
70
|
+
const base64 = await captureRef(viewRef, {
|
|
71
|
+
format: "png",
|
|
72
|
+
quality,
|
|
73
|
+
result: "base64",
|
|
74
|
+
useRenderInContext: Platform.OS === "ios",
|
|
75
|
+
});
|
|
76
|
+
return base64;
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
console.error("Error capturing view to base64:", error);
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export const ReceiptExporterAdapter = {
|
|
84
|
+
async captureAndShareAsPdf(viewRef, options) {
|
|
85
|
+
if (!viewRef.current) {
|
|
86
|
+
return { success: false, error: "View reference is not available" };
|
|
87
|
+
}
|
|
88
|
+
const quality = options?.quality ?? DEFAULT_CONFIG.IMAGE_QUALITY;
|
|
89
|
+
const receiptWidthMm = options?.receiptWidthMm ?? DEFAULT_CONFIG.RECEIPT_WIDTH_MM;
|
|
90
|
+
const fileName = generateFileName(options?.fileName);
|
|
91
|
+
const dialogTitle = options?.dialogTitle ?? "Compartir comprobante";
|
|
92
|
+
let tmpUri = null;
|
|
93
|
+
try {
|
|
94
|
+
tmpUri = await captureViewToTmpFile(viewRef, quality);
|
|
95
|
+
const jpgBase64 = await readImageAsBase64(tmpUri);
|
|
96
|
+
const pdfBase64 = await createPdfFromJpg(jpgBase64, receiptWidthMm);
|
|
97
|
+
const fileUri = await savePdfToFile(pdfBase64, fileName);
|
|
98
|
+
await shareFile(fileUri, dialogTitle);
|
|
99
|
+
return { success: true, fileUri };
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
103
|
+
console.error("Error exporting receipt:", errorMessage);
|
|
104
|
+
return { success: false, error: errorMessage };
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
if (tmpUri) {
|
|
108
|
+
await cleanupTmpFile(tmpUri);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
async captureAsPdf(viewRef, options) {
|
|
113
|
+
if (!viewRef.current) {
|
|
114
|
+
return { success: false, error: "View reference is not available" };
|
|
115
|
+
}
|
|
116
|
+
const quality = options?.quality ?? DEFAULT_CONFIG.IMAGE_QUALITY;
|
|
117
|
+
const receiptWidthMm = options?.receiptWidthMm ?? DEFAULT_CONFIG.RECEIPT_WIDTH_MM;
|
|
118
|
+
const fileName = generateFileName(options?.fileName);
|
|
119
|
+
let tmpUri = null;
|
|
120
|
+
try {
|
|
121
|
+
tmpUri = await captureViewToTmpFile(viewRef, quality);
|
|
122
|
+
const jpgBase64 = await readImageAsBase64(tmpUri);
|
|
123
|
+
const pdfBase64 = await createPdfFromJpg(jpgBase64, receiptWidthMm);
|
|
124
|
+
const fileUri = await savePdfToFile(pdfBase64, fileName);
|
|
125
|
+
return { success: true, fileUri };
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
129
|
+
console.error("Error creating PDF:", errorMessage);
|
|
130
|
+
return { success: false, error: errorMessage };
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
if (tmpUri) {
|
|
134
|
+
await cleanupTmpFile(tmpUri);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
async captureAsImageBase64(viewRef, quality) {
|
|
139
|
+
const imageQuality = quality ?? DEFAULT_CONFIG.IMAGE_QUALITY;
|
|
140
|
+
return captureViewToBase64(viewRef, imageQuality);
|
|
141
|
+
},
|
|
142
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { EntityCollection, ICriteria } from "../../domain/entity";
|
|
2
|
+
import { ECommonRepository } from "../../domain/repositories";
|
|
3
|
+
declare class DatabaseTableRepositoryImpl {
|
|
4
|
+
readonly name = ECommonRepository.DATABASE_TABLE;
|
|
5
|
+
findAll(): Promise<EntityCollection<any>>;
|
|
6
|
+
getTableData(tableName: string): Promise<any[]>;
|
|
7
|
+
save(entity: any | any[] | EntityCollection<any>): Promise<void>;
|
|
8
|
+
remove(entity: any | any[]): Promise<void>;
|
|
9
|
+
findById(id: number | string): Promise<any | null>;
|
|
10
|
+
findByCriteria(criteria: ICriteria<any>[]): Promise<EntityCollection<any>>;
|
|
11
|
+
}
|
|
12
|
+
export declare const DatabaseTableRepository: DatabaseTableRepositoryImpl;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { EntityCollection } from "../../domain/entity";
|
|
2
|
+
import { ECommonRepository } from "../../domain/repositories";
|
|
3
|
+
import { DBCursor } from "../../infrastructure/database";
|
|
4
|
+
import logger from "../../infrastructure/logger";
|
|
5
|
+
class DatabaseTableRepositoryImpl {
|
|
6
|
+
name = ECommonRepository.DATABASE_TABLE;
|
|
7
|
+
async findAll() {
|
|
8
|
+
logger.debug("Fetching all database tables");
|
|
9
|
+
const result = await DBCursor.getAllAsync(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';`);
|
|
10
|
+
return new EntityCollection((result ?? []));
|
|
11
|
+
}
|
|
12
|
+
async getTableData(tableName) {
|
|
13
|
+
const query = `SELECT * FROM ${tableName};`;
|
|
14
|
+
return await DBCursor.getAllAsync(query);
|
|
15
|
+
}
|
|
16
|
+
async save(entity) {
|
|
17
|
+
throw new Error("Not implemented");
|
|
18
|
+
}
|
|
19
|
+
async remove(entity) {
|
|
20
|
+
throw new Error("Not implemented");
|
|
21
|
+
}
|
|
22
|
+
async findById(id) {
|
|
23
|
+
throw new Error("Not implemented");
|
|
24
|
+
}
|
|
25
|
+
async findByCriteria(criteria) {
|
|
26
|
+
throw new Error("Not implemented");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export const DatabaseTableRepository = new DatabaseTableRepositoryImpl();
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { DomainEvent, EEventStatus } from "../../domain/event_sourcing";
|
|
2
|
+
import { ECommonRepository } from "../../domain/repositories";
|
|
3
|
+
import { DATABASE_TABLES } from "../../entrypoints/db/migrations";
|
|
4
|
+
declare class DomainEventRepositoryImpl {
|
|
5
|
+
readonly name = ECommonRepository.DOMAIN_EVENT;
|
|
6
|
+
readonly table = DATABASE_TABLES.DOMAIN_EVENTS;
|
|
7
|
+
private hasPendingDuplicate;
|
|
8
|
+
save(event: DomainEvent): Promise<void>;
|
|
9
|
+
saveMany(events: DomainEvent[]): Promise<void>;
|
|
10
|
+
findById(uuid: string): Promise<DomainEvent | null>;
|
|
11
|
+
findByIds(uuids: string[]): Promise<DomainEvent[]>;
|
|
12
|
+
findNextPending(internetConnected: boolean): Promise<DomainEvent | null>;
|
|
13
|
+
findByStatus(status: EEventStatus): Promise<DomainEvent[]>;
|
|
14
|
+
findByStatuses(statuses: EEventStatus[]): Promise<DomainEvent[]>;
|
|
15
|
+
findByCorrelationId(correlationId: string): Promise<DomainEvent[]>;
|
|
16
|
+
findByAggregateId(aggregateId: string): Promise<DomainEvent[]>;
|
|
17
|
+
findByName(name: string): Promise<DomainEvent[]>;
|
|
18
|
+
findPending(): Promise<DomainEvent[]>;
|
|
19
|
+
findFailed(): Promise<DomainEvent[]>;
|
|
20
|
+
remove(uuid: string): Promise<void>;
|
|
21
|
+
removeByCorrelationId(correlationId: string): Promise<void>;
|
|
22
|
+
removeAcknowledged(): Promise<void>;
|
|
23
|
+
clearAll(): Promise<void>;
|
|
24
|
+
count(): Promise<number>;
|
|
25
|
+
countByStatus(status: EEventStatus): Promise<number>;
|
|
26
|
+
findByIdSync(uuid: string): DomainEvent | null;
|
|
27
|
+
findByIdsSync(uuids: string[]): DomainEvent[];
|
|
28
|
+
findByRemoteIdSync(remoteId: number | string): DomainEvent | null;
|
|
29
|
+
findByRemoteIdsSync(remoteIds: (number | string)[]): DomainEvent[];
|
|
30
|
+
}
|
|
31
|
+
export declare const DomainEventRepository: DomainEventRepositoryImpl;
|
|
32
|
+
export {};
|