Conversation
Adds a third provider adapter alongside Linq and Photon: - src/providers/blooio.ts — send GamePigeon balloons (invite or move) and plain text over the Blooio v4 REST API, and parse the `message.received` webhook. Blooio delivers the inbound balloon already decoded, so the webhook's `data.imessage_app.url` is the ready-to-read app-state URL. - index.ts — export `Blooio` and a `fromBlooioWebhook()` helper. - examples/blooio-express-bot.ts — ~25-line Express bot mirroring the Linq one. - test/inbound.test.ts — Blooio receive-path assertions (parse + ignore). - README — list Blooio in the providers table and receive section. Disclosure: I contribute to Blooio. Game logic is identical across providers; this only adds the transport adapter. Co-authored-by: Cursor <cursoragent@cursor.com>
40eb8fc to
7c0e3de
Compare
There was a problem hiding this comment.
All reported issues were addressed
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
|
@davext fix thiese and ill merge |
…trict imessage_app parsing; drop promo copy - examples/blooio-express-bot.ts: verify X-Blooio-Signature (HMAC-SHA256 over `t.rawBody`, express.raw + replay guard) before trusting a webhook; ack 200 before the outbound send so provider retries can't double-fire; admin-gate /start behind ADMIN_TOKEN and validate/URL-encode the E.164 `to`. - src/providers/blooio.ts: only decode genuine `message_type: imessage_app` balloons via `data.imessage_app.url` (+ looksLikeGamePigeon), so a text that merely quotes a GamePigeon URL is ignored. - README.md: make the receive snippet runnable (define `app`/`BOT`) and point to the signed example; drop pricing/sales copy for a neutral note.
|
@time-attack pushed a fix commit ( examples/blooio-express-bot.ts
src/providers/blooio.ts
README.md
|
There was a problem hiding this comment.
All reported issues were addressed across 3 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Fail-closed is kept, but a missing signing secret now logs a loud startup warning and a reason on each rejected /webhook, so a quickstart bot that forgets the env var isn't left running-but-dead with no signal.
|
Pushed |
|
Should be good to go now @time-attack |
There was a problem hiding this comment.
2 issues found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="examples/blooio-express-bot.ts">
<violation number="1" location="examples/blooio-express-bot.ts:29">
P3: The new `console.warn` runs on every rejected POST, but `/webhook` is public and unauthenticated by design, so anyone can spam the endpoint and generate unbounded log output. The surrounding comment says the warning fires "on the first rejected request," but the code logs unconditionally, and in the unset-secret case it repeats a message the startup warning already printed. Throttle the per-request log (e.g., once per interval) or rely on the startup warning alone.</violation>
<violation number="2" location="examples/blooio-express-bot.ts:67">
P3: When a webhook is rejected because its `t=` timestamp is older than 300s, the new log reports 'invalid or missing X-Blooio-Signature' even though the signature is present and valid — only the timestamp expired. Rephrase the message to cover all rejection paths (e.g. 'missing, invalid, or expired signature') or have verifyBlooioSignature return a reason so the log distinguishes the expired-timestamp case.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| // so a bot that forgets this env var would run but never reply. Warn at startup | ||
| // (and again on the first rejected request) instead of dying silently. | ||
| if (!WEBHOOK_SECRET) { | ||
| console.warn( |
There was a problem hiding this comment.
P3: The new console.warn runs on every rejected POST, but /webhook is public and unauthenticated by design, so anyone can spam the endpoint and generate unbounded log output. The surrounding comment says the warning fires "on the first rejected request," but the code logs unconditionally, and in the unset-secret case it repeats a message the startup warning already printed. Throttle the per-request log (e.g., once per interval) or rely on the startup warning alone.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At examples/blooio-express-bot.ts, line 29:
<comment>The new `console.warn` runs on every rejected POST, but `/webhook` is public and unauthenticated by design, so anyone can spam the endpoint and generate unbounded log output. The surrounding comment says the warning fires "on the first rejected request," but the code logs unconditionally, and in the unset-secret case it repeats a message the startup warning already printed. Throttle the per-request log (e.g., once per interval) or rely on the startup warning alone.</comment>
<file context>
@@ -22,6 +22,17 @@ const bloo = new op.Blooio({
+// so a bot that forgets this env var would run but never reply. Warn at startup
+// (and again on the first rejected request) instead of dying silently.
+if (!WEBHOOK_SECRET) {
+ console.warn(
+ "[blooio] BLOOIO_WEBHOOK_SECRET is not set — every /webhook request will be " +
+ "rejected with 401 and the bot will never reply. Set it to your whsec_... " +
</file context>
| if (!verifyBlooioSignature(req.body as Buffer, req.headers["x-blooio-signature"])) { | ||
| console.warn( | ||
| WEBHOOK_SECRET | ||
| ? "[blooio] rejected /webhook: invalid or missing X-Blooio-Signature" |
There was a problem hiding this comment.
P3: When a webhook is rejected because its t= timestamp is older than 300s, the new log reports 'invalid or missing X-Blooio-Signature' even though the signature is present and valid — only the timestamp expired. Rephrase the message to cover all rejection paths (e.g. 'missing, invalid, or expired signature') or have verifyBlooioSignature return a reason so the log distinguishes the expired-timestamp case.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At examples/blooio-express-bot.ts, line 67:
<comment>When a webhook is rejected because its `t=` timestamp is older than 300s, the new log reports 'invalid or missing X-Blooio-Signature' even though the signature is present and valid — only the timestamp expired. Rephrase the message to cover all rejection paths (e.g. 'missing, invalid, or expired signature') or have verifyBlooioSignature return a reason so the log distinguishes the expired-timestamp case.</comment>
<file context>
@@ -51,6 +62,11 @@ app.post(
if (!verifyBlooioSignature(req.body as Buffer, req.headers["x-blooio-signature"])) {
+ console.warn(
+ WEBHOOK_SECRET
+ ? "[blooio] rejected /webhook: invalid or missing X-Blooio-Signature"
+ : "[blooio] rejected /webhook: BLOOIO_WEBHOOK_SECRET is unset (see startup warning)",
+ );
</file context>
| ? "[blooio] rejected /webhook: invalid or missing X-Blooio-Signature" | |
| ? "[blooio] rejected /webhook: X-Blooio-Signature missing, invalid, or expired (t > 300s old)" |
Summary
Adds a third provider adapter alongside Linq and Photon so an OpenPigeon bot can run over Blooio's iMessage API. The game codec is untouched — this is purely a transport adapter, and the same
read→replylogic works across all three providers.src/providers/blooio.ts—Blooio.send(url, { to })posts a GamePigeon balloon (invite or move) to the Blooio v4 API (POST /v4/channels/{number}/messageswith theimessage_appcontent type), plussendText().Blooio.fromWebhook(body)parses themessage.receivedwebhook.src/index.ts— exportsBlooioand afromBlooioWebhook()helper (mirrorsfromWebhook/fromPhotonMessage).examples/blooio-express-bot.ts— ~25-line Express bot, mirroringlinq-express-bot.ts.test/inbound.test.ts— Blooio receive-path assertions (parse a real captured move wrapped in Blooio's webhook shape; ignore non-GamePigeon).Why it's a natural fit
Blooio delivers the inbound app balloon already decoded, so the webhook's
data.imessage_app.urlis the ready-to-read app-state URL — your bot never decodes anything Apple-specific. Balloons render natively on the recipient's GamePigeon, and onboarding is self-serve (provision a number from the API/dashboard, no sales call), which keeps the "install → first 8-Ball invite in one sitting" story true for the quickstart.Test plan
npm test— all codecs OK; 16 inbound assertions pass (incl. newblooio:cases)tscerrors; the one pre-existingrequirediagnostic ininvite.tsis unrelated)invite("pool")URL sent via Blooio renders as a playable 8-Ball inviteDisclosure
I contribute to Blooio. Happy to adjust wording/placement — the goal is parity with the existing providers, not promotion.