Skip to content
Merged
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
8 changes: 8 additions & 0 deletions .changeset/crawler-name-beside-the-boolean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
---

Return the crawler's name beside `isCrawler` instead of inside it.

The ipapi.is worker returned the crawler's name — "GoogleBot", "BaiduSpider" — through a field called `is_crawler` that every consumer declared a boolean, so checking it for `true` matched nothing. The reputation service that replaced it answers a real boolean and a separate name, so `isCrawler` is now genuinely a boolean and the name is in `crawlerName`.

Nothing that makes a decision changes: a name and `true` are both truthy, which is how every caller already reads it. What comes back is the ability to say which crawler was seen.
1 change: 1 addition & 0 deletions packages/ipinfo/src/backends/ipapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export class IpapiBackend {
isMobile: data.is_mobile,
isSatellite: data.is_satellite,
isCrawler: data.is_crawler,
crawlerName: data.crawler,
providerName: data.company?.name || data.datacenter?.datacenter,
providerType: data.company?.type || data.asn?.type,
asnNumber: data.asn?.asn,
Expand Down
37 changes: 37 additions & 0 deletions packages/ipinfo/src/tests/ipapi.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,43 @@ describe("IpapiBackend.lookup responses", () => {
});
});

describe("the crawler name", () => {
// The worker this replaced returned the crawler's name through
// `is_crawler`, a field typed boolean, so `isCrawler === true` matched
// nothing and the name had nowhere to live. Ours answers both separately.
it("keeps the boolean and the name apart", async () => {
const result = expectValid(
await backend(
respondWith(baseResponse({ is_crawler: true, crawler: "GoogleBot" })),
).lookup(IP),
);

expect(result.isCrawler).toBe(true);
expect(result.crawlerName).toBe("GoogleBot");
});

// Some ranges are flagged without a name.
it("reports a crawler with no name as a crawler", async () => {
const result = expectValid(
await backend(respondWith(baseResponse({ is_crawler: true }))).lookup(IP),
);

expect(result.isCrawler).toBe(true);
expect(result.crawlerName).toBeUndefined();
});

it("leaves a non-crawler false and unnamed", async () => {
const result = expectValid(
await backend(respondWith(baseResponse({ is_crawler: false }))).lookup(
IP,
),
);

expect(result.isCrawler).toBe(false);
expect(result.crawlerName).toBeUndefined();
});
});

describe("IpapiBackend.lookup timeout", () => {
it("gives up on a hanging upstream and says so", async () => {
// An IP lookup sits in the request path: a backend that never answers
Expand Down
11 changes: 11 additions & 0 deletions packages/types/src/api/ipapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,15 @@ export interface IPApiResponse {
is_bogon: boolean;
is_mobile: boolean;
is_satellite: boolean;
/**
* A real boolean, unlike the ipapi.is worker this replaced: that returned
* the crawler's NAME through this field — "GoogleBot", "BaiduSpider" —
* while every consumer typed it `boolean`, so `=== true` matched nothing.
* The name is in `crawler`.
*/
is_crawler: boolean;
/** The declared crawler's name, present exactly when `is_crawler`. */
crawler?: string;
is_datacenter: boolean;
is_tor: boolean;
is_proxy: boolean;
Expand Down Expand Up @@ -135,6 +143,9 @@ export interface IPInfoResult {
isMobile: boolean;
isSatellite: boolean;
isCrawler: boolean;
/** The declared crawler's name where one is known: "GoogleBot". Set from
* the sidecar's `crawler`, or from the vendor's misnamed `is_crawler`. */
crawlerName?: string;

// Provider information
providerName?: string;
Expand Down
Loading