From 9da317a693215e7af4866edf09398dfb78342cc5 Mon Sep 17 00:00:00 2001 From: Mizan Ali Date: Tue, 13 Oct 2026 02:34:49 +0400 Subject: [PATCH 1/5] feat(mesh): sign and verify text with the phone's mesh identity key --- packages/mesh/README.md | 9 +++++++++ packages/mesh/__tests__/signing.test.ts | 14 +++++++++++++ packages/mesh/src/index.ts | 1 + packages/mesh/src/room.ts | 27 +++++++++++++++++++++++++ packages/mesh/src/signing.ts | 15 ++++++++++++++ packages/mesh/src/useNearbyRoom.ts | 2 ++ 6 files changed, 68 insertions(+) create mode 100644 packages/mesh/__tests__/signing.test.ts create mode 100644 packages/mesh/src/signing.ts diff --git a/packages/mesh/README.md b/packages/mesh/README.md index 6d17d8c..f014a40 100644 --- a/packages/mesh/README.md +++ b/packages/mesh/README.md @@ -43,6 +43,14 @@ room.broadcast({ type: 'snapshot', items }); `onPeerJoined`) as soon as it is heard from again. That is why host-authoritative apps should answer `onPeerJoined` with a full snapshot. +## Signing + +`sign(text)` signs with the device's mesh identity key (Ed25519) and returns +`{ publicKey, signature }` as hex. `verify(address, text, signed)` is true only if the +signature is valid and the key is the identity key of `address`, so a peer cannot +sign with a fresh key and claim to be someone else. Use it for records that must stay +provable after they leave the room, such as a signed handoff receipt. + ## Group mode (`{ group: true }`) For the SDK's replicated documents (`DataStore`), which live in an MLS group. @@ -82,4 +90,5 @@ await store.textInsert(room.groupId!, 'doc', 'body', 0, 'Hello'); | `src/useNearbyRoom.ts` | React hook around one `NearbyRoom` | | `src/hosts.ts` | Pure host-list logic (tested) | | `src/envelope.ts` | Message envelope encode/decode with the size bound (tested) | +| `src/signing.ts` | Hex and UTF-8 helpers for `sign` / `verify` (tested) | | `src/permissions.ts` | Android Bluetooth permissions | diff --git a/packages/mesh/__tests__/signing.test.ts b/packages/mesh/__tests__/signing.test.ts new file mode 100644 index 0000000..4790235 --- /dev/null +++ b/packages/mesh/__tests__/signing.test.ts @@ -0,0 +1,14 @@ +import { describe, expect, it } from '@jest/globals'; +import { fromHex, toHex, utf8 } from '../src/signing'; + +describe('signing helpers', () => { + it('round-trips bytes through hex and rejects anything that is not hex', () => { + expect(toHex([0, 15, 255])).toBe('000fff'); + expect(fromHex('000fff')).toEqual([0, 15, 255]); + expect(fromHex('000FFF')).toEqual([0, 15, 255]); + expect(fromHex('abc')).toBeNull(); + expect(fromHex('zz')).toBeNull(); + expect(fromHex(42)).toBeNull(); + expect(utf8('é')).toEqual([0xc3, 0xa9]); + }); +}); diff --git a/packages/mesh/src/index.ts b/packages/mesh/src/index.ts index 3800bbb..91b3e67 100644 --- a/packages/mesh/src/index.ts +++ b/packages/mesh/src/index.ts @@ -8,6 +8,7 @@ export { type NearbyHost, } from './hosts'; export { requestNearbyPermissions } from './permissions'; +export { fromHex, toHex, type Signed } from './signing'; export { bluetoothMeshProtocolConfig, pause, poll, waitForBluetooth } from './runtime'; export { useMeshRelay } from './useMeshRelay'; export type { RelaySnapshot, RelayStatus } from './relaySession'; diff --git a/packages/mesh/src/room.ts b/packages/mesh/src/room.ts index 1dccb8b..2d97cd5 100644 --- a/packages/mesh/src/room.ts +++ b/packages/mesh/src/room.ts @@ -16,6 +16,7 @@ import { } from './hosts'; import { requestNearbyPermissions } from './permissions'; import { bluetoothMeshProtocolConfig, pause, poll, waitForBluetooth } from './runtime'; +import { fromHex, toHex, utf8, type Signed } from './signing'; export type RoomStatus = | 'idle' // nothing running @@ -177,6 +178,32 @@ export class NearbyRoom { await Promise.all(this.snapshot.peers.map(peer => this.sendRaw(peer.id, content))); }; + /** Signs `text` with this device's mesh identity key (Ed25519). */ + sign = async (text: string): Promise => { + const p = this.protocol; + if (!p) throw new Error('The room is not running.'); + const [publicKey, signature] = await Promise.all([p.getIdentityPublicKey(), p.signData(utf8(text))]); + return { publicKey: toHex(publicKey), signature: toHex(signature) }; + }; + + /** + * True only if `signed` is a valid signature over `text` AND its key is the identity + * key of `address`. Without the address check, any peer could sign with a fresh key + * and claim to be someone else. + */ + verify = async (address: string, text: string, signed: Signed): Promise => { + const p = this.protocol; + const publicKey = fromHex(signed.publicKey); + const signature = fromHex(signed.signature); + if (!p || publicKey?.length !== 32 || signature?.length !== 64) return false; + try { + if ((await p.deriveAddress(publicKey)) !== address) return false; + return await p.verifySignature(publicKey, utf8(text), signature); + } catch { + return false; + } + }; + /** Says goodbye to peers, stops the protocol and returns to 'idle'. */ leave = async (): Promise => { this.stop(this.snapshot.peers); diff --git a/packages/mesh/src/signing.ts b/packages/mesh/src/signing.ts new file mode 100644 index 0000000..5c36d84 --- /dev/null +++ b/packages/mesh/src/signing.ts @@ -0,0 +1,15 @@ +/** An Ed25519 signature and the identity key that made it, hex-encoded so it fits in JSON. */ +export type Signed = { publicKey: string; signature: string }; + +export const utf8 = (text: string): number[] => Array.from(new TextEncoder().encode(text)); + +export const toHex = (bytes: number[]): string => + bytes.map(b => b.toString(16).padStart(2, '0')).join(''); + +/** Null unless `hex` is an even-length lowercase or uppercase hex string. */ +export function fromHex(hex: unknown): number[] | null { + if (typeof hex !== 'string' || hex.length % 2 !== 0 || !/^[0-9a-f]*$/i.test(hex)) return null; + const bytes: number[] = []; + for (let i = 0; i < hex.length; i += 2) bytes.push(parseInt(hex.slice(i, i + 2), 16)); + return bytes; +} diff --git a/packages/mesh/src/useNearbyRoom.ts b/packages/mesh/src/useNearbyRoom.ts index 2575e4f..9315bbc 100644 --- a/packages/mesh/src/useNearbyRoom.ts +++ b/packages/mesh/src/useNearbyRoom.ts @@ -26,6 +26,8 @@ export function useNearbyRoom(options: RoomOptions & RoomCallbacks) { join: room.join, send: room.send, broadcast: room.broadcast, + sign: room.sign, + verify: room.verify, leave: room.leave, }; } From e1cb10139d9277332ec3c73a0edf22c74c109c86 Mon Sep 17 00:00:00 2001 From: Mizan Ali Date: Tue, 13 Oct 2026 02:34:49 +0400 Subject: [PATCH 2/5] feat(dockhand): driver to dock office handoffs offline, synced to mygeotab --- .github/workflows/ci.yml | 4 +- README.md | 5 +- apps/dockhand/.eslintrc.js | 4 + apps/dockhand/.gitignore | 16 + apps/dockhand/.prettierrc.json | 1 + apps/dockhand/App.tsx | 384 +++ apps/dockhand/Gemfile | 17 + apps/dockhand/Gemfile.lock | 200 ++ apps/dockhand/README.md | 94 + apps/dockhand/__tests__/addin.test.ts | 119 + apps/dockhand/__tests__/handoff.test.ts | 135 + apps/dockhand/__tests__/sync.test.ts | 207 ++ apps/dockhand/android/app/build.gradle | 121 + apps/dockhand/android/app/debug.keystore | Bin 0 -> 2257 bytes apps/dockhand/android/app/proguard-rules.pro | 10 + .../android/app/src/main/AndroidManifest.xml | 36 + .../dockhand/MainActivity.kt | 22 + .../dockhand/MainApplication.kt | 31 + .../res/drawable/rn_edit_text_material.xml | 37 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 1639 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 0 -> 2086 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 2293 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1092 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 0 -> 1335 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 1540 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 2174 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 0 -> 3004 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 3273 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 3440 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 0 -> 5132 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 5188 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 4626 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 0 -> 7850 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 7124 bytes .../res/values/ic_launcher_background.xml | 4 + .../app/src/main/res/values/strings.xml | 3 + .../app/src/main/res/values/styles.xml | 9 + apps/dockhand/android/build.gradle | 21 + apps/dockhand/android/gradle.properties | 44 + .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 46175 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + apps/dockhand/android/gradlew | 248 ++ apps/dockhand/android/gradlew.bat | 98 + apps/dockhand/android/settings.gradle | 6 + apps/dockhand/app.json | 4 + apps/dockhand/babel.config.js | 3 + apps/dockhand/index.js | 9 + apps/dockhand/ios/.xcode.env | 11 + .../ios/DockHand.xcodeproj/project.pbxproj | 528 ++++ .../xcshareddata/xcschemes/DockHand.xcscheme | 88 + .../contents.xcworkspacedata | 10 + apps/dockhand/ios/DockHand/AppDelegate.swift | 79 + .../AppIcon.appiconset/Contents.json | 6 + .../AppIcon.appiconset/Icon.png | Bin 0 -> 28191 bytes .../DockHand/Images.xcassets/Contents.json | 6 + apps/dockhand/ios/DockHand/Info.plist | 98 + .../ios/DockHand/LaunchScreen.storyboard | 47 + .../ios/DockHand/PrivacyInfo.xcprivacy | 37 + .../dockhand/ios/DockHand/SceneDelegate.swift | 24 + apps/dockhand/ios/Podfile | 67 + apps/dockhand/ios/Podfile.lock | 2386 +++++++++++++++++ apps/dockhand/metro.config.js | 21 + apps/dockhand/mygeotab-addin/config.json | 12 + .../dockhand/mygeotab-addin/yardHandoffs.html | 65 + apps/dockhand/mygeotab-addin/yardHandoffs.js | 284 ++ apps/dockhand/nativewind-env.d.ts | 1 + apps/dockhand/package.json | 76 + apps/dockhand/src/domain/handoff.ts | 255 ++ apps/dockhand/src/domain/sync.ts | 101 + apps/dockhand/src/services/mygeotab.ts | 180 ++ apps/dockhand/src/services/persist.ts | 83 + apps/dockhand/src/ui/DriverScreen.tsx | 165 ++ apps/dockhand/src/ui/GeotabBar.tsx | 230 ++ apps/dockhand/src/ui/YardScreen.tsx | 85 + apps/dockhand/src/ui/common.tsx | 169 ++ apps/dockhand/src/ui/illustrations.tsx | 43 + apps/dockhand/tailwind.config.js | 6 + apps/dockhand/tsconfig.json | 15 + pnpm-lock.yaml | 115 + 81 files changed, 7200 insertions(+), 2 deletions(-) create mode 100644 apps/dockhand/.eslintrc.js create mode 100644 apps/dockhand/.gitignore create mode 100644 apps/dockhand/.prettierrc.json create mode 100644 apps/dockhand/App.tsx create mode 100644 apps/dockhand/Gemfile create mode 100644 apps/dockhand/Gemfile.lock create mode 100644 apps/dockhand/README.md create mode 100644 apps/dockhand/__tests__/addin.test.ts create mode 100644 apps/dockhand/__tests__/handoff.test.ts create mode 100644 apps/dockhand/__tests__/sync.test.ts create mode 100644 apps/dockhand/android/app/build.gradle create mode 100644 apps/dockhand/android/app/debug.keystore create mode 100644 apps/dockhand/android/app/proguard-rules.pro create mode 100644 apps/dockhand/android/app/src/main/AndroidManifest.xml create mode 100644 apps/dockhand/android/app/src/main/java/com/offlineappexamples/dockhand/MainActivity.kt create mode 100644 apps/dockhand/android/app/src/main/java/com/offlineappexamples/dockhand/MainApplication.kt create mode 100644 apps/dockhand/android/app/src/main/res/drawable/rn_edit_text_material.xml create mode 100644 apps/dockhand/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 apps/dockhand/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 apps/dockhand/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 apps/dockhand/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png create mode 100644 apps/dockhand/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 apps/dockhand/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 apps/dockhand/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png create mode 100644 apps/dockhand/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 apps/dockhand/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 apps/dockhand/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png create mode 100644 apps/dockhand/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 apps/dockhand/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 apps/dockhand/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png create mode 100644 apps/dockhand/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 apps/dockhand/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 apps/dockhand/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png create mode 100644 apps/dockhand/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 apps/dockhand/android/app/src/main/res/values/ic_launcher_background.xml create mode 100644 apps/dockhand/android/app/src/main/res/values/strings.xml create mode 100644 apps/dockhand/android/app/src/main/res/values/styles.xml create mode 100644 apps/dockhand/android/build.gradle create mode 100644 apps/dockhand/android/gradle.properties create mode 100644 apps/dockhand/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 apps/dockhand/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 apps/dockhand/android/gradlew create mode 100644 apps/dockhand/android/gradlew.bat create mode 100644 apps/dockhand/android/settings.gradle create mode 100644 apps/dockhand/app.json create mode 100644 apps/dockhand/babel.config.js create mode 100644 apps/dockhand/index.js create mode 100644 apps/dockhand/ios/.xcode.env create mode 100644 apps/dockhand/ios/DockHand.xcodeproj/project.pbxproj create mode 100644 apps/dockhand/ios/DockHand.xcodeproj/xcshareddata/xcschemes/DockHand.xcscheme create mode 100644 apps/dockhand/ios/DockHand.xcworkspace/contents.xcworkspacedata create mode 100644 apps/dockhand/ios/DockHand/AppDelegate.swift create mode 100644 apps/dockhand/ios/DockHand/Images.xcassets/AppIcon.appiconset/Contents.json create mode 100644 apps/dockhand/ios/DockHand/Images.xcassets/AppIcon.appiconset/Icon.png create mode 100644 apps/dockhand/ios/DockHand/Images.xcassets/Contents.json create mode 100644 apps/dockhand/ios/DockHand/Info.plist create mode 100644 apps/dockhand/ios/DockHand/LaunchScreen.storyboard create mode 100644 apps/dockhand/ios/DockHand/PrivacyInfo.xcprivacy create mode 100644 apps/dockhand/ios/DockHand/SceneDelegate.swift create mode 100644 apps/dockhand/ios/Podfile create mode 100644 apps/dockhand/ios/Podfile.lock create mode 100644 apps/dockhand/metro.config.js create mode 100644 apps/dockhand/mygeotab-addin/config.json create mode 100644 apps/dockhand/mygeotab-addin/yardHandoffs.html create mode 100644 apps/dockhand/mygeotab-addin/yardHandoffs.js create mode 100644 apps/dockhand/nativewind-env.d.ts create mode 100644 apps/dockhand/package.json create mode 100644 apps/dockhand/src/domain/handoff.ts create mode 100644 apps/dockhand/src/domain/sync.ts create mode 100644 apps/dockhand/src/services/mygeotab.ts create mode 100644 apps/dockhand/src/services/persist.ts create mode 100644 apps/dockhand/src/ui/DriverScreen.tsx create mode 100644 apps/dockhand/src/ui/GeotabBar.tsx create mode 100644 apps/dockhand/src/ui/YardScreen.tsx create mode 100644 apps/dockhand/src/ui/common.tsx create mode 100644 apps/dockhand/src/ui/illustrations.tsx create mode 100644 apps/dockhand/tailwind.config.js create mode 100644 apps/dockhand/tsconfig.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b13158..ebdf915 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,7 +49,7 @@ jobs: strategy: fail-fast: false matrix: - app: [tictactogether, stocksync, orderup, cowrite, outagenet, yardgate, eventfloor, agrimesh] + app: [tictactogether, stocksync, orderup, cowrite, outagenet, yardgate, eventfloor, agrimesh, dockhand] steps: - uses: actions/checkout@v7 - uses: pnpm/action-setup@v6 @@ -91,6 +91,8 @@ jobs: scheme: EventFloor - app: agrimesh scheme: AgriMesh + - app: dockhand + scheme: DockHand steps: - uses: actions/checkout@v7 - uses: pnpm/action-setup@v6 diff --git a/README.md b/README.md index 0a9acae..ae1637e 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ accounts or server, and shows a different way to keep shared state in sync. | YardGate | [`apps/yardgate`](./apps/yardgate) | Logistics yard check-in: MeshServices discover + invoke, gate officer approve/deny, optional relay hop. | | Event Floor | [`apps/eventfloor`](./apps/eventfloor) | Live events: Flow A staff MLS dispatch + Flow B gate admission ledger and mock platform sync (`scanId` dedupe). | | AgriMesh | [`apps/agrimesh`](./apps/agrimesh) | Agriculture: field readings store-and-forward over multi-hop Bluetooth mesh, office-edge ledger, mock farm sync (`readingId` dedupe). | +| DockHand | [`apps/dockhand`](./apps/dockhand) | Fleet yards: driver check-in and signed dock assignment over Bluetooth, synced to MyGeotab when online (`handoffId` dedupe, GO GPS fix, verifying Add-In page). | Each app's README explains how to run it, how its sync works and what its tests cover. @@ -77,6 +78,8 @@ Each app's README explains how to run it, how its sync works and what its tests No API key or developer portal account is needed to run these examples. They use only nearby Bluetooth: the SDK's internet transports and telemetry stay off. +DockHand can also sign in to a MyGeotab database to sync completed handoffs; +without one it runs fully offline with a mock sync. ## Run an app @@ -97,7 +100,7 @@ pnpm --filter @offline-app-examples/stocksync ios -- --device pnpm --filter @offline-app-examples/stocksync android ``` -Replace `stocksync` with `tictactogether`, `orderup`, `cowrite`, `outagenet`, `yardgate`, `eventfloor`, or `agrimesh` for the other apps. +Replace `stocksync` with `tictactogether`, `orderup`, `cowrite`, `outagenet`, `yardgate`, `eventfloor`, `agrimesh`, or `dockhand` for the other apps. For iOS you can also open the app's `ios/*.xcworkspace` in Xcode, choose your team under Signing & Capabilities, and run on each phone. Debug builds load JavaScript diff --git a/apps/dockhand/.eslintrc.js b/apps/dockhand/.eslintrc.js new file mode 100644 index 0000000..187894b --- /dev/null +++ b/apps/dockhand/.eslintrc.js @@ -0,0 +1,4 @@ +module.exports = { + root: true, + extends: '@react-native', +}; diff --git a/apps/dockhand/.gitignore b/apps/dockhand/.gitignore new file mode 100644 index 0000000..7846bf0 --- /dev/null +++ b/apps/dockhand/.gitignore @@ -0,0 +1,16 @@ +node_modules/ +.expo/ +dist/ +build/ +ios/Pods/ +ios/build/ +android/app/build/ +android/build/ +android/.gradle/ +**/.cxx/ +android/local.properties +*.jsbundle +.DS_Store +.env* +!.env.example +ios/.xcode.env.local diff --git a/apps/dockhand/.prettierrc.json b/apps/dockhand/.prettierrc.json new file mode 100644 index 0000000..0594d04 --- /dev/null +++ b/apps/dockhand/.prettierrc.json @@ -0,0 +1 @@ +{"singleQuote":true,"trailingComma":"all","bracketSpacing":true} diff --git a/apps/dockhand/App.tsx b/apps/dockhand/App.tsx new file mode 100644 index 0000000..f2fe7ba --- /dev/null +++ b/apps/dockhand/App.tsx @@ -0,0 +1,384 @@ +import '@offline-app-examples/ui/global.css'; +import { useNearbyRoom } from '@offline-app-examples/mesh'; +import { NearbyLobby } from '@offline-app-examples/ui'; +import React, { useEffect, useRef, useState } from 'react'; +import { StatusBar, View } from 'react-native'; +import { SafeAreaProvider } from 'react-native-safe-area-context'; +import { + addHandoff, + assign, + assignmentText, + awaitingYard, + checkInText, + DEMO_VEHICLES, + newHandoffId, + parseMessage, + yoursFor, + type Dock, + type Handoff, + type Load, +} from './src/domain/handoff'; +import { + isDemoVehicle, + mockIngest, + nearestFix, + toDetails, + unsynced, +} from './src/domain/sync'; +import { + goFixesAround, + listVehicles, + pushHandoff, + SessionExpired, + signIn, +} from './src/services/mygeotab'; +import { + emptyPersisted, + loadPersisted, + savePersisted, + type Persisted, +} from './src/services/persist'; +import { DriverScreen } from './src/ui/DriverScreen'; +import { GeotabBar, GeotabSheet } from './src/ui/GeotabBar'; +import { DockHero } from './src/ui/illustrations'; +import { YardScreen } from './src/ui/YardScreen'; + +const APP_ID = 'dockhand'; +const RESEND_MS = 8_000; +/** No signal is the normal case, so sync just keeps trying while there is work. */ +const AUTO_SYNC_MS = 15_000; + +export default function App() { + const [name, setName] = useState(''); + const [yardName, setYardName] = useState(''); + const [state, setState] = useState(emptyPersisted); + const [sheetOpen, setSheetOpen] = useState(false); + const [syncBusy, setSyncBusy] = useState(false); + const ref = useRef(state); + /** Driver: handoffIds the yard has acknowledged this session, so they are not resent. */ + const yardHas = useRef(new Set()); + const syncing = useRef(false); + + useEffect(() => { + loadPersisted().then((p) => { + ref.current = p; + setState(p); + }); + }, []); + + function update(patch: Partial) { + ref.current = { ...ref.current, ...patch }; + setState(ref.current); + void savePersisted(ref.current); + } + + const room = useNearbyRoom({ + appId: APP_ID, + onMessage: (from, data) => void handleMessage(from, data), + onPeerJoined: (peer) => { + if (room.role === 'host') + send(peer.id, yoursFor(ref.current.handoffs, peer.id)); + else resend(); + }, + }); + + const send = (peerId: string, data: unknown) => + room.send(peerId, data).catch((e) => console.warn('send failed', e)); + + async function handleMessage(from: string, data: unknown) { + const msg = parseMessage(data); + if (!msg) return; + + if (room.role === 'host' && msg.type === 'checkin') { + const h = msg.handoff; + if (!ref.current.handoffs.some((x) => x.handoffId === h.handoffId)) { + // A check-in counts only if the phone that sent it signed it. + if ( + h.driverId !== from || + !(await room.verify(from, checkInText(h), h.driverSig)) + ) + return; + update({ handoffs: addHandoff(ref.current.handoffs, h) }); + } + send(from, yoursFor(ref.current.handoffs, from)); // the acknowledgement, repeats included + } else if ( + room.role === 'member' && + msg.type === 'yours' && + from === room.hostId + ) { + for (const remote of msg.handoffs) { + yardHas.current.add(remote.handoffId); + const a = remote.assignment; + const local = ref.current.handoffs.find( + (x) => x.handoffId === remote.handoffId, + ); + if ( + !a || + !local || + local.assignment || + local.driverId !== room.localId || + a.yardId !== from + ) + continue; + // Verified against this phone's own copy, so the yard signed what the driver actually sent. + if (await room.verify(from, assignmentText(local, a), a.yardSig)) { + update({ + handoffs: assign(ref.current.handoffs, local.handoffId, a), + }); + } + } + } + } + + async function checkIn(load: Load) { + const vehicle = + vehicles.find((v) => v.id === ref.current.vehicleId) ?? vehicles[0]; + const base = { + handoffId: newHandoffId(), + vehicle, + driverId: room.localId, + driverName: name.trim().slice(0, 40) || 'Driver', + load, + checkedInAt: Date.now(), + }; + const h: Handoff = { + ...base, + driverSig: await room.sign(checkInText(base)), + }; + update({ handoffs: addHandoff(ref.current.handoffs, h) }); + if (room.hostId) send(room.hostId, { type: 'checkin', handoff: h }); + } + + function resend() { + if (!room.hostId) return; + for (const h of awaitingYard( + ref.current.handoffs, + room.localId, + yardHas.current, + )) { + send(room.hostId, { type: 'checkin', handoff: h }); + } + } + + async function assignDock(handoffId: string, dock: Dock) { + const h = ref.current.handoffs.find((x) => x.handoffId === handoffId); + if (!h || h.assignment) return; + const base = { + dock, + yardId: room.localId, + yardName: yardName || 'Yard', + assignedAt: Date.now(), + }; + const yardSig = await room.sign(assignmentText(h, base)); + update({ + handoffs: assign(ref.current.handoffs, handoffId, { ...base, yardSig }), + }); + send(h.driverId, yoursFor(ref.current.handoffs, h.driverId)); + } + + /** + * Pushes completed handoffs to MyGeotab, or to the mock when not signed in. + * A manual sync with nothing new re-sends everything, to show the duplicate check. + */ + async function sync(manual: boolean) { + const s = ref.current.session; + const todo = unsynced(ref.current.handoffs, ref.current.syncedIds); + if (syncing.current || (!manual && (!s || todo.length === 0))) return; + const batch = + todo.length > 0 ? todo : ref.current.handoffs.filter((h) => h.assignment); + const synced = (ids: string[]) => [ + ...new Set([...ref.current.syncedIds, ...ids]), + ]; + + if (!s) { + const r = mockIngest( + ref.current.mockStored, + batch.map((h) => h.handoffId), + ); + update({ + mockStored: r.stored, + syncedIds: synced(batch.map((h) => h.handoffId)), + lastSync: { + at: Date.now(), + target: 'mock', + added: r.added.length, + duplicates: r.duplicates, + }, + }); + return; + } + + syncing.current = true; + setSyncBusy(true); + let added = 0; + let duplicates = 0; + try { + for (const h of batch) { + const fixes = isDemoVehicle(h.vehicle.id) + ? [] + : await goFixesAround(s, h.vehicle.id, h.checkedInAt).catch(() => []); + const result = await pushHandoff( + s, + toDetails(h, nearestFix(fixes, h.checkedInAt)), + ); + if (result === 'added') added++; + else duplicates++; + update({ syncedIds: synced([h.handoffId]) }); // per record, so a dropped connection loses nothing + } + update({ + lastSync: { at: Date.now(), target: 'mygeotab', added, duplicates }, + }); + } catch (e) { + const expired = e instanceof SessionExpired; + const offline = e instanceof TypeError; // fetch's "Network request failed" + update({ + session: expired ? null : ref.current.session, + lastSync: { + at: Date.now(), + target: 'mygeotab', + added, + duplicates, + error: expired + ? 'Session expired, sign in again' + : offline + ? 'No connection, retrying' + : e instanceof Error + ? e.message + : String(e), + }, + }); + } finally { + syncing.current = false; + setSyncBusy(false); + } + } + + async function geotabSignIn( + server: string, + database: string, + user: string, + password: string, + ) { + const session = await signIn(server, database, user, password); + const list = await listVehicles(session).catch(() => []); + update({ session, vehicles: list, vehicleId: list[0]?.id ?? '' }); + } + + // `sync` and `resend` only read refs, so the first render's copies stay correct. + useEffect(() => { + const timer = setInterval(() => void sync(false), AUTO_SYNC_MS); + return () => clearInterval(timer); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const isDriver = room.status === 'connected'; + useEffect(() => { + if (!isDriver) return; + const timer = setInterval(resend, RESEND_MS); + return () => clearInterval(timer); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isDriver]); + + const vehicles = state.vehicles.length > 0 ? state.vehicles : DEMO_VEHICLES; + const vehicleId = vehicles.some((v) => v.id === state.vehicleId) + ? state.vehicleId + : vehicles[0].id; + const me = { + id: room.localId || 'me', + name: (room.role === 'host' ? yardName : name.trim()) || 'Me', + }; + + function start(action: () => void) { + yardHas.current.clear(); + action(); + } + + let screen: React.ReactNode; + if (room.status === 'hosting') { + screen = ( + void assignDock(id, dock)} + onLeave={room.leave} + /> + ); + } else if (room.status === 'connected') { + screen = ( + h.id === room.hostId)?.name ?? 'Yard'} + people={[me, ...room.peers]} + reconnecting={room.peers.length === 0} + vehicles={vehicles} + vehicleId={vehicleId} + onVehicle={(id) => update({ vehicleId: id })} + handoffs={state.handoffs.filter((h) => h.driverId === room.localId)} + syncedIds={state.syncedIds} + onCheckIn={checkIn} + onLeave={room.leave} + /> + ); + } else { + screen = ( + } + accentColor="#fbbf24" + status={room.status} + error={room.error} + hosts={room.hosts} + peers={room.peers} + joiningId={room.hostId ?? undefined} + name={name} + onNameChange={setName} + onHost={() => + start(() => { + const label = name.trim() || 'Dock office'; + setYardName(label); + room.host(label); + }) + } + onDiscover={() => start(room.discover)} + onJoin={(host) => room.join(host.id, name.trim() || 'Driver')} + onCancel={room.leave} + labels={{ + host: 'Open dock office', + join: 'Check in as driver', + namePlaceholder: 'Your name, or the yard name', + scanningTitle: 'Nearby dock offices', + scanningHint: + 'Keep the phones close. Bluetooth only, no signal needed.', + starting: 'Starting…', + }} + /> + ); + } + + return ( + + + + {screen} + void sync(true)} + onOpen={() => setSheetOpen(true)} + /> + + update({ session: null, vehicles: [], vehicleId: '' })} + onClose={() => setSheetOpen(false)} + /> + + ); +} diff --git a/apps/dockhand/Gemfile b/apps/dockhand/Gemfile new file mode 100644 index 0000000..0263838 --- /dev/null +++ b/apps/dockhand/Gemfile @@ -0,0 +1,17 @@ +source 'https://rubygems.org' + +# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version +ruby ">= 2.6.10" + +# Exclude problematic versions of cocoapods and activesupport that causes build failures. +gem 'cocoapods', '~> 1.16.0' +gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0' +gem 'xcodeproj', '>= 1.25.0', '< 1.28.0' +gem 'concurrent-ruby', '< 1.3.4' + +# Ruby 3.4.0 has removed some libraries from the standard library. +gem 'bigdecimal' +gem 'logger' +gem 'benchmark' +gem 'mutex_m' +gem 'nkf' diff --git a/apps/dockhand/Gemfile.lock b/apps/dockhand/Gemfile.lock new file mode 100644 index 0000000..0df39b4 --- /dev/null +++ b/apps/dockhand/Gemfile.lock @@ -0,0 +1,200 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.8) + activesupport (7.2.4) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5, < 3) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1, < 6) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + addressable (2.9.0) + public_suffix (>= 2.0.2, < 8.0) + algoliasearch (1.27.5) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) + atomos (0.1.3) + base64 (0.3.0) + benchmark (0.5.0) + bigdecimal (4.1.3) + claide (1.1.0) + cocoapods (1.16.2) + addressable (~> 2.8) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.16.2) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 2.1, < 3.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.6.0, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored2 (~> 3.1) + escape (~> 0.0.4) + fourflusher (>= 2.3.0, < 3.0) + gh_inspector (~> 1.0) + molinillo (~> 0.8.0) + nap (~> 1.0) + ruby-macho (>= 2.3.0, < 3.0) + xcodeproj (>= 1.27.0, < 2.0) + cocoapods-core (1.16.2) + activesupport (>= 5.0, < 8) + addressable (~> 2.8) + algoliasearch (~> 1.0) + concurrent-ruby (~> 1.1) + fuzzy_match (~> 2.0.4) + nap (~> 1.0) + netrc (~> 0.11) + public_suffix (~> 4.0) + typhoeus (~> 1.0) + cocoapods-deintegrate (1.0.5) + cocoapods-downloader (2.1) + cocoapods-plugins (1.0.0) + nap + cocoapods-search (1.0.1) + cocoapods-trunk (1.6.0) + nap (>= 0.8, < 2.0) + netrc (~> 0.11) + cocoapods-try (1.2.0) + colored2 (3.1.2) + concurrent-ruby (1.3.3) + connection_pool (2.5.5) + drb (2.2.3) + escape (0.0.4) + ethon (0.18.0) + ffi (>= 1.15.0) + logger + ffi (1.17.4) + ffi (1.17.4-aarch64-linux-gnu) + ffi (1.17.4-aarch64-linux-musl) + ffi (1.17.4-arm-linux-gnu) + ffi (1.17.4-arm-linux-musl) + ffi (1.17.4-arm64-darwin) + ffi (1.17.4-x86-linux-gnu) + ffi (1.17.4-x86-linux-musl) + ffi (1.17.4-x86_64-darwin) + ffi (1.17.4-x86_64-linux-gnu) + ffi (1.17.4-x86_64-linux-musl) + fourflusher (2.3.1) + fuzzy_match (2.0.4) + gh_inspector (1.1.3) + httpclient (2.9.0) + mutex_m + i18n (1.15.2) + concurrent-ruby (~> 1.0) + json (3.0.2) + logger (1.7.0) + minitest (5.27.0) + molinillo (0.8.0) + mutex_m (0.3.0) + nanaimo (0.4.0) + nap (1.1.0) + netrc (0.11.0) + nkf (0.3.0) + public_suffix (4.0.7) + rexml (3.4.4) + ruby-macho (2.5.1) + securerandom (0.4.1) + typhoeus (1.6.0) + ethon (>= 0.18.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + xcodeproj (1.27.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.4.0) + rexml (>= 3.3.6, < 4.0) + +PLATFORMS + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + ruby + x86-linux-gnu + x86-linux-musl + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + activesupport (>= 6.1.7.5, != 7.1.0) + benchmark + bigdecimal + cocoapods (~> 1.16.0) + concurrent-ruby (< 1.3.4) + logger + mutex_m + nkf + xcodeproj (>= 1.25.0, < 1.28.0) + +CHECKSUMS + CFPropertyList (3.0.8) sha256=2c99d0d980536d3d7ab252f7bd59ac8be50fbdd1ff487c98c949bb66bb114261 + activesupport (7.2.4) sha256=b97027b31e111675dba67b86edcd589acc25a0dddf8eecacca18d33a703a0788 + addressable (2.9.0) sha256=7fdf6ac3660f7f4e867a0838be3f6cf722ace541dd97767fa42bc6cfa980c7af + algoliasearch (1.27.5) sha256=26c1cddf3c2ec4bd60c148389e42702c98fdac862881dc6b07a4c0b89ffec853 + atomos (0.1.3) sha256=7d43b22f2454a36bace5532d30785b06de3711399cb1c6bf932573eda536789f + base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b + benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c + bigdecimal (4.1.3) sha256=61ebe1e5e559bdc3cc6f2c0ee7f427321fc838f59611c294356eb04d6e21cf66 + claide (1.1.0) sha256=6d3c5c089dde904d96aa30e73306d0d4bd444b1accb9b3125ce14a3c0183f82e + cocoapods (1.16.2) sha256=0ff1c860f32df3db8b16df09b58da1a6bb2a12fe55f6d5e8be994a74fadd1e5e + cocoapods-core (1.16.2) sha256=4bb1b5c420691e60cf36fa227dec6bc48c096c34c97bb7aa512ea7f3246fc12b + cocoapods-deintegrate (1.0.5) sha256=517c2a448ef563afe99b6e7668704c27f5de9e02715a88ee9de6974dc1b3f6a2 + cocoapods-downloader (2.1) sha256=bb6ebe1b3966dc4055de54f7a28b773485ac724fdf575d9bee2212d235e7b6d1 + cocoapods-plugins (1.0.0) sha256=725d17ce90b52f862e73476623fd91441b4430b742d8a071000831efb440ca9a + cocoapods-search (1.0.1) sha256=1b133b0e6719ed439bd840e84a1828cca46425ab73a11eff5e096c3b2df05589 + cocoapods-trunk (1.6.0) sha256=5f5bda8c172afead48fa2d43a718cf534b1313c367ba1194cebdeb9bfee9ed31 + cocoapods-try (1.2.0) sha256=145b946c6e7747ed0301d975165157951153d27469e6b2763c83e25c84b9defe + colored2 (3.1.2) sha256=b13c2bd7eeae2cf7356a62501d398e72fde78780bd26aec6a979578293c28b4a + concurrent-ruby (1.3.3) sha256=4f9cd28965c4dcf83ffd3ea7304f9323277be8525819cb18a3b61edcb56a7c6a + connection_pool (2.5.5) sha256=e54ff92855753df1fd7c59fa04a398833355f27dd14c074f8c83a05f72a716ad + drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373 + escape (0.0.4) sha256=e49f44ae2b4f47c6a3abd544ae77fe4157802794e32f19b8e773cbc4dcec4169 + ethon (0.18.0) sha256=b598afc9f30448cb068b850714b7d6948e941476095d04f90a4ac65b8d6efcb2 + ffi (1.17.4) sha256=bcd1642e06f0d16fc9e09ac6d49c3a7298b9789bcb58127302f934e437d60acf + ffi (1.17.4-aarch64-linux-gnu) sha256=b208f06f91ffd8f5e1193da3cae3d2ccfc27fc36fba577baf698d26d91c080df + ffi (1.17.4-aarch64-linux-musl) sha256=9286b7a615f2676245283aef0a0a3b475ae3aae2bb5448baace630bb77b91f39 + ffi (1.17.4-arm-linux-gnu) sha256=d6dbddf7cb77bf955411af5f187a65b8cd378cb003c15c05697f5feee1cb1564 + ffi (1.17.4-arm-linux-musl) sha256=9d4838ded0465bef6e2426935f6bcc93134b6616785a84ffd2a3d82bc3cf6f95 + ffi (1.17.4-arm64-darwin) sha256=19071aaf1419251b0a46852abf960e77330a3b334d13a4ab51d58b31a937001b + ffi (1.17.4-x86-linux-gnu) sha256=38e150df5f4ca555e25beca4090823ae09657bceded154e3c52f8631c1ed72cf + ffi (1.17.4-x86-linux-musl) sha256=fbeec0fc7c795bcf86f623bb18d31ea1820f7bd580e1703a3d3740d527437809 + ffi (1.17.4-x86_64-darwin) sha256=aa70390523cf3235096cf64962b709b4cfbd5c082a2cb2ae714eb0fe2ccda496 + ffi (1.17.4-x86_64-linux-gnu) sha256=9d3db14c2eae074b382fa9c083fe95aec6e0a1451da249eab096c34002bc752d + ffi (1.17.4-x86_64-linux-musl) sha256=3fdf9888483de005f8ef8d1cf2d3b20d86626af206cbf780f6a6a12439a9c49e + fourflusher (2.3.1) sha256=1b3de61c7c791b6a4e64f31e3719eb25203d151746bb519a0292bff1065ccaa9 + fuzzy_match (2.0.4) sha256=b5de4f95816589c5b5c3ad13770c0af539b75131c158135b3f3bbba75d0cfca5 + gh_inspector (1.1.3) sha256=04cca7171b87164e053aa43147971d3b7f500fcb58177698886b48a9fc4a1939 + httpclient (2.9.0) sha256=4b645958e494b2f86c2f8a2f304c959baa273a310e77a2931ddb986d83e498c8 + i18n (1.15.2) sha256=00f9eb62412fe593b2a65a97daa75300d37abb8f7202ec748e94b6d46a9dd1b5 + json (3.0.2) sha256=8e6d7e7b11384c21230430cef90b71f14849a34a1f4452796670f7c981bd19df + logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 + minitest (5.27.0) sha256=2d3b17f8a36fe7801c1adcffdbc38233b938eb0b4966e97a6739055a45fa77d5 + molinillo (0.8.0) sha256=efbff2716324e2a30bccd3eba1ff3a735f4d5d53ffddbc6a2f32c0ca9433045d + mutex_m (0.3.0) sha256=cfcb04ac16b69c4813777022fdceda24e9f798e48092a2b817eb4c0a782b0751 + nanaimo (0.4.0) sha256=faf069551bab17f15169c1f74a1c73c220657e71b6e900919897a10d991d0723 + nap (1.1.0) sha256=949691660f9d041d75be611bb2a8d2fd559c467537deac241f4097d9b5eea576 + netrc (0.11.0) sha256=de1ce33da8c99ab1d97871726cba75151113f117146becbe45aa85cb3dabee3f + nkf (0.3.0) sha256=357a8dbeba38b727b75930f665146546076a394a1c243faf634ff176e3588895 + public_suffix (4.0.7) sha256=8be161e2421f8d45b0098c042c06486789731ea93dc3a896d30554ee38b573b8 + rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142 + ruby-macho (2.5.1) sha256=9075e52e0f9270b552a90b24fcc6219ad149b0d15eae1bc364ecd0ac8984f5c9 + securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1 + typhoeus (1.6.0) sha256=bacc41c23e379547e29801dc235cd1699b70b955a1ba3d32b2b877aa844c331d + tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b + xcodeproj (1.27.0) sha256=8cc7a73b4505c227deab044dce118ede787041c702bc47636856a2e566f854d3 + +RUBY VERSION + ruby 4.0.3 + +BUNDLED WITH + 4.0.10 diff --git a/apps/dockhand/README.md b/apps/dockhand/README.md new file mode 100644 index 0000000..2a233e4 --- /dev/null +++ b/apps/dockhand/README.md @@ -0,0 +1,94 @@ +# DockHand + +Driver to dock office handoffs with no signal, synced to **MyGeotab** when a phone is back online. A **driver** checks in with load details over **Bluetooth**; the **dock office** assigns a dock; both phones sign, so the handoff is a receipt neither side can change afterwards. When either phone reaches the internet, the handoff is written to MyGeotab as Add-In data, next to the GPS fix the vehicle's GO device logged at check-in. No GO hardware changes. + +Built on `@offline-app-examples/mesh` and `@offline-app-examples/ui`, Mesh SDK 0.28. + +## What it demonstrates + +- **Offline handoff.** Check-in and dock assignment travel phone to phone over Bluetooth only. Check-ins are resent until the dock office acknowledges them, and every record has a stable `handoffId`, so resends never duplicate. +- **Signed receipts.** The driver signs the load details with the phone's mesh identity key (Ed25519). The dock office stores a check-in only if the signature verifies *and* the key belongs to the phone that sent it. It then countersigns the driver's exact check-in plus the dock. The driver checks that countersignature against its own copy of what it sent. +- **Sync when online.** Signed in to MyGeotab, a phone retries every 15 s while it has completed handoffs to sync, so taking a phone out of airplane mode is all it takes. Each record is checked by `handoffId` before it is added, so a retry, or both phones syncing, never makes a second record. +- **GO corroboration.** For a MyGeotab vehicle, the synced record carries the GO device's `LogRecord` nearest to check-in time: independent evidence that the truck was at the yard. +- **Verification inside MyGeotab.** The `mygeotab-addin/` page lists the handoffs and checks both signatures in the browser, from the signed texts themselves. What it shows is what was signed. +- **Works without a database.** Not signed in, **Mock** sync runs the same duplicate check on the phone. + +## Demo script (~5 min, two phones and a laptop) + +Before the demo: sign both phones in to a MyGeotab **test** database (bottom bar, then **Sign in**), and install the Add-In page (below) on the laptop. + +1. Put **both phones in airplane mode, then turn Bluetooth back on.** No signal from here on. Do this before anyone joins: in one device run, cutting Wi-Fi seconds after the join left dock assignments not reaching the driver until it left and rejoined. +2. **Phone A:** **Open dock office**, named e.g. "North Yard". +3. **Phone B:** **Check in as driver**, e.g. "Ada", and join North Yard. +4. **B:** pick the vehicle (your MyGeotab devices), keep or edit the load, **Sign and check in**. +5. **A:** the check-in appears. Tap **Dock 3**. +6. **B:** shows **Dock 3 · Driver + yard signed**. The bottom bar says **1 to sync**. +7. **B:** leave the yard, then turn airplane mode off. Within 15 s the bar shows **1 added**. +8. **Laptop:** open **Yard Handoffs** in MyGeotab. The handoff is there with both signatures valid and the GO fix at check-in. +9. **A:** turn airplane mode off and tap **Sync**. It reports **1 duplicate dropped**: the yard's copy of the same handoff was not added twice. + +Without a database, skip the sign-in and use **Mock** in step 7; tap it again to see the duplicate check. + +## MyGeotab setup + +**Use a test database.** MyGeotab API writes take effect immediately. + +1. **Sign in on each phone:** bottom bar, then server (`my.geotab.com`), database, user, password. The password is sent once to `Authenticate` and never stored. Only the session is kept, on the phone, until **Sign out** or until it expires (the app then asks again). +2. **Install the page:** host `mygeotab-addin/yardHandoffs.html` and `yardHandoffs.js` together on any HTTPS host. Put that URL in `mygeotab-addin/config.json`, then in MyGeotab open **Administration > System > System Settings > Add-Ins**, turn on **Allow unverified Add-Ins**, add a new Add-In and paste the config. **Yard Handoffs** appears under Activity. + +Records are `AddInData` under the add-in id `ADD_IN_ID` (`src/services/mygeotab.ts`, mirrored in `yardHandoffs.js`; a test keeps the two equal). Generate your own id for a real integration. + +## Files + +| File | What it does | +| --- | --- | +| `src/domain/handoff.ts` | Records, the exact texts each side signs, ledger rules, message validation | +| `src/domain/sync.ts` | What a MyGeotab record contains, which handoffs to sync, mock ingest | +| `src/services/mygeotab.ts` | MyGeotab JSON-RPC: sign in, vehicles, GO GPS records, idempotent AddInData write | +| `src/services/persist.ts` | AsyncStorage: handoffs, sync state, session | +| `App.tsx` | Room roles, signing and verifying, resend and auto-sync loops | +| `mygeotab-addin/` | The MyGeotab page and its Add-In config | + +## Limits + +- Bluetooth only, as in every app here. iPhone to Android works; keep phones within a few metres for the first join. +- The duplicate check is check-then-add. Two phones syncing the same handoff in the same second can both add it; the MyGeotab page shows it once. A server-side sync service would make it one transaction, and is what a production integration should use, with a service account instead of a driver's session on the phone. +- The page checks that both signatures are valid. It does not re-derive each phone's `off1…` address from its key; the phones did that when the records were made. +- Signature checks in the page need a browser with Ed25519 in WebCrypto (current Chrome, Edge, Safari and Firefox). Older browsers show "cannot check". +- The page and the MyGeotab client are tested against recorded API shapes, not a live database. Run the demo once against your test database before showing it. + +## Run + +From the monorepo root: + +```sh +pnpm install +cd apps/dockhand/ios && bundle exec pod install && cd ../../.. +pnpm --filter @offline-app-examples/dockhand dev +``` + +Second terminal: + +```sh +pnpm --filter @offline-app-examples/dockhand ios -- --device +# or +pnpm --filter @offline-app-examples/dockhand android +``` + +Use **two physical phones** with Bluetooth on. Debug builds need Metro, which needs the network; for the airplane-mode demo install a **Release** build. + +## Reset demo + +Clear app data or reinstall. Records already written to MyGeotab stay there; remove them through the API (`Remove` on `AddInData`) or use a fresh test database. + +## Tests + +```sh +pnpm --filter @offline-app-examples/dockhand test +``` + +Covers the signed texts, ledger dedupe and single assignment, message validation, the record size limit, GO fix selection, mock ingest, the MyGeotab client against a fake server (sign-in redirect, duplicate check, expired session), and the MyGeotab page verifying real Ed25519 signatures and rejecting edits. + +## Licensing + +Example app code: [MIT-0](../../LICENSE). `@offline-protocol/mesh-sdk` is [AGPL-3.0-only or commercial](https://www.offlineprotocol.com/docs/operations/licensing). Geotab, MyGeotab and GO are trademarks of Geotab Inc.; this example is not affiliated with or endorsed by Geotab. diff --git a/apps/dockhand/__tests__/addin.test.ts b/apps/dockhand/__tests__/addin.test.ts new file mode 100644 index 0000000..5a8fe69 --- /dev/null +++ b/apps/dockhand/__tests__/addin.test.ts @@ -0,0 +1,119 @@ +import { describe, expect, it } from '@jest/globals'; +import { webcrypto } from 'crypto'; +import { toHex } from '@offline-app-examples/mesh/src/signing'; +import { + assignmentText, + checkInText, + type Handoff, +} from '../src/domain/handoff'; +import { toDetails } from '../src/domain/sync'; +import { ADD_IN_ID } from '../src/services/mygeotab'; + +// The MyGeotab page is plain browser JavaScript; it exports for this test only. +const addin = require('../mygeotab-addin/yardHandoffs.js'); +const subtle = webcrypto.subtle; + +async function signer() { + const keys = (await subtle.generateKey({ name: 'Ed25519' }, true, [ + 'sign', + 'verify', + ])) as unknown as webcrypto.CryptoKeyPair; + const publicKey = toHex([ + ...new Uint8Array(await subtle.exportKey('raw', keys.publicKey)), + ]); + return async (text: string) => ({ + publicKey, + signature: toHex([ + ...new Uint8Array( + await subtle.sign( + { name: 'Ed25519' }, + keys.privateKey, + new TextEncoder().encode(text), + ), + ), + ]), + }); +} + +/** A handoff signed the way the phones sign it, with real Ed25519 keys. */ +async function signedHandoff(): Promise { + const [driver, yard] = [await signer(), await signer()]; + const base = { + handoffId: 'ho-1', + vehicle: { id: 'b1', name: 'Truck 12' }, + driverId: 'off1driver', + driverName: 'Ada', + load: { bol: 'BOL-1', trailer: 'TR-1', seal: 'SL-1', pallets: 22 }, + checkedInAt: 1_700_000_000_000, + }; + const h: Handoff = { ...base, driverSig: await driver(checkInText(base)) }; + const a = { + dock: 'Dock 3' as const, + yardId: 'off1yard', + yardName: 'North Yard', + assignedAt: 1_700_000_060_000, + }; + return { + ...h, + assignment: { ...a, yardSig: await yard(assignmentText(h, a)) }, + }; +} + +describe('MyGeotab page', () => { + it('uses the same add-in id as the phones', () => { + expect(addin.ADD_IN_ID).toBe(ADD_IN_ID); + }); + + it('verifies a record the phones signed, and shows what was signed', async () => { + const details = toDetails(await signedHandoff(), null); + const result = await addin.verifyRecord(details, subtle); + expect(result.status).toBe('valid'); + expect(result.view).toMatchObject({ + handoffId: 'ho-1', + bol: 'BOL-1', + dock: 'Dock 3', + pallets: 22, + yardName: 'North Yard', + }); + }); + + it('rejects edits to the load, the dock or a signature', async () => { + const details = toDetails(await signedHandoff(), null); + const editedLoad = { + ...details, + signed: { + ...details.signed, + checkIn: details.signed.checkIn.replace('22', '21'), + }, + }; + expect((await addin.verifyRecord(editedLoad, subtle)).status).toBe( + 'invalid', + ); + const editedDock = { + ...details, + signed: { + ...details.signed, + assignment: details.signed.assignment.replace('Dock 3', 'Dock 4'), + }, + }; + expect((await addin.verifyRecord(editedDock, subtle)).reason).toMatch( + /Yard signature/, + ); + const swapped = { + ...details, + signed: { ...details.signed, yard: details.signed.driver }, + }; + expect((await addin.verifyRecord(swapped, subtle)).status).toBe('invalid'); + expect((await addin.verifyRecord({ signed: {} }, subtle)).status).toBe( + 'invalid', + ); + expect((await addin.verifyRecord(details, undefined)).status).toBe( + 'unsupported', + ); + }); + + it('shows a handoff synced from two phones once', () => { + const r = (id: string) => ({ details: { handoffId: id } }); + expect(addin.dedupe([r('a'), r('b'), r('a')])).toEqual([r('a'), r('b')]); + }); +}); diff --git a/apps/dockhand/__tests__/handoff.test.ts b/apps/dockhand/__tests__/handoff.test.ts new file mode 100644 index 0000000..352e3eb --- /dev/null +++ b/apps/dockhand/__tests__/handoff.test.ts @@ -0,0 +1,135 @@ +import { describe, expect, it } from '@jest/globals'; +import { + addHandoff, + assign, + assignmentText, + awaitingYard, + checkInText, + parseMessage, + yoursFor, + type Assignment, + type Handoff, +} from '../src/domain/handoff'; + +const sig = (c: string) => ({ + publicKey: c.repeat(64), + signature: c.repeat(128), +}); + +const handoff = (over: Partial = {}): Handoff => ({ + handoffId: 'ho-1', + vehicle: { id: 'b1', name: 'Truck 12' }, + driverId: 'off1driver', + driverName: 'Ada', + load: { bol: 'BOL-1', trailer: 'TR-1', seal: 'SL-1', pallets: 22 }, + checkedInAt: 1_700_000_000_000, + driverSig: sig('a'), + ...over, +}); + +const assignment: Assignment = { + dock: 'Dock 3', + yardId: 'off1yard', + yardName: 'North Yard', + assignedAt: 1_700_000_060_000, + yardSig: sig('b'), +}; + +describe('signed texts', () => { + it('change when any signed field changes, and the yard text covers the driver signature', () => { + const h = handoff(); + expect(checkInText(h)).toBe(checkInText(handoff())); + expect(checkInText(handoff({ load: { ...h.load, pallets: 21 } }))).not.toBe( + checkInText(h), + ); + expect( + checkInText(handoff({ vehicle: { id: 'b2', name: 'Truck 12' } })), + ).not.toBe(checkInText(h)); + const text = assignmentText(h, assignment); + expect(text).toContain(checkInText(h).replace(/"/g, '\\"')); + expect( + assignmentText(handoff({ driverSig: sig('c') }), assignment), + ).not.toBe(text); + expect(assignmentText(h, { ...assignment, dock: 'Dock 4' })).not.toBe(text); + }); +}); + +describe('ledger', () => { + it('dedupes by handoffId and assigns a dock only once', () => { + const one = addHandoff([], handoff()); + expect(addHandoff(one, handoff({ driverName: 'Mallory' }))).toBe(one); + const assigned = assign(one, 'ho-1', assignment); + expect(assigned[0].assignment?.dock).toBe('Dock 3'); + expect(assign(assigned, 'ho-1', { ...assignment, dock: 'Dock 5' })).toBe( + assigned, + ); + expect(assign(one, 'missing', assignment)).toBe(one); + }); + + it('resends only own, unassigned check-ins the yard has not acknowledged', () => { + const list = [ + handoff({ handoffId: 'ho-a' }), + handoff({ handoffId: 'ho-b' }), + handoff({ handoffId: 'ho-c', assignment }), + handoff({ handoffId: 'ho-d', driverId: 'off1other' }), + ]; + expect( + awaitingYard(list, 'off1driver', new Set(['ho-b'])).map( + (h) => h.handoffId, + ), + ).toEqual(['ho-a']); + }); + + it('tells each driver only about their own handoffs', () => { + const list = [ + handoff({ handoffId: 'ho-a' }), + handoff({ handoffId: 'ho-b', driverId: 'off1other' }), + ]; + expect(yoursFor(list, 'off1driver')).toEqual({ + type: 'yours', + handoffs: [list[0]], + }); + }); +}); + +describe('parseMessage', () => { + it('accepts well-formed messages and rejects the rest', () => { + expect(parseMessage({ type: 'checkin', handoff: handoff() })).toEqual({ + type: 'checkin', + handoff: handoff(), + }); + expect( + parseMessage({ type: 'yours', handoffs: [handoff({ assignment })] }) + ?.type, + ).toBe('yours'); + // A check-in arriving already assigned is not a check-in. + expect( + parseMessage({ type: 'checkin', handoff: handoff({ assignment }) }), + ).toBeNull(); + expect( + parseMessage({ + type: 'checkin', + handoff: handoff({ handoffId: 'x" = "y' }), + }), + ).toBeNull(); + expect( + parseMessage({ + type: 'checkin', + handoff: { + ...handoff(), + driverSig: { publicKey: 'ab', signature: 'cd' }, + }, + }), + ).toBeNull(); + expect( + parseMessage({ + type: 'yours', + handoffs: [ + handoff({ assignment: { ...assignment, dock: 'Dock 99' as never } }), + ], + }), + ).toBeNull(); + expect(parseMessage({ type: 'nope' })).toBeNull(); + expect(parseMessage('checkin')).toBeNull(); + }); +}); diff --git a/apps/dockhand/__tests__/sync.test.ts b/apps/dockhand/__tests__/sync.test.ts new file mode 100644 index 0000000..ee92558 --- /dev/null +++ b/apps/dockhand/__tests__/sync.test.ts @@ -0,0 +1,207 @@ +import { describe, expect, it, jest } from '@jest/globals'; +import type { Handoff } from '../src/domain/handoff'; +import { + DETAILS_MAX_CHARS, + mockIngest, + nearestFix, + toDetails, + unsynced, +} from '../src/domain/sync'; +import { + cleanServer, + pushHandoff, + SessionExpired, + signIn, + type Session, +} from '../src/services/mygeotab'; + +const sig = (c: string) => ({ + publicKey: c.repeat(64), + signature: c.repeat(128), +}); +const done: Handoff = { + handoffId: 'ho-1', + vehicle: { id: 'b1', name: 'Truck 12' }, + driverId: 'off1driver', + driverName: 'Ada', + load: { bol: 'BOL-1', trailer: 'TR-1', seal: 'SL-1', pallets: 22 }, + checkedInAt: 1_700_000_000_000, + driverSig: sig('a'), + assignment: { + dock: 'Dock 3', + yardId: 'off1yard', + yardName: 'North Yard', + assignedAt: 1_700_000_060_000, + yardSig: sig('b'), + }, +}; + +describe('what gets synced', () => { + it('picks completed handoffs not yet synced', () => { + const waiting = { ...done, handoffId: 'ho-2', assignment: undefined }; + expect(unsynced([done, waiting], [])).toEqual([done]); + expect(unsynced([done, waiting], ['ho-1'])).toEqual([]); + }); + + it('fits a worst-case record under the AddInData limit', () => { + const long = 'W'.repeat(40); + const big: Handoff = { + ...done, + vehicle: { id: long, name: long }, + driverId: 'o'.repeat(80), + driverName: long, + load: { + bol: long.slice(0, 30), + trailer: long.slice(0, 30), + seal: long.slice(0, 30), + pallets: 99, + }, + assignment: { + ...done.assignment!, + yardId: 'o'.repeat(80), + yardName: long, + }, + }; + const details = toDetails(big, { + latitude: -33.123456789, + longitude: 151.123456789, + dateTime: new Date().toISOString(), + }); + expect(JSON.stringify(details).length).toBeLessThan(DETAILS_MAX_CHARS); + expect(() => toDetails({ ...done, assignment: undefined }, null)).toThrow( + /no dock/, + ); + }); + + it('takes the GO fix closest to check-in', () => { + const at = Date.parse('2026-10-12T10:00:00Z'); + const fix = (t: string) => ({ latitude: 1, longitude: 2, dateTime: t }); + expect( + nearestFix( + [ + fix('2026-10-12T09:50:00Z'), + fix('2026-10-12T10:01:00Z'), + fix('2026-10-12T10:09:00Z'), + ], + at, + )?.dateTime, + ).toBe('2026-10-12T10:01:00Z'); + expect(nearestFix([], at)).toBeNull(); + }); + + it('mock ingest drops duplicates by handoffId', () => { + const first = mockIngest([], ['a', 'b']); + expect(first).toEqual({ + stored: ['a', 'b'], + added: ['a', 'b'], + duplicates: 0, + }); + expect(mockIngest(first.stored, ['b', 'c'])).toEqual({ + stored: ['a', 'b', 'c'], + added: ['c'], + duplicates: 1, + }); + }); +}); + +type Call = { url: string; method: string; params: Record }; + +/** A fake MyGeotab: answers by method name and records every call. */ +function fakeGeotab(answer: (c: Call) => unknown) { + const calls: Call[] = []; + const fetchImpl = jest.fn(async (url: any, init: any) => { + const { method, params } = JSON.parse(init.body); + const call = { url, method, params }; + calls.push(call); + return { json: async () => answer(call) } as Response; + }); + return { calls, fetchImpl: fetchImpl as unknown as typeof fetch }; +} + +const session: Session = { + server: 'my3.geotab.com', + credentials: { database: 'demo', userName: 'a@b.c', sessionId: 's1' }, +}; + +describe('MyGeotab client', () => { + it('signs in and follows the server named in path', async () => { + const credentials = { + database: 'demo', + userName: 'a@b.c', + sessionId: 's1', + }; + const moved = fakeGeotab(() => ({ + result: { credentials, path: 'my3.geotab.com' }, + })); + expect( + await signIn( + 'https://my.geotab.com/', + 'demo', + 'a@b.c', + 'pw', + moved.fetchImpl, + ), + ).toEqual({ server: 'my3.geotab.com', credentials }); + expect(moved.calls[0].url).toBe('https://my.geotab.com/apiv1'); + expect(moved.calls[0].method).toBe('Authenticate'); + + const here = fakeGeotab(() => ({ + result: { credentials, path: 'ThisServer' }, + })); + expect( + (await signIn('my.geotab.com', 'demo', 'a@b.c', 'pw', here.fetchImpl)) + .server, + ).toBe('my.geotab.com'); + expect(cleanServer(' ')).toBe('my.geotab.com'); + }); + + it('adds a handoff once and reports the second push as a duplicate', async () => { + const stored: unknown[] = []; + const geotab = fakeGeotab(({ method, params }) => { + if (method === 'Get') + return { + result: stored.filter( + (d: any) => + params.search.whereClause === `handoffId = "${d.handoffId}"`, + ), + }; + if (method === 'Add') { + stored.push(params.entity.details); + return { result: 'id1' }; + } + return { error: { message: 'unexpected' } }; + }); + const details = toDetails(done, null); + expect(await pushHandoff(session, details, geotab.fetchImpl)).toBe('added'); + expect(await pushHandoff(session, details, geotab.fetchImpl)).toBe( + 'duplicate', + ); + expect(stored).toHaveLength(1); + expect( + geotab.calls.every( + (c) => + c.url === 'https://my3.geotab.com/apiv1' && + c.params.credentials.sessionId === 's1', + ), + ).toBe(true); + await expect( + pushHandoff( + session, + { ...details, handoffId: 'x" or "1' }, + geotab.fetchImpl, + ), + ).rejects.toThrow(/Bad handoff id/); + }); + + it('turns an expired session into SessionExpired', async () => { + const geotab = fakeGeotab(() => ({ + error: { + message: 'x', + errors: [{ name: 'InvalidUserException', message: 'Invalid session' }], + }, + })); + await expect( + pushHandoff(session, toDetails(done, null), geotab.fetchImpl), + ).rejects.toBeInstanceOf(SessionExpired); + }); +}); diff --git a/apps/dockhand/android/app/build.gradle b/apps/dockhand/android/app/build.gradle new file mode 100644 index 0000000..911b6de --- /dev/null +++ b/apps/dockhand/android/app/build.gradle @@ -0,0 +1,121 @@ +apply plugin: "com.android.application" +apply plugin: "org.jetbrains.kotlin.android" +apply plugin: "com.facebook.react" + +/** + * This is the configuration block to customize your React Native Android app. + * By default you don't need to apply any configuration, just uncomment the lines you need. + */ +react { + /* Folders */ + // The root of your project, i.e. where "package.json" lives. Default is '../..' + // root = file("../../") + // The folder where the react-native NPM package is. Default is ../../node_modules/react-native + // reactNativeDir = file("../../node_modules/react-native") + // The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen + // codegenDir = file("../../node_modules/@react-native/codegen") + // The cli.js file which is the React Native CLI entrypoint. Default is ../../node_modules/react-native/cli.js + // cliFile = file("../../node_modules/react-native/cli.js") + + /* Variants */ + // The list of variants to that are debuggable. For those we're going to + // skip the bundling of the JS bundle and the assets. Default is "debug", "debugOptimized". + // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. + // debuggableVariants = ["liteDebug", "liteDebugOptimized", "prodDebug", "prodDebugOptimized"] + + /* Bundling */ + // A list containing the node command and its flags. Default is just 'node'. + // nodeExecutableAndArgs = ["node"] + // + // The command to run when bundling. By default is 'bundle' + // bundleCommand = "ram-bundle" + // + // The path to the CLI configuration file. Default is empty. + // bundleConfig = file(../rn-cli.config.js) + // + // The name of the generated asset file containing your JS bundle + // bundleAssetName = "MyApplication.android.bundle" + // + // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' + // entryFile = file("../js/MyApplication.android.js") + // + // A list of extra flags to pass to the 'bundle' commands. + // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle + // extraPackagerArgs = [] + + /* Hermes Commands */ + // The hermes compiler command to run. By default it is 'hermesc' + // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" + // + // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" + // hermesFlags = ["-O", "-output-source-map"] + + /* Autolinking */ + autolinkLibrariesWithApp() +} + +/** + * Set this to true to Run Proguard on Release builds to minify the Java bytecode. + */ +def enableProguardInReleaseBuilds = false + +/** + * The preferred build flavor of JavaScriptCore (JSC) + * + * For example, to use the international variant, you can use: + * `def jscFlavor = io.github.react-native-community:jsc-android-intl:2026004.+` + * + * The international variant includes ICU i18n library and necessary data + * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that + * give correct results when using with locales other than en-US. Note that + * this variant is about 6MiB larger per architecture than default. + */ +def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+' + +android { + ndkVersion rootProject.ext.ndkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + compileSdk rootProject.ext.compileSdkVersion + + namespace "com.offlineappexamples.dockhand" + // Shared fonts (Space Grotesk, DM Serif Display) from the ui package. + sourceSets.main.res.srcDirs += "../../../../packages/ui/fonts" + defaultConfig { + applicationId "com.offlineappexamples.dockhand" + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode 1 + versionName "1.0" + } + signingConfigs { + debug { + storeFile file('debug.keystore') + storePassword 'android' + keyAlias 'androiddebugkey' + keyPassword 'android' + } + } + buildTypes { + debug { + signingConfig signingConfigs.debug + } + release { + // Caution! In production, you need to generate your own keystore file. + // see https://reactnative.dev/docs/signed-apk-android. + signingConfig signingConfigs.debug + minifyEnabled enableProguardInReleaseBuilds + proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" + } + } +} + +dependencies { + // The version of react-native is set by the React Native Gradle Plugin + implementation("com.facebook.react:react-android") + + if (hermesEnabled.toBoolean()) { + implementation("com.facebook.react:hermes-android") + } else { + implementation jscFlavor + } +} diff --git a/apps/dockhand/android/app/debug.keystore b/apps/dockhand/android/app/debug.keystore new file mode 100644 index 0000000000000000000000000000000000000000..364e105ed39fbfd62001429a68140672b06ec0de GIT binary patch literal 2257 zcmchYXEfYt8;7T1^dLH$VOTZ%2NOdOH5j5LYLtZ0q7x-V8_6gU5)#7dkq{HTmsfNq zB3ZqcAxeY^G10@?efK?Q&)M(qInVv!xjx+IKEL}p*K@LYvIzo#AZG>st5|P)KF1_Z;y){W{<7K{nl!CPuE z_^(!C(Ol0n8 zK13*rzAtW>(wULKPRYLd7G18F8#1P`V*9`(Poj26eOXYyBVZPno~Cvvhx7vPjAuZo zF?VD!zB~QG(!zbw#qsxT8%BSpqMZ4f70ZPn-3y$L8{EVbbN9$H`B&Z1quk9tgp5FM zuxp3pJ0b8u|3+#5bkJ4SRnCF2l7#DyLYXYY8*?OuAwK4E6J{0N=O3QNVzQ$L#FKkR zi-c@&!nDvezOV$i$Lr}iF$XEcwnybQ6WZrMKuw8gCL^U#D;q3t&HpTbqyD%vG=TeDlzCT~MXUPC|Leb-Uk+ z=vnMd(|>ld?Fh>V8poP;q;;nc@en$|rnP0ytzD&fFkCeUE^kG9Kx4wUh!!rpjwKDP zyw_e|a^x_w3E zP}}@$g>*LLJ4i0`Gx)qltL}@;mDv}D*xR^oeWcWdPkW@Uu)B^X&4W1$p6}ze!zudJ zyiLg@uggoMIArBr*27EZV7djDg@W1MaL+rcZ-lrANJQ%%>u8)ZMWU@R2qtnmG(acP z0d_^!t>}5W zpT`*2NR+0+SpTHb+6Js4b;%LJB;B_-ChhnU5py}iJtku*hm5F0!iql8Hrpcy1aYbT z1*dKC5ua6pMX@@iONI?Hpr%h;&YaXp9n!ND7-=a%BD7v&g zOO41M6EbE24mJ#S$Ui0-brR5ML%@|ndz^)YLMMV1atna{Fw<;TF@>d&F|!Z>8eg>>hkFrV)W+uv=`^F9^e zzzM2*oOjT9%gLoub%(R57p-`TXFe#oh1_{&N-YN z<}artH|m=d8TQuKSWE)Z%puU|g|^^NFwC#N=@dPhasyYjoy(fdEVfKR@cXKHZV-`06HsP`|Ftx;8(YD$fFXumLWbGnu$GMqRncXYY9mwz9$ap zQtfZB^_BeNYITh^hA7+(XNFox5WMeG_LtJ%*Q}$8VKDI_p8^pqX)}NMb`0e|wgF7D zuQACY_Ua<1ri{;Jwt@_1sW9zzdgnyh_O#8y+C;LcZq6=4e^cs6KvmK@$vVpKFGbQ= z$)Eux5C|Fx;Gtmv9^#Y-g@7Rt7*eLp5n!gJmn7&B_L$G?NCN`AP>cXQEz}%F%K;vUs{+l4Q{}eWW;ATe2 zqvXzxoIDy(u;F2q1JH7Sf;{jy_j})F+cKlIOmNfjBGHoG^CN zM|Ho&&X|L-36f}Q-obEACz`sI%2f&k>z5c$2TyTSj~vmO)BW~+N^kt`Jt@R|s!){H ze1_eCrlNaPkJQhL$WG&iRvF*YG=gXd1IyYQ9ew|iYn7r~g!wOnw;@n42>enAxBv*A zEmV*N#sxdicyNM=A4|yaOC5MByts}s_Hpfj|y<6G=o=!3S@eIFKDdpR7|FY>L&Wat&oW&cm&X~ z5Bt>Fcq(fgnvlvLSYg&o6>&fY`ODg4`V^lWWD=%oJ#Kbad2u~! zLECFS*??>|vDsNR&pH=Ze0Eo`sC_G`OjoEKVHY|wmwlX&(XBE<@sx3Hd^gtd-fNwUHsylg06p`U2y_={u}Bc + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/dockhand/android/app/src/main/java/com/offlineappexamples/dockhand/MainActivity.kt b/apps/dockhand/android/app/src/main/java/com/offlineappexamples/dockhand/MainActivity.kt new file mode 100644 index 0000000..bb2bc19 --- /dev/null +++ b/apps/dockhand/android/app/src/main/java/com/offlineappexamples/dockhand/MainActivity.kt @@ -0,0 +1,22 @@ +package com.offlineappexamples.dockhand + +import com.facebook.react.ReactActivity +import com.facebook.react.ReactActivityDelegate +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled +import com.facebook.react.defaults.DefaultReactActivityDelegate + +class MainActivity : ReactActivity() { + + /** + * Returns the name of the main component registered from JavaScript. This is used to schedule + * rendering of the component. + */ + override fun getMainComponentName(): String = "DockHand" + + /** + * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] + * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] + */ + override fun createReactActivityDelegate(): ReactActivityDelegate = + DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) +} diff --git a/apps/dockhand/android/app/src/main/java/com/offlineappexamples/dockhand/MainApplication.kt b/apps/dockhand/android/app/src/main/java/com/offlineappexamples/dockhand/MainApplication.kt new file mode 100644 index 0000000..3aba4df --- /dev/null +++ b/apps/dockhand/android/app/src/main/java/com/offlineappexamples/dockhand/MainApplication.kt @@ -0,0 +1,31 @@ +package com.offlineappexamples.dockhand + +import android.app.Application +import com.facebook.react.PackageList +import com.facebook.react.ReactApplication +import com.facebook.react.ReactHost +import com.facebook.react.common.assets.ReactFontManager +import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative +import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost + +class MainApplication : Application(), ReactApplication { + + override val reactHost: ReactHost by lazy { + getDefaultReactHost( + context = applicationContext, + packageList = + PackageList(this).packages.apply { + // Packages that cannot be autolinked yet can be added manually here, for example: + // add(MyReactNativePackage()) + }, + ) + } + + override fun onCreate() { + super.onCreate() + loadReactNative(this) + // Registered as families so fontWeight picks the right file (see packages/ui/fonts). + ReactFontManager.getInstance().addCustomFont(this, "Space Grotesk", R.font.space_grotesk) + ReactFontManager.getInstance().addCustomFont(this, "DM Serif Display", R.font.dm_serif_display) + } +} diff --git a/apps/dockhand/android/app/src/main/res/drawable/rn_edit_text_material.xml b/apps/dockhand/android/app/src/main/res/drawable/rn_edit_text_material.xml new file mode 100644 index 0000000..5c25e72 --- /dev/null +++ b/apps/dockhand/android/app/src/main/res/drawable/rn_edit_text_material.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + diff --git a/apps/dockhand/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/apps/dockhand/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..be43858 --- /dev/null +++ b/apps/dockhand/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/dockhand/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/apps/dockhand/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..be43858 --- /dev/null +++ b/apps/dockhand/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/dockhand/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/dockhand/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..ed5c3f735a15f84c8093d4f0a654b6827145e201 GIT binary patch literal 1639 zcmV-t2AKJYP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS=(Md!>RCodHoNZ_vMHt8byO)^O z+E-((skOug8xpNAH8EPL35rrtBa~Evg$gPN`ppVz(QoP({Z>Fa``%YMWY<%liCx-R$1nZmzk^+||2zAb0b&&olGO%SuVS9vph^n8^)v!qwuEp!^1}yoZRnVPd2U6PEv*^8ql`A z2N}F1`Atb>DkyCjS?t2#yL$wNG(A`H0C^jG>rvyr569`sEn49*&+Et3^fu?4zVUcV za{vjPX4GUplm62fFH|*Q49Yz6!e_T!K#H55(yx+hds$USq zP6&tJ96iFqXChG_kUt?;+nf`JqK3(WHz^Uvzm<2?9hdMrwOp0QhjHLsv+J*OES#ji z&_p&aGVX&(xi(D*2pd;s=-Vue6avCVCTVUTtRxfg8U&E+t*oRFa8|i;-~WGZJX4ya zHCfmM>maRKD@&OLRN((<0a-u=2o%V+J;(woK%hXj*&eiLA+Yr>VEK{))hHsHI;opB z42oGmU2VY59l%>J0+%f>+>E7{0Q@T?)J2`tO&bQqETAtB0mHunS6&Xh{`~Bao_!Sf z;?3ZX;WQzfH+BU&shc(oidjIXCx9nk2_UVJ9ZedfPU!{Rol!FxW|}I5LmdQ8IMhuW z2E{BOR{YfId`K55yPLHM+d(yr*ANathj6Idu$TwL2857?egPU*0?QWXHf!JrFg+cm zG0e2Ma8l;A)WM!p0ys49cpcm2`E-dSBma*jG6|nNm2Ysz{lMc}gY>sQ0N(i|zYHM} zOg08Hzm(RL8m-z2DPzNu;PAT9KjaBsCeXlelB8XmR8~`3X@;1l%9cZo1yq1~X@{6v znT2&g`*lF$YM}M%DEIDLfz@&=ANdV9_&sp;T$EShP-!RWuyI`d-nSXJxfM`v`C*yx z`yV;6a_9%((|thyV3=C6@6rOgu^D)(7ifv~mbo-7FxkYhHaC z=-3do^sh5AM2-SSWw;zUA+P0%>;=?G-Qn6PvY<6&7SJ<~05`0UDm*q0e0Ll;b2do3 z>IzxQ&m2*T%AIeBx@p6pm;Mo(?oNQV42c;) zb+Yq*q9>{{w+P&UQ&ubmZfcPSV~c~hj!)X6O#@;E(6)Pkl`DekPRR+|p`U~B#S4Ju zMxL6`+Z5WOO#`C2rvKIOp;%=n{sy?%tC0!2rODi+8n>wtlzu?1>wrs_MO9p~2v{$@ z=6YEqa%*8qXp^=zi+(_xH|AH7Lx97}v?SS{r%nBUu9aa_E-_$RKcM>9@G3PZ1GWtS zI*<2Xroh$fx2^wq&%9r z$5h?ri+8ol+m8u(>pK4jYQh=yY3rHoR#4R8N zh#IwkR3b{8Kd6)Ep;i(J2pj1^^krcsnSeJYfW*;|?giVV=XkbI6rrK&XKyT=hnOMbtJ9ia04<%n~ks)iIfSdxQGf#hs8e9uc$ z*>*X@(;pV7ns47lSC)>Ia!!c)bC3$bpOXS7Z!b%p{u2SJ!ScrE=HxA2*BLi~^bq4a lL8%}&fm|SS4OqZU;J?#yeDQqKN_PMN002ovPDHLkV1iA^@Q?ri literal 0 HcmV?d00001 diff --git a/apps/dockhand/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/apps/dockhand/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..e2a2cbc3a2ee8205116a3326387860a2e7d74c60 GIT binary patch literal 2086 zcmd5-`#;kQAOCLX40YTO$E8*|j)x;hikeH5#U8oFOhdvXW6Inj3O$FDOBg9k3$e|e zxf`!0bmo%fy17-%Wg3plT&IU$p66dUKfEuW&*%Ny`-jh;_O_NMB$Oop062lLvT!`q zf+IkV9WrC>CzV41;TrIWJp=$H+yg|9B8N~u1OSMTK!C`h zf{u11@c*s_B$5C7qoBAn=Nth1kcO}@cZvYZTfM|4d^qv!=_ddf0{!PwFC2=F6in%WJYn!CAXHEN_QyLmJHaCfVWmqh7TtN9O$CMf}xKuW3SJz%-FwII5 z+zm?B!<3?7n5BG=?hrpPZy}n=zEG0xVDvNc;7m&`r{;@oAdMKLPMcg=3b~yrWoD+< zG9771fJs~rY_ZxI?=}5YTVKJ~PhlEJGK8Qs$Id1R^31$KGAHJFum@+*R!EbJt5PGQ zdNhGK)ntxfvQ?9I{$ZHbHR-w2&UfhGcZ$ggb&n9G0<18B`EW5lFZwsukKa)2_lu1R zZe>gIVr`xiBUC!4E1X3*L4Kk5vsSB4OYEFD1G!pVSqoT+b<&D0(RHp%F50iH0|`W0 zXenNxM?D`_#;V!R z0C59gpLKc}Zm|awvhsc!p3`MMgdo@Y{7uV)#4oXUR(Y!_mArRhwDh`4=ICh6O8yK%>#j?GH+F}dZ?}&gEK&vG z3~7L){bc2ESp4n-OcYQ*Xt<0C^p@(4Dc(G5aARV?X!|BA{)|C-mC}(qVv(rtBs1CbovqEx7_z!F1Pa z)(ZdEgZs;$HtM`O8erk*o`j0ZEdJEcUh~Sf+g6y%vWfGYdkOTBt2@gWqO;od7{9#l z~21MxtLrZp4zIt=3i3nw1;i?>pJ}_WY23+a7kvAPIjKn}?2c z53@;MpAW72K_Phtev-D}4OdH)U~s`g^M^#m5mTuHV^*WT*<*2BNTW-kr+4%QlEp}o z=z9D}+Z#1^#kq5!MmsRVM)v?}Y(%;16B87$n0BB!+GW%u{}gG7jj#KKBhODHvsQ7N zIVpc6sYI?Y;Pa^qHw^V}wli%zutAjD$o$KD0JiX}9hc&`vpDKu+Dm66$oC<=vtF7iiyAt2)MSh7H1^ zbBk{an`10ak1N$q@q4Q8Tb8GA$u`;kGVV|&*OmL$<2AQgNB?Cru@N`c1bXbSBH*I@ z6zUDGXT>Q?aMDNp^ooOXb6?cSJmAHd?kk}wszdYdbpkUbi>;gvtDv#I z<^|vHa7&OW+_S^kzw@hfC>pcvlcKnINnMpR#unCH%#lY!O;v3^&Kjyy>$`qR6wGW< zi^!aRL;p~z7xnscZ&@HaT0ZWaL@lpQNfd0|qh=KNV=puWg)Ch+JVrEm(V?=_dMPDh26%FQ*MiYo4F8+oKX zI~Y2|R})q=~RhNd%r)+(mJr7nijAg|*v8{x;#_zcXjAi;O#AA=$vmT3^qZ6{O&U p5|p2gWUGrxn*pGIGI_~upYn%f73SRda^>hpBjC0c6<0mu{tJV;k8uD1 literal 0 HcmV?d00001 diff --git a/apps/dockhand/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/apps/dockhand/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..aa967d0699e0e2744628b049c7f4a05ac90be5a1 GIT binary patch literal 2293 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 literal 0 HcmV?d00001 diff --git a/apps/dockhand/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/apps/dockhand/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..4dfc1667d9ba0d99a721e9b9e453fa7d9c1504d2 GIT binary patch literal 3273 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 literal 0 HcmV?d00001 diff --git a/apps/dockhand/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/apps/dockhand/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..f88d6fce4615588996591a97bbccc57dc7926fd6 GIT binary patch literal 5132 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 XffGj_3I!m3-xC|&G1INM1&{tWvJdu+ literal 0 HcmV?d00001 diff --git a/apps/dockhand/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/apps/dockhand/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..b6ab5932590e9cf3d5cad031e42a6bf369985728 GIT binary patch literal 7124 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 literal 0 HcmV?d00001 diff --git a/apps/dockhand/android/app/src/main/res/values/ic_launcher_background.xml b/apps/dockhand/android/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..c342fa2 --- /dev/null +++ b/apps/dockhand/android/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #FF385C + diff --git a/apps/dockhand/android/app/src/main/res/values/strings.xml b/apps/dockhand/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..3f7163d --- /dev/null +++ b/apps/dockhand/android/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + DockHand + diff --git a/apps/dockhand/android/app/src/main/res/values/styles.xml b/apps/dockhand/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..7ba83a2 --- /dev/null +++ b/apps/dockhand/android/app/src/main/res/values/styles.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/apps/dockhand/android/build.gradle b/apps/dockhand/android/build.gradle new file mode 100644 index 0000000..dad99b0 --- /dev/null +++ b/apps/dockhand/android/build.gradle @@ -0,0 +1,21 @@ +buildscript { + ext { + buildToolsVersion = "36.0.0" + minSdkVersion = 24 + compileSdkVersion = 36 + targetSdkVersion = 36 + ndkVersion = "27.1.12297006" + kotlinVersion = "2.1.20" + } + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle") + classpath("com.facebook.react:react-native-gradle-plugin") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") + } +} + +apply plugin: "com.facebook.react.rootproject" diff --git a/apps/dockhand/android/gradle.properties b/apps/dockhand/android/gradle.properties new file mode 100644 index 0000000..9afe615 --- /dev/null +++ b/apps/dockhand/android/gradle.properties @@ -0,0 +1,44 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m +org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true + +# Use this property to specify which architecture you want to build. +# You can also override it from the CLI using +# ./gradlew -PreactNativeArchitectures=x86_64 +reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 + +# Use this property to enable support to the new architecture. +# This will allow you to use TurboModules and the Fabric render in +# your application. You should enable this flag either if you want +# to write custom TurboModules/Fabric components OR use libraries that +# are providing them. +newArchEnabled=true + +# Use this property to enable or disable the Hermes JS engine. +# If set to false, you will be using JSC instead. +hermesEnabled=true + +# Use this property to enable edge-to-edge display support. +# This allows your app to draw behind system bars for an immersive UI. +# Note: Only works with ReactActivity and should not be used with custom Activity. +edgeToEdgeEnabled=false diff --git a/apps/dockhand/android/gradle/wrapper/gradle-wrapper.jar b/apps/dockhand/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..61285a659d17295f1de7c53e24fdf13ad755c379 GIT binary patch literal 46175 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

