fix: enable open-mapping module with TypeScript fixes
- Fix unused parameter errors (prefix with underscore)
- Fix TrustCircleManager API: add getTrustLevel/setTrustLevel methods
- Fix MyceliumNetwork method calls: addNode→createNode, addHypha→createHypha
- Fix createCommitment signature to use CommitmentParams object
- Fix GeohashCommitment type with proper geohash field
- Fix PRECISION_CELL_SIZE usage (returns {lat,lng} object)
- Add type assertions for fetch response data
- Fix MapCanvas attributionControl type
- Fix GPSCollaborationLayer markerStyle merge with defaults
- Update MapShapeUtil with better event handling:
- Raise z-index to 10000 for all map buttons
- Add pointerEvents: auto for button clickability
- Add handleWheel with preventDefault to enable map zoom
- Add capturePointerEvents for proper interaction
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
d491d3ea72
commit
637f05b715
|
|
@ -11,11 +11,11 @@
|
||||||
"dev:client": "vite --host 0.0.0.0 --port 5173",
|
"dev:client": "vite --host 0.0.0.0 --port 5173",
|
||||||
"dev:worker": "wrangler dev --config wrangler.dev.toml --remote --port 5172",
|
"dev:worker": "wrangler dev --config wrangler.dev.toml --remote --port 5172",
|
||||||
"dev:worker:local": "wrangler dev --config wrangler.dev.toml --port 5172 --ip 0.0.0.0",
|
"dev:worker:local": "wrangler dev --config wrangler.dev.toml --port 5172 --ip 0.0.0.0",
|
||||||
"build": "tsc && vite build",
|
"build": "NODE_OPTIONS=\"--max-old-space-size=8192\" tsc && NODE_OPTIONS=\"--max-old-space-size=8192\" vite build",
|
||||||
"build:worker": "wrangler build --config wrangler.dev.toml",
|
"build:worker": "wrangler build --config wrangler.dev.toml",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"deploy": "tsc && vite build && wrangler deploy",
|
"deploy": "NODE_OPTIONS=\"--max-old-space-size=8192\" tsc && NODE_OPTIONS=\"--max-old-space-size=8192\" vite build && wrangler deploy",
|
||||||
"deploy:pages": "tsc && vite build",
|
"deploy:pages": "NODE_OPTIONS=\"--max-old-space-size=8192\" tsc && NODE_OPTIONS=\"--max-old-space-size=8192\" vite build",
|
||||||
"deploy:worker": "wrangler deploy",
|
"deploy:worker": "wrangler deploy",
|
||||||
"deploy:worker:dev": "wrangler deploy --config wrangler.dev.toml",
|
"deploy:worker:dev": "wrangler deploy --config wrangler.dev.toml",
|
||||||
"types": "tsc --noEmit",
|
"types": "tsc --noEmit",
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,11 @@ interface LayerPanelProps {
|
||||||
export function LayerPanel({
|
export function LayerPanel({
|
||||||
layers,
|
layers,
|
||||||
onLayerToggle,
|
onLayerToggle,
|
||||||
onLayerOpacity,
|
onLayerOpacity: _onLayerOpacity,
|
||||||
onLayerReorder,
|
onLayerReorder: _onLayerReorder,
|
||||||
onLayerAdd,
|
onLayerAdd: _onLayerAdd,
|
||||||
onLayerRemove,
|
onLayerRemove: _onLayerRemove,
|
||||||
onLayerEdit,
|
onLayerEdit: _onLayerEdit,
|
||||||
}: LayerPanelProps) {
|
}: LayerPanelProps) {
|
||||||
// TODO: Implement layer panel UI
|
// TODO: Implement layer panel UI
|
||||||
// This will be implemented in Phase 2
|
// This will be implemented in Phase 2
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ export function MapCanvas({
|
||||||
center: viewport.center as [number, number],
|
center: viewport.center as [number, number],
|
||||||
zoom: viewport.zoom,
|
zoom: viewport.zoom,
|
||||||
interactive,
|
interactive,
|
||||||
attributionControl: true,
|
attributionControl: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add navigation controls
|
// Add navigation controls
|
||||||
|
|
@ -142,8 +142,8 @@ export function MapCanvas({
|
||||||
// Handle clicks
|
// Handle clicks
|
||||||
map.on('click', (e) => {
|
map.on('click', (e) => {
|
||||||
onMapClick?.({
|
onMapClick?.({
|
||||||
latitude: e.lngLat.lat,
|
lat: e.lngLat.lat,
|
||||||
longitude: e.lngLat.lng,
|
lng: e.lngLat.lng,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,15 +34,15 @@ const DEFAULT_PROFILE_COLORS: Record<RoutingProfile, string> = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function RouteLayer({
|
export function RouteLayer({
|
||||||
routes,
|
routes: _routes,
|
||||||
selectedRouteId,
|
selectedRouteId: _selectedRouteId,
|
||||||
showAlternatives = true,
|
showAlternatives: _showAlternatives = true,
|
||||||
showElevation = false,
|
showElevation: _showElevation = false,
|
||||||
onRouteSelect,
|
onRouteSelect: _onRouteSelect,
|
||||||
onRouteEdit,
|
onRouteEdit: _onRouteEdit,
|
||||||
profileColors = {},
|
profileColors = {},
|
||||||
}: RouteLayerProps) {
|
}: RouteLayerProps) {
|
||||||
const colors = { ...DEFAULT_PROFILE_COLORS, ...profileColors };
|
const _colors = { ...DEFAULT_PROFILE_COLORS, ...profileColors };
|
||||||
|
|
||||||
// TODO: Implement route rendering with MapLibre GL JS
|
// TODO: Implement route rendering with MapLibre GL JS
|
||||||
// This will be implemented in Phase 2
|
// This will be implemented in Phase 2
|
||||||
|
|
|
||||||
|
|
@ -24,16 +24,16 @@ interface WaypointMarkerProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WaypointMarker({
|
export function WaypointMarker({
|
||||||
waypoint,
|
waypoint: _waypoint,
|
||||||
index,
|
index: _index,
|
||||||
isSelected = false,
|
isSelected: _isSelected = false,
|
||||||
isDraggable = true,
|
isDraggable: _isDraggable = true,
|
||||||
showLabel = true,
|
showLabel: _showLabel = true,
|
||||||
showTime = false,
|
showTime: _showTime = false,
|
||||||
showBudget = false,
|
showBudget: _showBudget = false,
|
||||||
onSelect,
|
onSelect: _onSelect,
|
||||||
onDragEnd,
|
onDragEnd: _onDragEnd,
|
||||||
onDelete,
|
onDelete: _onDelete,
|
||||||
}: WaypointMarkerProps) {
|
}: WaypointMarkerProps) {
|
||||||
// TODO: Implement marker rendering with MapLibre GL JS
|
// TODO: Implement marker rendering with MapLibre GL JS
|
||||||
// This will be implemented in Phase 1
|
// This will be implemented in Phase 1
|
||||||
|
|
|
||||||
|
|
@ -609,6 +609,73 @@ export function sampleConeSurface(
|
||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Aliases for backwards compatibility
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/** Alias for addVectors */
|
||||||
|
export const vectorAdd = addVectors;
|
||||||
|
|
||||||
|
/** Alias for subtractVectors */
|
||||||
|
export const vectorSubtract = subtractVectors;
|
||||||
|
|
||||||
|
/** Alias for scaleVector */
|
||||||
|
export const vectorScale = scaleVector;
|
||||||
|
|
||||||
|
/** Alias for dotProduct */
|
||||||
|
export const vectorDot = dotProduct;
|
||||||
|
|
||||||
|
/** Alias for magnitude */
|
||||||
|
export const vectorNorm = magnitude;
|
||||||
|
|
||||||
|
/** Alias for normalize */
|
||||||
|
export const vectorNormalize = normalize;
|
||||||
|
|
||||||
|
/** Alias for crossProduct (3D) */
|
||||||
|
export const vectorCross3D = crossProduct;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate angle from a reference axis
|
||||||
|
*/
|
||||||
|
export function angleFromAxis(v: SpaceVector, axis: SpaceVector): number {
|
||||||
|
const normV = normalize(v);
|
||||||
|
const normAxis = normalize(axis);
|
||||||
|
const dot = dotProduct(normV, normAxis);
|
||||||
|
return Math.acos(Math.max(-1, Math.min(1, dot)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine two cones by intersection
|
||||||
|
*/
|
||||||
|
export function combineCones(
|
||||||
|
cone1: PossibilityCone,
|
||||||
|
cone2: PossibilityCone
|
||||||
|
): PossibilityCone {
|
||||||
|
// Simplified: use the more restrictive aperture and average axes
|
||||||
|
const avgAxis = normalize(addVectors(cone1.axis, cone2.axis));
|
||||||
|
return createCone({
|
||||||
|
apex: cone1.apex, // Use first cone's apex
|
||||||
|
axis: avgAxis,
|
||||||
|
aperture: Math.min(cone1.aperture, cone2.aperture),
|
||||||
|
direction: cone1.direction,
|
||||||
|
extent: cone1.extent !== null && cone2.extent !== null
|
||||||
|
? Math.min(cone1.extent, cone2.extent)
|
||||||
|
: cone1.extent ?? cone2.extent,
|
||||||
|
constraints: [...cone1.constraints, ...cone2.constraints],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slice a cone with a plane and return the conic section
|
||||||
|
*/
|
||||||
|
export function sliceConeWithPlane(
|
||||||
|
cone: PossibilityCone,
|
||||||
|
planeNormal: SpaceVector,
|
||||||
|
planeOffset: number
|
||||||
|
): ConicSection {
|
||||||
|
return createConicSection(cone, planeNormal, planeOffset);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a vector orthogonal to the given vector
|
* Find a vector orthogonal to the given vector
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -171,8 +171,6 @@ export {
|
||||||
export {
|
export {
|
||||||
PathOptimizer,
|
PathOptimizer,
|
||||||
createPathOptimizer,
|
createPathOptimizer,
|
||||||
DEFAULT_OPTIMIZER_CONFIG,
|
|
||||||
type OptimizerConfig,
|
|
||||||
} from './optimization';
|
} from './optimization';
|
||||||
|
|
||||||
// Visualization
|
// Visualization
|
||||||
|
|
|
||||||
|
|
@ -343,7 +343,7 @@ export class PathOptimizer {
|
||||||
// Helper Methods
|
// Helper Methods
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
|
|
||||||
private createGrid(resolution: number): void {
|
private createGrid(_resolution: number): void {
|
||||||
// Grid is implicit - we just use resolution to map points to cells
|
// Grid is implicit - we just use resolution to map points to cells
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -381,7 +381,7 @@ export class PathOptimizer {
|
||||||
if (index === dim) {
|
if (index === dim) {
|
||||||
if (!current.every((v, i) => v === cell[i])) {
|
if (!current.every((v, i) => v === cell[i])) {
|
||||||
// Check bounds
|
// Check bounds
|
||||||
if (current.every((v, i) => v >= 0 && v < resolution)) {
|
if (current.every((v, _i) => v >= 0 && v < resolution)) {
|
||||||
neighbors.push([...current]);
|
neighbors.push([...current]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -401,7 +401,7 @@ export class PathOptimizer {
|
||||||
private heuristic(
|
private heuristic(
|
||||||
cell: number[],
|
cell: number[],
|
||||||
goalCell: number[],
|
goalCell: number[],
|
||||||
resolution: number
|
_resolution: number
|
||||||
): number {
|
): number {
|
||||||
// Euclidean distance in cell space
|
// Euclidean distance in cell space
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
|
|
@ -561,7 +561,7 @@ export class PathOptimizer {
|
||||||
cameFrom: Map<string, number[]>,
|
cameFrom: Map<string, number[]>,
|
||||||
goalCell: number[],
|
goalCell: number[],
|
||||||
start: SpacePoint,
|
start: SpacePoint,
|
||||||
goal: SpacePoint,
|
_goal: SpacePoint,
|
||||||
resolution: number
|
resolution: number
|
||||||
): PossibilityPath {
|
): PossibilityPath {
|
||||||
const waypoints: PathWaypoint[] = [];
|
const waypoints: PathWaypoint[] = [];
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,15 @@ import type {
|
||||||
GameEventListener,
|
GameEventListener,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { TEMPERATURE_THRESHOLDS } from './types';
|
import { TEMPERATURE_THRESHOLDS } from './types';
|
||||||
import type { GeohashCommitment, ProximityProof } from '../privacy/types';
|
import type { GeohashCommitment, ProximityProof, GeohashPrecision } from '../privacy/types';
|
||||||
import {
|
import {
|
||||||
createCommitment,
|
createCommitment,
|
||||||
verifyCommitment,
|
|
||||||
generateProximityProof,
|
generateProximityProof,
|
||||||
verifyProximityProof,
|
verifyProximityProof,
|
||||||
|
generateSalt,
|
||||||
|
encodeGeohash,
|
||||||
|
decodeBounds,
|
||||||
|
PRECISION_CELL_SIZE,
|
||||||
} from '../privacy';
|
} from '../privacy';
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
@ -101,13 +104,23 @@ export class AnchorManager {
|
||||||
const id = `anchor-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const id = `anchor-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
// Create zkGPS commitment for the location
|
// Create zkGPS commitment for the location
|
||||||
const locationCommitment = await createCommitment(
|
const salt = generateSalt();
|
||||||
params.latitude,
|
const locationCommitmentBase = await createCommitment({
|
||||||
params.longitude,
|
coordinate: { lat: params.latitude, lng: params.longitude },
|
||||||
12, // Full precision internally
|
precision: 12 as GeohashPrecision, // Full precision internally
|
||||||
params.creatorPubKey,
|
salt,
|
||||||
params.creatorPrivKey
|
});
|
||||||
);
|
|
||||||
|
// Create GeohashCommitment from LocationCommitment
|
||||||
|
const fullGeohash = encodeGeohash(params.latitude, params.longitude, 12);
|
||||||
|
const locationCommitment: GeohashCommitment = {
|
||||||
|
commitment: locationCommitmentBase.commitment,
|
||||||
|
geohash: fullGeohash,
|
||||||
|
precision: locationCommitmentBase.precision,
|
||||||
|
timestamp: locationCommitmentBase.timestamp,
|
||||||
|
expiresAt: locationCommitmentBase.expiresAt,
|
||||||
|
salt,
|
||||||
|
};
|
||||||
|
|
||||||
const anchor: DiscoveryAnchor = {
|
const anchor: DiscoveryAnchor = {
|
||||||
id,
|
id,
|
||||||
|
|
@ -227,22 +240,28 @@ export class AnchorManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the anchor target location from geohash
|
||||||
|
const anchorBounds = decodeBounds(anchor.locationCommitment.geohash);
|
||||||
|
const targetPoint = {
|
||||||
|
lat: (anchorBounds.minLat + anchorBounds.maxLat) / 2,
|
||||||
|
lng: (anchorBounds.minLng + anchorBounds.maxLng) / 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert precision to approximate max distance in meters
|
||||||
|
const cellSize = PRECISION_CELL_SIZE[anchor.requiredPrecision as keyof typeof PRECISION_CELL_SIZE];
|
||||||
|
const maxDistance = cellSize ? Math.max(cellSize.lat, cellSize.lng) : 100;
|
||||||
|
|
||||||
// Generate proximity proof
|
// Generate proximity proof
|
||||||
const proximityProof = await generateProximityProof(
|
const proximityProof = await generateProximityProof(
|
||||||
params.playerLatitude,
|
{ lat: params.playerLatitude, lng: params.playerLongitude },
|
||||||
params.playerLongitude,
|
targetPoint,
|
||||||
anchor.locationCommitment,
|
maxDistance,
|
||||||
anchor.requiredPrecision,
|
params.playerPrivKey,
|
||||||
params.playerPubKey,
|
params.playerPubKey
|
||||||
params.playerPrivKey
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify proximity
|
// Verify proximity
|
||||||
const proofValid = await verifyProximityProof(
|
const proofValid = await verifyProximityProof(proximityProof);
|
||||||
proximityProof,
|
|
||||||
anchor.locationCommitment,
|
|
||||||
params.playerPubKey
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!proofValid) {
|
if (!proofValid) {
|
||||||
return { success: false, error: 'Not close enough to anchor' };
|
return { success: false, error: 'Not close enough to anchor' };
|
||||||
|
|
@ -404,7 +423,7 @@ export class AnchorManager {
|
||||||
anchorId: string,
|
anchorId: string,
|
||||||
playerLatitude: number,
|
playerLatitude: number,
|
||||||
playerLongitude: number,
|
playerLongitude: number,
|
||||||
playerPrecision: number = 7
|
_playerPrecision: number = 7
|
||||||
): Promise<NavigationHint | null> {
|
): Promise<NavigationHint | null> {
|
||||||
const anchor = this.anchors.get(anchorId);
|
const anchor = this.anchors.get(anchorId);
|
||||||
if (!anchor) return null;
|
if (!anchor) return null;
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import type {
|
||||||
GameEvent,
|
GameEvent,
|
||||||
GameEventListener,
|
GameEventListener,
|
||||||
} from './types';
|
} from './types';
|
||||||
import type { GeohashCommitment } from '../privacy/types';
|
import type { GeohashCommitment, GeohashPrecision } from '../privacy/types';
|
||||||
import type { MyceliumNode, Hypha, Signal, NodeType, HyphaType } from '../mycelium/types';
|
import type { MyceliumNode, Hypha, Signal, NodeType, HyphaType } from '../mycelium/types';
|
||||||
import { MyceliumNetwork, createMyceliumNetwork } from '../mycelium';
|
import { MyceliumNetwork, createMyceliumNetwork } from '../mycelium';
|
||||||
|
|
||||||
|
|
@ -225,14 +225,16 @@ export class SporeManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create mycelium node at location
|
// Create mycelium node at location
|
||||||
const node = this.network.addNode({
|
const position = this.geohashToPosition(params.locationCommitment.geohash);
|
||||||
|
const node = this.network.createNode({
|
||||||
type: this.sporeTypeToNodeType(params.spore.type),
|
type: this.sporeTypeToNodeType(params.spore.type),
|
||||||
position: this.geohashToPosition(params.locationCommitment.geohash),
|
label: params.spore.id,
|
||||||
strength: params.spore.nutrientCapacity / 100,
|
position: { lat: position.y / 100, lng: position.x / 10000 },
|
||||||
data: {
|
metadata: {
|
||||||
sporeId: params.spore.id,
|
sporeId: params.spore.id,
|
||||||
planterPubKey: params.planterPubKey,
|
planterPubKey: params.planterPubKey,
|
||||||
sporeType: params.spore.type,
|
sporeType: params.spore.type,
|
||||||
|
strength: params.spore.nutrientCapacity / 100,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -276,21 +278,23 @@ export class SporeManager {
|
||||||
const maxRange = Math.min(planted.spore.maxReach, other.spore.maxReach);
|
const maxRange = Math.min(planted.spore.maxReach, other.spore.maxReach);
|
||||||
if (distance <= maxRange) {
|
if (distance <= maxRange) {
|
||||||
// Create hypha connection
|
// Create hypha connection
|
||||||
const hypha = this.network.addHypha({
|
const hypha = this.network.createHypha({
|
||||||
type: this.getHyphaType(planted.spore.type, other.spore.type),
|
type: this.getHyphaType(planted.spore.type, other.spore.type),
|
||||||
fromId: planted.nodeId,
|
sourceId: planted.nodeId,
|
||||||
toId: other.nodeId,
|
targetId: other.nodeId,
|
||||||
strength: 0.5,
|
strength: 0.5,
|
||||||
data: {
|
metadata: {
|
||||||
plantedSporeIds: [planted.id, other.id],
|
plantedSporeIds: [planted.id, other.id],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
planted.hyphaIds.push(hypha.id);
|
if (hypha) {
|
||||||
other.hyphaIds.push(hypha.id);
|
planted.hyphaIds.push(hypha.id);
|
||||||
|
other.hyphaIds.push(hypha.id);
|
||||||
|
|
||||||
// Check for fruiting body conditions
|
// Check for fruiting body conditions
|
||||||
this.checkFruitingConditions(planted);
|
this.checkFruitingConditions(planted);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -360,11 +364,12 @@ export class SporeManager {
|
||||||
|
|
||||||
// Find connections via hyphae
|
// Find connections via hyphae
|
||||||
for (const hyphaId of spore.hyphaIds) {
|
for (const hyphaId of spore.hyphaIds) {
|
||||||
const hypha = this.network.getHypha(hyphaId);
|
const hyphae = this.network.getNodeHyphae(spore.nodeId);
|
||||||
|
const hypha = hyphae.find((h) => h.id === hyphaId);
|
||||||
if (hypha) {
|
if (hypha) {
|
||||||
// Find the other node
|
// Find the other node
|
||||||
const otherNodeId =
|
const otherNodeId =
|
||||||
hypha.fromId === spore.nodeId ? hypha.toId : hypha.fromId;
|
hypha.sourceId === spore.nodeId ? hypha.targetId : hypha.sourceId;
|
||||||
|
|
||||||
// Find spore by node ID
|
// Find spore by node ID
|
||||||
for (const [id, s] of this.plantedSpores.entries()) {
|
for (const [id, s] of this.plantedSpores.entries()) {
|
||||||
|
|
@ -404,9 +409,10 @@ export class SporeManager {
|
||||||
type,
|
type,
|
||||||
locationCommitment: {
|
locationCommitment: {
|
||||||
geohash: centerGeohash,
|
geohash: centerGeohash,
|
||||||
hash: '', // Would be calculated
|
commitment: '', // Would be calculated
|
||||||
timestamp: now,
|
timestamp: now.getTime(),
|
||||||
precision: 7,
|
expiresAt: now.getTime() + lifespanMinutes * 60 * 1000,
|
||||||
|
precision: 7 as GeohashPrecision,
|
||||||
},
|
},
|
||||||
sourceSporeIds: spores.map((s) => s.id),
|
sourceSporeIds: spores.map((s) => s.id),
|
||||||
harvestableRewards: rewards,
|
harvestableRewards: rewards,
|
||||||
|
|
@ -420,15 +426,17 @@ export class SporeManager {
|
||||||
|
|
||||||
this.emit({ type: 'fruit:emerged', fruit });
|
this.emit({ type: 'fruit:emerged', fruit });
|
||||||
|
|
||||||
// Notify network
|
// Emit signal to network
|
||||||
this.network.emit({
|
this.network.emitSignal(
|
||||||
id: `signal-fruit-${fruit.id}`,
|
spores[0].nodeId,
|
||||||
type: 'discovery',
|
`fruit-${fruit.id}`,
|
||||||
sourceId: spores[0].nodeId,
|
{
|
||||||
strength: 1,
|
type: 'discovery',
|
||||||
timestamp: now,
|
strength: 1,
|
||||||
data: { fruitId: fruit.id, type },
|
payload: { fruitId: fruit.id, type },
|
||||||
});
|
ttl: lifespanMinutes * 60 * 1000,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return fruit;
|
return fruit;
|
||||||
}
|
}
|
||||||
|
|
@ -629,8 +637,7 @@ export class SporeManager {
|
||||||
fruit.maturity = Math.min(100, (ageMs / lifespanMs) * 100 * 2); // Mature at 50% lifespan
|
fruit.maturity = Math.min(100, (ageMs / lifespanMs) * 100 * 2); // Mature at 50% lifespan
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update network
|
// Network handles signal propagation internally via maintenance loop
|
||||||
this.network.propagateSignals(0.9);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -640,11 +647,13 @@ export class SporeManager {
|
||||||
const growthRate = spore.spore.growthRate * this.config.baseGrowthRate;
|
const growthRate = spore.spore.growthRate * this.config.baseGrowthRate;
|
||||||
|
|
||||||
// Try to extend existing hyphae or create new connections
|
// Try to extend existing hyphae or create new connections
|
||||||
for (const hyphaId of spore.hyphaIds) {
|
const hyphae = this.network.getNodeHyphae(spore.nodeId);
|
||||||
const hypha = this.network.getHypha(hyphaId);
|
for (const hypha of hyphae) {
|
||||||
if (hypha) {
|
if (spore.hyphaIds.includes(hypha.id)) {
|
||||||
// Strengthen existing connection
|
// Strengthen existing connection
|
||||||
hypha.strength = Math.min(1, hypha.strength + growthRate * 0.01);
|
this.network.updateHypha(hypha.id, {
|
||||||
|
strength: Math.min(1, hypha.strength + growthRate * 0.01),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,14 +45,14 @@ interface UseCollaborationReturn {
|
||||||
|
|
||||||
export function useCollaboration({
|
export function useCollaboration({
|
||||||
sessionId,
|
sessionId,
|
||||||
userId,
|
userId: _userId,
|
||||||
userName,
|
userName: _userName,
|
||||||
userColor = '#3B82F6',
|
userColor: _userColor = '#3B82F6',
|
||||||
serverUrl,
|
serverUrl,
|
||||||
onParticipantJoin,
|
onParticipantJoin: _onParticipantJoin,
|
||||||
onParticipantLeave,
|
onParticipantLeave: _onParticipantLeave,
|
||||||
onRouteUpdate,
|
onRouteUpdate: _onRouteUpdate,
|
||||||
onWaypointUpdate,
|
onWaypointUpdate: _onWaypointUpdate,
|
||||||
}: UseCollaborationOptions): UseCollaborationReturn {
|
}: UseCollaborationOptions): UseCollaborationReturn {
|
||||||
const [session, setSession] = useState<CollaborationSession | null>(null);
|
const [session, setSession] = useState<CollaborationSession | null>(null);
|
||||||
const [participants, setParticipants] = useState<Participant[]>([]);
|
const [participants, setParticipants] = useState<Participant[]>([]);
|
||||||
|
|
@ -95,7 +95,7 @@ export function useCollaboration({
|
||||||
setIsConnected(false);
|
setIsConnected(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const updateCursor = useCallback((coordinate: Coordinate) => {
|
const updateCursor = useCallback((_coordinate: Coordinate) => {
|
||||||
// TODO: Broadcast cursor position via Y.js awareness
|
// TODO: Broadcast cursor position via Y.js awareness
|
||||||
// awareness.setLocalStateField('cursor', coordinate);
|
// awareness.setLocalStateField('cursor', coordinate);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ interface UseMapInstanceReturn {
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_VIEWPORT: MapViewport = {
|
const DEFAULT_VIEWPORT: MapViewport = {
|
||||||
center: { lat: 40.7128, lng: -74.006 }, // NYC default
|
center: [-74.006, 40.7128], // [lng, lat] NYC default
|
||||||
zoom: 10,
|
zoom: 10,
|
||||||
bearing: 0,
|
bearing: 0,
|
||||||
pitch: 0,
|
pitch: 0,
|
||||||
|
|
@ -183,10 +183,10 @@ export function useMapInstance({
|
||||||
const map = new maplibregl.Map({
|
const map = new maplibregl.Map({
|
||||||
container,
|
container,
|
||||||
style,
|
style,
|
||||||
center: [initialViewport.center.lng, initialViewport.center.lat],
|
center: initialViewport.center,
|
||||||
zoom: initialViewport.zoom,
|
zoom: initialViewport.zoom,
|
||||||
bearing: initialViewport.bearing,
|
bearing: initialViewport.bearing ?? 0,
|
||||||
pitch: initialViewport.pitch,
|
pitch: initialViewport.pitch ?? 0,
|
||||||
interactive,
|
interactive,
|
||||||
attributionControl: false,
|
attributionControl: false,
|
||||||
maxZoom: config.maxZoom ?? 22,
|
maxZoom: config.maxZoom ?? 22,
|
||||||
|
|
@ -210,7 +210,7 @@ export function useMapInstance({
|
||||||
map.on('move', () => {
|
map.on('move', () => {
|
||||||
const center = map.getCenter();
|
const center = map.getCenter();
|
||||||
const newViewport: MapViewport = {
|
const newViewport: MapViewport = {
|
||||||
center: { lat: center.lat, lng: center.lng },
|
center: [center.lng, center.lat],
|
||||||
zoom: map.getZoom(),
|
zoom: map.getZoom(),
|
||||||
bearing: map.getBearing(),
|
bearing: map.getBearing(),
|
||||||
pitch: map.getPitch(),
|
pitch: map.getPitch(),
|
||||||
|
|
@ -227,7 +227,7 @@ export function useMapInstance({
|
||||||
map.on('moveend', () => {
|
map.on('moveend', () => {
|
||||||
const center = map.getCenter();
|
const center = map.getCenter();
|
||||||
const finalViewport: MapViewport = {
|
const finalViewport: MapViewport = {
|
||||||
center: { lat: center.lat, lng: center.lng },
|
center: [center.lng, center.lat],
|
||||||
zoom: map.getZoom(),
|
zoom: map.getZoom(),
|
||||||
bearing: map.getBearing(),
|
bearing: map.getBearing(),
|
||||||
pitch: map.getPitch(),
|
pitch: map.getPitch(),
|
||||||
|
|
@ -269,19 +269,20 @@ export function useMapInstance({
|
||||||
const currentPitch = map.getPitch();
|
const currentPitch = map.getPitch();
|
||||||
|
|
||||||
// Only update if significantly different to avoid feedback loops
|
// Only update if significantly different to avoid feedback loops
|
||||||
|
const [lng, lat] = initialViewport.center;
|
||||||
const centerChanged =
|
const centerChanged =
|
||||||
Math.abs(currentCenter.lat - initialViewport.center.lat) > 0.0001 ||
|
Math.abs(currentCenter.lat - lat) > 0.0001 ||
|
||||||
Math.abs(currentCenter.lng - initialViewport.center.lng) > 0.0001;
|
Math.abs(currentCenter.lng - lng) > 0.0001;
|
||||||
const zoomChanged = Math.abs(currentZoom - initialViewport.zoom) > 0.01;
|
const zoomChanged = Math.abs(currentZoom - initialViewport.zoom) > 0.01;
|
||||||
const bearingChanged = Math.abs(currentBearing - initialViewport.bearing) > 0.1;
|
const bearingChanged = Math.abs(currentBearing - (initialViewport.bearing ?? 0)) > 0.1;
|
||||||
const pitchChanged = Math.abs(currentPitch - initialViewport.pitch) > 0.1;
|
const pitchChanged = Math.abs(currentPitch - (initialViewport.pitch ?? 0)) > 0.1;
|
||||||
|
|
||||||
if (centerChanged || zoomChanged || bearingChanged || pitchChanged) {
|
if (centerChanged || zoomChanged || bearingChanged || pitchChanged) {
|
||||||
map.jumpTo({
|
map.jumpTo({
|
||||||
center: [initialViewport.center.lng, initialViewport.center.lat],
|
center: initialViewport.center,
|
||||||
zoom: initialViewport.zoom,
|
zoom: initialViewport.zoom,
|
||||||
bearing: initialViewport.bearing,
|
bearing: initialViewport.bearing ?? 0,
|
||||||
pitch: initialViewport.pitch,
|
pitch: initialViewport.pitch ?? 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [initialViewport, isLoaded]);
|
}, [initialViewport, isLoaded]);
|
||||||
|
|
@ -293,10 +294,10 @@ export function useMapInstance({
|
||||||
|
|
||||||
if (mapRef.current && isLoaded) {
|
if (mapRef.current && isLoaded) {
|
||||||
mapRef.current.jumpTo({
|
mapRef.current.jumpTo({
|
||||||
center: [newViewport.center.lng, newViewport.center.lat],
|
center: newViewport.center,
|
||||||
zoom: newViewport.zoom,
|
zoom: newViewport.zoom,
|
||||||
bearing: newViewport.bearing,
|
bearing: newViewport.bearing ?? 0,
|
||||||
pitch: newViewport.pitch,
|
pitch: newViewport.pitch ?? 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,15 @@ export class GPSCollaborationLayer {
|
||||||
|
|
||||||
constructor(map: maplibregl.Map, options: GPSLayerOptions = {}) {
|
constructor(map: maplibregl.Map, options: GPSLayerOptions = {}) {
|
||||||
this.map = map;
|
this.map = map;
|
||||||
this.options = { ...DEFAULT_OPTIONS, ...options };
|
// Deep merge markerStyle to ensure all properties exist
|
||||||
|
this.options = {
|
||||||
|
...DEFAULT_OPTIONS,
|
||||||
|
...options,
|
||||||
|
markerStyle: {
|
||||||
|
...DEFAULT_OPTIONS.markerStyle,
|
||||||
|
...options.markerStyle,
|
||||||
|
},
|
||||||
|
};
|
||||||
this.injectStyles();
|
this.injectStyles();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -314,7 +322,7 @@ export class GPSCollaborationLayer {
|
||||||
const el = document.createElement('div');
|
const el = document.createElement('div');
|
||||||
el.className = `gps-marker ${isCurrentUser ? 'gps-marker-self' : 'gps-marker-peer'}`;
|
el.className = `gps-marker ${isCurrentUser ? 'gps-marker-self' : 'gps-marker-peer'}`;
|
||||||
|
|
||||||
const { size, borderWidth } = this.options.markerStyle;
|
const { size = 40, borderWidth = 3 } = this.options.markerStyle;
|
||||||
const emoji = this.getPersonEmoji(user.userId);
|
const emoji = this.getPersonEmoji(user.userId);
|
||||||
|
|
||||||
el.style.cssText = `
|
el.style.cssText = `
|
||||||
|
|
|
||||||
|
|
@ -321,7 +321,7 @@ export function transformIncentive(
|
||||||
*/
|
*/
|
||||||
export function transformRelational(
|
export function transformRelational(
|
||||||
point: DataPoint,
|
point: DataPoint,
|
||||||
config: RelationalLensConfig,
|
_config: RelationalLensConfig,
|
||||||
viewport: LensState['viewport'],
|
viewport: LensState['viewport'],
|
||||||
allPoints?: DataPoint[],
|
allPoints?: DataPoint[],
|
||||||
layoutCache?: Map<string, { x: number; y: number }>
|
layoutCache?: Map<string, { x: number; y: number }>
|
||||||
|
|
|
||||||
|
|
@ -30,10 +30,10 @@ import {
|
||||||
getRadiusForPrecision,
|
getRadiusForPrecision,
|
||||||
getPrecisionForTrustLevel,
|
getPrecisionForTrustLevel,
|
||||||
} from './types';
|
} from './types';
|
||||||
import type { TrustLevel, GeohashCommitment } from '../privacy/types';
|
import type { TrustLevel, GeohashCommitment, GeohashPrecision } from '../privacy/types';
|
||||||
import { TrustCircleManager, createTrustCircleManager } from '../privacy/trustCircles';
|
import { TrustCircleManager, createTrustCircleManager } from '../privacy/trustCircles';
|
||||||
import { createCommitment } from '../privacy/commitments';
|
import { createCommitment, generateSalt } from '../privacy/commitments';
|
||||||
import { encodeGeohash, decodeGeohash, getGeohashBounds } from '../privacy/geohash';
|
import { encodeGeohash, getGeohashBounds } from '../privacy/geohash';
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Presence Manager
|
// Presence Manager
|
||||||
|
|
@ -60,7 +60,7 @@ export class PresenceManager {
|
||||||
...config,
|
...config,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.trustCircles = createTrustCircleManager(this.config.userPubKey);
|
this.trustCircles = createTrustCircleManager(this.config.userPubKey, this.config.userPubKey);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
config: this.config,
|
config: this.config,
|
||||||
|
|
@ -175,14 +175,23 @@ export class PresenceManager {
|
||||||
const isMoving = (coords.speed ?? 0) > 0.5; // > 0.5 m/s = moving
|
const isMoving = (coords.speed ?? 0) > 0.5; // > 0.5 m/s = moving
|
||||||
|
|
||||||
// Create zkGPS commitment for the location
|
// Create zkGPS commitment for the location
|
||||||
const geohash = encodeGeohash(coords.latitude, coords.longitude, 12);
|
const fullGeohash = encodeGeohash(coords.latitude, coords.longitude, 12);
|
||||||
const commitment = await createCommitment(
|
const salt = generateSalt();
|
||||||
coords.latitude,
|
const baseCommitment = await createCommitment({
|
||||||
coords.longitude,
|
coordinate: { lat: coords.latitude, lng: coords.longitude },
|
||||||
12,
|
precision: 12 as GeohashPrecision,
|
||||||
this.config.userPubKey,
|
salt,
|
||||||
this.config.userPrivKey
|
});
|
||||||
);
|
|
||||||
|
// Create GeohashCommitment from LocationCommitment
|
||||||
|
const commitment: GeohashCommitment = {
|
||||||
|
commitment: baseCommitment.commitment,
|
||||||
|
geohash: fullGeohash,
|
||||||
|
precision: baseCommitment.precision,
|
||||||
|
timestamp: baseCommitment.timestamp,
|
||||||
|
expiresAt: baseCommitment.expiresAt,
|
||||||
|
salt,
|
||||||
|
};
|
||||||
|
|
||||||
// Update self location
|
// Update self location
|
||||||
this.state.self.location = {
|
this.state.self.location = {
|
||||||
|
|
@ -223,13 +232,23 @@ export class PresenceManager {
|
||||||
longitude: number,
|
longitude: number,
|
||||||
source: LocationSource = 'manual'
|
source: LocationSource = 'manual'
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const commitment = await createCommitment(
|
const salt = generateSalt();
|
||||||
latitude,
|
const fullGeohash = encodeGeohash(latitude, longitude, 12);
|
||||||
longitude,
|
const baseCommitment = await createCommitment({
|
||||||
12,
|
coordinate: { lat: latitude, lng: longitude },
|
||||||
this.config.userPubKey,
|
precision: 12 as GeohashPrecision,
|
||||||
this.config.userPrivKey
|
salt,
|
||||||
);
|
});
|
||||||
|
|
||||||
|
// Create GeohashCommitment from LocationCommitment
|
||||||
|
const commitment: GeohashCommitment = {
|
||||||
|
commitment: baseCommitment.commitment,
|
||||||
|
geohash: fullGeohash,
|
||||||
|
precision: baseCommitment.precision,
|
||||||
|
timestamp: baseCommitment.timestamp,
|
||||||
|
expiresAt: baseCommitment.expiresAt,
|
||||||
|
salt,
|
||||||
|
};
|
||||||
|
|
||||||
this.state.self.location = {
|
this.state.self.location = {
|
||||||
coordinates: {
|
coordinates: {
|
||||||
|
|
@ -528,7 +547,7 @@ export class PresenceManager {
|
||||||
longitude: (bounds.minLng + bounds.maxLng) / 2,
|
longitude: (bounds.minLng + bounds.maxLng) / 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ageSeconds = (Date.now() - payload.commitment.timestamp.getTime()) / 1000;
|
const ageSeconds = (Date.now() - payload.commitment.timestamp) / 1000;
|
||||||
|
|
||||||
location = {
|
location = {
|
||||||
geohash,
|
geohash,
|
||||||
|
|
|
||||||
|
|
@ -427,3 +427,22 @@ export function precisionForRadius(radiusMeters: number): number {
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Convenience Aliases
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for encode() - encode latitude/longitude to geohash
|
||||||
|
*/
|
||||||
|
export const encodeGeohash = encode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for decode() - decode geohash to latitude/longitude
|
||||||
|
*/
|
||||||
|
export const decodeGeohash = decode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for decodeBounds() - get bounding box for a geohash
|
||||||
|
*/
|
||||||
|
export const getGeohashBounds = decodeBounds;
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ export {
|
||||||
encode as encodeGeohash,
|
encode as encodeGeohash,
|
||||||
decode as decodeGeohash,
|
decode as decodeGeohash,
|
||||||
decodeBounds,
|
decodeBounds,
|
||||||
|
decodeBounds as getGeohashBounds,
|
||||||
neighbors,
|
neighbors,
|
||||||
contains,
|
contains,
|
||||||
cellsInRadius,
|
cellsInRadius,
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ export class TrustCircleManager {
|
||||||
customPrecision: params.customPrecision,
|
customPrecision: params.customPrecision,
|
||||||
members: [],
|
members: [],
|
||||||
updateInterval: params.updateInterval ?? this.getDefaultInterval(params.level),
|
updateInterval: params.updateInterval ?? this.getDefaultInterval(params.level),
|
||||||
requireMutual: params.requireMutual ?? params.level === 'intimate' || params.level === 'close',
|
requireMutual: params.requireMutual ?? (params.level === 'intimate' || params.level === 'close'),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -329,6 +329,47 @@ export class TrustCircleManager {
|
||||||
return TRUST_LEVEL_PRECISION[level];
|
return TRUST_LEVEL_PRECISION[level];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get trust level for a contact based on their highest circle membership
|
||||||
|
*/
|
||||||
|
getTrustLevel(contactId: string): TrustLevel | null {
|
||||||
|
const precision = this.getPrecisionForContact(contactId);
|
||||||
|
if (precision === null) return null;
|
||||||
|
return getTrustLevelFromPrecision(precision);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set trust level for a contact by adding them to the appropriate circle
|
||||||
|
*/
|
||||||
|
setTrustLevel(contactId: string, level: TrustLevel): void {
|
||||||
|
// Remove from all current circles first
|
||||||
|
for (const circle of this.circles.values()) {
|
||||||
|
if (circle.members.includes(contactId)) {
|
||||||
|
this.removeFromCircle(circle.id, contactId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find or create a circle at this level
|
||||||
|
let targetCircle: TrustCircle | undefined;
|
||||||
|
for (const circle of this.circles.values()) {
|
||||||
|
if (circle.level === level) {
|
||||||
|
targetCircle = circle;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetCircle) {
|
||||||
|
// Create a new circle at this level
|
||||||
|
targetCircle = this.createCircle({
|
||||||
|
name: `${level.charAt(0).toUpperCase() + level.slice(1)} Circle`,
|
||||||
|
level,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to the circle
|
||||||
|
this.addToCircle(targetCircle.id, contactId);
|
||||||
|
}
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
// Broadcast Helpers
|
// Broadcast Helpers
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
|
|
@ -359,7 +400,7 @@ export class TrustCircleManager {
|
||||||
* Returns a map of circleId -> encrypted commitment
|
* Returns a map of circleId -> encrypted commitment
|
||||||
*/
|
*/
|
||||||
async createCircleCommitments(
|
async createCircleCommitments(
|
||||||
coordinate: { lat: number; lng: number },
|
_coordinate: { lat: number; lng: number },
|
||||||
createCommitmentFn: (precision: GeohashPrecision) => Promise<LocationCommitment>
|
createCommitmentFn: (precision: GeohashPrecision) => Promise<LocationCommitment>
|
||||||
): Promise<Map<string, { commitment: LocationCommitment; precision: GeohashPrecision }>> {
|
): Promise<Map<string, { commitment: LocationCommitment; precision: GeohashPrecision }>> {
|
||||||
const commitments = new Map<
|
const commitments = new Map<
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,31 @@
|
||||||
* Types for privacy-preserving location sharing protocol
|
* Types for privacy-preserving location sharing protocol
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { GeohashPrecision } from './geohash';
|
import type { GeohashPrecision as _GeohashPrecision } from './geohash';
|
||||||
|
|
||||||
|
// Re-export GeohashPrecision for convenience
|
||||||
|
export type GeohashPrecision = _GeohashPrecision;
|
||||||
|
export { GEOHASH_PRECISION } from './geohash';
|
||||||
|
export type { GeohashBounds } from './geohash';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A geohash-based location commitment (alias for LocationCommitment)
|
||||||
|
* Used in discovery and presence systems
|
||||||
|
*/
|
||||||
|
export interface GeohashCommitment {
|
||||||
|
/** The commitment hash */
|
||||||
|
commitment: string;
|
||||||
|
/** The geohash prefix that is revealed */
|
||||||
|
geohash: string;
|
||||||
|
/** Precision level (1-12) */
|
||||||
|
precision: GeohashPrecision;
|
||||||
|
/** When this commitment was created */
|
||||||
|
timestamp: number;
|
||||||
|
/** When this commitment expires */
|
||||||
|
expiresAt: number;
|
||||||
|
/** Salt used in the commitment (for verification) */
|
||||||
|
salt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Core Location Types
|
// Core Location Types
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,9 @@ export class OptimizationService {
|
||||||
const vehicles = [{ id: 0, start: [waypoints[0].coordinate.lng, waypoints[0].coordinate.lat] }];
|
const vehicles = [{ id: 0, start: [waypoints[0].coordinate.lng, waypoints[0].coordinate.lat] }];
|
||||||
try {
|
try {
|
||||||
const res = await fetch(this.config.baseUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jobs, vehicles }) });
|
const res = await fetch(this.config.baseUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jobs, vehicles }) });
|
||||||
const data = await res.json();
|
const data = await res.json() as { code: number; error?: string; routes: { steps: { type: string; job: number }[] }[]; summary: { distance: number; duration: number } };
|
||||||
if (data.code !== 0) throw new Error(data.error);
|
if (data.code !== 0) throw new Error(data.error);
|
||||||
const indices = data.routes[0].steps.filter((s: any) => s.type === 'job').map((s: any) => s.job);
|
const indices = data.routes[0].steps.filter((s) => s.type === 'job').map((s) => s.job);
|
||||||
return { orderedWaypoints: indices.map((i: number) => waypoints[i]), totalDistance: data.summary.distance, totalDuration: data.summary.duration, estimatedCost: this.estimateCosts(data.summary.distance, data.summary.duration) };
|
return { orderedWaypoints: indices.map((i: number) => waypoints[i]), totalDistance: data.summary.distance, totalDuration: data.summary.duration, estimatedCost: this.estimateCosts(data.summary.distance, data.summary.duration) };
|
||||||
} catch { return this.nearestNeighbor(waypoints); }
|
} catch { return this.nearestNeighbor(waypoints); }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,9 +34,9 @@ export class RoutingService {
|
||||||
const url = `${this.config.baseUrl}/trip/v1/driving/${coords}?roundtrip=false&source=first&destination=last`;
|
const url = `${this.config.baseUrl}/trip/v1/driving/${coords}?roundtrip=false&source=first&destination=last`;
|
||||||
try {
|
try {
|
||||||
const res = await fetch(url);
|
const res = await fetch(url);
|
||||||
const data = await res.json();
|
const data = await res.json() as { code: string; waypoints: { waypoint_index: number }[] };
|
||||||
if (data.code !== 'Ok') return waypoints;
|
if (data.code !== 'Ok') return waypoints;
|
||||||
return data.waypoints.map((wp: { waypoint_index: number }) => waypoints[wp.waypoint_index]);
|
return data.waypoints.map((wp) => waypoints[wp.waypoint_index]);
|
||||||
} catch { return waypoints; }
|
} catch { return waypoints; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,7 +56,7 @@ export class RoutingService {
|
||||||
url.searchParams.set('steps', 'true');
|
url.searchParams.set('steps', 'true');
|
||||||
if (options?.alternatives) url.searchParams.set('alternatives', 'true');
|
if (options?.alternatives) url.searchParams.set('alternatives', 'true');
|
||||||
const res = await fetch(url.toString());
|
const res = await fetch(url.toString());
|
||||||
const data = await res.json();
|
const data = await res.json() as { code: string; message?: string; routes: unknown[] };
|
||||||
if (data.code !== 'Ok') throw new Error(`OSRM error: ${data.message || data.code}`);
|
if (data.code !== 'Ok') throw new Error(`OSRM error: ${data.message || data.code}`);
|
||||||
return this.parseOSRMResponse(data, profile);
|
return this.parseOSRMResponse(data, profile);
|
||||||
}
|
}
|
||||||
|
|
@ -65,7 +65,7 @@ export class RoutingService {
|
||||||
const costing = profile === 'bicycle' ? 'bicycle' : profile === 'foot' ? 'pedestrian' : 'auto';
|
const costing = profile === 'bicycle' ? 'bicycle' : profile === 'foot' ? 'pedestrian' : 'auto';
|
||||||
const body = { locations: coords.map((c) => ({ lat: c.lat, lon: c.lng })), costing, alternates: options?.alternatives ?? 0 };
|
const body = { locations: coords.map((c) => ({ lat: c.lat, lon: c.lng })), costing, alternates: options?.alternatives ?? 0 };
|
||||||
const res = await fetch(`${this.config.baseUrl}/route`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
|
const res = await fetch(`${this.config.baseUrl}/route`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
|
||||||
const data = await res.json();
|
const data = await res.json() as { error?: string };
|
||||||
if (data.error) throw new Error(`Valhalla error: ${data.error}`);
|
if (data.error) throw new Error(`Valhalla error: ${data.error}`);
|
||||||
return this.parseValhallaResponse(data, profile);
|
return this.parseValhallaResponse(data, profile);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -221,10 +221,11 @@ export interface ParticipantPermissions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MapViewport {
|
export interface MapViewport {
|
||||||
center: Coordinate;
|
/** Center as [lng, lat] tuple for MapLibre GL JS compatibility */
|
||||||
|
center: [number, number];
|
||||||
zoom: number;
|
zoom: number;
|
||||||
bearing: number;
|
bearing?: number;
|
||||||
pitch: number;
|
pitch?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,5 @@
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"include": ["src", "worker", "src/client"],
|
"include": ["src", "worker", "src/client"],
|
||||||
"exclude": [
|
|
||||||
"src/open-mapping/**"
|
|
||||||
],
|
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,13 @@ export default defineConfig(({ mode }) => {
|
||||||
host: wslIp,
|
host: wslIp,
|
||||||
port: 5173,
|
port: 5173,
|
||||||
},
|
},
|
||||||
|
// Proxy API requests to the worker server
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:5172',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
sourcemap: false, // Disable sourcemaps in production to reduce bundle size
|
sourcemap: false, // Disable sourcemaps in production to reduce bundle size
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue