@jsm-mit / tigi-sharded-package
Tigi网络组的一个包:它接受协调器的id并隐藏
协调员、核心和工人——对于一个消费者来说,这个群体是一个罐子。它还
携带着车手的几个驱动罐端点(U-14); getRide, cancelRide,
getLicence, getRideRoute),由 protocol/tigi-driver.did,所以消费者永远看不到司机
罐的id。
状态:骑手侧(1夜,U-42);0.2.0(阶段2a, U-43)增加了骑手自己的
评级和在哪里与司机聊天;0.3.0 (U-45)添加 DriverCanistersActor - - -
示范租约及一次性索赔代码;0.4.0(路线,2026-10-24)增加了驾驶员的
地图上的路线-提供的线路,乘车线路-以及确切的针
报价(pinExact);0.5.0(第7阶段,骑行历史,2026-10-24)添加了过去的位置
ride (Skąd, Dokąd)和历史上过去一次骑行的评分。版本0.1.0是
首先发表。
骑手
import { ProfilesActor, RidesActor, TestManagerActor, REQUEST_POLL_MS } from "@jsm-mit/tigi-sharded-package";
const profiles = new ProfilesActor(coordinatorId, identity, { host });
await profiles.setMyProfileAsyncUnsafe("Ala"); // C-2: once, before the first request
const rides = new RidesActor(coordinatorId, identity, { host });
const request = await rides.requestRideAsyncUnsafe({ pickup, destination });
localStorage.setItem("tigi-request", request.token); // rides.resumeRequest(token) after a reload
const snapshot = await request.getAsyncUnsafe(); // every REQUEST_POLL_MS (1 s)
// snapshot.phase: searching | waitingForDriver ("Zamawiam…", a second or two) | accepted | closed
// snapshot.offers: a FIXED list (U-42) — by first arrival, new ones at the end, nothing
// re-sorted; an offer that left keeps its entry with `gone` (rejected | driverNotFree |
// taken | closed): draw a grey row of the same height there
// offer.pin: the driver's exact position (offer.pinExact) — the geohash7 centre from an old driver canister
const line = await request.getOfferRouteAsyncUnsafe(offer.offerId); // ZO-21, after a tap: { toPickup: Geo[]; ride: Geo[] } | undefined
await request.rejectOfferAsyncUnsafe(offer.offerId); // ✕ → gone "rejected"; the next nearest driver
await request.refreshOfferAsyncUnsafe(offer.offerId); // a stale (grey) offer asks again
await request.chooseOfferAsyncUnsafe(offer.offerId); // ordered (= the ride, accepted at once) | busy | offerExpired
// | driverNotFree ("Kierowca jest już zajęty") | closed
await request.giveUpAsyncUnsafe();
// After "Kierowca anulował kurs" (ride status cancelledByDriver): "Szukaj ponownie" —
const again = await request.searchAgainAsyncUnsafe(); // a new request, the same pickup and destination
const ride = await request.getRideAsyncUnsafe(); // from the driver canister, every RIDE_POLL_MS (2 s)
await request.getRideRouteAsyncUnsafe(); // K-13: the ride's line while it is active (once; undefined after the end)
await request.cancelRideAsyncUnsafe(); // before inProgress, once
// ZT-*: a ride at a chosen time (1 h … 7 days ahead; drivers asked by their base, 3 → 25 km by the workers themselves):
const later = await rides.requestScheduledAsyncUnsafe({ pickup, destination, pickupAtMs }); // keep later.token; rides.resumeScheduled(token)
const s = await later.getAsyncUnsafe(); // phase collecting | booking | booked | started | rescue | closed; offers cheapest first
await later.chooseAsyncUnsafe(s.offers[0].offerId); // a booking; read again for booked / lastOrderRefused
// started → rides.resumeRequest(s.rideToken) is the ride; rescue → requestRideAsyncUnsafe with the same A, B from T−10 min (RESCUE_BEFORE_MS)
// P-20: card straight to the driver (Stripe) — when ride.cardAccepted, during inProgress:
const card = await request.startCardPaymentAsyncUnsafe(returnUrl); // open → go to card.url; the same page again while it stands
await request.checkCardPaymentAsyncUnsafe(); // back from Stripe (card=paid|cancelled in the URL); ride.cardPayment shows it too
await request.getLicenceAsyncUnsafe(); // from accepted to the end of the ride
await request.rateDriverAsyncUnsafe(5); // after completed, once
const page = await profiles.getMyRidesAsyncUnsafe(); // the history in the core, 20 a page
// 0.5.0 — a line: pickup?, destination? (KU-4; none before stage 7), ratable (KU-5), myRating?
const past = await profiles.getMyRideAsyncUnsafe(page.rides[0].rideRef); // one line (a reload); undefined until the core has it
if (past?.ratable) await profiles.ratePastRideAsyncUnsafe(past.rideRef, 5);
// → "ok" | "alreadyRated" (also after rateDriverAsyncUnsafe) | "notRatable" (not completed,
// the 24 h are over, not in the core yet) | "notAuthorized" (another rider's ride)
const mine = await profiles.getMyRatingAsyncUnsafe(); // PF-4: { average: 4.87, count, sum } | undefined ("Brak ocen")
await rides.getPastRideAsyncUnsafe(page.rides[0].rideRef); // its details from the driver canister
这个包对网络的作用(zgloszenie-i-oferty.md):
- 该请求发送给皮卡车(ZO-1)周围3公里范围内的每个工人
使用协调器的前缀映射(保持30秒);一个工人的
NOT_MY_AREA让它再问一次); - 15秒无报价→6公里,在相同的和新的工人(ZO-8),在里面
getAsyncUnsafe; - 在所有工人中一次选择一个(ZO-14);在接受之后,
closeRequest otherOfferChosen在其他工人(ZO-15); - 一个停止的罐(升级,IC0508/IC0509)等待大约一分钟。
错误: CanisterError (与 errorKey; refusalKeyOf(err) 给出工人的拒绝:
ALREADY_OPEN, NOT_MY_AREA, FULL, CLOSED, ORDERING, DRIVER_NOT_FREE,
NOT_COMPLETED, EXPIRED, BLOCKED), CallRefusedAtInspectionStage,
CriticalCanisterError.
正面:U-43
每个司机有一个聊天室 chat-sharded (不是每乘一次):它的接受者是
驱动程序,并且每个Tigi帐户都可以在那里打开聊天(聊天询问Tigi)
协调者账户所在的位置和核心 isUser). 核心使房间得以贯通
协调器;这个包只说WHERE -前台发送和读取消息
@jsm-mit/chat-sharded-package 本身(此包不包装聊天)。
import { ChatPlacesActor, driverRoomId } from "@jsm-mit/tigi-sharded-package";
import { ChatsActor, MessagesActor } from "@jsm-mit/chat-sharded-package";
// The rider (PR-25): from `accepted` to the end of the ride.
const place = await request.getChatPlaceAsyncUnsafe(); // { chatCoordinatorId, projectId, roomId } | undefined
if (place) {
const chats = new ChatsActor(place.chatCoordinatorId, identity, { host });
const chat = await chats.openChatAsyncUnsafe({ projectId: place.projectId, roomId: place.roomId, customerName: profile.name });
// … MessagesActor(place.chatCoordinatorId, identity).postAsyncUnsafe / pollNewMessagesAsyncUnsafe
}
// The driver (FK-20): the room of their own canister; they are its recipient.
const driverPlace = await new ChatPlacesActor(coordinatorId, identity, { host }).getDriverChatPlaceAsyncUnsafe(myDriverCanisterId);
// list the chats of driverPlace.roomId (ChatsActor.getMyChatsAsyncUnsafe({ projectId, roomId }))
// and show only the current rider's chat
undefined=在这个环境中没有聊天(核心中没有聊天配置)或a 模拟司机(MVP中没有聊天)。- 房间id是确定的:
driverRoomId(canister)=drv-<canister id>( 核心的ChatRoomFunctions.driverRoomId)。 - 车手需要一个Tigi档案(
setMyProfileAsyncUnsafe) -没有一个聊天 拒绝打开它(CanisterError,NotAuthorized)。 - 每个聊天(房间,乘客,收件人):乘客与司机有一次聊天 他们的游乐设施。由另一个测试人员接管的演示池罐将使该测试人员成为 收件人;与前一个测试人员的聊天记录会留在他们身边。
- 房间可能会在司机的罐子(核心的发箱)之后几秒钟出现;
openChatAsyncUnsafe答案NotFound在那之前,再试一次。
前方:驾驶员自备油箱(U-45)
互联网身份赋予一个人不同的身份。示范租约为
拍摄于骑手的场地;司机在罐子的正面签到
(https://<canister>.icp0.io)。新租约是一次性的
声明代码(15分钟,单次使用;核心只保留其sha256),将罐子交给
谁在那里认领它(fabryka.md F-20)。
import { DriverCanistersActor, claimLinkOf, claimCodeOfHash } from "@jsm-mit/tigi-sharded-package";
// The rider's site (the lease holder).
const canisters = new DriverCanistersActor(coordinatorId, identity, { host });
const lease = await canisters.leaseDemoAsyncUnsafe(); // { kind: "leased", canister, claim? } | { kind: "busy", untilMs }
if (lease.kind === "leased" && lease.claim) {
link = claimLinkOf(lease.canister.canisterId, lease.claim.code); // "Otwórz swój kanister"
}
const fresh = await canisters.newClaimCodeAsyncUnsafe(); // "Nowy link": the old code stops working
await canisters.extendDemoAsyncUnsafe(); // F-8: 15 min more
await canisters.getMineAsyncUnsafe(); // the held demo canister (or the own one) | undefined
// The driver's front, signed in as a non-owner, with #claim=<code> in the address.
const code = claimCodeOfHash(window.location.hash);
if (code) {
await new DriverCanistersActor(coordinatorId, identity, { host }).claimAsyncUnsafe(canisterId, code);
// clear the fragment, reload: the driver is the owner now
}
- 被拒绝的索赔总是
CanisterErrorNotAuthorized-错误,过期,使用或不使用 这个罐子的密码是一样的。 - 租赁权归其所有者所有:
getMineAsyncUnsafe,extendDemoAsyncUnsafe和newClaimCodeAsyncUnsafe继续从骑手的位置工作;ownerPrincipal的名字 司机索赔后。罐子的聊天室得到司机作为它的 接收方。 - 相同的持有人再次租赁(没有重新安装)没有得到代码:
newClaimCodeAsyncUnsafe. - 这些视图携带驱动筒的id:它是驱动自己的地址。
测试管理器(阶段和测试模式): TestManagerActor.hasSimulatedDriversAsyncUnsafe,
spawnDriversAsyncUnsafe (12驱动程序), setSpeedUpAsyncUnsafe(request, 10) 在选择。
模拟器的报价 simulated: true.
脚本
npm run build # tsc (src → dist) and the type-check of the tests
npm run test-unit # no replica needed
npm run test-drivers # (a) 5 real driver canisters, driver-bots, route-stub
npm run test-simulator # (b) the simulator, 20 requests, 20 rides
npm run test-split # (c) an area split during traffic
npm run test-claim # (d) the demo lease and the claim code (U-45)
npm run test-route-line # (e) the route line of an offer and of a ride
npm run test-ride-history # (f) the history's places and the rating of a past ride (0.5.0)
npm run test-smoke # one whole ride with the simulator (run by publish-public)
npm run load-local # the load test: 200 simulated drivers, 100 riders (docs/load-test-2026-10-24.md)
npm run publish-public # human-run: publish from master (see below)
npm run sync-declarations # after a change of a .did (`… protocol` for the protocol and the simulator only)
端到端套件仅在LOCAL副本上运行(scripts/run-tests-local.sh):新鲜的
组,局部循环,驱动程序和模拟器wasms ../../tigi-driver-rust (建
那里,从未改变)。日志和测量值为 /tmp/claude-1000/tigi/
(TEST_LOG_DIR). 负载测试用它自己的协调器构建它的组
(tigi-sharded-coordinator-load),因此共享的本地组保持不变;它的设置是
环境变量(scripts/load-local.ts, scripts/run-load-local.sh)。
出版
只有人跑:碰撞 version 在 package.json 那么,手工(不要提交)
npm run publish-public 在 master. 该脚本拒绝,除非此repo和
../../tigi-driver-rust 都在戒酒吗 master 等于 origin/master,版本为
npm新增功能,声明匹配当前构建(core、worker、coordinator、protocol、
模拟器)和冒烟测试通过本地副本(dfx start 第一个)。这邮票
tigiSharded.commit 和 tigiDriverRust.commit 成 package.json发布,然后提交
“版本碰撞到”,然后按下。