pVX@HRij*jw$g4?}Pn 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 literal 0 HcmV?d00001 diff --git a/apps/dockhand/android/gradle/wrapper/gradle-wrapper.properties b/apps/dockhand/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..37f78a6 --- /dev/null +++ b/apps/dockhand/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/apps/dockhand/android/gradlew b/apps/dockhand/android/gradlew new file mode 100755 index 0000000..adff685 --- /dev/null +++ b/apps/dockhand/android/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/apps/dockhand/android/gradlew.bat b/apps/dockhand/android/gradlew.bat new file mode 100644 index 0000000..39baf4d --- /dev/null +++ b/apps/dockhand/android/gradlew.bat @@ -0,0 +1,98 @@ +@REM Copyright (c) Meta Platforms, Inc. and affiliates. +@REM +@REM This source code is licensed under the MIT license found in the +@REM LICENSE file in the root directory of this source tree. + +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/apps/dockhand/android/settings.gradle b/apps/dockhand/android/settings.gradle new file mode 100644 index 0000000..07152aa --- /dev/null +++ b/apps/dockhand/android/settings.gradle @@ -0,0 +1,6 @@ +pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") } +plugins { id("com.facebook.react.settings") } +extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() } +rootProject.name = 'DockHand' +include ':app' +includeBuild('../node_modules/@react-native/gradle-plugin') diff --git a/apps/dockhand/app.json b/apps/dockhand/app.json new file mode 100644 index 0000000..c4839f0 --- /dev/null +++ b/apps/dockhand/app.json @@ -0,0 +1,4 @@ +{ + "name": "DockHand", + "displayName": "DockHand" +} diff --git a/apps/dockhand/babel.config.js b/apps/dockhand/babel.config.js new file mode 100644 index 0000000..61d5d89 --- /dev/null +++ b/apps/dockhand/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: ['module:@react-native/babel-preset', 'nativewind/babel'], +}; diff --git a/apps/dockhand/index.js b/apps/dockhand/index.js new file mode 100644 index 0000000..9b73932 --- /dev/null +++ b/apps/dockhand/index.js @@ -0,0 +1,9 @@ +/** + * @format + */ + +import { AppRegistry } from 'react-native'; +import App from './App'; +import { name as appName } from './app.json'; + +AppRegistry.registerComponent(appName, () => App); diff --git a/apps/dockhand/ios/.xcode.env b/apps/dockhand/ios/.xcode.env new file mode 100644 index 0000000..3d5782c --- /dev/null +++ b/apps/dockhand/ios/.xcode.env @@ -0,0 +1,11 @@ +# This `.xcode.env` file is versioned and is used to source the environment +# used when running script phases inside Xcode. +# To customize your local environment, you can create an `.xcode.env.local` +# file that is not versioned. + +# NODE_BINARY variable contains the PATH to the node executable. +# +# Customize the NODE_BINARY variable here. +# For example, to use nvm with brew, add the following line +# . "$(brew --prefix nvm)/nvm.sh" --no-use +export NODE_BINARY=$(command -v node) diff --git a/apps/dockhand/ios/DockHand.xcodeproj/project.pbxproj b/apps/dockhand/ios/DockHand.xcodeproj/project.pbxproj new file mode 100644 index 0000000..f6debaa --- /dev/null +++ b/apps/dockhand/ios/DockHand.xcodeproj/project.pbxproj @@ -0,0 +1,528 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 0C80B921A6F3F58F76C31292 /* libPods-DockHand.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-DockHand.a */; }; + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; + 4204E37E1345127821B1D61D /* space_grotesk_medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7D7566AB5B03E0DD2AE8D975 /* space_grotesk_medium.ttf */; }; + 4DADA4D9FCF7ACF7CD308512 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; }; + 59FA85C7DABD4C424CC92A6D /* dm_serif_display_regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0DDFFF2E5319DC6CB07FB356 /* dm_serif_display_regular.ttf */; }; + 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; }; + 77425900FDE6A239FBDE1587 /* space_grotesk_semibold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 16DDDA6F0AE168DD28C46529 /* space_grotesk_semibold.ttf */; }; + 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; + ACF648FCD514FC7967494DCF /* space_grotesk_regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 8077B93F1BA98961C21AE16E /* space_grotesk_regular.ttf */; }; + B2E8F1A12CA45701006654EE /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2E8F1A02CA45701006654EE /* SceneDelegate.swift */; }; + C79EC88C8F01E8069AAC7EB9 /* space_grotesk_bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A7E51F5E996CDBEB4D2962DC /* space_grotesk_bold.ttf */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 0DDFFF2E5319DC6CB07FB356 /* dm_serif_display_regular.ttf */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file; name = dm_serif_display_regular.ttf; path = ../../../packages/ui/fonts/font/dm_serif_display_regular.ttf; sourceTree = SOURCE_ROOT; }; + 13B07F961A680F5B00A75B9A /* DockHand.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DockHand.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = DockHand/Images.xcassets; sourceTree = ""; }; + 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = DockHand/Info.plist; sourceTree = ""; }; + 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = DockHand/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 16DDDA6F0AE168DD28C46529 /* space_grotesk_semibold.ttf */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file; name = space_grotesk_semibold.ttf; path = ../../../packages/ui/fonts/font/space_grotesk_semibold.ttf; sourceTree = SOURCE_ROOT; }; + 3B4392A12AC88292D35C810B /* Pods-DockHand.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DockHand.debug.xcconfig"; path = "Target Support Files/Pods-DockHand/Pods-DockHand.debug.xcconfig"; sourceTree = ""; }; + 5709B34CF0A7D63546082F79 /* Pods-DockHand.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DockHand.release.xcconfig"; path = "Target Support Files/Pods-DockHand/Pods-DockHand.release.xcconfig"; sourceTree = ""; }; + 5DCACB8F33CDC322A6C60F78 /* libPods-DockHand.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-DockHand.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = DockHand/AppDelegate.swift; sourceTree = ""; }; + 7D7566AB5B03E0DD2AE8D975 /* space_grotesk_medium.ttf */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file; name = space_grotesk_medium.ttf; path = ../../../packages/ui/fonts/font/space_grotesk_medium.ttf; sourceTree = SOURCE_ROOT; }; + 8077B93F1BA98961C21AE16E /* space_grotesk_regular.ttf */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file; name = space_grotesk_regular.ttf; path = ../../../packages/ui/fonts/font/space_grotesk_regular.ttf; sourceTree = SOURCE_ROOT; }; + 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = DockHand/LaunchScreen.storyboard; sourceTree = ""; }; + A7E51F5E996CDBEB4D2962DC /* space_grotesk_bold.ttf */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file; name = space_grotesk_bold.ttf; path = ../../../packages/ui/fonts/font/space_grotesk_bold.ttf; sourceTree = SOURCE_ROOT; }; + B2E8F1A02CA45701006654EE /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SceneDelegate.swift; path = DockHand/SceneDelegate.swift; sourceTree = ""; }; + ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0C80B921A6F3F58F76C31292 /* libPods-DockHand.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 13B07FAE1A68108700A75B9A /* DockHand */ = { + isa = PBXGroup; + children = ( + 13B07FB51A68108700A75B9A /* Images.xcassets */, + 761780EC2CA45674006654EE /* AppDelegate.swift */, + B2E8F1A02CA45701006654EE /* SceneDelegate.swift */, + 13B07FB61A68108700A75B9A /* Info.plist */, + 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, + 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */, + ); + name = DockHand; + sourceTree = ""; + }; + 2D16E6871FA4F8E400B85C8A /* Frameworks */ = { + isa = PBXGroup; + children = ( + ED297162215061F000B7C4FE /* JavaScriptCore.framework */, + 5DCACB8F33CDC322A6C60F78 /* libPods-DockHand.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 4EAC6312E6044F5B6A468D1D /* Fonts */ = { + isa = PBXGroup; + children = ( + 0DDFFF2E5319DC6CB07FB356 /* dm_serif_display_regular.ttf */, + A7E51F5E996CDBEB4D2962DC /* space_grotesk_bold.ttf */, + 7D7566AB5B03E0DD2AE8D975 /* space_grotesk_medium.ttf */, + 8077B93F1BA98961C21AE16E /* space_grotesk_regular.ttf */, + 16DDDA6F0AE168DD28C46529 /* space_grotesk_semibold.ttf */, + ); + name = Fonts; + sourceTree = ""; + }; + 832341AE1AAA6A7D00B99B32 /* Libraries */ = { + isa = PBXGroup; + children = ( + ); + name = Libraries; + sourceTree = ""; + }; + 83CBB9F61A601CBA00E9B192 = { + isa = PBXGroup; + children = ( + 13B07FAE1A68108700A75B9A /* DockHand */, + 832341AE1AAA6A7D00B99B32 /* Libraries */, + 83CBBA001A601CBA00E9B192 /* Products */, + 2D16E6871FA4F8E400B85C8A /* Frameworks */, + BBD78D7AC51CEA395F1C20DB /* Pods */, + 4EAC6312E6044F5B6A468D1D /* Fonts */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 83CBBA001A601CBA00E9B192 /* Products */ = { + isa = PBXGroup; + children = ( + 13B07F961A680F5B00A75B9A /* DockHand.app */, + ); + name = Products; + sourceTree = ""; + }; + BBD78D7AC51CEA395F1C20DB /* Pods */ = { + isa = PBXGroup; + children = ( + 3B4392A12AC88292D35C810B /* Pods-DockHand.debug.xcconfig */, + 5709B34CF0A7D63546082F79 /* Pods-DockHand.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 13B07F861A680F5B00A75B9A /* DockHand */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "DockHand" */; + buildPhases = ( + C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */, + 13B07F871A680F5B00A75B9A /* Sources */, + 13B07F8C1A680F5B00A75B9A /* Frameworks */, + 13B07F8E1A680F5B00A75B9A /* Resources */, + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, + 00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */, + E235C05ADACE081382539298 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = DockHand; + productName = DockHand; + productReference = 13B07F961A680F5B00A75B9A /* DockHand.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 83CBB9F71A601CBA00E9B192 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1210; + TargetAttributes = { + 13B07F861A680F5B00A75B9A = { + LastSwiftMigration = 1120; + }; + }; + }; + buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "DockHand" */; + compatibilityVersion = "Xcode 12.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 83CBB9F61A601CBA00E9B192; + productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 13B07F861A680F5B00A75B9A /* DockHand */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 13B07F8E1A680F5B00A75B9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */, + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, + 4DADA4D9FCF7ACF7CD308512 /* PrivacyInfo.xcprivacy in Resources */, + 59FA85C7DABD4C424CC92A6D /* dm_serif_display_regular.ttf in Resources */, + C79EC88C8F01E8069AAC7EB9 /* space_grotesk_bold.ttf in Resources */, + 4204E37E1345127821B1D61D /* space_grotesk_medium.ttf in Resources */, + ACF648FCD514FC7967494DCF /* space_grotesk_regular.ttf in Resources */, + 77425900FDE6A239FBDE1587 /* space_grotesk_semibold.ttf in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "$(SRCROOT)/.xcode.env.local", + "$(SRCROOT)/.xcode.env", + ); + name = "Bundle React Native code and images"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "set -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"\\\"$WITH_ENVIRONMENT\\\" \\\"$REACT_NATIVE_XCODE\\\"\"\n"; + }; + 00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-DockHand/Pods-DockHand-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-DockHand/Pods-DockHand-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-DockHand/Pods-DockHand-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-DockHand-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + E235C05ADACE081382539298 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-DockHand/Pods-DockHand-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-DockHand/Pods-DockHand-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-DockHand/Pods-DockHand-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 13B07F871A680F5B00A75B9A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */, + B2E8F1A12CA45701006654EE /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 13B07F941A680F5B00A75B9A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3B4392A12AC88292D35C810B /* Pods-DockHand.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 4NZGXQRKBG; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = DockHand/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.offlineappexamples.dockhand; + PRODUCT_NAME = DockHand; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 13B07F951A680F5B00A75B9A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5709B34CF0A7D63546082F79 /* Pods-DockHand.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 4NZGXQRKBG; + INFOPLIST_FILE = DockHand/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.offlineappexamples.dockhand; + PRODUCT_NAME = DockHand; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; + 83CBBA201A601CBA00E9B192 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++20"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD_RUNPATH_SEARCH_PATHS = ( + /usr/lib/swift, + "$(inherited)", + ); + LIBRARY_SEARCH_PATHS = ( + "\"$(SDKROOT)/usr/lib/swift\"", + "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", + "\"$(inherited)\"", + ); + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + OTHER_CFLAGS = ( + "$(inherited)", + "-DRCT_REMOVE_LEGACY_ARCH=1", + ); + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-DFOLLY_NO_CONFIG", + "-DFOLLY_MOBILE=1", + "-DFOLLY_USE_LIBCPP=1", + "-DFOLLY_CFG_NO_COROUTINES=1", + "-DFOLLY_HAVE_CLOCK_GETTIME=1", + "-DRCT_REMOVE_LEGACY_ARCH=1", + ); + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; + SWIFT_ENABLE_EXPLICIT_MODULES = NO; + USE_HERMES = true; + }; + name = Debug; + }; + 83CBBA211A601CBA00E9B192 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++20"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD_RUNPATH_SEARCH_PATHS = ( + /usr/lib/swift, + "$(inherited)", + ); + LIBRARY_SEARCH_PATHS = ( + "\"$(SDKROOT)/usr/lib/swift\"", + "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", + "\"$(inherited)\"", + ); + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_CFLAGS = ( + "$(inherited)", + "-DRCT_REMOVE_LEGACY_ARCH=1", + ); + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-DFOLLY_NO_CONFIG", + "-DFOLLY_MOBILE=1", + "-DFOLLY_USE_LIBCPP=1", + "-DFOLLY_CFG_NO_COROUTINES=1", + "-DFOLLY_HAVE_CLOCK_GETTIME=1", + "-DRCT_REMOVE_LEGACY_ARCH=1", + ); + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + SDKROOT = iphoneos; + SWIFT_ENABLE_EXPLICIT_MODULES = NO; + USE_HERMES = true; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "DockHand" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13B07F941A680F5B00A75B9A /* Debug */, + 13B07F951A680F5B00A75B9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "DockHand" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 83CBBA201A601CBA00E9B192 /* Debug */, + 83CBBA211A601CBA00E9B192 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; +} diff --git a/apps/dockhand/ios/DockHand.xcodeproj/xcshareddata/xcschemes/DockHand.xcscheme b/apps/dockhand/ios/DockHand.xcodeproj/xcshareddata/xcschemes/DockHand.xcscheme new file mode 100644 index 0000000..895e842 --- /dev/null +++ b/apps/dockhand/ios/DockHand.xcodeproj/xcshareddata/xcschemes/DockHand.xcscheme @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/dockhand/ios/DockHand.xcworkspace/contents.xcworkspacedata b/apps/dockhand/ios/DockHand.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..70a24a2 --- /dev/null +++ b/apps/dockhand/ios/DockHand.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/apps/dockhand/ios/DockHand/AppDelegate.swift b/apps/dockhand/ios/DockHand/AppDelegate.swift new file mode 100644 index 0000000..2feaa19 --- /dev/null +++ b/apps/dockhand/ios/DockHand/AppDelegate.swift @@ -0,0 +1,79 @@ +import UIKit +import React +import React_RCTAppDelegate +import ReactAppDependencyProvider +#if DEBUG +import Network +#endif + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + var window: UIWindow? + + var reactNativeDelegate: ReactNativeDelegate? + var reactNativeFactory: RCTReactNativeFactory? + + #if DEBUG + /// Keeps a Bonjour browser alive so iOS shows Local Network permission (needed for Metro on device). + private static var localNetworkBrowser: NWBrowser? + #endif + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil + ) -> Bool { + #if DEBUG + triggerLocalNetworkPermissionIfNeeded() + #endif + + let delegate = ReactNativeDelegate() + let factory = RCTReactNativeFactory(delegate: delegate) + delegate.dependencyProvider = RCTAppDependencyProvider() + + reactNativeDelegate = delegate + reactNativeFactory = factory + + window = UIWindow(frame: UIScreen.main.bounds) + + factory.startReactNative( + withModuleName: "DockHand", + in: window, + launchOptions: launchOptions + ) + + return true + } + + #if DEBUG + private func triggerLocalNetworkPermissionIfNeeded() { + let params = NWParameters() + params.includePeerToPeer = true + let browser = NWBrowser(for: .bonjour(type: "_http._tcp", domain: nil), using: params) + browser.start(queue: .main) + AppDelegate.localNetworkBrowser = browser + } + #endif +} + +class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { + override func sourceURL(for bridge: RCTBridge) -> URL? { + self.bundleURL() + } + + override func bundleURL() -> URL? { +#if DEBUG + #if targetEnvironment(simulator) + return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") + #else + // Device Debug builds embed main.jsbundle at compile time; prefer it so the app + // opens when Metro / Local Network is unavailable (see react-native-xcode.sh). + if let embedded = Bundle.main.url(forResource: "main", withExtension: "jsbundle") { + return embedded + } + return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") + #endif +#else + return Bundle.main.url(forResource: "main", withExtension: "jsbundle") +#endif + } +} diff --git a/apps/dockhand/ios/DockHand/Images.xcassets/AppIcon.appiconset/Contents.json b/apps/dockhand/ios/DockHand/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..b2ee29e --- /dev/null +++ b/apps/dockhand/ios/DockHand/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,6 @@ +{ + "images" : [ + { "filename" : "Icon.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } + ], + "info" : { "author" : "xcode", "version" : 1 } +} diff --git a/apps/dockhand/ios/DockHand/Images.xcassets/AppIcon.appiconset/Icon.png b/apps/dockhand/ios/DockHand/Images.xcassets/AppIcon.appiconset/Icon.png new file mode 100644 index 0000000000000000000000000000000000000000..fddcd5c6364324558339221254b69bc23dfab539 GIT binary patch literal 28191 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 + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + DockHand + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSAllowsLocalNetworking + + + NSBluetoothAlwaysUsageDescription + Hand off loads and dock assignments to nearby phones over Bluetooth, with no signal. + NSBluetoothPeripheralUsageDescription + Hand off loads and dock assignments to nearby phones over Bluetooth, with no signal. + NSLocalNetworkUsageDescription + DockHand connects to the development server on your Wi‑Fi while you test debug builds. + NSBonjourServices + + _http._tcp + + RCTNewArchEnabled + + UIAppFonts + + dm_serif_display_regular.ttf + space_grotesk_bold.ttf + space_grotesk_medium.ttf + space_grotesk_regular.ttf + space_grotesk_semibold.ttf + + UIBackgroundModes + + bluetooth-central + bluetooth-peripheral + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + UIViewControllerBasedStatusBarAppearance + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + + + diff --git a/apps/dockhand/ios/DockHand/LaunchScreen.storyboard b/apps/dockhand/ios/DockHand/LaunchScreen.storyboard new file mode 100644 index 0000000..7ef5a65 --- /dev/null +++ b/apps/dockhand/ios/DockHand/LaunchScreen.storyboard @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/dockhand/ios/DockHand/PrivacyInfo.xcprivacy b/apps/dockhand/ios/DockHand/PrivacyInfo.xcprivacy new file mode 100644 index 0000000..bad3276 --- /dev/null +++ b/apps/dockhand/ios/DockHand/PrivacyInfo.xcprivacy @@ -0,0 +1,37 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 35F9.1 + + + + NSPrivacyCollectedDataTypes + + NSPrivacyTracking + + + diff --git a/apps/dockhand/ios/DockHand/SceneDelegate.swift b/apps/dockhand/ios/DockHand/SceneDelegate.swift new file mode 100644 index 0000000..e10d231 --- /dev/null +++ b/apps/dockhand/ios/DockHand/SceneDelegate.swift @@ -0,0 +1,24 @@ +import UIKit + +/// iOS 27+ requires UIScene lifecycle when built with Xcode 27 (see TN3187). +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + var window: UIWindow? + + func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + guard + let windowScene = scene as? UIWindowScene, + let appDelegate = UIApplication.shared.delegate as? AppDelegate, + let appWindow = appDelegate.window + else { + return + } + + appWindow.windowScene = windowScene + window = appWindow + window?.makeKeyAndVisible() + } +} diff --git a/apps/dockhand/ios/Podfile b/apps/dockhand/ios/Podfile new file mode 100644 index 0000000..5052e12 --- /dev/null +++ b/apps/dockhand/ios/Podfile @@ -0,0 +1,67 @@ +# Resolve react_native_pods.rb with node to allow for hoisting +require Pod::Executable.execute_command('node', ['-p', + 'require.resolve( + "react-native/scripts/react_native_pods.rb", + {paths: [process.argv[1]]}, + )', __dir__]).strip + +platform :ios, min_ios_version_supported +prepare_react_native_project! + +linkage = ENV['USE_FRAMEWORKS'] +if linkage != nil + Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green + use_frameworks! :linkage => linkage.to_sym +end + +target 'DockHand' do + config = use_native_modules! + + use_react_native!( + :path => config[:reactNativePath], + # An absolute path to your application root. + :app_path => "#{Pod::Config.instance.installation_root}/.." + ) + + post_install do |installer| + react_native_post_install( + installer, + config[:reactNativePath], + :mac_catalyst_enabled => false, + # :ccache_enabled => true + ) + # offline-rn-shell: fmt + modular includes for Xcode 26 / RN 0.82+ + fmt_base = File.join(installer.sandbox.root, 'fmt', 'include', 'fmt', 'base.h') + if File.exist?(fmt_base) + original = File.read(fmt_base) + patched = original.gsub('# define FMT_USE_CONSTEVAL 1', + '# define FMT_USE_CONSTEVAL 0') + if patched != original + File.write(fmt_base, patched) + Pod::UI.puts 'Patched fmt/base.h: forced FMT_USE_CONSTEVAL=0 (Xcode 26.5 / clang 21)'.yellow + end + end + + installer.pods_project.targets.each do |target| + target.build_configurations.each do |build_config| + build_config.build_settings['CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES'] = 'YES' + # These settings may be arrays; join them, or "#{array}" writes Ruby syntax into the flags. + other_cflags = Array(build_config.build_settings['OTHER_CFLAGS'] || '$(inherited)').join(' ') + other_cxxflags = Array(build_config.build_settings['OTHER_CPLUSPLUSFLAGS'] || '$(inherited)').join(' ') + warning_flag = '-Wno-error=return-type' + unless other_cflags.include?(warning_flag) + build_config.build_settings['OTHER_CFLAGS'] = "#{other_cflags} #{warning_flag}" + end + unless other_cxxflags.include?(warning_flag) + build_config.build_settings['OTHER_CPLUSPLUSFLAGS'] = "#{other_cxxflags} #{warning_flag}" + end + build_config.build_settings['SWIFT_STRICT_CONCURRENCY'] ||= 'minimal' + # Xcode 27 refuses pod targets (e.g. resource bundles) below iOS 15. + if build_config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'].to_f < 15.1 + build_config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.1' + end + end + end + + end +end diff --git a/apps/dockhand/ios/Podfile.lock b/apps/dockhand/ios/Podfile.lock new file mode 100644 index 0000000..3742e61 --- /dev/null +++ b/apps/dockhand/ios/Podfile.lock @@ -0,0 +1,2386 @@ +PODS: + - FBLazyVector (0.85.2) + - hermes-engine (250829098.0.10): + - hermes-engine/Pre-built (= 250829098.0.10) + - hermes-engine/Pre-built (250829098.0.10) + - MeshSdk (0.28.0): + - React-Core + - RCTDeprecation (0.85.2) + - RCTRequired (0.85.2) + - RCTSwiftUI (0.85.2) + - RCTSwiftUIWrapper (0.85.2): + - RCTSwiftUI + - RCTTypeSafety (0.85.2): + - FBLazyVector (= 0.85.2) + - RCTRequired (= 0.85.2) + - React-Core (= 0.85.2) + - React (0.85.2): + - React-Core (= 0.85.2) + - React-Core/DevSupport (= 0.85.2) + - React-Core/RCTWebSocket (= 0.85.2) + - React-RCTActionSheet (= 0.85.2) + - React-RCTAnimation (= 0.85.2) + - React-RCTBlob (= 0.85.2) + - React-RCTImage (= 0.85.2) + - React-RCTLinking (= 0.85.2) + - React-RCTNetwork (= 0.85.2) + - React-RCTSettings (= 0.85.2) + - React-RCTText (= 0.85.2) + - React-RCTVibration (= 0.85.2) + - React-callinvoker (0.85.2) + - React-Core (0.85.2): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default (= 0.85.2) + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core-prebuilt (0.85.2): + - ReactNativeDependencies + - React-Core/CoreModulesHeaders (0.85.2): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/Default (0.85.2): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/DevSupport (0.85.2): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default (= 0.85.2) + - React-Core/RCTWebSocket (= 0.85.2) + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTActionSheetHeaders (0.85.2): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTAnimationHeaders (0.85.2): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTBlobHeaders (0.85.2): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTImageHeaders (0.85.2): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTLinkingHeaders (0.85.2): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTNetworkHeaders (0.85.2): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTSettingsHeaders (0.85.2): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTTextHeaders (0.85.2): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTVibrationHeaders (0.85.2): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTWebSocket (0.85.2): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default (= 0.85.2) + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-CoreModules (0.85.2): + - RCTTypeSafety (= 0.85.2) + - React-Core-prebuilt + - React-Core/CoreModulesHeaders (= 0.85.2) + - React-debug + - React-jsi (= 0.85.2) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-NativeModulesApple + - React-RCTBlob + - React-RCTFBReactNativeSpec + - React-RCTImage (= 0.85.2) + - React-runtimeexecutor + - React-utils + - ReactCommon + - ReactNativeDependencies + - React-cxxreact (0.85.2): + - hermes-engine + - React-callinvoker (= 0.85.2) + - React-Core-prebuilt + - React-debug (= 0.85.2) + - React-jsi (= 0.85.2) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-logger (= 0.85.2) + - React-perflogger (= 0.85.2) + - React-runtimeexecutor + - React-timing (= 0.85.2) + - React-utils + - ReactNativeDependencies + - React-debug (0.85.2) + - React-defaultsnativemodule (0.85.2): + - hermes-engine + - React-Core-prebuilt + - React-domnativemodule + - React-Fabric/animated + - React-featureflags + - React-featureflagsnativemodule + - React-idlecallbacksnativemodule + - React-intersectionobservernativemodule + - React-jsi + - React-jsiexecutor + - React-microtasksnativemodule + - React-RCTFBReactNativeSpec + - React-webperformancenativemodule + - ReactNativeDependencies + - Yoga + - React-domnativemodule (0.85.2): + - hermes-engine + - React-Core-prebuilt + - React-Fabric + - React-Fabric/bridging + - React-FabricComponents + - React-graphics + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-Fabric (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric/animated (= 0.85.2) + - React-Fabric/animationbackend (= 0.85.2) + - React-Fabric/animations (= 0.85.2) + - React-Fabric/attributedstring (= 0.85.2) + - React-Fabric/bridging (= 0.85.2) + - React-Fabric/componentregistry (= 0.85.2) + - React-Fabric/componentregistrynative (= 0.85.2) + - React-Fabric/components (= 0.85.2) + - React-Fabric/consistency (= 0.85.2) + - React-Fabric/core (= 0.85.2) + - React-Fabric/dom (= 0.85.2) + - React-Fabric/imagemanager (= 0.85.2) + - React-Fabric/leakchecker (= 0.85.2) + - React-Fabric/mounting (= 0.85.2) + - React-Fabric/observers (= 0.85.2) + - React-Fabric/scheduler (= 0.85.2) + - React-Fabric/telemetry (= 0.85.2) + - React-Fabric/uimanager (= 0.85.2) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/animated (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric/animationbackend + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/animationbackend (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/animations (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/attributedstring (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/bridging (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/componentregistry (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/componentregistrynative (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/components (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric/components/legacyviewmanagerinterop (= 0.85.2) + - React-Fabric/components/root (= 0.85.2) + - React-Fabric/components/scrollview (= 0.85.2) + - React-Fabric/components/view (= 0.85.2) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/components/legacyviewmanagerinterop (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/components/root (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/components/scrollview (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/components/view (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-renderercss + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-Fabric/consistency (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/core (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/dom (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/imagemanager (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/leakchecker (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/mounting (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/observers (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric/observers/events (= 0.85.2) + - React-Fabric/observers/intersection (= 0.85.2) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/observers/events (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/observers/intersection (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/scheduler (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric/animationbackend + - React-Fabric/observers/events + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-performancecdpmetrics + - React-performancetimeline + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/telemetry (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/uimanager (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric/uimanager/consistency (= 0.85.2) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererconsistency + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/uimanager/consistency (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererconsistency + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-FabricComponents (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-FabricComponents/components (= 0.85.2) + - React-FabricComponents/textlayoutmanager (= 0.85.2) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-FabricComponents/components/inputaccessory (= 0.85.2) + - React-FabricComponents/components/iostextinput (= 0.85.2) + - React-FabricComponents/components/modal (= 0.85.2) + - React-FabricComponents/components/rncore (= 0.85.2) + - React-FabricComponents/components/safeareaview (= 0.85.2) + - React-FabricComponents/components/scrollview (= 0.85.2) + - React-FabricComponents/components/switch (= 0.85.2) + - React-FabricComponents/components/text (= 0.85.2) + - React-FabricComponents/components/textinput (= 0.85.2) + - React-FabricComponents/components/unimplementedview (= 0.85.2) + - React-FabricComponents/components/virtualview (= 0.85.2) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/inputaccessory (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/iostextinput (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/modal (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/rncore (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/safeareaview (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/scrollview (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/switch (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/text (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/textinput (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/unimplementedview (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/virtualview (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/textlayoutmanager (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricImage (0.85.2): + - hermes-engine + - RCTRequired (= 0.85.2) + - RCTTypeSafety (= 0.85.2) + - React-Core-prebuilt + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-jsiexecutor (= 0.85.2) + - React-logger + - React-rendererdebug + - React-utils + - ReactCommon + - ReactNativeDependencies + - Yoga + - React-featureflags (0.85.2): + - React-Core-prebuilt + - ReactNativeDependencies + - React-featureflagsnativemodule (0.85.2): + - hermes-engine + - React-Core-prebuilt + - React-featureflags + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-graphics (0.85.2): + - hermes-engine + - React-Core-prebuilt + - React-featureflags + - React-jsi + - React-jsiexecutor + - React-utils + - ReactNativeDependencies + - React-hermes (0.85.2): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact (= 0.85.2) + - React-jsi + - React-jsiexecutor (= 0.85.2) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-jsitooling + - React-oscompat + - React-perflogger (= 0.85.2) + - React-runtimeexecutor + - ReactNativeDependencies + - React-idlecallbacksnativemodule (0.85.2): + - hermes-engine + - React-Core-prebuilt + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - React-runtimescheduler + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-ImageManager (0.85.2): + - React-Core-prebuilt + - React-Core/Default + - React-debug + - React-Fabric + - React-graphics + - React-rendererdebug + - React-utils + - ReactNativeDependencies + - React-intersectionobservernativemodule (0.85.2): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact + - React-Fabric + - React-Fabric/bridging + - React-graphics + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - React-runtimescheduler + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-jserrorhandler (0.85.2): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-jsi + - ReactCommon/turbomodule/bridging + - ReactNativeDependencies + - React-jsi (0.85.2): + - hermes-engine + - React-Core-prebuilt + - ReactNativeDependencies + - React-jsiexecutor (0.85.2): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-jserrorhandler + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-utils + - ReactNativeDependencies + - React-jsinspector (0.85.2): + - hermes-engine + - React-Core-prebuilt + - React-featureflags + - React-jsi + - React-jsinspectorcdp + - React-jsinspectornetwork + - React-jsinspectortracing + - React-oscompat + - React-perflogger (= 0.85.2) + - React-runtimeexecutor + - React-utils + - ReactNativeDependencies + - React-jsinspectorcdp (0.85.2): + - React-Core-prebuilt + - ReactNativeDependencies + - React-jsinspectornetwork (0.85.2): + - React-Core-prebuilt + - React-jsinspectorcdp + - ReactNativeDependencies + - React-jsinspectortracing (0.85.2): + - hermes-engine + - React-Core-prebuilt + - React-jsi + - React-jsinspectornetwork + - React-oscompat + - React-timing + - ReactNativeDependencies + - React-jsitooling (0.85.2): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact (= 0.85.2) + - React-debug + - React-jsi (= 0.85.2) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-runtimeexecutor + - React-utils + - ReactNativeDependencies + - React-jsitracing (0.85.2): + - React-jsi + - React-logger (0.85.2): + - React-Core-prebuilt + - ReactNativeDependencies + - React-Mapbuffer (0.85.2): + - React-Core-prebuilt + - React-debug + - ReactNativeDependencies + - React-microtasksnativemodule (0.85.2): + - hermes-engine + - React-Core-prebuilt + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - react-native-safe-area-context (5.9.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - react-native-safe-area-context/common (= 5.9.1) + - react-native-safe-area-context/fabric (= 5.9.1) + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - react-native-safe-area-context/common (5.9.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - react-native-safe-area-context/fabric (5.9.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - react-native-safe-area-context/common + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-NativeModulesApple (0.85.2): + - hermes-engine + - React-callinvoker + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-runtimeexecutor + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-networking (0.85.2): + - React-Core-prebuilt + - React-jsinspectornetwork + - React-jsinspectortracing + - React-performancetimeline + - React-timing + - ReactNativeDependencies + - React-oscompat (0.85.2) + - React-perflogger (0.85.2): + - React-Core-prebuilt + - ReactNativeDependencies + - React-performancecdpmetrics (0.85.2): + - hermes-engine + - React-Core-prebuilt + - React-jsi + - React-performancetimeline + - React-runtimeexecutor + - React-timing + - ReactNativeDependencies + - React-performancetimeline (0.85.2): + - React-Core-prebuilt + - React-featureflags + - React-jsinspector + - React-jsinspectortracing + - React-perflogger + - React-timing + - ReactNativeDependencies + - React-RCTActionSheet (0.85.2): + - React-Core/RCTActionSheetHeaders (= 0.85.2) + - React-RCTAnimation (0.85.2): + - RCTTypeSafety + - React-Core-prebuilt + - React-Core/RCTAnimationHeaders + - React-debug + - React-featureflags + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactNativeDependencies + - React-RCTAppDelegate (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-CoreModules + - React-debug + - React-defaultsnativemodule + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-jsitooling + - React-NativeModulesApple + - React-RCTFabric + - React-RCTFBReactNativeSpec + - React-RCTImage + - React-RCTNetwork + - React-RCTRuntime + - React-rendererdebug + - React-RuntimeApple + - React-RuntimeCore + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon + - ReactNativeDependencies + - React-RCTBlob (0.85.2): + - hermes-engine + - React-Core-prebuilt + - React-Core/RCTBlobHeaders + - React-Core/RCTWebSocket + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - React-RCTNetwork + - ReactCommon + - ReactNativeDependencies + - React-RCTFabric (0.85.2): + - hermes-engine + - RCTSwiftUIWrapper + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-FabricComponents + - React-FabricImage + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-networking + - React-performancecdpmetrics + - React-performancetimeline + - React-RCTAnimation + - React-RCTFBReactNativeSpec + - React-RCTImage + - React-RCTText + - React-rendererconsistency + - React-renderercss + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-RCTFBReactNativeSpec (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec/components (= 0.85.2) + - ReactCommon + - ReactNativeDependencies + - React-RCTFBReactNativeSpec/components (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-NativeModulesApple + - React-rendererdebug + - React-utils + - ReactCommon + - ReactNativeDependencies + - Yoga + - React-RCTImage (0.85.2): + - RCTTypeSafety + - React-Core-prebuilt + - React-Core/RCTImageHeaders + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - React-RCTNetwork + - ReactCommon + - ReactNativeDependencies + - React-RCTLinking (0.85.2): + - React-Core/RCTLinkingHeaders (= 0.85.2) + - React-jsi (= 0.85.2) + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactCommon/turbomodule/core (= 0.85.2) + - React-RCTNetwork (0.85.2): + - RCTTypeSafety + - React-Core-prebuilt + - React-Core/RCTNetworkHeaders + - React-debug + - React-featureflags + - React-jsi + - React-jsinspectorcdp + - React-jsinspectornetwork + - React-NativeModulesApple + - React-networking + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactNativeDependencies + - React-RCTRuntime (0.85.2): + - hermes-engine + - React-Core + - React-Core-prebuilt + - React-debug + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-jsitooling + - React-RuntimeApple + - React-RuntimeCore + - React-runtimeexecutor + - React-RuntimeHermes + - React-utils + - ReactNativeDependencies + - React-RCTSettings (0.85.2): + - RCTTypeSafety + - React-Core-prebuilt + - React-Core/RCTSettingsHeaders + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactNativeDependencies + - React-RCTText (0.85.2): + - React-Core/RCTTextHeaders (= 0.85.2) + - Yoga + - React-RCTVibration (0.85.2): + - React-Core-prebuilt + - React-Core/RCTVibrationHeaders + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactNativeDependencies + - React-rendererconsistency (0.85.2) + - React-renderercss (0.85.2): + - React-debug + - React-utils + - React-rendererdebug (0.85.2): + - React-Core-prebuilt + - React-debug + - ReactNativeDependencies + - React-RuntimeApple (0.85.2): + - hermes-engine + - React-callinvoker + - React-Core-prebuilt + - React-Core/Default + - React-CoreModules + - React-cxxreact + - React-featureflags + - React-jserrorhandler + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsitooling + - React-Mapbuffer + - React-NativeModulesApple + - React-RCTFabric + - React-RCTFBReactNativeSpec + - React-RuntimeCore + - React-runtimeexecutor + - React-RuntimeHermes + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - React-RuntimeCore (0.85.2): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact + - React-Fabric + - React-featureflags + - React-jserrorhandler + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsitooling + - React-performancetimeline + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - React-runtimeexecutor (0.85.2): + - React-Core-prebuilt + - React-debug + - React-featureflags + - React-jsi (= 0.85.2) + - React-utils + - ReactNativeDependencies + - React-RuntimeHermes (0.85.2): + - hermes-engine + - React-Core-prebuilt + - React-featureflags + - React-hermes + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-jsitooling + - React-jsitracing + - React-RuntimeCore + - React-runtimeexecutor + - React-utils + - ReactNativeDependencies + - React-runtimescheduler (0.85.2): + - hermes-engine + - React-callinvoker + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-jsi + - React-jsinspectortracing + - React-performancetimeline + - React-rendererconsistency + - React-rendererdebug + - React-runtimeexecutor + - React-timing + - React-utils + - ReactNativeDependencies + - React-timing (0.85.2): + - React-debug + - React-utils (0.85.2): + - hermes-engine + - React-Core-prebuilt + - React-debug + - React-jsi (= 0.85.2) + - ReactNativeDependencies + - React-webperformancenativemodule (0.85.2): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact + - React-jsi + - React-jsiexecutor + - React-performancetimeline + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - ReactAppDependencyProvider (0.85.2): + - ReactCodegen + - ReactCodegen (0.85.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-FabricImage + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-NativeModulesApple + - React-RCTAppDelegate + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - ReactCommon (0.85.2): + - React-Core-prebuilt + - ReactCommon/turbomodule (= 0.85.2) + - ReactNativeDependencies + - ReactCommon/turbomodule (0.85.2): + - hermes-engine + - React-callinvoker (= 0.85.2) + - React-Core-prebuilt + - React-cxxreact (= 0.85.2) + - React-jsi (= 0.85.2) + - React-logger (= 0.85.2) + - React-perflogger (= 0.85.2) + - ReactCommon/turbomodule/bridging (= 0.85.2) + - ReactCommon/turbomodule/core (= 0.85.2) + - ReactNativeDependencies + - ReactCommon/turbomodule/bridging (0.85.2): + - hermes-engine + - React-callinvoker (= 0.85.2) + - React-Core-prebuilt + - React-cxxreact (= 0.85.2) + - React-jsi (= 0.85.2) + - React-logger (= 0.85.2) + - React-perflogger (= 0.85.2) + - ReactNativeDependencies + - ReactCommon/turbomodule/core (0.85.2): + - hermes-engine + - React-callinvoker (= 0.85.2) + - React-Core-prebuilt + - React-cxxreact (= 0.85.2) + - React-debug (= 0.85.2) + - React-featureflags (= 0.85.2) + - React-jsi (= 0.85.2) + - React-logger (= 0.85.2) + - React-perflogger (= 0.85.2) + - React-utils (= 0.85.2) + - ReactNativeDependencies + - ReactNativeDependencies (0.85.2) + - RNCAsyncStorage (2.2.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - RNReanimated (4.6.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNReanimated/apple (= 4.6.0) + - RNReanimated/common (= 4.6.0) + - RNReanimated/view (= 4.6.0) + - RNWorklets + - Yoga + - RNReanimated/apple (4.6.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNWorklets + - Yoga + - RNReanimated/common (4.6.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNWorklets + - Yoga + - RNReanimated/view (4.6.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNWorklets + - Yoga + - RNSVG (15.15.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNSVG/common (= 15.15.5) + - Yoga + - RNSVG/common (15.15.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - RNWorklets (0.12.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNWorklets/apple (= 0.12.2) + - RNWorklets/common (= 0.12.2) + - Yoga + - RNWorklets/apple (0.12.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - RNWorklets/common (0.12.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - Yoga (0.0.0) + +DEPENDENCIES: + - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) + - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) + - "MeshSdk (from `../node_modules/@offline-protocol/mesh-sdk`)" + - RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) + - RCTRequired (from `../node_modules/react-native/Libraries/Required`) + - RCTSwiftUI (from `../node_modules/react-native/ReactApple/RCTSwiftUI`) + - RCTSwiftUIWrapper (from `../node_modules/react-native/ReactApple/RCTSwiftUIWrapper`) + - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) + - React (from `../node_modules/react-native/`) + - React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`) + - React-Core (from `../node_modules/react-native/`) + - React-Core-prebuilt (from `../node_modules/react-native/React-Core-prebuilt.podspec`) + - React-Core/RCTWebSocket (from `../node_modules/react-native/`) + - React-CoreModules (from `../node_modules/react-native/React/CoreModules`) + - React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`) + - React-debug (from `../node_modules/react-native/ReactCommon/react/debug`) + - React-defaultsnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/defaults`) + - React-domnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/dom`) + - React-Fabric (from `../node_modules/react-native/ReactCommon`) + - React-FabricComponents (from `../node_modules/react-native/ReactCommon`) + - React-FabricImage (from `../node_modules/react-native/ReactCommon`) + - React-featureflags (from `../node_modules/react-native/ReactCommon/react/featureflags`) + - React-featureflagsnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/featureflags`) + - React-graphics (from `../node_modules/react-native/ReactCommon/react/renderer/graphics`) + - React-hermes (from `../node_modules/react-native/ReactCommon/hermes`) + - React-idlecallbacksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks`) + - React-ImageManager (from `../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`) + - React-intersectionobservernativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/intersectionobserver`) + - React-jserrorhandler (from `../node_modules/react-native/ReactCommon/jserrorhandler`) + - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`) + - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) + - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector-modern`) + - React-jsinspectorcdp (from `../node_modules/react-native/ReactCommon/jsinspector-modern/cdp`) + - React-jsinspectornetwork (from `../node_modules/react-native/ReactCommon/jsinspector-modern/network`) + - React-jsinspectortracing (from `../node_modules/react-native/ReactCommon/jsinspector-modern/tracing`) + - React-jsitooling (from `../node_modules/react-native/ReactCommon/jsitooling`) + - React-jsitracing (from `../node_modules/react-native/ReactCommon/hermes/executor/`) + - React-logger (from `../node_modules/react-native/ReactCommon/logger`) + - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) + - React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) + - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) + - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) + - React-networking (from `../node_modules/react-native/ReactCommon/react/networking`) + - React-oscompat (from `../node_modules/react-native/ReactCommon/oscompat`) + - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) + - React-performancecdpmetrics (from `../node_modules/react-native/ReactCommon/react/performance/cdpmetrics`) + - React-performancetimeline (from `../node_modules/react-native/ReactCommon/react/performance/timeline`) + - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) + - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) + - React-RCTAppDelegate (from `../node_modules/react-native/Libraries/AppDelegate`) + - React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`) + - React-RCTFabric (from `../node_modules/react-native/React`) + - React-RCTFBReactNativeSpec (from `../node_modules/react-native/React`) + - React-RCTImage (from `../node_modules/react-native/Libraries/Image`) + - React-RCTLinking (from `../node_modules/react-native/Libraries/LinkingIOS`) + - React-RCTNetwork (from `../node_modules/react-native/Libraries/Network`) + - React-RCTRuntime (from `../node_modules/react-native/React/Runtime`) + - React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`) + - React-RCTText (from `../node_modules/react-native/Libraries/Text`) + - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) + - React-rendererconsistency (from `../node_modules/react-native/ReactCommon/react/renderer/consistency`) + - React-renderercss (from `../node_modules/react-native/ReactCommon/react/renderer/css`) + - React-rendererdebug (from `../node_modules/react-native/ReactCommon/react/renderer/debug`) + - React-RuntimeApple (from `../node_modules/react-native/ReactCommon/react/runtime/platform/ios`) + - React-RuntimeCore (from `../node_modules/react-native/ReactCommon/react/runtime`) + - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`) + - React-RuntimeHermes (from `../node_modules/react-native/ReactCommon/react/runtime`) + - React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) + - React-timing (from `../node_modules/react-native/ReactCommon/react/timing`) + - React-utils (from `../node_modules/react-native/ReactCommon/react/utils`) + - React-webperformancenativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/webperformance`) + - ReactAppDependencyProvider (from `build/generated/ios/ReactAppDependencyProvider`) + - ReactCodegen (from `build/generated/ios/ReactCodegen`) + - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) + - ReactNativeDependencies (from `../node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec`) + - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" + - RNReanimated (from `../node_modules/react-native-reanimated`) + - RNSVG (from `../node_modules/react-native-svg`) + - RNWorklets (from `../node_modules/react-native-worklets`) + - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) + +EXTERNAL SOURCES: + FBLazyVector: + :path: "../node_modules/react-native/Libraries/FBLazyVector" + hermes-engine: + :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" + :tag: hermes-v250829098.0.10 + MeshSdk: + :path: "../node_modules/@offline-protocol/mesh-sdk" + RCTDeprecation: + :path: "../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation" + RCTRequired: + :path: "../node_modules/react-native/Libraries/Required" + RCTSwiftUI: + :path: "../node_modules/react-native/ReactApple/RCTSwiftUI" + RCTSwiftUIWrapper: + :path: "../node_modules/react-native/ReactApple/RCTSwiftUIWrapper" + RCTTypeSafety: + :path: "../node_modules/react-native/Libraries/TypeSafety" + React: + :path: "../node_modules/react-native/" + React-callinvoker: + :path: "../node_modules/react-native/ReactCommon/callinvoker" + React-Core: + :path: "../node_modules/react-native/" + React-Core-prebuilt: + :podspec: "../node_modules/react-native/React-Core-prebuilt.podspec" + React-CoreModules: + :path: "../node_modules/react-native/React/CoreModules" + React-cxxreact: + :path: "../node_modules/react-native/ReactCommon/cxxreact" + React-debug: + :path: "../node_modules/react-native/ReactCommon/react/debug" + React-defaultsnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/defaults" + React-domnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/dom" + React-Fabric: + :path: "../node_modules/react-native/ReactCommon" + React-FabricComponents: + :path: "../node_modules/react-native/ReactCommon" + React-FabricImage: + :path: "../node_modules/react-native/ReactCommon" + React-featureflags: + :path: "../node_modules/react-native/ReactCommon/react/featureflags" + React-featureflagsnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/featureflags" + React-graphics: + :path: "../node_modules/react-native/ReactCommon/react/renderer/graphics" + React-hermes: + :path: "../node_modules/react-native/ReactCommon/hermes" + React-idlecallbacksnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks" + React-ImageManager: + :path: "../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios" + React-intersectionobservernativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/intersectionobserver" + React-jserrorhandler: + :path: "../node_modules/react-native/ReactCommon/jserrorhandler" + React-jsi: + :path: "../node_modules/react-native/ReactCommon/jsi" + React-jsiexecutor: + :path: "../node_modules/react-native/ReactCommon/jsiexecutor" + React-jsinspector: + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern" + React-jsinspectorcdp: + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/cdp" + React-jsinspectornetwork: + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/network" + React-jsinspectortracing: + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/tracing" + React-jsitooling: + :path: "../node_modules/react-native/ReactCommon/jsitooling" + React-jsitracing: + :path: "../node_modules/react-native/ReactCommon/hermes/executor/" + React-logger: + :path: "../node_modules/react-native/ReactCommon/logger" + React-Mapbuffer: + :path: "../node_modules/react-native/ReactCommon" + React-microtasksnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" + react-native-safe-area-context: + :path: "../node_modules/react-native-safe-area-context" + React-NativeModulesApple: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" + React-networking: + :path: "../node_modules/react-native/ReactCommon/react/networking" + React-oscompat: + :path: "../node_modules/react-native/ReactCommon/oscompat" + React-perflogger: + :path: "../node_modules/react-native/ReactCommon/reactperflogger" + React-performancecdpmetrics: + :path: "../node_modules/react-native/ReactCommon/react/performance/cdpmetrics" + React-performancetimeline: + :path: "../node_modules/react-native/ReactCommon/react/performance/timeline" + React-RCTActionSheet: + :path: "../node_modules/react-native/Libraries/ActionSheetIOS" + React-RCTAnimation: + :path: "../node_modules/react-native/Libraries/NativeAnimation" + React-RCTAppDelegate: + :path: "../node_modules/react-native/Libraries/AppDelegate" + React-RCTBlob: + :path: "../node_modules/react-native/Libraries/Blob" + React-RCTFabric: + :path: "../node_modules/react-native/React" + React-RCTFBReactNativeSpec: + :path: "../node_modules/react-native/React" + React-RCTImage: + :path: "../node_modules/react-native/Libraries/Image" + React-RCTLinking: + :path: "../node_modules/react-native/Libraries/LinkingIOS" + React-RCTNetwork: + :path: "../node_modules/react-native/Libraries/Network" + React-RCTRuntime: + :path: "../node_modules/react-native/React/Runtime" + React-RCTSettings: + :path: "../node_modules/react-native/Libraries/Settings" + React-RCTText: + :path: "../node_modules/react-native/Libraries/Text" + React-RCTVibration: + :path: "../node_modules/react-native/Libraries/Vibration" + React-rendererconsistency: + :path: "../node_modules/react-native/ReactCommon/react/renderer/consistency" + React-renderercss: + :path: "../node_modules/react-native/ReactCommon/react/renderer/css" + React-rendererdebug: + :path: "../node_modules/react-native/ReactCommon/react/renderer/debug" + React-RuntimeApple: + :path: "../node_modules/react-native/ReactCommon/react/runtime/platform/ios" + React-RuntimeCore: + :path: "../node_modules/react-native/ReactCommon/react/runtime" + React-runtimeexecutor: + :path: "../node_modules/react-native/ReactCommon/runtimeexecutor" + React-RuntimeHermes: + :path: "../node_modules/react-native/ReactCommon/react/runtime" + React-runtimescheduler: + :path: "../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler" + React-timing: + :path: "../node_modules/react-native/ReactCommon/react/timing" + React-utils: + :path: "../node_modules/react-native/ReactCommon/react/utils" + React-webperformancenativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/webperformance" + ReactAppDependencyProvider: + :path: build/generated/ios/ReactAppDependencyProvider + ReactCodegen: + :path: build/generated/ios/ReactCodegen + ReactCommon: + :path: "../node_modules/react-native/ReactCommon" + ReactNativeDependencies: + :podspec: "../node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec" + RNCAsyncStorage: + :path: "../node_modules/@react-native-async-storage/async-storage" + RNReanimated: + :path: "../node_modules/react-native-reanimated" + RNSVG: + :path: "../node_modules/react-native-svg" + RNWorklets: + :path: "../node_modules/react-native-worklets" + Yoga: + :path: "../node_modules/react-native/ReactCommon/yoga" + +SPEC CHECKSUMS: + FBLazyVector: 26fd21c75314e101f280d401e97f27d54f3f7064 + hermes-engine: 25cc110921930e4293b718f2d5d325536b0ed52b + MeshSdk: d4a40ad67dd577e5b41da02576c04f9519545545 + RCTDeprecation: c7a2768f905d76ca6d2cfefb26e4349eacbdfca3 + RCTRequired: 5e502c3553cfbed090a991c444448da452fb752e + RCTSwiftUI: 5ce3ccbdc58b78cc4ebbaace01709ec22d58e131 + RCTSwiftUIWrapper: a8317a87bb70ef8a07dcecaf4977db7f60cf04c1 + RCTTypeSafety: 5826cf1f2269e44caee5e7c8d64e2a43af8cf0ef + React: 13cf8451582adb1bb324306e1893b91d1cba28c6 + React-callinvoker: 91e6a605826b684ad2e623811253b4d0c4196bef + React-Core: 46818de5f211b2a2759ac823b591af8a0a95c2c1 + React-Core-prebuilt: ce047d347e757b24a0bff6d93789c23f8d6b48a4 + React-CoreModules: a6a37afee48d4a31ab398640b0795462647d5c67 + React-cxxreact: 2ec3e2f7a8ae9303460d4ba94cde183ea90d64cd + React-debug: 0d21117b897ce0359c9d2c9dfe952f237476a14a + React-defaultsnativemodule: d39e708d4da6badf4ecca767da448da50b67c7c9 + React-domnativemodule: 100e502f718502d0f9c22071339e690f911aabc6 + React-Fabric: 8595b459278e6c6bf9757f3df9c6d1bfbbf00e9f + React-FabricComponents: 7da0ce24bcf163d2e447876b249aa2bd81689165 + React-FabricImage: 87d397fe918e2725633211c121457b5aa3ddc437 + React-featureflags: 2302476d727ad40a684724e3e137ebfa21640386 + React-featureflagsnativemodule: dbb8429313c66b48d9bcdf0f446ee095ef28f16a + React-graphics: 086f042572c2b957c505bc3e3138ee8e1c6415c1 + React-hermes: 540a5f1f008eb04fe59b931112b10854ee6c7061 + React-idlecallbacksnativemodule: 0dcf39e4842a8255ce48f834e82524b394c016d2 + React-ImageManager: 367a9979094651c157a35f7cb0e0f7b072451035 + React-intersectionobservernativemodule: 6b695a44eec274569a438d690e468a9e4a74a3dd + React-jserrorhandler: b23306d6d8309693189a89efdd0cf25a5c35b17d + React-jsi: 533b0f879f5e60d6c4047d429d9ba1b6c2a7e113 + React-jsiexecutor: e4380339286988d17dd1bf1b0f31e4e6ac5ef1a0 + React-jsinspector: 6360a071b0c8bd3c77f519e8836ea57bab2456d4 + React-jsinspectorcdp: 7a33cd5cfd16ca10a9b7eff7462456b18daf5d14 + React-jsinspectornetwork: dd8dbb9f54308408e4483c3160aebccb6f03780d + React-jsinspectortracing: 90f7ad9acafd2de4bc56ed5f0b13aca5d221ce4b + React-jsitooling: 713feac6b772e11c9bf2a5d75570bb74d9bfe15f + React-jsitracing: cacd0f0ef50ea61e7035c8ba3a4dc5b38c4da834 + React-logger: 2c87840a9f6322a1226d0df337d9662f44ea6094 + React-Mapbuffer: 1fc10d873f00aa895836c316a9737ce7d43875ce + React-microtasksnativemodule: 85ac7286ff84e8fc8e1956233748b8d4b2a6dcea + react-native-safe-area-context: 8368d1052ccc8fe8725adfb6a83498c8bdb48250 + React-NativeModulesApple: 993744f42aab769eb02bf5d256b6767c546d0bb5 + React-networking: 251bfddc651caca5e3039d1afa1d2eca890d0c56 + React-oscompat: ed8fa636665935e962d4091180ba6f07dcad7ea9 + React-perflogger: 2c9c7a2cb14c78d12803609d289a1bb8761d4ac3 + React-performancecdpmetrics: 613cb5ee8ac9ef50e9511e7b82aeed4d81b57b81 + React-performancetimeline: 1dde052682d06f61b1472ab79d26598ba0c883f1 + React-RCTActionSheet: f498102098d724dcf921224e213f946368cd482b + React-RCTAnimation: 9b42153018cc5f998ec911736a586cb885e33ccf + React-RCTAppDelegate: daf9f6f1fb452fbe13c81df1a8175378de7ac203 + React-RCTBlob: 8d165c9942d1a591cacadb1f073e3d9f5234d1ea + React-RCTFabric: 8071a9ca7628518420a6634c6457ecde8460e6a3 + React-RCTFBReactNativeSpec: e8eb38cca9abecf12d10a0787e413c84dc2a630f + React-RCTImage: f36bd87ac6e4aa27860518d221fd3860569a57fc + React-RCTLinking: af169ba3619e9fd02e4a0666ac01349ee642a446 + React-RCTNetwork: e31b7555e1c2c4dff1e1c594d8dac0f1a9b22fe5 + React-RCTRuntime: 397c81200ce8c2bf0d2b52b689241ed6463087a3 + React-RCTSettings: f71c195191839901491b5b2e9301514b014fbe8b + React-RCTText: 6b0cac11ad4ea9153f526a80aeecdcb0315a2039 + React-RCTVibration: 46b9020e5aab8c0c986bc4005bc2b05e1698c77d + React-rendererconsistency: fba5adaef458215b81f62459f3b1cdcc629348b8 + React-renderercss: d3bb39665d1c2f59e96fb8a04ae5bfd2baf05ca9 + React-rendererdebug: 2634d95dd3fc09f2cf2f0d2664c46a560fb57778 + React-RuntimeApple: 0f8f09f8d8031bb906ab52c683bbd6f18789faf4 + React-RuntimeCore: d4728c58d2143f503391b1389082e3044e8b0f23 + React-runtimeexecutor: a21a37ce38ff623c5f51b41d7a0f05de8e1fb01f + React-RuntimeHermes: 0e7833979b1b14e64cbda469104f48d1d12a573a + React-runtimescheduler: 13582d42d9d9ff6bd2d59c25576b1c13eb62308c + React-timing: 9f1af3753eef091257c424d3856cd6cbedcad01e + React-utils: 862e61256698fe3841aec1af476abf924a6737c3 + React-webperformancenativemodule: e745e43264767cb8f4334fc11bf3fbff880050d0 + ReactAppDependencyProvider: 22e2265d86a4e871e5e858f4e7ef1c8d01103680 + ReactCodegen: ff525841ad04271ac014124551cc27fe2504e966 + ReactCommon: a804bb8d1dcf3ecdec3a77eb8bba19b7863bbbdb + ReactNativeDependencies: 0fb44f80cf2822e179f7c72ead1c31683347b935 + RNCAsyncStorage: 2ad919e88b8bc2cd80e8697ce66d04d006743283 + RNReanimated: f194a821533c12bd52284f5f7cbc39f44f5e70b8 + RNSVG: 0e52210d4d43165e7e2cf9c890a9848b27e513ac + RNWorklets: 6df88df4d498668192571738b0492db7bc6463da + Yoga: 04bb4bfeb02c0000b940c1e6e89e856cd8de5a71 + +PODFILE CHECKSUM: b427703f6ec39655c673e9d4c2b5f0bbd16c897d + +COCOAPODS: 1.16.2 diff --git a/apps/dockhand/metro.config.js b/apps/dockhand/metro.config.js new file mode 100644 index 0000000..36527b3 --- /dev/null +++ b/apps/dockhand/metro.config.js @@ -0,0 +1,21 @@ +const path = require('path'); +const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); +const { withNativeWind } = require('nativewind/metro'); + +const monorepoRoot = path.resolve(__dirname, '../..'); + +/** @type {import('@react-native/metro-config').MetroConfig} */ +const config = { + watchFolders: [monorepoRoot], + resolver: { + nodeModulesPaths: [ + path.resolve(__dirname, 'node_modules'), + path.resolve(monorepoRoot, 'node_modules'), + ], + }, +}; + +// The theme lives in the shared ui package; App.tsx imports the same file. +module.exports = withNativeWind(mergeConfig(getDefaultConfig(__dirname), config), { + input: require.resolve('@offline-app-examples/ui/global.css'), +}); diff --git a/apps/dockhand/mygeotab-addin/config.json b/apps/dockhand/mygeotab-addin/config.json new file mode 100644 index 0000000..bd10522 --- /dev/null +++ b/apps/dockhand/mygeotab-addin/config.json @@ -0,0 +1,12 @@ +{ + "name": "Yard Handoffs", + "supportEmail": "support@offlineprotocol.com", + "version": "0.1.0", + "items": [ + { + "url": "https://YOUR-HTTPS-HOST/yardHandoffs.html", + "path": "ActivityLink/", + "menuName": { "en": "Yard Handoffs" } + } + ] +} diff --git a/apps/dockhand/mygeotab-addin/yardHandoffs.html b/apps/dockhand/mygeotab-addin/yardHandoffs.html new file mode 100644 index 0000000..b1faaff --- /dev/null +++ b/apps/dockhand/mygeotab-addin/yardHandoffs.html @@ -0,0 +1,65 @@ + + + + + Yard Handoffs + + + +

+

Yard Handoffs

+

+ Driver to dock office handoffs made offline in DockHand, each signed by + the driver's phone and countersigned by the yard, with the GO device's + GPS fix nearest to check-in. +

+
+
+ + + diff --git a/apps/dockhand/mygeotab-addin/yardHandoffs.js b/apps/dockhand/mygeotab-addin/yardHandoffs.js new file mode 100644 index 0000000..5512c02 --- /dev/null +++ b/apps/dockhand/mygeotab-addin/yardHandoffs.js @@ -0,0 +1,284 @@ +/* eslint-env browser */ +/* global globalThis, module */ +/* MyGeotab Add-In page: lists DockHand handoffs stored as AddInData and checks + * both signatures in the browser, independently of the phones that made them. + * Everything shown is parsed from the signed texts, so what you read is what was signed. */ +(function (root) { + 'use strict'; + + // Must match ADD_IN_ID in src/services/mygeotab.ts. + var ADD_IN_ID = 'aQ1J3cGt1UkW1vXoJmZ6h3w'; + + function fromHex(hex) { + if ( + typeof hex !== 'string' || + hex.length % 2 !== 0 || + !/^[0-9a-f]*$/i.test(hex) + ) + return null; + var bytes = new Uint8Array(hex.length / 2); + for (var i = 0; i < bytes.length; i++) + bytes[i] = parseInt(hex.substr(i * 2, 2), 16); + return bytes; + } + + async function verifySignature(subtle, signed, text) { + var key = fromHex(signed && signed.publicKey); + var sig = fromHex(signed && signed.signature); + if (!key || key.length !== 32 || !sig || sig.length !== 64) return false; + var cryptoKey = await subtle.importKey( + 'raw', + key, + { name: 'Ed25519' }, + false, + ['verify'], + ); + return subtle.verify( + { name: 'Ed25519' }, + cryptoKey, + sig, + new TextEncoder().encode(text), + ); + } + + /** + * Returns { status: 'valid' | 'invalid' | 'unsupported', reason, view }. + * `view` comes from the signed texts, never from the plain fields beside them. + */ + async function verifyRecord(details, subtle) { + var s = details && details.signed; + var checkIn, assignment; + try { + checkIn = JSON.parse(s.checkIn); + assignment = JSON.parse(s.assignment); + } catch (e) { + return { + status: 'invalid', + reason: 'Signed texts are missing or malformed', + view: null, + }; + } + if ( + checkIn[0] !== 'dockhand/checkin/v1' || + assignment[0] !== 'dockhand/assign/v1' + ) { + return { + status: 'invalid', + reason: 'Unknown record version', + view: null, + }; + } + var view = { + handoffId: checkIn[1], + vehicleName: checkIn[3], + driverName: checkIn[5], + bol: checkIn[6], + trailer: checkIn[7], + seal: checkIn[8], + pallets: checkIn[9], + checkedInAt: new Date(checkIn[10]), + dock: assignment[3], + yardName: assignment[5], + assignedAt: new Date(assignment[6]), + goFix: details.goFix || null, + }; + // The yard's signature must cover this exact check-in and the driver's signature on it. + if ( + assignment[1] !== s.checkIn || + assignment[2] !== (s.driver && s.driver.signature) + ) { + return { + status: 'invalid', + reason: 'Yard signed a different check-in', + view: view, + }; + } + if (!subtle) + return { + status: 'unsupported', + reason: 'This browser cannot check Ed25519', + view: view, + }; + try { + var driverOk = await verifySignature(subtle, s.driver, s.checkIn); + var yardOk = await verifySignature(subtle, s.yard, s.assignment); + if (!driverOk) + return { + status: 'invalid', + reason: 'Driver signature does not match', + view: view, + }; + if (!yardOk) + return { + status: 'invalid', + reason: 'Yard signature does not match', + view: view, + }; + return { + status: 'valid', + reason: 'Driver and yard signatures valid', + view: view, + }; + } catch (e) { + return { + status: 'unsupported', + reason: 'This browser cannot check Ed25519', + view: view, + }; + } + } + + /** Two phones can sync the same handoff at the same moment; show it once. */ + function dedupe(records) { + var seen = {}; + return records.filter(function (r) { + var id = r.details && r.details.handoffId; + if (!id || seen[id]) return false; + seen[id] = true; + return true; + }); + } + + // ------------------------------------------------------------- rendering + + function el(tag, text, className) { + var node = document.createElement(tag); + if (text !== undefined) node.textContent = text; // never innerHTML: these strings came from phones + if (className) node.className = className; + return node; + } + + function fmt(date) { + return isNaN(date) ? '?' : date.toLocaleString(); + } + + async function render(container, records) { + var subtle = root.crypto && root.crypto.subtle; + var rows = await Promise.all( + dedupe(records).map(function (r) { + return verifyRecord(r.details, subtle); + }), + ); + rows.sort(function (a, b) { + return ( + ((b.view && b.view.assignedAt) || 0) - + ((a.view && a.view.assignedAt) || 0) + ); + }); + container.textContent = ''; + if (rows.length === 0) { + container.appendChild( + el( + 'p', + 'No handoffs yet. Complete one in DockHand and sync it.', + 'empty', + ), + ); + return; + } + var table = el('table'); + var head = el('tr'); + [ + 'Assigned', + 'Vehicle', + 'Driver', + 'Load', + 'Dock', + 'Yard', + 'GO fix at check-in', + 'Signatures', + ].forEach(function (h) { + head.appendChild(el('th', h)); + }); + table.appendChild(head); + rows.forEach(function (row) { + var v = row.view || {}; + var tr = el('tr'); + tr.appendChild(el('td', v.assignedAt ? fmt(v.assignedAt) : '?')); + tr.appendChild(el('td', v.vehicleName || '?')); + tr.appendChild(el('td', v.driverName || '?')); + tr.appendChild( + el( + 'td', + 'BOL ' + + v.bol + + ' · Trailer ' + + v.trailer + + ' · Seal ' + + (v.seal || 'none') + + ' · ' + + v.pallets + + ' pallets', + ), + ); + tr.appendChild(el('td', v.dock || '?')); + tr.appendChild(el('td', v.yardName || '?')); + var fix = el('td'); + if (v.goFix) { + var link = el( + 'a', + v.goFix.latitude.toFixed(5) + ', ' + v.goFix.longitude.toFixed(5), + ); + link.href = + 'https://www.openstreetmap.org/?mlat=' + + v.goFix.latitude + + '&mlon=' + + v.goFix.longitude + + '#map=17/' + + v.goFix.latitude + + '/' + + v.goFix.longitude; + link.target = '_blank'; + link.rel = 'noopener'; + fix.appendChild(link); + fix.appendChild(el('div', fmt(new Date(v.goFix.dateTime)), 'muted')); + } else fix.textContent = 'none (demo vehicle)'; + tr.appendChild(fix); + tr.appendChild(el('td', row.reason, 'sig ' + row.status)); + table.appendChild(tr); + }); + container.appendChild(table); + } + + function load(api) { + var container = document.getElementById('yardHandoffs-list'); + container.textContent = 'Loading…'; + api.call( + 'Get', + { + typeName: 'AddInData', + search: { addInId: ADD_IN_ID }, + resultsLimit: 500, + }, + function (records) { + render(container, records || []); + }, + function (error) { + container.textContent = + 'Could not load handoffs: ' + + (error && error.message ? error.message : error); + }, + ); + } + + if (root.geotab && root.geotab.addin) { + root.geotab.addin.yardHandoffs = function () { + return { + initialize: function (api, state, callback) { + callback(); + }, + focus: function (api) { + load(api); + }, + blur: function () {}, + }; + }; + } + + if (typeof module !== 'undefined') + module.exports = { + verifyRecord: verifyRecord, + dedupe: dedupe, + ADD_IN_ID: ADD_IN_ID, + }; +})(typeof window !== 'undefined' ? window : globalThis); diff --git a/apps/dockhand/nativewind-env.d.ts b/apps/dockhand/nativewind-env.d.ts new file mode 100644 index 0000000..a13e313 --- /dev/null +++ b/apps/dockhand/nativewind-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/apps/dockhand/package.json b/apps/dockhand/package.json new file mode 100644 index 0000000..fc1d920 --- /dev/null +++ b/apps/dockhand/package.json @@ -0,0 +1,76 @@ +{ + "name": "@offline-app-examples/dockhand", + "version": "0.1.0", + "private": true, + "license": "MIT-0", + "scripts": { + "android": "react-native run-android", + "ios": "react-native run-ios", + "start": "react-native start", + "dev": "react-native start", + "start:fresh": "react-native start --reset-cache", + "test": "jest --runInBand --passWithNoTests", + "lint": "eslint .", + "typecheck": "tsc --noEmit", + "format": "prettier --write App.tsx src __tests__", + "format:check": "prettier --check App.tsx src __tests__" + }, + "dependencies": { + "@offline-app-examples/mesh": "workspace:*", + "@offline-app-examples/ui": "workspace:*", + "@offline-protocol/mesh-sdk": "^0.28.0", + "@react-native-async-storage/async-storage": "^2.2.0", + "lucide-react-native": "^1.48.0", + "nativewind": "^4.2.7", + "react": "19.2.3", + "react-native": "0.85.2", + "react-native-css-interop": "^0.2.7", + "react-native-reanimated": "~4.6.0", + "react-native-safe-area-context": "^5.5.2", + "react-native-svg": "^15.15.5", + "react-native-worklets": "~0.12.2" + }, + "devDependencies": { + "@babel/core": "^7.25.2", + "@babel/plugin-transform-react-jsx": "^7.27.1", + "@babel/preset-env": "^7.25.3", + "@babel/runtime": "^7.25.0", + "@jest/globals": "^29.6.3", + "@react-native-community/cli": "20.1.0", + "@react-native-community/cli-platform-android": "20.1.0", + "@react-native-community/cli-platform-ios": "20.1.0", + "@react-native/babel-preset": "0.85.2", + "@react-native/codegen": "0.85.2", + "@react-native/eslint-config": "0.85.2", + "@react-native/gradle-plugin": "0.85.2", + "@react-native/jest-preset": "0.85.2", + "@react-native/metro-config": "0.85.2", + "@react-native/typescript-config": "0.85.2", + "@types/react": "^19.2.0", + "babel-jest": "^29.6.3", + "eslint": "^8.19.0", + "hermes-compiler": "250829098.0.10", + "jest": "^29.6.3", + "prettier": "^3.9.6", + "react-test-renderer": "19.2.3", + "tailwindcss": "^3.4.19", + "typescript": "^5.8.3" + }, + "engines": { + "node": ">=20" + }, + "jest": { + "testMatch": [ + "**/__tests__/**/*.test.ts" + ], + "transform": { + "^.+\\.(ts|tsx)$": "babel-jest" + }, + "moduleFileExtensions": [ + "ts", + "tsx", + "js", + "json" + ] + } +} diff --git a/apps/dockhand/src/domain/handoff.ts b/apps/dockhand/src/domain/handoff.ts new file mode 100644 index 0000000..3e29db7 --- /dev/null +++ b/apps/dockhand/src/domain/handoff.ts @@ -0,0 +1,255 @@ +// Driver -> yard handoffs: pure functions only (no React, no Bluetooth, no network). +// +// A driver checks in with load details and signs them. The yard (room host) +// stores the check-in only if the driver's signature verifies, assigns a dock +// and countersigns. The yard's signature covers the driver's exact check-in and +// signature, so the pair is a receipt neither side can change afterwards. +// +// Every record carries a stable handoffId: mesh resends, repeat check-ins and +// repeat syncs to MyGeotab all dedupe on it. + +import type { Signed } from '@offline-app-examples/mesh'; + +export const DOCKS = [ + 'Dock 1', + 'Dock 2', + 'Dock 3', + 'Dock 4', + 'Dock 5', + 'Dock 6', +] as const; +export type Dock = (typeof DOCKS)[number]; + +/** A MyGeotab Device (id + name), or a demo vehicle when not signed in. */ +export type Vehicle = { id: string; name: string }; + +export type Load = { + bol: string; + trailer: string; + seal: string; + pallets: number; +}; + +export type Assignment = { + dock: Dock; + yardId: string; + yardName: string; + assignedAt: number; + yardSig: Signed; +}; + +export type Handoff = { + handoffId: string; + vehicle: Vehicle; + driverId: string; + driverName: string; + load: Load; + checkedInAt: number; + driverSig: Signed; + assignment?: Assignment; +}; + +export type Message = + /** driver -> yard */ + | { type: 'checkin'; handoff: Handoff } + /** yard -> one driver: that driver's handoffs as the yard holds them */ + | { type: 'yours'; handoffs: Handoff[] }; + +export const DEMO_VEHICLES: Vehicle[] = [ + { id: 'demo-truck-12', name: 'Truck 12 (demo)' }, + { id: 'demo-truck-27', name: 'Truck 27 (demo)' }, +]; + +export const HANDOFFS_MAX = 50; +export const YOURS_MAX = 20; +const ID_MAX = 48; +const NAME_MAX = 40; +const FIELD_MAX = 30; + +// ------------------------------------------------------- signed payloads + +/** The exact text the driver signs. An array, so field order can never drift. */ +export function checkInText( + h: Omit, +): string { + return JSON.stringify([ + 'dockhand/checkin/v1', + h.handoffId, + h.vehicle.id, + h.vehicle.name, + h.driverId, + h.driverName, + h.load.bol, + h.load.trailer, + h.load.seal, + h.load.pallets, + h.checkedInAt, + ]); +} + +/** The exact text the yard signs: the driver's check-in and signature, plus the dock. */ +export function assignmentText( + h: Handoff, + a: Omit, +): string { + return JSON.stringify([ + 'dockhand/assign/v1', + checkInText(h), + h.driverSig.signature, + a.dock, + a.yardId, + a.yardName, + a.assignedAt, + ]); +} + +// ---------------------------------------------------------------- ledger + +/** Adds `h` unless its handoffId is already known. Returns the same array when unchanged. */ +export function addHandoff(list: Handoff[], h: Handoff): Handoff[] { + if (list.some((x) => x.handoffId === h.handoffId)) return list; + return [...list, h].slice(-HANDOFFS_MAX); +} + +/** Sets the assignment on an unassigned handoff. A handoff is assigned once. */ +export function assign( + list: Handoff[], + handoffId: string, + a: Assignment, +): Handoff[] { + const i = list.findIndex((x) => x.handoffId === handoffId); + if (i < 0 || list[i].assignment) return list; + const next = list.slice(); + next[i] = { ...list[i], assignment: a }; + return next; +} + +/** Driver: own handoffs still waiting for the yard to acknowledge them. */ +export function awaitingYard( + list: Handoff[], + driverId: string, + yardHas: Set, +): Handoff[] { + return list.filter( + (h) => + h.driverId === driverId && !h.assignment && !yardHas.has(h.handoffId), + ); +} + +export function yoursFor(list: Handoff[], driverId: string): Message { + return { + type: 'yours', + handoffs: list.filter((h) => h.driverId === driverId).slice(-YOURS_MAX), + }; +} + +export function newHandoffId(): string { + return `ho-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`; +} + +// ------------------------------------------------------------ validation + +export function parseMessage(data: unknown): Message | null { + if (!isObject(data)) return null; + if (data.type === 'checkin') { + const handoff = parseHandoff(data.handoff); + return handoff && !handoff.assignment ? { type: 'checkin', handoff } : null; + } + if (data.type === 'yours') { + if (!Array.isArray(data.handoffs) || data.handoffs.length > YOURS_MAX) + return null; + const handoffs = data.handoffs.map(parseHandoff); + if (handoffs.some((h) => h === null)) return null; + return { type: 'yours', handoffs: handoffs as Handoff[] }; + } + return null; +} + +export function parseHandoff(data: unknown): Handoff | null { + if (!isObject(data)) return null; + const { + handoffId, + vehicle, + driverId, + driverName, + load, + checkedInAt, + driverSig, + assignment, + } = data; + if (!isHandoffId(handoffId)) return null; + if ( + !isObject(vehicle) || + !isText(vehicle.id, 1, ID_MAX) || + !isText(vehicle.name, 1, NAME_MAX) + ) + return null; + if (!isText(driverId, 1, 80) || !isText(driverName, 0, NAME_MAX)) return null; + if (!isObject(load)) return null; + if (!isText(load.bol, 1, FIELD_MAX) || !isText(load.trailer, 1, FIELD_MAX)) + return null; + if (!isText(load.seal, 0, FIELD_MAX) || !isInt(load.pallets, 0, 99)) + return null; + if (!isInt(checkedInAt, 0, Number.MAX_SAFE_INTEGER)) return null; + const sig = parseSigned(driverSig); + if (!sig) return null; + const h: Handoff = { + handoffId, + vehicle: { id: vehicle.id, name: vehicle.name }, + driverId, + driverName, + load: { + bol: load.bol, + trailer: load.trailer, + seal: load.seal, + pallets: load.pallets, + }, + checkedInAt, + driverSig: sig, + }; + if (assignment === undefined) return h; + if (!isObject(assignment)) return null; + const { dock, yardId, yardName, assignedAt, yardSig } = assignment; + const ySig = parseSigned(yardSig); + if ( + !DOCKS.includes(dock as Dock) || + !isText(yardId, 1, 80) || + !isText(yardName, 0, NAME_MAX) + ) + return null; + if (!isInt(assignedAt, 0, Number.MAX_SAFE_INTEGER) || !ySig) return null; + return { + ...h, + assignment: { + dock: dock as Dock, + yardId, + yardName, + assignedAt, + yardSig: ySig, + }, + }; +} + +/** Also guards the MyGeotab where clause, which interpolates this id. */ +export function isHandoffId(v: unknown): v is string { + return typeof v === 'string' && /^[a-z0-9-]{1,48}$/.test(v); +} + +function parseSigned(v: unknown): Signed | null { + if (!isObject(v)) return null; + const { publicKey, signature } = v; + if (!isText(publicKey, 64, 64) || !isText(signature, 128, 128)) return null; + return { publicKey, signature }; +} + +function isObject(v: unknown): v is Record { + return typeof v === 'object' && v !== null && !Array.isArray(v); +} + +function isInt(v: unknown, min: number, max: number): v is number { + return Number.isInteger(v) && (v as number) >= min && (v as number) <= max; +} + +function isText(v: unknown, min: number, max: number): v is string { + return typeof v === 'string' && v.length >= min && v.length <= max; +} diff --git a/apps/dockhand/src/domain/sync.ts b/apps/dockhand/src/domain/sync.ts new file mode 100644 index 0000000..38fd76a --- /dev/null +++ b/apps/dockhand/src/domain/sync.ts @@ -0,0 +1,101 @@ +// What gets synced to MyGeotab, and the mock ingest used when not signed in. +// Pure functions only. + +import type { Signed } from '@offline-app-examples/mesh'; +import { assignmentText, checkInText, type Handoff } from './handoff'; + +/** A GPS point the vehicle's GO device logged. */ +export type GoFix = { latitude: number; longitude: number; dateTime: string }; + +/** One AddInData `details` object. MyGeotab caps it at 10,000 characters. */ +export type HandoffDetails = { + handoffId: string; + vehicleId: string; + vehicleName: string; + driverName: string; + driverAddress: string; + bol: string; + trailer: string; + seal: string; + pallets: number; + dock: string; + yardName: string; + checkedInAt: string; + assignedAt: string; + /** The signed texts themselves, so a verifier needs no copy of our encoding. */ + signed: { checkIn: string; driver: Signed; assignment: string; yard: Signed }; + /** Where the GO device put the vehicle closest to check-in time. Null for demo vehicles. */ + goFix: GoFix | null; + source: 'dockhand/1'; +}; + +export const DETAILS_MAX_CHARS = 10_000; + +/** Handoffs that are complete (dock assigned) and not yet synced from this device. */ +export function unsynced(list: Handoff[], syncedIds: string[]): Handoff[] { + const done = new Set(syncedIds); + return list.filter((h) => h.assignment && !done.has(h.handoffId)); +} + +export function toDetails(h: Handoff, goFix: GoFix | null): HandoffDetails { + const a = h.assignment; + if (!a) throw new Error(`Handoff ${h.handoffId} has no dock yet.`); + return { + handoffId: h.handoffId, + vehicleId: h.vehicle.id, + vehicleName: h.vehicle.name, + driverName: h.driverName, + driverAddress: h.driverId, + bol: h.load.bol, + trailer: h.load.trailer, + seal: h.load.seal, + pallets: h.load.pallets, + dock: a.dock, + yardName: a.yardName, + checkedInAt: new Date(h.checkedInAt).toISOString(), + assignedAt: new Date(a.assignedAt).toISOString(), + signed: { + checkIn: checkInText(h), + driver: h.driverSig, + assignment: assignmentText(h, a), + yard: a.yardSig, + }, + goFix, + source: 'dockhand/1', + }; +} + +export const isDemoVehicle = (vehicleId: string) => + vehicleId.startsWith('demo-'); + +/** The fix closest in time to `at`, or null. */ +export function nearestFix(fixes: GoFix[], at: number): GoFix | null { + let best: GoFix | null = null; + let bestGap = Infinity; + for (const f of fixes) { + const gap = Math.abs(Date.parse(f.dateTime) - at); + if (gap < bestGap) { + best = f; + bestGap = gap; + } + } + return best; +} + +/** Mock ingest, idempotent by handoffId: what MyGeotab's duplicate check does, offline. */ +export function mockIngest( + stored: string[], + ids: string[], +): { stored: string[]; added: string[]; duplicates: number } { + const next = new Set(stored); + const added: string[] = []; + let duplicates = 0; + for (const id of ids) { + if (next.has(id)) duplicates++; + else { + next.add(id); + added.push(id); + } + } + return { stored: [...next], added, duplicates }; +} diff --git a/apps/dockhand/src/services/mygeotab.ts b/apps/dockhand/src/services/mygeotab.ts new file mode 100644 index 0000000..e5f5e48 --- /dev/null +++ b/apps/dockhand/src/services/mygeotab.ts @@ -0,0 +1,180 @@ +// The smallest MyGeotab JSON-RPC client this demo needs: sign in, list vehicles, +// read GO GPS records, and write handoffs as AddInData. +// https://developers.geotab.com/myGeotab/introduction/ + +import { isHandoffId } from '../domain/handoff'; +import type { GoFix, HandoffDetails } from '../domain/sync'; + +/** + * Keys the Storage API records of this add-in (an encoded GUID: "a" + 22 base64url + * characters). Fixed so that every phone and the MyGeotab page read the same records. + * Generate your own for a real integration. + */ +export const ADD_IN_ID = 'aQ1J3cGt1UkW1vXoJmZ6h3w'; + +/** Sign-in result. The password is never kept; only this session. */ +export type Session = { + server: string; + credentials: { database: string; userName: string; sessionId: string }; +}; + +/** The session is no longer valid: sign in again. */ +export class SessionExpired extends Error {} + +type Fetch = typeof fetch; + +async function rpc( + server: string, + method: string, + params: object, + fetchImpl: Fetch, +): Promise { + // POST only: MyGeotab asks that credentials never travel in a URL. + const res = await fetchImpl(`https://${server}/apiv1`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ method, params }), + }); + const json = (await res.json()) as { + result?: T; + error?: { + message?: string; + errors?: { name?: string; message?: string }[]; + }; + }; + if (json.error) { + const names = (json.error.errors ?? []).map((e) => e.name); + const message = + json.error.errors?.[0]?.message ?? json.error.message ?? 'MyGeotab error'; + if ( + names.includes('InvalidUserException') || + /invalid session/i.test(message) + ) { + throw new SessionExpired(message); + } + throw new Error(message); + } + return json.result as T; +} + +const call = ( + s: Session, + method: string, + params: object, + fetchImpl: Fetch = fetch, +) => + rpc( + s.server, + method, + { ...params, credentials: s.credentials }, + fetchImpl, + ); + +export function cleanServer(server: string): string { + return ( + server + .trim() + .replace(/^https?:\/\//i, '') + .replace(/\/.*$/, '') || 'my.geotab.com' + ); +} + +export async function signIn( + server: string, + database: string, + userName: string, + password: string, + fetchImpl: Fetch = fetch, +): Promise { + const host = cleanServer(server); + const result = await rpc<{ + credentials: Session['credentials']; + path: string; + }>( + host, + 'Authenticate', + { database: database.trim(), userName: userName.trim(), password }, + fetchImpl, + ); + // `path` names the server that holds this database, or "ThisServer". + const path = + result.path && result.path !== 'ThisServer' + ? cleanServer(result.path) + : host; + return { server: path, credentials: result.credentials }; +} + +export async function listVehicles(s: Session, fetchImpl?: Fetch) { + const devices = await call<{ id: string; name: string }[]>( + s, + 'Get', + { typeName: 'Device', resultsLimit: 50 }, + fetchImpl, + ); + return devices.map((d) => ({ id: d.id, name: String(d.name).slice(0, 40) })); +} + +/** GO GPS records within `windowMs` either side of `at`. */ +export async function goFixesAround( + s: Session, + deviceId: string, + at: number, + windowMs = 10 * 60_000, + fetchImpl?: Fetch, +) { + const records = await call( + s, + 'Get', + { + typeName: 'LogRecord', + search: { + deviceSearch: { id: deviceId }, + fromDate: new Date(at - windowMs).toISOString(), + toDate: new Date(at + windowMs).toISOString(), + }, + resultsLimit: 100, + }, + fetchImpl, + ); + return records.map((r) => ({ + latitude: r.latitude, + longitude: r.longitude, + dateTime: r.dateTime, + })); +} + +/** + * Writes one handoff unless a record with its handoffId already exists, so a + * retry, or the yard and the driver both syncing, never makes a second record. + */ +export async function pushHandoff( + s: Session, + details: HandoffDetails, + fetchImpl?: Fetch, +): Promise<'added' | 'duplicate'> { + if (!isHandoffId(details.handoffId)) throw new Error('Bad handoff id.'); + // ponytail: check-then-add, so two devices syncing the same handoff in the same + // second can both add it. The MyGeotab page dedupes on handoffId; a server-side + // sync service would make this one transaction. + const existing = await call( + s, + 'Get', + { + typeName: 'AddInData', + search: { + addInId: ADD_IN_ID, + selectClause: 'handoffId', + whereClause: `handoffId = "${details.handoffId}"`, + }, + }, + fetchImpl, + ); + if (existing.length > 0) return 'duplicate'; + await call( + s, + 'Add', + { typeName: 'AddInData', entity: { addInId: ADD_IN_ID, details } }, + fetchImpl, + ); + return 'added'; +} diff --git a/apps/dockhand/src/services/persist.ts b/apps/dockhand/src/services/persist.ts new file mode 100644 index 0000000..7e560a7 --- /dev/null +++ b/apps/dockhand/src/services/persist.ts @@ -0,0 +1,83 @@ +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { parseHandoff, type Handoff, type Vehicle } from '../domain/handoff'; +import type { Session } from './mygeotab'; + +const KEY = '@dockhand/persist/v1'; + +export type SyncReport = { + at: number; + target: 'mygeotab' | 'mock'; + added: number; + duplicates: number; + error?: string; +}; + +export type Persisted = { + /** Yard: check-ins received. Driver: own check-ins. Both: what gets synced. */ + handoffs: Handoff[]; + /** handoffIds this device has synced (to MyGeotab or the mock). */ + syncedIds: string[]; + mockStored: string[]; + session: Session | null; + /** MyGeotab devices from the last sign-in. Empty means demo vehicles. */ + vehicles: Vehicle[]; + vehicleId: string; + lastSync: SyncReport | null; +}; + +export const emptyPersisted: Persisted = { + handoffs: [], + syncedIds: [], + mockStored: [], + session: null, + vehicles: [], + vehicleId: '', + lastSync: null, +}; + +export async function loadPersisted(): Promise { + try { + const raw = await AsyncStorage.getItem(KEY); + return raw ? sanitize(JSON.parse(raw)) : emptyPersisted; + } catch { + return emptyPersisted; + } +} + +export async function savePersisted(data: Persisted): Promise { + try { + await AsyncStorage.setItem(KEY, JSON.stringify(data)); + } catch (e) { + console.warn('[dockhand] persist failed', e); + } +} + +const strings = (v: unknown) => + Array.isArray(v) ? v.filter((x): x is string => typeof x === 'string') : []; + +export function sanitize(data: unknown): Persisted { + if (!data || typeof data !== 'object') return emptyPersisted; + const d = data as Record; + const s = d.session as Session | null | undefined; + const session = + s && + typeof s.server === 'string' && + typeof s.credentials?.sessionId === 'string' + ? s + : null; + return { + handoffs: Array.isArray(d.handoffs) + ? d.handoffs.map(parseHandoff).filter((h): h is Handoff => h !== null) + : [], + syncedIds: strings(d.syncedIds), + mockStored: strings(d.mockStored), + session, + vehicles: Array.isArray(d.vehicles) + ? (d.vehicles as Vehicle[]).filter( + (v) => typeof v?.id === 'string' && typeof v?.name === 'string', + ) + : [], + vehicleId: typeof d.vehicleId === 'string' ? d.vehicleId : '', + lastSync: (d.lastSync as SyncReport | null) ?? null, + }; +} diff --git a/apps/dockhand/src/ui/DriverScreen.tsx b/apps/dockhand/src/ui/DriverScreen.tsx new file mode 100644 index 0000000..01a6941 --- /dev/null +++ b/apps/dockhand/src/ui/DriverScreen.tsx @@ -0,0 +1,165 @@ +import { Button, Icon, Input, Text } from '@offline-app-examples/ui'; +import { Send } from 'lucide-react-native'; +import React, { useState } from 'react'; +import { ActivityIndicator, FlatList, Keyboard, View } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import type { Handoff, Load, Vehicle } from '../domain/handoff'; +import { Chips, HandoffCard, TopBar, useNow } from './common'; + +type Person = { id: string; name: string }; + +export function DriverScreen({ + yardName, + people, + reconnecting, + vehicles, + vehicleId, + onVehicle, + handoffs, + syncedIds, + onCheckIn, + onLeave, +}: { + yardName: string; + people: Person[]; + reconnecting: boolean; + vehicles: Vehicle[]; + vehicleId: string; + onVehicle: (id: string) => void; + handoffs: Handoff[]; + syncedIds: string[]; + onCheckIn: (load: Load) => Promise; + onLeave: () => void; +}) { + const now = useNow(); + const [bol, setBol] = useState('BOL-48213'); + const [trailer, setTrailer] = useState('TR-5530'); + const [seal, setSeal] = useState('SL-90871'); + const [pallets, setPallets] = useState('22'); + const [busy, setBusy] = useState(false); + const [error, setError] = useState(''); + + const palletCount = Number.parseInt(pallets, 10); + const valid = + bol.trim() && + trailer.trim() && + Number.isInteger(palletCount) && + palletCount >= 0 && + palletCount <= 99; + + async function submit() { + Keyboard.dismiss(); // otherwise it stays up over the new card and the sync bar + setBusy(true); + setError(''); + try { + await onCheckIn({ + bol: bol.trim(), + trailer: trailer.trim(), + seal: seal.trim(), + pallets: palletCount, + }); + } catch (e) { + setError(e instanceof Error ? e.message : String(e)); + } finally { + setBusy(false); + } + } + + return ( + + + h.handoffId} + contentContainerClassName="gap-3 px-5 pb-4" + keyboardShouldPersistTaps="handled" + keyboardDismissMode="on-drag" + ListHeaderComponent={ + + + + Load + + + + + + + + + + {error ? ( + {error} + ) : null} + + + Sent over Bluetooth, signed with this phone's key. The dock + assignment comes back countersigned by the yard. + + + } + renderItem={({ item }) => ( + + {!item.assignment ? ( + + Waiting for a dock… + + ) : null} + + )} + /> + + ); +} diff --git a/apps/dockhand/src/ui/GeotabBar.tsx b/apps/dockhand/src/ui/GeotabBar.tsx new file mode 100644 index 0000000..c1a8b9f --- /dev/null +++ b/apps/dockhand/src/ui/GeotabBar.tsx @@ -0,0 +1,230 @@ +import { Button, Icon, Input, Text } from '@offline-app-examples/ui'; +import { CloudOff, CloudUpload, X } from 'lucide-react-native'; +import React, { useState } from 'react'; +import { + ActivityIndicator, + KeyboardAvoidingView, + Modal, + Platform, + Pressable, + ScrollView, + View, +} from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import type { Session } from '../services/mygeotab'; +import type { SyncReport } from '../services/persist'; + +/** Bottom strip on every screen: where handoffs sync to, how many are waiting, the last result. */ +export function GeotabBar({ + session, + toSync, + lastSync, + busy, + onSync, + onOpen, +}: { + session: Session | null; + toSync: number; + lastSync: SyncReport | null; + busy: boolean; + onSync: () => void; + onOpen: () => void; +}) { + return ( + + + + + {session + ? `MyGeotab · ${session.credentials.database}` + : 'MyGeotab · not connected (mock sync)'} + + + {toSync} to sync · {describe(lastSync)} + + + + + + ); +} + +function describe(r: SyncReport | null) { + if (!r) return 'never synced'; + const time = new Date(r.at).toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit', + }); + if (r.error) return `${time}: ${r.error}`; + return `${time}: ${r.added} added, ${r.duplicates} duplicate${r.duplicates === 1 ? '' : 's'} dropped`; +} + +/** Sign in to MyGeotab. Only the resulting session is stored, never the password. */ +export function GeotabSheet({ + visible, + session, + vehicleCount, + onSignIn, + onSignOut, + onClose, +}: { + visible: boolean; + session: Session | null; + vehicleCount: number; + onSignIn: ( + server: string, + database: string, + user: string, + password: string, + ) => Promise; + onSignOut: () => void; + onClose: () => void; +}) { + const [server, setServer] = useState('my.geotab.com'); + const [database, setDatabase] = useState(''); + const [user, setUser] = useState(''); + const [password, setPassword] = useState(''); + const [busy, setBusy] = useState(false); + const [error, setError] = useState(''); + + async function submit() { + setBusy(true); + setError(''); + try { + await onSignIn(server, database, user, password); + setPassword(''); + onClose(); + } catch (e) { + setError(e instanceof Error ? e.message : String(e)); + } finally { + setBusy(false); + } + } + + return ( + + + + + MyGeotab + + + + + Completed handoffs are written to this database as Add-In data, + with the GO device's GPS fix nearest to check-in. Use a test + database: API writes take effect at once. + + {session ? ( + <> + + Signed in as{' '} + + {session.credentials.userName} + {' '} + on{' '} + + {session.credentials.database} + {' '} + ({session.server}). {vehicleCount} vehicles. + + + + ) : ( + <> + + + + + {error ? ( + + + {error} + + + ) : null} + + + Not signed in, the Sync button runs a mock ingest on this + phone with the same duplicate check, so the demo works without + a database. + + + )} + + + + + ); +} diff --git a/apps/dockhand/src/ui/YardScreen.tsx b/apps/dockhand/src/ui/YardScreen.tsx new file mode 100644 index 0000000..d1b7506 --- /dev/null +++ b/apps/dockhand/src/ui/YardScreen.tsx @@ -0,0 +1,85 @@ +import { Button, Text } from '@offline-app-examples/ui'; +import React from 'react'; +import { FlatList, View } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { DOCKS, type Dock, type Handoff } from '../domain/handoff'; +import { HandoffCard, TopBar, useNow } from './common'; + +type Person = { id: string; name: string }; + +export function YardScreen({ + yardName, + people, + handoffs, + syncedIds, + onAssign, + onLeave, +}: { + yardName: string; + people: Person[]; + handoffs: Handoff[]; + syncedIds: string[]; + onAssign: (handoffId: string, dock: Dock) => void; + onLeave: () => void; +}) { + const now = useNow(); + // Waiting check-ins first, then the rest newest first. + const ordered = [...handoffs] + .reverse() + .sort((a, b) => Number(!!a.assignment) - Number(!!b.assignment)); + + return ( + + + h.handoffId} + contentContainerClassName="gap-3 px-5 pb-4 grow" + ListHeaderComponent={ + + Drivers check in over Bluetooth. Only check-ins whose signature + matches the sending phone appear here. + + } + ListEmptyComponent={ + + Waiting for drivers to check in… + + } + renderItem={({ item }) => ( + + {!item.assignment ? ( + + + Assign a dock + + + {DOCKS.map((dock) => ( + + ))} + + + ) : null} + + )} + /> + + ); +} diff --git a/apps/dockhand/src/ui/common.tsx b/apps/dockhand/src/ui/common.tsx new file mode 100644 index 0000000..0603854 --- /dev/null +++ b/apps/dockhand/src/ui/common.tsx @@ -0,0 +1,169 @@ +import { Button, cn, Icon, PersonAvatar, Text } from '@offline-app-examples/ui'; +import { BadgeCheck, CloudCheck, LogOut } from 'lucide-react-native'; +import React, { useEffect, useState } from 'react'; +import { Pressable, View } from 'react-native'; +import type { Handoff } from '../domain/handoff'; + +type Person = { id: string; name: string }; + +export function TopBar({ + eyebrow, + title, + people, + onLeave, +}: { + eyebrow: string; + title: string; + people: Person[]; + onLeave: () => void; +}) { + return ( + + + + {eyebrow} + + + {title} + + + p.name).join(', ')} + > + {people.slice(0, 4).map((person, i) => ( + 0 && '-ml-2')} + /> + ))} + + + + ); +} + +export function Chips({ + options, + value, + onChange, + label, +}: { + options: readonly { id: T; name: string }[]; + value: T; + onChange: (id: T) => void; + label: string; +}) { + return ( + + + {label} + + + {options.map((o) => ( + onChange(o.id)} + accessibilityRole="button" + accessibilityState={{ selected: o.id === value }} + className={cn( + 'rounded-sm border-2 px-3 py-2', + o.id === value + ? 'border-foreground bg-sunny' + : 'border-muted bg-muted/30', + )} + > + {o.name} + + ))} + + + ); +} + +/** One handoff: load, dock, who signed, whether this device has synced it. */ +export function HandoffCard({ + handoff, + synced, + now, + children, +}: { + handoff: Handoff; + synced: boolean; + now: number; + children?: React.ReactNode; +}) { + const a = handoff.assignment; + return ( + + + + + {handoff.vehicle.name} · {handoff.driverName || 'Driver'} + + + BOL {handoff.load.bol} · Trailer {handoff.load.trailer} + + + Seal {handoff.load.seal || 'none'} · {handoff.load.pallets} pallets + + + {a ? ( + + {a.dock} + + ) : null} + + {a ? ( + + + {synced ? : null} + + ) : null} + {children} + + {handoff.handoffId} · {timeAgo(handoff.checkedInAt, now)} + {a ? ` · ${a.yardName}` : ''} + + + ); +} + +function Badge({ icon, text }: { icon: typeof BadgeCheck; text: string }) { + return ( + + + {text} + + ); +} + +export function useNow(ms = 15_000) { + const [now, setNow] = useState(Date.now); + useEffect(() => { + const timer = setInterval(() => setNow(Date.now()), ms); + return () => clearInterval(timer); + }, [ms]); + return now; +} + +export function timeAgo(since: number, now: number) { + const min = Math.floor((now - since) / 60_000); + if (min < 1) return 'just now'; + if (min < 60) return `${min} min ago`; + return `${Math.floor(min / 60)} h ${min % 60} min ago`; +} diff --git a/apps/dockhand/src/ui/illustrations.tsx b/apps/dockhand/src/ui/illustrations.tsx new file mode 100644 index 0000000..49865e9 --- /dev/null +++ b/apps/dockhand/src/ui/illustrations.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import Svg, { Circle, Path, Rect } from 'react-native-svg'; + +/** A truck backing onto a dock door. */ +export function DockHero({ size = 180 }: { size?: number }) { + return ( + + + + + + + + + + + ); +} diff --git a/apps/dockhand/tailwind.config.js b/apps/dockhand/tailwind.config.js new file mode 100644 index 0000000..a76801d --- /dev/null +++ b/apps/dockhand/tailwind.config.js @@ -0,0 +1,6 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + // Include the shared ui package so its component classes are generated. + content: ['./App.tsx', './src/**/*.{ts,tsx}', '../../packages/ui/src/**/*.{ts,tsx}'], + presets: [require('@offline-app-examples/ui/tailwind-preset')], +}; diff --git a/apps/dockhand/tsconfig.json b/apps/dockhand/tsconfig.json new file mode 100644 index 0000000..e56fffa --- /dev/null +++ b/apps/dockhand/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "@react-native/typescript-config", + "compilerOptions": { + "skipLibCheck": true + }, + "include": [ + "**/*.ts", + "**/*.tsx", + "nativewind-env.d.ts" + ], + "exclude": [ + "**/node_modules", + "**/Pods" + ] +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6a6d1f2..9358dbe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -438,6 +438,121 @@ importers: specifier: ^5.8.3 version: 5.9.3 + apps/dockhand: + dependencies: + '@offline-app-examples/mesh': + specifier: workspace:* + version: link:../../packages/mesh + '@offline-app-examples/ui': + specifier: workspace:* + version: link:../../packages/ui + '@offline-protocol/mesh-sdk': + specifier: ^0.28.0 + version: 0.28.0(react-native@0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native-community/cli@20.1.0(supports-color@8.1.1)(typescript@5.9.3))(@react-native/jest-preset@0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.3.0)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3) + '@react-native-async-storage/async-storage': + specifier: ^2.2.0 + version: 2.2.0(react-native@0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native-community/cli@20.1.0(supports-color@8.1.1)(typescript@5.9.3))(@react-native/jest-preset@0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.3.0)(react@19.2.3)(supports-color@8.1.1)) + lucide-react-native: + specifier: ^1.48.0 + version: 1.48.0(react-native-svg@15.15.5(react-native@0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native-community/cli@20.1.0(supports-color@8.1.1)(typescript@5.9.3))(@react-native/jest-preset@0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.3.0)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3))(react-native@0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native-community/cli@20.1.0(supports-color@8.1.1)(typescript@5.9.3))(@react-native/jest-preset@0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.3.0)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3) + nativewind: + specifier: ^4.2.7 + version: 4.2.7(6af35d6757d3fd7944cdf994ae66f9a7) + react: + specifier: 19.2.3 + version: 19.2.3 + react-native: + specifier: 0.85.2 + version: 0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native-community/cli@20.1.0(supports-color@8.1.1)(typescript@5.9.3))(@react-native/jest-preset@0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.3.0)(react@19.2.3)(supports-color@8.1.1) + react-native-css-interop: + specifier: ^0.2.7 + version: 0.2.7(6af35d6757d3fd7944cdf994ae66f9a7) + react-native-reanimated: + specifier: ~4.6.0 + version: 4.6.0(46d81d4174c4966f579d17df97e5129d) + react-native-safe-area-context: + specifier: ^5.5.2 + version: 5.9.1(react-native@0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native-community/cli@20.1.0(supports-color@8.1.1)(typescript@5.9.3))(@react-native/jest-preset@0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.3.0)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3) + react-native-svg: + specifier: ^15.15.5 + version: 15.15.5(react-native@0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native-community/cli@20.1.0(supports-color@8.1.1)(typescript@5.9.3))(@react-native/jest-preset@0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.3.0)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3) + react-native-worklets: + specifier: ~0.12.2 + version: 0.12.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native-community/cli@20.1.0(supports-color@8.1.1)(typescript@5.9.3))(@react-native/jest-preset@0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.3.0)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1) + devDependencies: + '@babel/core': + specifier: ^7.25.2 + version: 7.29.7(supports-color@8.1.1) + '@babel/plugin-transform-react-jsx': + specifier: ^7.27.1 + version: 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/preset-env': + specifier: ^7.25.3 + version: 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/runtime': + specifier: ^7.25.0 + version: 7.29.7 + '@jest/globals': + specifier: ^29.6.3 + version: 29.7.0(supports-color@8.1.1) + '@react-native-community/cli': + specifier: 20.1.0 + version: 20.1.0(supports-color@8.1.1)(typescript@5.9.3) + '@react-native-community/cli-platform-android': + specifier: 20.1.0 + version: 20.1.0 + '@react-native-community/cli-platform-ios': + specifier: 20.1.0 + version: 20.1.0 + '@react-native/babel-preset': + specifier: 0.85.2 + version: 0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@react-native/codegen': + specifier: 0.85.2 + version: 0.85.2(@babel/core@7.29.7(supports-color@8.1.1)) + '@react-native/eslint-config': + specifier: 0.85.2 + version: 0.85.2(eslint@8.57.1(supports-color@8.1.1))(jest@29.7.0(@types/node@26.5.1)(supports-color@8.1.1))(prettier@3.9.6)(supports-color@8.1.1)(typescript@5.9.3) + '@react-native/gradle-plugin': + specifier: 0.85.2 + version: 0.85.2 + '@react-native/jest-preset': + specifier: 0.85.2 + version: 0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1) + '@react-native/metro-config': + specifier: 0.85.2 + version: 0.85.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@react-native/typescript-config': + specifier: 0.85.2 + version: 0.85.2 + '@types/react': + specifier: ^19.2.0 + version: 19.3.0 + babel-jest: + specifier: ^29.6.3 + version: 29.7.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + eslint: + specifier: ^8.19.0 + version: 8.57.1(supports-color@8.1.1) + hermes-compiler: + specifier: 250829098.0.10 + version: 250829098.0.10 + jest: + specifier: ^29.6.3 + version: 29.7.0(@types/node@26.5.1)(supports-color@8.1.1) + prettier: + specifier: ^3.9.6 + version: 3.9.6 + react-test-renderer: + specifier: 19.2.3 + version: 19.2.3(react@19.2.3) + tailwindcss: + specifier: ^3.4.19 + version: 3.4.19(yaml@2.9.1) + typescript: + specifier: ^5.8.3 + version: 5.9.3 + apps/eventfloor: dependencies: '@offline-app-examples/mesh': From af12095dfbc8c25d22a1a5c2bec975d97e4918d4 Mon Sep 17 00:00:00 2001 From: Mizan Ali Date: Tue, 13 Oct 2026 23:13:09 +0400 Subject: [PATCH 3/5] test(dockhand): check the countersigned check-in by parsing, not by escaping --- apps/dockhand/__tests__/handoff.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dockhand/__tests__/handoff.test.ts b/apps/dockhand/__tests__/handoff.test.ts index 352e3eb..99ede4c 100644 --- a/apps/dockhand/__tests__/handoff.test.ts +++ b/apps/dockhand/__tests__/handoff.test.ts @@ -46,7 +46,7 @@ describe('signed texts', () => { checkInText(handoff({ vehicle: { id: 'b2', name: 'Truck 12' } })), ).not.toBe(checkInText(h)); const text = assignmentText(h, assignment); - expect(text).toContain(checkInText(h).replace(/"/g, '\\"')); + expect(JSON.parse(text)[1]).toBe(checkInText(h)); expect( assignmentText(handoff({ driverSig: sig('c') }), assignment), ).not.toBe(text); From 65b76c55c40f322fe6c6a60eec9f864b12f61c84 Mon Sep 17 00:00:00 2001 From: Mizan Ali Date: Wed, 14 Oct 2026 22:35:15 +0400 Subject: [PATCH 4/5] docs(dockhand): add demo gif --- README.md | 4 ++++ apps/dockhand/README.md | 2 ++ apps/dockhand/demo.gif | Bin 0 -> 1771328 bytes 3 files changed, 6 insertions(+) create mode 100644 apps/dockhand/demo.gif diff --git a/README.md b/README.md index 1106a76..32bf214 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,10 @@ Each app's README explains how to run it, how its sync works and what its tests ![Event Floor Flow B — iOS gate scanner (ticket scans and duplicate detection)](./apps/eventfloor/demo-staff.gif) +### DockHand + +![DockHand demo: Android dock office and iOS driver handing off a signed dock assignment over Bluetooth, then syncing](./apps/dockhand/demo.gif) + ## Shared packages | Package | Path | Description | diff --git a/apps/dockhand/README.md b/apps/dockhand/README.md index 2a233e4..e9a1056 100644 --- a/apps/dockhand/README.md +++ b/apps/dockhand/README.md @@ -4,6 +4,8 @@ Driver to dock office handoffs with no signal, synced to **MyGeotab** when a pho Built on `@offline-app-examples/mesh` and `@offline-app-examples/ui`, Mesh SDK 0.28. +![DockHand demo: Android dock office and iOS driver over Bluetooth](./demo.gif) + ## What it demonstrates - **Offline handoff.** Check-in and dock assignment travel phone to phone over Bluetooth only. Check-ins are resent until the dock office acknowledges them, and every record has a stable `handoffId`, so resends never duplicate. diff --git a/apps/dockhand/demo.gif b/apps/dockhand/demo.gif new file mode 100644 index 0000000000000000000000000000000000000000..97d8f7887a22aac8ba75ad99527c69b0355b3951 GIT binary patch literal 1771328 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

PCyS09x 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