doordash-cli 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +1 -1
- package/dist/cli.js +1 -1
- package/dist/direct-api.d.ts +7 -0
- package/dist/direct-api.js +56 -17
- package/dist/direct-api.test.js +20 -1
- package/docs/install.md +1 -1
- package/man/dd-cli.1 +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,12 @@ All notable changes to `doordash-cli` will be documented in this file.
|
|
|
9
9
|
|
|
10
10
|
See [docs/releasing.md](docs/releasing.md) for the maintainer release flow.
|
|
11
11
|
|
|
12
|
+
## [0.3.1](https://github.com/LatencyTDH/doordash-cli/compare/v0.3.0...v0.3.1) (2026-03-11)
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* keep managed browser session imports quiet ([#28](https://github.com/LatencyTDH/doordash-cli/issues/28)) ([dc25e04](https://github.com/LatencyTDH/doordash-cli/commit/dc25e0444ca63c7af1c2feaa2f32ede54b1c1f98))
|
|
17
|
+
|
|
12
18
|
## [0.3.0](https://github.com/LatencyTDH/doordash-cli/compare/v0.2.0...v0.3.0) (2026-03-11)
|
|
13
19
|
|
|
14
20
|
### Features
|
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ doordash-cli search --query sushi
|
|
|
65
65
|
|
|
66
66
|
If you are running from a checkout without `npm link`, replace `doordash-cli` with `npm run cli --`.
|
|
67
67
|
|
|
68
|
-
If you already have a compatible signed-in DoorDash browser session available, `auth-check` may reuse it instead of asking you to sign in again.
|
|
68
|
+
If you already have a compatible signed-in DoorDash managed-browser session available, `auth-check` may quietly reuse it instead of asking you to sign in again. Routine direct commands stay quiet; use `login` when you want explicit browser interaction.
|
|
69
69
|
|
|
70
70
|
## Command surface
|
|
71
71
|
|
package/dist/cli.js
CHANGED
|
@@ -44,7 +44,7 @@ export function usage() {
|
|
|
44
44
|
" - Manual pages ship with the project: man dd-cli or man doordash-cli.",
|
|
45
45
|
" - Direct GraphQL/HTTP is the default path for auth-check, set-address, search, menu, item, orders, order, cart, add-to-cart, and update-cart.",
|
|
46
46
|
" - login launches Chromium for a one-time manual sign-in flow and saves reusable state for later direct API calls.",
|
|
47
|
-
" - auth-check can reuse an already-signed-in compatible DoorDash
|
|
47
|
+
" - auth-check can quietly reuse an already-signed-in compatible managed-browser DoorDash session when one is available; use login for explicit browser interaction.",
|
|
48
48
|
" - set-address now uses DoorDash autocomplete/get-or-create plus addConsumerAddressV2 for brand-new address enrollment when needed.",
|
|
49
49
|
" - configurable items require explicit --options-json selections.",
|
|
50
50
|
' - standalone recommended add-ons that open a nested cursor step are supported via children, e.g. [{"groupId":"recommended_option_546935995","optionId":"546936011","children":[{"groupId":"780057412","optionId":"4702669757"}]}].',
|
package/dist/direct-api.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Cookie } from "playwright";
|
|
1
2
|
export type AuthResult = {
|
|
2
3
|
success: true;
|
|
3
4
|
isLoggedIn: boolean;
|
|
@@ -521,6 +522,12 @@ export declare function resolveAvailableAddressMatch(input: {
|
|
|
521
522
|
printableAddress: string | null;
|
|
522
523
|
source: SetAddressResult["matchedAddressSource"];
|
|
523
524
|
} | null;
|
|
525
|
+
export declare function isDoorDashUrl(value: string): boolean;
|
|
526
|
+
export declare function hasDoorDashCookies(cookies: ReadonlyArray<Pick<Cookie, "domain">>): boolean;
|
|
527
|
+
export declare function selectManagedBrowserImportMode(input: {
|
|
528
|
+
pageUrls: readonly string[];
|
|
529
|
+
cookies: ReadonlyArray<Pick<Cookie, "domain">>;
|
|
530
|
+
}): "page" | "cookies" | "skip";
|
|
524
531
|
export declare function parseSearchRestaurants(body: unknown[]): SearchRestaurantResult[];
|
|
525
532
|
export declare function parseSearchRestaurantRow(entry: unknown): SearchRestaurantResult | null;
|
|
526
533
|
export declare function parseExistingOrderLifecycleStatus(orderRoot: unknown): ExistingOrderLifecycleStatus;
|
package/dist/direct-api.js
CHANGED
|
@@ -808,6 +808,9 @@ class DoorDashDirectSession {
|
|
|
808
808
|
return;
|
|
809
809
|
}
|
|
810
810
|
this.attemptedManagedImport = true;
|
|
811
|
+
if (await hasPersistedSessionArtifacts()) {
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
811
814
|
await importManagedBrowserSessionIfAvailable().catch(() => { });
|
|
812
815
|
}
|
|
813
816
|
}
|
|
@@ -1535,39 +1538,69 @@ function normalizeAddressText(value) {
|
|
|
1535
1538
|
.replace(/[.,]/g, "")
|
|
1536
1539
|
.replace(/\s+/g, " ");
|
|
1537
1540
|
}
|
|
1541
|
+
export function isDoorDashUrl(value) {
|
|
1542
|
+
try {
|
|
1543
|
+
const url = new URL(value);
|
|
1544
|
+
return url.hostname === "doordash.com" || url.hostname.endsWith(".doordash.com");
|
|
1545
|
+
}
|
|
1546
|
+
catch {
|
|
1547
|
+
return false;
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
export function hasDoorDashCookies(cookies) {
|
|
1551
|
+
return cookies.some((cookie) => {
|
|
1552
|
+
const domain = cookie.domain.trim().replace(/^\./, "").toLowerCase();
|
|
1553
|
+
return domain === "doordash.com" || domain.endsWith(".doordash.com");
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
export function selectManagedBrowserImportMode(input) {
|
|
1557
|
+
if (input.pageUrls.some((url) => isDoorDashUrl(url))) {
|
|
1558
|
+
return "page";
|
|
1559
|
+
}
|
|
1560
|
+
if (hasDoorDashCookies(input.cookies)) {
|
|
1561
|
+
return "cookies";
|
|
1562
|
+
}
|
|
1563
|
+
return "skip";
|
|
1564
|
+
}
|
|
1538
1565
|
async function importManagedBrowserSessionIfAvailable() {
|
|
1539
1566
|
for (const cdpUrl of await getManagedBrowserCdpCandidates()) {
|
|
1540
1567
|
if (!(await isCdpEndpointReachable(cdpUrl))) {
|
|
1541
1568
|
continue;
|
|
1542
1569
|
}
|
|
1543
1570
|
let browser = null;
|
|
1544
|
-
let tempPage = null;
|
|
1545
1571
|
try {
|
|
1546
1572
|
browser = await chromium.connectOverCDP(cdpUrl);
|
|
1547
1573
|
const context = browser.contexts()[0];
|
|
1548
1574
|
if (!context) {
|
|
1549
1575
|
continue;
|
|
1550
1576
|
}
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
}
|
|
1558
|
-
|
|
1559
|
-
const consumer = consumerData.consumer ?? null;
|
|
1560
|
-
if (!consumer || consumer.isGuest !== false) {
|
|
1577
|
+
const cookies = await context.cookies();
|
|
1578
|
+
const pages = context.pages();
|
|
1579
|
+
const reusablePage = pages.find((candidate) => isDoorDashUrl(candidate.url())) ?? null;
|
|
1580
|
+
const importMode = selectManagedBrowserImportMode({
|
|
1581
|
+
pageUrls: reusablePage ? [reusablePage.url()] : [],
|
|
1582
|
+
cookies,
|
|
1583
|
+
});
|
|
1584
|
+
if (importMode === "skip") {
|
|
1561
1585
|
continue;
|
|
1562
1586
|
}
|
|
1563
|
-
|
|
1564
|
-
|
|
1587
|
+
if (reusablePage) {
|
|
1588
|
+
const consumerData = await fetchConsumerViaPage(reusablePage).catch(() => null);
|
|
1589
|
+
const consumer = consumerData?.consumer ?? null;
|
|
1590
|
+
if (consumer?.isGuest === false) {
|
|
1591
|
+
await saveContextState(context, cookies);
|
|
1592
|
+
return true;
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
if (hasDoorDashCookies(cookies)) {
|
|
1596
|
+
await saveContextState(context, cookies);
|
|
1597
|
+
return true;
|
|
1598
|
+
}
|
|
1565
1599
|
}
|
|
1566
1600
|
catch {
|
|
1567
1601
|
continue;
|
|
1568
1602
|
}
|
|
1569
1603
|
finally {
|
|
1570
|
-
await tempPage?.close().catch(() => { });
|
|
1571
1604
|
await browser?.close().catch(() => { });
|
|
1572
1605
|
}
|
|
1573
1606
|
}
|
|
@@ -1588,12 +1621,12 @@ async function fetchConsumerViaPage(page) {
|
|
|
1588
1621
|
});
|
|
1589
1622
|
return parseGraphQlResponse("managedBrowserConsumerImport", raw.status, raw.text);
|
|
1590
1623
|
}
|
|
1591
|
-
async function saveContextState(context) {
|
|
1624
|
+
async function saveContextState(context, cookies = null) {
|
|
1592
1625
|
const storageStatePath = getStorageStatePath();
|
|
1593
1626
|
await ensureConfigDir();
|
|
1594
1627
|
await context.storageState({ path: storageStatePath });
|
|
1595
|
-
const
|
|
1596
|
-
await writeFile(getCookiesPath(), JSON.stringify(
|
|
1628
|
+
const resolvedCookies = cookies ?? (await context.cookies());
|
|
1629
|
+
await writeFile(getCookiesPath(), JSON.stringify(resolvedCookies, null, 2));
|
|
1597
1630
|
}
|
|
1598
1631
|
async function getManagedBrowserCdpCandidates() {
|
|
1599
1632
|
const candidates = new Set();
|
|
@@ -2282,6 +2315,12 @@ async function hasStorageState() {
|
|
|
2282
2315
|
return false;
|
|
2283
2316
|
}
|
|
2284
2317
|
}
|
|
2318
|
+
async function hasPersistedSessionArtifacts() {
|
|
2319
|
+
if (await hasStorageState()) {
|
|
2320
|
+
return true;
|
|
2321
|
+
}
|
|
2322
|
+
return (await readStoredCookies()).length > 0;
|
|
2323
|
+
}
|
|
2285
2324
|
async function readStoredCookies() {
|
|
2286
2325
|
try {
|
|
2287
2326
|
const raw = await readFile(getCookiesPath(), "utf8");
|
package/dist/direct-api.test.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
-
import { buildAddConsumerAddressPayload, buildAddToCartPayload, buildUpdateCartPayload, extractExistingOrdersFromApolloCache, normalizeItemName, parseExistingOrderLifecycleStatus, parseExistingOrdersResponse, parseOptionSelectionsJson, parseSearchRestaurantRow, resolveAvailableAddressMatch, } from "./direct-api.js";
|
|
3
|
+
import { buildAddConsumerAddressPayload, buildAddToCartPayload, buildUpdateCartPayload, extractExistingOrdersFromApolloCache, hasDoorDashCookies, normalizeItemName, parseExistingOrderLifecycleStatus, parseExistingOrdersResponse, parseOptionSelectionsJson, parseSearchRestaurantRow, resolveAvailableAddressMatch, selectManagedBrowserImportMode, } from "./direct-api.js";
|
|
4
4
|
function configurableItemDetail() {
|
|
5
5
|
return {
|
|
6
6
|
success: true,
|
|
@@ -144,6 +144,25 @@ test("parseSearchRestaurantRow extracts restaurant metadata from facet rows", ()
|
|
|
144
144
|
url: "https://www.doordash.com/store/24633898/?pickup=false",
|
|
145
145
|
});
|
|
146
146
|
});
|
|
147
|
+
test("managed browser import falls back to cookie reuse without opening a DoorDash page", () => {
|
|
148
|
+
const cookies = [{ domain: ".doordash.com" }];
|
|
149
|
+
assert.equal(hasDoorDashCookies(cookies), true);
|
|
150
|
+
assert.equal(selectManagedBrowserImportMode({ pageUrls: [], cookies }), "cookies");
|
|
151
|
+
});
|
|
152
|
+
test("managed browser import prefers an existing DoorDash page when one is already open", () => {
|
|
153
|
+
const cookies = [{ domain: ".doordash.com" }];
|
|
154
|
+
assert.equal(selectManagedBrowserImportMode({
|
|
155
|
+
pageUrls: ["https://github.com/LatencyTDH/doordash-cli/pulls", "https://www.doordash.com/home"],
|
|
156
|
+
cookies,
|
|
157
|
+
}), "page");
|
|
158
|
+
});
|
|
159
|
+
test("managed browser import skips unrelated browser contexts", () => {
|
|
160
|
+
assert.equal(hasDoorDashCookies([{ domain: ".github.com" }]), false);
|
|
161
|
+
assert.equal(selectManagedBrowserImportMode({
|
|
162
|
+
pageUrls: ["https://github.com/LatencyTDH/doordash-cli"],
|
|
163
|
+
cookies: [{ domain: ".github.com" }],
|
|
164
|
+
}), "skip");
|
|
165
|
+
});
|
|
147
166
|
test("parseOptionSelectionsJson parses structured recursive option selections", () => {
|
|
148
167
|
assert.deepEqual(parseOptionSelectionsJson('[{"groupId":"703393388","optionId":"4716032529"},{"groupId":"recommended_option_546935995","optionId":"546936011","children":[{"groupId":"780057412","optionId":"4702669757","quantity":2}]}]'), [
|
|
149
168
|
{ groupId: "703393388", optionId: "4716032529" },
|
package/docs/install.md
CHANGED
|
@@ -58,6 +58,6 @@ doordash-cli search --query sushi
|
|
|
58
58
|
|
|
59
59
|
## Session reuse
|
|
60
60
|
|
|
61
|
-
If you already have a compatible signed-in DoorDash browser session available, direct commands may reuse it instead of opening a
|
|
61
|
+
If you already have a compatible signed-in DoorDash managed-browser session available, direct commands may quietly reuse it instead of opening or navigating a DoorDash tab.
|
|
62
62
|
|
|
63
63
|
If not, run `doordash-cli login` once to save reusable state for later commands.
|
package/man/dd-cli.1
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH DD-CLI 1 "2026-03-11" "doordash-cli 0.
|
|
1
|
+
.TH DD-CLI 1 "2026-03-11" "doordash-cli 0.3.0" "User Commands"
|
|
2
2
|
.SH NAME
|
|
3
3
|
dd-cli, doordash-cli \- unofficial cart-safe DoorDash CLI for browsing, read-only existing-order inspection, and cart management
|
|
4
4
|
.SH SYNOPSIS
|
|
@@ -34,8 +34,8 @@ Install the matching Playwright Chromium build used by this package.
|
|
|
34
34
|
.TP
|
|
35
35
|
.B auth-check
|
|
36
36
|
Verify whether the saved session appears authenticated. This command can also
|
|
37
|
-
|
|
38
|
-
usable CDP endpoint is available.
|
|
37
|
+
quietly reuse an already-signed-in compatible managed-browser DoorDash session
|
|
38
|
+
when a usable CDP endpoint is available.
|
|
39
39
|
.TP
|
|
40
40
|
.B login
|
|
41
41
|
Launch Chromium for a one-time manual sign-in flow, then save reusable browser
|
package/package.json
CHANGED