Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"node": ">=18.17"
},
"scripts": {
"generate": "PATH=./node_modules/.bin:$PATH buf generate",
"generate": "PATH=./node_modules/.bin:$PATH buf generate && bun run scripts/soften-int64.mjs",
"generate:http": "openapi-ts -i gen/openapi/imessage.swagger.json -o packages/http/src/generated/http -c @hey-api/client-fetch",
"verify:codegen": "bun run generate && bun run generate:http && git diff --exit-code -- gen packages/core/src/generated packages/http/src/generated",
"build": "bun run generate && bun run --cwd packages/advanced-imessage build",
Expand Down
9 changes: 1 addition & 8 deletions packages/core/src/generated/google/protobuf/descriptor.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 1 addition & 8 deletions packages/core/src/generated/google/protobuf/timestamp.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 44 additions & 0 deletions packages/core/tests/long-to-number.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { describe, expect, it } from "bun:test";
import { Timestamp } from "../src/generated/google/protobuf/timestamp.ts";
import { SubscribePollEventsResponse } from "../src/generated/photon/imessage/v1/poll_service.ts";

// Far past Number.MAX_SAFE_INTEGER (2^53 - 1). ts-proto types these fields as
// `number`, but the wire format can carry the full signed/unsigned 64-bit
// range, so the cast is required to construct a regression fixture for #23.
const OVERSIZED_UINT64 = Number("18446744073709551615"); // 2^64 - 1 (max uint64)
const OVERSIZED_INT64 = Number("9223372036854775807"); // 2^63 - 1 (max int64)

describe("longToNumber overflow handling (issue #23)", () => {
it("decodes a top-level oversized uint64 sequence without throwing", () => {
const bytes = SubscribePollEventsResponse.encode(
SubscribePollEventsResponse.create({
sequence: OVERSIZED_UINT64,
pollChanged: {
chatGuid: "iMessage;-;+15551234567",
pollMessageGuid: "poll-guid",
occurredAt: new Date("2026-07-16T00:00:00Z"),
isFromMe: false,
created: { title: "Lunch?", options: [] },
},
})
).finish();

const decoded = SubscribePollEventsResponse.decode(bytes);

expect(decoded.pollChanged?.created?.title).toBe("Lunch?");
expect(typeof decoded.sequence).toBe("number");
expect(Number.isFinite(decoded.sequence)).toBe(true);
});

it("decodes a nested Timestamp.seconds oversized past MAX_SAFE_INTEGER without throwing", () => {
const bytes = Timestamp.encode(
Timestamp.create({ seconds: OVERSIZED_INT64, nanos: 0 })
).finish();

expect(() => Timestamp.decode(bytes)).not.toThrow();

const decoded = Timestamp.decode(bytes);
expect(typeof decoded.seconds).toBe("number");
expect(Number.isFinite(decoded.seconds)).toBe(true);
});
});
59 changes: 59 additions & 0 deletions scripts/soften-int64.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env node
import { readdirSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";

const ROOT = "packages/core/src/generated";

function walk(dir) {
const files = [];
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const path = join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...walk(path));
} else if (entry.name.endsWith(".ts")) {
files.push(path);
}
}
return files;
}

const THROWING_BODY = `function longToNumber(int64: { toString(): string }): number {
const num = globalThis.Number(int64.toString());
if (num > globalThis.Number.MAX_SAFE_INTEGER) {
throw new globalThis.Error("Value is larger than Number.MAX_SAFE_INTEGER");
}
if (num < globalThis.Number.MIN_SAFE_INTEGER) {
throw new globalThis.Error("Value is smaller than Number.MIN_SAFE_INTEGER");
}
return num;
}`;

const SOFTENED_BODY = `function longToNumber(int64: { toString(): string }): number {
return globalThis.Number(int64.toString());
}`;

const files = walk(ROOT);
let patched = 0;
let alreadySoft = 0;

for (const file of files) {
const contents = readFileSync(file, "utf8");
if (contents.includes(THROWING_BODY)) {
writeFileSync(file, contents.replace(THROWING_BODY, SOFTENED_BODY));
patched++;
} else if (contents.includes("function longToNumber")) {
alreadySoft++;
}
}

if (patched === 0 && alreadySoft === 0) {
throw new Error(
"soften-int64: found no longToNumber helper under " +
ROOT +
" — ts-proto's emitted shape may have changed; update THROWING_BODY."
);
}

console.log(
`soften-int64: patched ${patched} file(s), ${alreadySoft} already softened`
);