doordash-cli 0.2.0

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.
@@ -0,0 +1,2297 @@
1
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { createInterface } from "node:readline/promises";
5
+ import { stdin as input, stdout as output } from "node:process";
6
+ import { chromium } from "playwright";
7
+ import { getCookiesPath } from "@striderlabs/mcp-doordash/dist/auth.js";
8
+ const BASE_URL = "https://www.doordash.com";
9
+ const DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36";
10
+ const GRAPHQL_HEADERS = {
11
+ accept: "*/*",
12
+ "content-type": "application/json",
13
+ "x-channel-id": "marketplace",
14
+ "x-experience-id": "doordash",
15
+ "apollographql-client-name": "@doordash/app-consumer-production-ssr-client",
16
+ "apollographql-client-version": "3.0",
17
+ };
18
+ const CONSUMER_QUERY = `query consumer {
19
+ consumer {
20
+ id
21
+ userId
22
+ firstName
23
+ lastName
24
+ email
25
+ isGuest
26
+ marketId
27
+ defaultAddress {
28
+ printableAddress
29
+ zipCode
30
+ submarketId
31
+ }
32
+ }
33
+ }`;
34
+ const SEARCH_QUERY = `query searchWithFilterFacetFeed(
35
+ $query: String!
36
+ $cursor: String
37
+ $filterQuery: String
38
+ $isDebug: Boolean
39
+ $fromFilterChange: Boolean
40
+ $serializedBundleGlobalSearchContext: String
41
+ $address: String
42
+ $searchType: String
43
+ ) {
44
+ searchWithFilterFacetFeed(
45
+ query: $query
46
+ cursor: $cursor
47
+ filterQuery: $filterQuery
48
+ isDebug: $isDebug
49
+ fromFilterChange: $fromFilterChange
50
+ serializedBundleGlobalSearchContext: $serializedBundleGlobalSearchContext
51
+ address: $address
52
+ searchType: $searchType
53
+ ) {
54
+ body {
55
+ id
56
+ header {
57
+ id
58
+ name
59
+ text {
60
+ title
61
+ subtitle
62
+ description
63
+ accessory
64
+ custom {
65
+ key
66
+ value
67
+ }
68
+ }
69
+ }
70
+ body {
71
+ id
72
+ name
73
+ text {
74
+ title
75
+ subtitle
76
+ description
77
+ accessory
78
+ custom {
79
+ key
80
+ value
81
+ }
82
+ }
83
+ images {
84
+ main {
85
+ uri
86
+ }
87
+ }
88
+ events {
89
+ click {
90
+ name
91
+ data
92
+ }
93
+ }
94
+ childrenMap {
95
+ id
96
+ name
97
+ text {
98
+ title
99
+ subtitle
100
+ description
101
+ accessory
102
+ custom {
103
+ key
104
+ value
105
+ }
106
+ }
107
+ images {
108
+ main {
109
+ uri
110
+ }
111
+ }
112
+ events {
113
+ click {
114
+ name
115
+ data
116
+ }
117
+ }
118
+ component {
119
+ id
120
+ category
121
+ }
122
+ }
123
+ component {
124
+ id
125
+ category
126
+ }
127
+ }
128
+ }
129
+ }
130
+ }`;
131
+ const STOREPAGE_QUERY = `query storepageFeed(
132
+ $storeId: ID!
133
+ $menuId: ID
134
+ $isMerchantPreview: Boolean
135
+ $fulfillmentType: FulfillmentType
136
+ $cursor: String
137
+ $menuSurfaceArea: MenuSurfaceArea
138
+ $scheduledTime: String
139
+ $scheduledMinTimeUtc: String
140
+ $scheduledMaxTimeUtc: String
141
+ $entryPoint: StoreEntryPoint
142
+ $DMGroups: [DMGroup]
143
+ ) {
144
+ storepageFeed(
145
+ storeId: $storeId
146
+ menuId: $menuId
147
+ isMerchantPreview: $isMerchantPreview
148
+ fulfillmentType: $fulfillmentType
149
+ cursor: $cursor
150
+ menuSurfaceArea: $menuSurfaceArea
151
+ scheduledTime: $scheduledTime
152
+ scheduledMinTimeUtc: $scheduledMinTimeUtc
153
+ scheduledMaxTimeUtc: $scheduledMaxTimeUtc
154
+ entryPoint: $entryPoint
155
+ DMGroups: $DMGroups
156
+ ) {
157
+ storeHeader {
158
+ id
159
+ name
160
+ description
161
+ business {
162
+ id
163
+ name
164
+ }
165
+ address {
166
+ displayAddress
167
+ }
168
+ ratings {
169
+ averageRating
170
+ numRatingsDisplayString
171
+ }
172
+ coverImgUrl
173
+ }
174
+ menuBook {
175
+ id
176
+ name
177
+ menuCategories {
178
+ id
179
+ name
180
+ numItems
181
+ next {
182
+ anchor
183
+ cursor
184
+ }
185
+ }
186
+ }
187
+ itemLists {
188
+ id
189
+ name
190
+ description
191
+ items {
192
+ id
193
+ name
194
+ description
195
+ displayPrice
196
+ imageUrl
197
+ nextCursor
198
+ storeId
199
+ }
200
+ itemCategoryTabs {
201
+ id
202
+ name
203
+ items {
204
+ id
205
+ name
206
+ displayPrice
207
+ nextCursor
208
+ storeId
209
+ }
210
+ }
211
+ }
212
+ }
213
+ }`;
214
+ const ITEM_QUERY = `query itemPage(
215
+ $storeId: ID!
216
+ $itemId: ID!
217
+ $consumerId: ID
218
+ $isMerchantPreview: Boolean
219
+ $isNested: Boolean!
220
+ $fulfillmentType: FulfillmentType
221
+ $cursorContext: ItemPageCursorContextInput
222
+ $scheduledMinTimeUtc: String
223
+ $scheduledMaxTimeUtc: String
224
+ ) {
225
+ itemPage(
226
+ storeId: $storeId
227
+ itemId: $itemId
228
+ consumerId: $consumerId
229
+ isMerchantPreview: $isMerchantPreview
230
+ fulfillmentType: $fulfillmentType
231
+ cursorContext: $cursorContext
232
+ scheduledMinTimeUtc: $scheduledMinTimeUtc
233
+ scheduledMaxTimeUtc: $scheduledMaxTimeUtc
234
+ ) {
235
+ itemHeader @skip(if: $isNested) {
236
+ id
237
+ name
238
+ description
239
+ displayString
240
+ unitAmount
241
+ currency
242
+ decimalPlaces
243
+ menuId
244
+ specialInstructionsMaxLength
245
+ dietaryTagsList {
246
+ type
247
+ abbreviatedTagDisplayString
248
+ fullTagDisplayString
249
+ }
250
+ reviewData {
251
+ ratingDisplayString
252
+ reviewCount
253
+ itemReviewRankingCount
254
+ }
255
+ }
256
+ optionLists {
257
+ id
258
+ name
259
+ subtitle
260
+ minNumOptions
261
+ maxNumOptions
262
+ numFreeOptions
263
+ isOptional
264
+ options {
265
+ id
266
+ name
267
+ displayString
268
+ unitAmount
269
+ defaultQuantity
270
+ nextCursor
271
+ }
272
+ }
273
+ itemPreferences {
274
+ id
275
+ title
276
+ specialInstructions {
277
+ title
278
+ characterMaxLength
279
+ isEnabled
280
+ placeholderText
281
+ }
282
+ substitutionPreferences {
283
+ title
284
+ substitutionPreferencesList {
285
+ id
286
+ displayString
287
+ isDefault
288
+ value
289
+ }
290
+ }
291
+ }
292
+ }
293
+ }`;
294
+ const GET_AVAILABLE_ADDRESSES_QUERY = `query getAvailableAddresses {
295
+ getAvailableAddresses {
296
+ id
297
+ addressId
298
+ street
299
+ city
300
+ subpremise
301
+ state
302
+ zipCode
303
+ country
304
+ countryCode
305
+ lat
306
+ lng
307
+ districtId
308
+ manualLat
309
+ manualLng
310
+ timezone
311
+ shortname
312
+ printableAddress
313
+ driverInstructions
314
+ buildingName
315
+ entryCode
316
+ addressLinkType
317
+ formattedAddressSegmentedList
318
+ formattedAddressSegmentedNonUserEditableFieldsList
319
+ personalAddressLabel {
320
+ labelIcon
321
+ labelName
322
+ }
323
+ dropoffPreferences {
324
+ allPreferences {
325
+ optionId
326
+ isDefault
327
+ instructions
328
+ }
329
+ }
330
+ }
331
+ }`;
332
+ const ADD_CONSUMER_ADDRESS_MUTATION = `mutation addConsumerAddressV2(
333
+ $lat: Float!
334
+ $lng: Float!
335
+ $city: String!
336
+ $state: String!
337
+ $zipCode: String!
338
+ $printableAddress: String!
339
+ $shortname: String!
340
+ $googlePlaceId: String!
341
+ $subpremise: String
342
+ $driverInstructions: String
343
+ $dropoffOptionId: String
344
+ $manualLat: Float
345
+ $manualLng: Float
346
+ $addressLinkType: AddressLinkType
347
+ $buildingName: String
348
+ $entryCode: String
349
+ $personalAddressLabel: PersonalAddressLabelInput
350
+ $addressId: String
351
+ ) {
352
+ addConsumerAddressV2(
353
+ lat: $lat
354
+ lng: $lng
355
+ city: $city
356
+ state: $state
357
+ zipCode: $zipCode
358
+ printableAddress: $printableAddress
359
+ shortname: $shortname
360
+ googlePlaceId: $googlePlaceId
361
+ subpremise: $subpremise
362
+ driverInstructions: $driverInstructions
363
+ dropoffOptionId: $dropoffOptionId
364
+ manualLat: $manualLat
365
+ manualLng: $manualLng
366
+ addressLinkType: $addressLinkType
367
+ buildingName: $buildingName
368
+ entryCode: $entryCode
369
+ personalAddressLabel: $personalAddressLabel
370
+ addressId: $addressId
371
+ ) {
372
+ defaultAddress {
373
+ id
374
+ addressId
375
+ printableAddress
376
+ shortname
377
+ zipCode
378
+ submarketId
379
+ }
380
+ availableAddresses {
381
+ id
382
+ addressId
383
+ printableAddress
384
+ shortname
385
+ }
386
+ }
387
+ }`;
388
+ const UPDATE_CONSUMER_DEFAULT_ADDRESS_MUTATION = `mutation updateConsumerDefaultAddressV2($defaultAddressId: ID!) {
389
+ updateConsumerDefaultAddressV2(defaultAddressId: $defaultAddressId) {
390
+ defaultAddress {
391
+ id
392
+ addressId
393
+ printableAddress
394
+ shortname
395
+ zipCode
396
+ submarketId
397
+ }
398
+ availableAddresses {
399
+ id
400
+ addressId
401
+ printableAddress
402
+ shortname
403
+ }
404
+ orderCart {
405
+ id
406
+ }
407
+ }
408
+ }`;
409
+ const CURRENT_CART_QUERY = `query consumerOrderCart {
410
+ consumerOrderCart {
411
+ id
412
+ subtotal
413
+ total
414
+ currencyCode
415
+ restaurant {
416
+ id
417
+ name
418
+ slug
419
+ business {
420
+ id
421
+ name
422
+ }
423
+ }
424
+ menu {
425
+ id
426
+ name
427
+ }
428
+ orders {
429
+ id
430
+ orderItems {
431
+ id
432
+ quantity
433
+ specialInstructions
434
+ priceDisplayString
435
+ singlePrice
436
+ priceOfTotalQuantity
437
+ cartItemStatusType
438
+ item {
439
+ id
440
+ name
441
+ storeId
442
+ }
443
+ options {
444
+ id
445
+ name
446
+ quantity
447
+ }
448
+ }
449
+ }
450
+ }
451
+ }`;
452
+ const EXISTING_ORDERS_QUERY = `query getConsumerOrdersWithDetails($offset: Int!, $limit: Int!, $includeCancelled: Boolean) {
453
+ getConsumerOrdersWithDetails(offset: $offset, limit: $limit, includeCancelled: $includeCancelled) {
454
+ id
455
+ orderUuid
456
+ deliveryUuid
457
+ createdAt
458
+ submittedAt
459
+ cancelledAt
460
+ fulfilledAt
461
+ specialInstructions
462
+ isReorderable
463
+ isGift
464
+ isPickup
465
+ isRetail
466
+ isMerchantShipping
467
+ containsAlcohol
468
+ fulfillmentType
469
+ shoppingProtocol
470
+ orderFilterType
471
+ pollingInterval
472
+ creator {
473
+ id
474
+ firstName
475
+ lastName
476
+ }
477
+ deliveryAddress {
478
+ id
479
+ formattedAddress
480
+ }
481
+ orders {
482
+ id
483
+ items {
484
+ id
485
+ name
486
+ quantity
487
+ specialInstructions
488
+ substitutionPreferences
489
+ originalItemPrice
490
+ purchaseType
491
+ purchaseQuantity {
492
+ continuousQuantity {
493
+ quantity
494
+ unit
495
+ }
496
+ discreteQuantity {
497
+ quantity
498
+ unit
499
+ }
500
+ }
501
+ fulfillQuantity {
502
+ continuousQuantity {
503
+ quantity
504
+ unit
505
+ }
506
+ discreteQuantity {
507
+ quantity
508
+ unit
509
+ }
510
+ }
511
+ orderItemExtras {
512
+ menuItemExtraId
513
+ name
514
+ orderItemExtraOptions {
515
+ menuExtraOptionId
516
+ name
517
+ description
518
+ price
519
+ quantity
520
+ orderItemExtras {
521
+ menuItemExtraId
522
+ name
523
+ orderItemExtraOptions {
524
+ menuExtraOptionId
525
+ name
526
+ description
527
+ price
528
+ quantity
529
+ }
530
+ }
531
+ }
532
+ }
533
+ }
534
+ }
535
+ grandTotal {
536
+ unitAmount
537
+ currency
538
+ decimalPlaces
539
+ displayString
540
+ sign
541
+ }
542
+ likelyOosItems {
543
+ menuItemId
544
+ name
545
+ photoUrl
546
+ }
547
+ store {
548
+ id
549
+ name
550
+ business {
551
+ id
552
+ name
553
+ }
554
+ phoneNumber
555
+ fulfillsOwnDeliveries
556
+ customerArrivedPickupInstructions
557
+ rerouteStoreId
558
+ }
559
+ recurringOrderDetails {
560
+ itemNames
561
+ consumerId
562
+ recurringOrderUpcomingOrderUuid
563
+ scheduledDeliveryDate
564
+ arrivalTimeDisplayString
565
+ storeName
566
+ isCancelled
567
+ }
568
+ bundleOrderInfo {
569
+ primaryBundleOrderUuid
570
+ primaryBundleOrderId
571
+ bundleOrderUuids
572
+ bundleOrderConfig {
573
+ bundleType
574
+ bundleOrderRole
575
+ }
576
+ }
577
+ cancellationPendingRefundInfo {
578
+ state
579
+ }
580
+ }
581
+ }`;
582
+ const ADD_TO_CART_MUTATION = `mutation addCartItem(
583
+ $addCartItemInput: AddCartItemInput!
584
+ $fulfillmentContext: FulfillmentContextInput!
585
+ $cartContext: CartContextInput
586
+ $returnCartFromOrderService: Boolean
587
+ $monitoringContext: MonitoringContextInput
588
+ $lowPriorityBatchAddCartItemInput: [AddCartItemInput!]
589
+ $shouldKeepOnlyOneActiveCart: Boolean
590
+ $selectedDeliveryOption: SelectedDeliveryOptionInput
591
+ ) {
592
+ addCartItemV2(
593
+ addCartItemInput: $addCartItemInput
594
+ fulfillmentContext: $fulfillmentContext
595
+ cartContext: $cartContext
596
+ returnCartFromOrderService: $returnCartFromOrderService
597
+ monitoringContext: $monitoringContext
598
+ lowPriorityBatchAddCartItemInput: $lowPriorityBatchAddCartItemInput
599
+ shouldKeepOnlyOneActiveCart: $shouldKeepOnlyOneActiveCart
600
+ selectedDeliveryOption: $selectedDeliveryOption
601
+ ) {
602
+ id
603
+ subtotal
604
+ total
605
+ currencyCode
606
+ restaurant {
607
+ id
608
+ name
609
+ slug
610
+ business {
611
+ id
612
+ name
613
+ }
614
+ }
615
+ menu {
616
+ id
617
+ name
618
+ }
619
+ orders {
620
+ id
621
+ orderItems {
622
+ id
623
+ quantity
624
+ specialInstructions
625
+ priceDisplayString
626
+ singlePrice
627
+ priceOfTotalQuantity
628
+ item {
629
+ id
630
+ name
631
+ storeId
632
+ }
633
+ options {
634
+ id
635
+ name
636
+ quantity
637
+ }
638
+ }
639
+ }
640
+ }
641
+ }`;
642
+ const UPDATE_CART_MUTATION = `mutation updateCartItem(
643
+ $updateCartItemApiParams: UpdateCartItemInput!
644
+ $fulfillmentContext: FulfillmentContextInput!
645
+ $returnCartFromOrderService: Boolean
646
+ $shouldKeepOnlyOneActiveCart: Boolean
647
+ $cartContextFilter: CartContextV2
648
+ ) {
649
+ updateCartItemV2(
650
+ updateCartItemInput: $updateCartItemApiParams
651
+ fulfillmentContext: $fulfillmentContext
652
+ returnCartFromOrderService: $returnCartFromOrderService
653
+ shouldKeepOnlyOneActiveCart: $shouldKeepOnlyOneActiveCart
654
+ cartContextFilter: $cartContextFilter
655
+ ) {
656
+ id
657
+ subtotal
658
+ total
659
+ currencyCode
660
+ restaurant {
661
+ id
662
+ name
663
+ slug
664
+ business {
665
+ id
666
+ name
667
+ }
668
+ }
669
+ menu {
670
+ id
671
+ name
672
+ }
673
+ orders {
674
+ id
675
+ orderItems {
676
+ id
677
+ quantity
678
+ specialInstructions
679
+ priceDisplayString
680
+ singlePrice
681
+ priceOfTotalQuantity
682
+ item {
683
+ id
684
+ name
685
+ storeId
686
+ }
687
+ options {
688
+ id
689
+ name
690
+ quantity
691
+ }
692
+ }
693
+ }
694
+ }
695
+ }`;
696
+ class DoorDashDirectSession {
697
+ browser = null;
698
+ context = null;
699
+ page = null;
700
+ attemptedManagedImport = false;
701
+ async init(options = {}) {
702
+ if (this.page) {
703
+ return this.page;
704
+ }
705
+ await this.maybeImportManagedBrowserSession();
706
+ const storageStatePath = getStorageStatePath();
707
+ this.browser = await chromium.launch({
708
+ headless: options.headed ? false : true,
709
+ args: ["--disable-blink-features=AutomationControlled", "--no-sandbox", "--disable-setuid-sandbox"],
710
+ });
711
+ this.context = await this.browser.newContext({
712
+ userAgent: DEFAULT_USER_AGENT,
713
+ locale: "en-US",
714
+ viewport: { width: 1280, height: 900 },
715
+ ...(await hasStorageState()) ? { storageState: storageStatePath } : {},
716
+ });
717
+ if (!(await hasStorageState())) {
718
+ const cookies = await readStoredCookies();
719
+ if (cookies.length > 0) {
720
+ await this.context.addCookies(cookies);
721
+ }
722
+ }
723
+ this.page = await this.context.newPage();
724
+ await this.page.goto(`${BASE_URL}/`, { waitUntil: "domcontentloaded", timeout: 90_000 });
725
+ await this.page.waitForTimeout(1_500);
726
+ return this.page;
727
+ }
728
+ async graphql(operationName, query, variables) {
729
+ const raw = await this.requestRaw({
730
+ url: `${BASE_URL}/graphql/${operationName}?operation=${operationName}`,
731
+ method: "POST",
732
+ headers: GRAPHQL_HEADERS,
733
+ body: JSON.stringify({ operationName, variables, query }),
734
+ });
735
+ return parseGraphQlResponse(operationName, raw.status, raw.text);
736
+ }
737
+ async requestJson(input) {
738
+ const raw = await this.requestRaw(input);
739
+ const parsed = safeJsonParse(raw.text);
740
+ if (!parsed) {
741
+ const label = input.operationName ?? input.url;
742
+ throw new Error(`DoorDash ${label} returned HTTP ${raw.status} with a non-JSON response. Response snippet: ${truncate(raw.text, 240)}`);
743
+ }
744
+ return parsed;
745
+ }
746
+ async ordersPageSnapshot() {
747
+ const page = await this.init();
748
+ await page.goto(`${BASE_URL}/orders/`, { waitUntil: "domcontentloaded", timeout: 90_000 });
749
+ await page.waitForTimeout(3_000);
750
+ return page.evaluate(() => {
751
+ const globalWindow = window;
752
+ const bodyText = document.body?.innerText ?? "";
753
+ return {
754
+ cache: globalWindow.__APOLLO_CLIENT__?.cache?.extract?.() ?? null,
755
+ noOrdersBanner: /No orders yet/i.test(bodyText),
756
+ turnstileOverlayVisible: Boolean(document.querySelector('[data-testid="turnstile/overlay"]')),
757
+ url: window.location.href,
758
+ };
759
+ });
760
+ }
761
+ async saveState() {
762
+ if (!this.context) {
763
+ return;
764
+ }
765
+ await saveContextState(this.context);
766
+ }
767
+ async close() {
768
+ await this.page?.close().catch(() => { });
769
+ await this.context?.close().catch(() => { });
770
+ await this.browser?.close().catch(() => { });
771
+ this.page = null;
772
+ this.context = null;
773
+ this.browser = null;
774
+ }
775
+ async requestRaw(input) {
776
+ for (let attempt = 0; attempt < 2; attempt += 1) {
777
+ const page = await this.init();
778
+ try {
779
+ return await page.evaluate(async ({ targetUrl, method, headers, body }) => {
780
+ const response = await fetch(targetUrl, {
781
+ method,
782
+ headers,
783
+ body,
784
+ });
785
+ return {
786
+ status: response.status,
787
+ text: await response.text(),
788
+ };
789
+ }, {
790
+ targetUrl: input.url,
791
+ method: input.method ?? "GET",
792
+ headers: input.headers ?? {},
793
+ body: input.body,
794
+ });
795
+ }
796
+ catch (error) {
797
+ if (attempt === 1 || !isRetryablePageEvaluateError(error)) {
798
+ throw error;
799
+ }
800
+ await page.waitForLoadState("domcontentloaded", { timeout: 10_000 }).catch(() => { });
801
+ await page.waitForTimeout(500).catch(() => { });
802
+ }
803
+ }
804
+ throw new Error(`DoorDash request failed for ${input.url}`);
805
+ }
806
+ async maybeImportManagedBrowserSession() {
807
+ if (this.attemptedManagedImport) {
808
+ return;
809
+ }
810
+ this.attemptedManagedImport = true;
811
+ await importManagedBrowserSessionIfAvailable().catch(() => { });
812
+ }
813
+ }
814
+ const session = new DoorDashDirectSession();
815
+ export async function checkAuthDirect() {
816
+ const data = await session.graphql("consumer", CONSUMER_QUERY, {});
817
+ const consumer = data.consumer ?? null;
818
+ return {
819
+ success: true,
820
+ isLoggedIn: Boolean(consumer && consumer.isGuest === false),
821
+ email: consumer?.email ?? null,
822
+ firstName: consumer?.firstName ?? null,
823
+ lastName: consumer?.lastName ?? null,
824
+ consumerId: consumer?.id ?? null,
825
+ marketId: consumer?.marketId ?? null,
826
+ defaultAddress: consumer?.defaultAddress
827
+ ? {
828
+ printableAddress: consumer.defaultAddress.printableAddress ?? null,
829
+ zipCode: consumer.defaultAddress.zipCode ?? null,
830
+ submarketId: consumer.defaultAddress.submarketId ?? null,
831
+ }
832
+ : null,
833
+ cookiesPath: getCookiesPath(),
834
+ storageStatePath: getStorageStatePath(),
835
+ };
836
+ }
837
+ export async function bootstrapAuthSession() {
838
+ const page = await session.init({ headed: true });
839
+ console.error("A Chromium window is open for DoorDash session bootstrap.");
840
+ console.error("1) Sign in if needed.");
841
+ console.error("2) Confirm your delivery address if needed.");
842
+ console.error("3) Return here and press Enter to save the session for direct API use.");
843
+ await page.goto(`${BASE_URL}/home`, { waitUntil: "domcontentloaded", timeout: 90_000 }).catch(() => { });
844
+ const rl = createInterface({ input, output });
845
+ try {
846
+ await rl.question("");
847
+ }
848
+ finally {
849
+ rl.close();
850
+ }
851
+ await session.saveState();
852
+ const auth = await checkAuthDirect();
853
+ return {
854
+ ...auth,
855
+ message: auth.isLoggedIn
856
+ ? "DoorDash session saved for direct API use."
857
+ : "DoorDash session state saved, but the consumer still appears to be logged out or guest-only.",
858
+ };
859
+ }
860
+ export async function clearStoredSession() {
861
+ await session.close();
862
+ await rm(getCookiesPath(), { force: true }).catch(() => { });
863
+ await rm(getStorageStatePath(), { force: true }).catch(() => { });
864
+ return {
865
+ success: true,
866
+ message: "DoorDash cookies and stored browser session state cleared.",
867
+ cookiesPath: getCookiesPath(),
868
+ storageStatePath: getStorageStatePath(),
869
+ };
870
+ }
871
+ export async function setAddressDirect(address) {
872
+ const requestedAddress = address.trim();
873
+ if (!requestedAddress) {
874
+ throw new Error("Missing required address text.");
875
+ }
876
+ const availableAddresses = await getAvailableAddressesDirect();
877
+ const directMatch = resolveAvailableAddressMatch({
878
+ input: requestedAddress,
879
+ availableAddresses,
880
+ });
881
+ if (directMatch) {
882
+ return updateConsumerDefaultAddressDirect(requestedAddress, directMatch);
883
+ }
884
+ const autocomplete = await autocompleteAddressDirect(requestedAddress);
885
+ const prediction = autocomplete[0];
886
+ if (!prediction) {
887
+ throw new Error(`DoorDash returned no address predictions for "${requestedAddress}".`);
888
+ }
889
+ const createdAddress = await getOrCreateAddressDirect(prediction);
890
+ const autocompleteMatch = resolveAvailableAddressMatch({
891
+ input: requestedAddress,
892
+ availableAddresses,
893
+ prediction,
894
+ createdAddress,
895
+ });
896
+ if (autocompleteMatch) {
897
+ return updateConsumerDefaultAddressDirect(requestedAddress, autocompleteMatch);
898
+ }
899
+ const enrollmentPayload = buildAddConsumerAddressPayload({
900
+ requestedAddress,
901
+ prediction,
902
+ createdAddress,
903
+ });
904
+ return addConsumerAddressDirect(requestedAddress, enrollmentPayload);
905
+ }
906
+ export async function searchRestaurantsDirect(query, cuisine) {
907
+ const data = await session.graphql("searchWithFilterFacetFeed", SEARCH_QUERY, {
908
+ query,
909
+ cursor: "",
910
+ filterQuery: "",
911
+ isDebug: false,
912
+ searchType: "",
913
+ });
914
+ const rows = parseSearchRestaurants(data.searchWithFilterFacetFeed?.body ?? []);
915
+ const cuisineFilter = cuisine?.trim() ? cuisine.trim() : null;
916
+ const restaurants = cuisineFilter
917
+ ? rows.filter((row) => row.description?.toLowerCase().includes(cuisineFilter.toLowerCase()))
918
+ : rows;
919
+ return {
920
+ success: true,
921
+ query,
922
+ cuisineFilter,
923
+ count: restaurants.length,
924
+ restaurants,
925
+ };
926
+ }
927
+ export async function getMenuDirect(restaurantId) {
928
+ const data = await session.graphql("storepageFeed", STOREPAGE_QUERY, {
929
+ storeId: restaurantId,
930
+ menuId: null,
931
+ isMerchantPreview: false,
932
+ fulfillmentType: "Delivery",
933
+ cursor: null,
934
+ scheduledTime: null,
935
+ entryPoint: "External",
936
+ });
937
+ return parseMenuResponse(data.storepageFeed, restaurantId);
938
+ }
939
+ export async function getItemDirect(restaurantId, itemId) {
940
+ const auth = await checkAuthDirect();
941
+ const data = await session.graphql("itemPage", ITEM_QUERY, {
942
+ storeId: restaurantId,
943
+ itemId,
944
+ consumerId: auth.consumerId,
945
+ isMerchantPreview: false,
946
+ isNested: false,
947
+ fulfillmentType: "Delivery",
948
+ cursorContext: null,
949
+ });
950
+ return parseItemResponse(data.itemPage, restaurantId);
951
+ }
952
+ export async function getCartDirect() {
953
+ const data = await session.graphql("consumerOrderCart", CURRENT_CART_QUERY, {});
954
+ return parseCartResponse(data.consumerOrderCart ?? null);
955
+ }
956
+ export async function getOrdersDirect(params = {}) {
957
+ const requestedLimit = params.limit;
958
+ const activeOnly = params.activeOnly ?? false;
959
+ try {
960
+ const orders = await fetchExistingOrdersGraphql({ limit: requestedLimit });
961
+ return buildOrdersResult({
962
+ source: "graphql",
963
+ warning: null,
964
+ orders,
965
+ activeOnly,
966
+ requestedLimit,
967
+ });
968
+ }
969
+ catch (error) {
970
+ if (!isOrderHistoryChallengeError(error)) {
971
+ throw error;
972
+ }
973
+ const snapshot = await session.ordersPageSnapshot();
974
+ const orders = extractExistingOrdersFromApolloCache(snapshot.cache);
975
+ const warningParts = [
976
+ "DoorDash challenged the direct order-history GraphQL request, so this response was recovered from the consumer-web orders page cache.",
977
+ "This fallback is read-only and can be temporarily empty or limited to the first cached page.",
978
+ ];
979
+ if (snapshot.turnstileOverlayVisible) {
980
+ warningParts.push("The orders page was still showing DoorDash's security check banner while this snapshot was captured.");
981
+ }
982
+ if (snapshot.noOrdersBanner) {
983
+ warningParts.push("The live orders page rendered its 'No orders yet' state for this session.");
984
+ }
985
+ return buildOrdersResult({
986
+ source: "orders-page-cache",
987
+ warning: warningParts.join(" "),
988
+ orders,
989
+ activeOnly,
990
+ requestedLimit,
991
+ });
992
+ }
993
+ }
994
+ export async function getOrderDirect(orderId) {
995
+ const requestedOrderId = orderId.trim();
996
+ if (!requestedOrderId) {
997
+ throw new Error("Missing required order identifier.");
998
+ }
999
+ const orders = await getOrdersDirect();
1000
+ const match = findExistingOrderByIdentifier(orders.orders, requestedOrderId);
1001
+ if (!match) {
1002
+ throw new Error(`Could not find order ${requestedOrderId} in the available existing-order history.`);
1003
+ }
1004
+ return {
1005
+ success: true,
1006
+ source: orders.source,
1007
+ warning: orders.warning,
1008
+ matchedField: match.matchedField,
1009
+ order: match.order,
1010
+ };
1011
+ }
1012
+ export async function addToCartDirect(params) {
1013
+ const { item, itemDetail } = await resolveMenuItem(params.restaurantId, params.itemId, params.itemName);
1014
+ const currentCart = await getCartDirect();
1015
+ const currentCartStoreId = currentCart.restaurant?.id ?? null;
1016
+ const cartId = currentCartStoreId && currentCartStoreId !== params.restaurantId ? "" : (currentCart.cartId ?? "");
1017
+ const auth = await checkAuthDirect();
1018
+ const payload = await buildAddToCartPayload({
1019
+ restaurantId: params.restaurantId,
1020
+ cartId,
1021
+ quantity: params.quantity,
1022
+ specialInstructions: params.specialInstructions ?? null,
1023
+ optionSelections: params.optionSelections ?? [],
1024
+ item,
1025
+ itemDetail,
1026
+ consumerId: auth.consumerId,
1027
+ });
1028
+ const data = await session.graphql("addCartItem", ADD_TO_CART_MUTATION, payload);
1029
+ const cart = parseCartResponse(data.addCartItemV2 ?? null);
1030
+ await session.saveState();
1031
+ return {
1032
+ ...cart,
1033
+ sourceItem: {
1034
+ id: item.id,
1035
+ name: item.name,
1036
+ menuId: itemDetail.item.menuId,
1037
+ },
1038
+ };
1039
+ }
1040
+ export async function updateCartDirect(params) {
1041
+ const currentCart = await getCartDirect();
1042
+ if (!currentCart.cartId || !currentCart.restaurant?.id) {
1043
+ throw new Error("No active cart found to update.");
1044
+ }
1045
+ const cartItem = currentCart.items.find((item) => item.cartItemId === params.cartItemId);
1046
+ if (!cartItem?.itemId) {
1047
+ throw new Error(`Could not find cart item ${params.cartItemId} in the active cart.`);
1048
+ }
1049
+ const payload = buildUpdateCartPayload({
1050
+ cartId: currentCart.cartId,
1051
+ cartItemId: cartItem.cartItemId,
1052
+ itemId: cartItem.itemId,
1053
+ quantity: params.quantity,
1054
+ storeId: currentCart.restaurant.id,
1055
+ });
1056
+ const data = await session.graphql("updateCartItem", UPDATE_CART_MUTATION, payload);
1057
+ const cart = parseCartResponse(data.updateCartItemV2 ?? null);
1058
+ await session.saveState();
1059
+ return {
1060
+ ...cart,
1061
+ updatedCartItemId: params.cartItemId,
1062
+ };
1063
+ }
1064
+ export async function cleanupDirect() {
1065
+ await session.close();
1066
+ }
1067
+ export function normalizeItemName(value) {
1068
+ return value.trim().replace(/\s+/g, " ").toLowerCase();
1069
+ }
1070
+ export async function buildAddToCartPayload(input) {
1071
+ const header = input.itemDetail.item;
1072
+ if (!header.id || !header.name || !header.menuId || header.unitAmount == null || !header.currency) {
1073
+ throw new Error("DoorDash item details were incomplete; cannot build a cart mutation safely.");
1074
+ }
1075
+ if (!Number.isInteger(input.quantity) || input.quantity < 1) {
1076
+ throw new Error(`Invalid quantity: ${input.quantity}`);
1077
+ }
1078
+ const resolveNestedOptionLists = input.resolveNestedOptionLists ??
1079
+ (async ({ restaurantId, consumerId, option }) => fetchNestedOptionListsDirect({ restaurantId, consumerId, option }));
1080
+ const builtOptions = await buildNestedOptionsPayload({
1081
+ restaurantId: input.restaurantId,
1082
+ menuId: header.menuId,
1083
+ currency: header.currency,
1084
+ consumerId: input.consumerId ?? null,
1085
+ optionLists: input.itemDetail.item.optionLists,
1086
+ selections: input.optionSelections ?? [],
1087
+ mode: "regular",
1088
+ resolveNestedOptionLists,
1089
+ });
1090
+ return {
1091
+ addCartItemInput: {
1092
+ storeId: input.restaurantId,
1093
+ menuId: header.menuId,
1094
+ itemId: header.id,
1095
+ itemName: header.name,
1096
+ itemDescription: header.description,
1097
+ currency: header.currency,
1098
+ quantity: input.quantity,
1099
+ nestedOptions: JSON.stringify(builtOptions.nestedOptions),
1100
+ specialInstructions: input.specialInstructions,
1101
+ substitutionPreference: "substitute",
1102
+ isBundle: false,
1103
+ bundleType: "BUNDLE_TYPE_UNSPECIFIED",
1104
+ unitPrice: header.unitAmount,
1105
+ cartId: input.cartId,
1106
+ },
1107
+ lowPriorityBatchAddCartItemInput: builtOptions.lowPriorityItems.map((item) => ({
1108
+ cartId: input.cartId,
1109
+ storeId: item.storeId,
1110
+ menuId: item.menuId,
1111
+ itemId: item.itemId,
1112
+ itemName: item.itemName,
1113
+ currency: item.currency,
1114
+ quantity: item.quantity,
1115
+ unitPrice: item.unitPrice,
1116
+ isBundle: false,
1117
+ bundleType: "BUNDLE_TYPE_UNSPECIFIED",
1118
+ nestedOptions: JSON.stringify(item.nestedOptions),
1119
+ })),
1120
+ fulfillmentContext: {
1121
+ shouldUpdateFulfillment: false,
1122
+ fulfillmentType: "Delivery",
1123
+ },
1124
+ monitoringContext: {
1125
+ isGroup: false,
1126
+ },
1127
+ cartContext: {
1128
+ isBundle: false,
1129
+ },
1130
+ returnCartFromOrderService: false,
1131
+ shouldKeepOnlyOneActiveCart: false,
1132
+ };
1133
+ }
1134
+ export function buildUpdateCartPayload(input) {
1135
+ if (!Number.isInteger(input.quantity) || input.quantity < 0) {
1136
+ throw new Error(`Invalid quantity: ${input.quantity}`);
1137
+ }
1138
+ return {
1139
+ updateCartItemApiParams: {
1140
+ cartId: input.cartId,
1141
+ cartItemId: input.cartItemId,
1142
+ itemId: input.itemId,
1143
+ quantity: input.quantity,
1144
+ storeId: input.storeId,
1145
+ purchaseTypeOptions: {
1146
+ purchaseType: "PURCHASE_TYPE_UNSPECIFIED",
1147
+ continuousQuantity: 0,
1148
+ unit: null,
1149
+ },
1150
+ cartFilter: null,
1151
+ },
1152
+ fulfillmentContext: {
1153
+ shouldUpdateFulfillment: false,
1154
+ },
1155
+ returnCartFromOrderService: false,
1156
+ };
1157
+ }
1158
+ export function parseOptionSelectionsJson(value) {
1159
+ const parsed = safeJsonParse(value);
1160
+ if (!Array.isArray(parsed)) {
1161
+ throw new Error("--options-json must be a JSON array of { groupId, optionId, quantity?, children? } objects.");
1162
+ }
1163
+ return parsed.map((entry, index) => parseRequestedOptionSelection(entry, `index ${index}`));
1164
+ }
1165
+ async function buildNestedOptionsPayload(input) {
1166
+ const selections = normalizeRequestedOptionSelections(input.selections);
1167
+ const requiredGroups = input.optionLists.filter((group) => group.minNumOptions > 0 && !group.isOptional);
1168
+ if (selections.length === 0) {
1169
+ if (requiredGroups.length === 0) {
1170
+ return { nestedOptions: [], lowPriorityItems: [] };
1171
+ }
1172
+ const labels = requiredGroups.map((group) => `${group.name} (${group.minNumOptions}-${group.maxNumOptions})`);
1173
+ throw new Error(`This item has required option groups. Provide --options-json with validated groupId/optionId selections. Required groups: ${labels.join(", ")}`);
1174
+ }
1175
+ const groupsById = new Map(input.optionLists.map((group) => [group.id, group]));
1176
+ const selectionsByGroup = new Map();
1177
+ for (const selection of selections) {
1178
+ const group = groupsById.get(selection.groupId);
1179
+ if (!group) {
1180
+ throw new Error(`Unknown option group: ${selection.groupId}`);
1181
+ }
1182
+ const option = group.options.find((candidate) => candidate.id === selection.optionId);
1183
+ if (!option) {
1184
+ throw new Error(`Unknown option ${selection.optionId} for group ${group.name} (${group.id}).`);
1185
+ }
1186
+ const entries = selectionsByGroup.get(group.id) ?? [];
1187
+ entries.push({ selection, option, quantity: selection.quantity ?? 1, group });
1188
+ selectionsByGroup.set(group.id, entries);
1189
+ }
1190
+ validateSelectedOptionCounts(input.optionLists, selectionsByGroup);
1191
+ const nestedOptions = [];
1192
+ const lowPriorityItems = [];
1193
+ for (const group of input.optionLists) {
1194
+ const selectedEntries = selectionsByGroup.get(group.id) ?? [];
1195
+ for (const { selection, option, quantity } of selectedEntries) {
1196
+ if (!option.nextCursor) {
1197
+ if (selection.children && selection.children.length > 0) {
1198
+ throw new Error(`Option ${option.name} (${option.id}) does not open a nested configuration step, so child selections are invalid.`);
1199
+ }
1200
+ nestedOptions.push(input.mode === "standalone-child"
1201
+ ? buildStandaloneChildLeafOption({ option, quantity })
1202
+ : buildRegularLeafOption({ option, quantity, group }));
1203
+ continue;
1204
+ }
1205
+ if (!isStandaloneRecommendedGroup(group)) {
1206
+ throw new Error(`Option ${option.name} (${option.id}) opens an additional nested configuration step, but DoorDash's safe direct cart shape is only confirmed for standalone recommended add-on groups (recommended_option_*). Group ${group.id} does not match that transport, so the CLI refuses to guess.`);
1207
+ }
1208
+ const childOptionLists = await input.resolveNestedOptionLists({
1209
+ restaurantId: input.restaurantId,
1210
+ consumerId: input.consumerId,
1211
+ option,
1212
+ group,
1213
+ selection,
1214
+ });
1215
+ const childPayload = await buildNestedOptionsPayload({
1216
+ restaurantId: input.restaurantId,
1217
+ menuId: input.menuId,
1218
+ currency: input.currency,
1219
+ consumerId: input.consumerId,
1220
+ optionLists: childOptionLists,
1221
+ selections: selection.children ?? [],
1222
+ mode: "standalone-child",
1223
+ resolveNestedOptionLists: input.resolveNestedOptionLists,
1224
+ });
1225
+ lowPriorityItems.push({
1226
+ storeId: input.restaurantId,
1227
+ menuId: input.menuId,
1228
+ itemId: option.id,
1229
+ itemName: option.name,
1230
+ currency: input.currency,
1231
+ quantity,
1232
+ unitPrice: option.unitAmount ?? 0,
1233
+ nestedOptions: childPayload.nestedOptions,
1234
+ });
1235
+ lowPriorityItems.push(...childPayload.lowPriorityItems);
1236
+ }
1237
+ }
1238
+ return {
1239
+ nestedOptions,
1240
+ lowPriorityItems,
1241
+ };
1242
+ }
1243
+ function buildRegularLeafOption(input) {
1244
+ return {
1245
+ id: input.option.id,
1246
+ quantity: input.quantity,
1247
+ options: [],
1248
+ itemExtraOption: {
1249
+ id: input.option.id,
1250
+ name: input.option.name,
1251
+ description: input.option.name,
1252
+ price: input.option.unitAmount ?? 0,
1253
+ itemExtraName: null,
1254
+ chargeAbove: 0,
1255
+ defaultQuantity: input.option.defaultQuantity ?? 0,
1256
+ itemExtraId: input.group.id,
1257
+ itemExtraNumFreeOptions: input.group.numFreeOptions,
1258
+ menuItemExtraOptionPrice: input.option.unitAmount ?? 0,
1259
+ menuItemExtraOptionBasePrice: null,
1260
+ },
1261
+ };
1262
+ }
1263
+ function buildStandaloneChildLeafOption(input) {
1264
+ return {
1265
+ id: input.option.id,
1266
+ quantity: input.quantity,
1267
+ options: [],
1268
+ itemExtraOption: {
1269
+ id: input.option.id,
1270
+ name: input.option.name,
1271
+ description: input.option.name,
1272
+ price: input.option.unitAmount ?? 0,
1273
+ chargeAbove: 0,
1274
+ defaultQuantity: input.option.defaultQuantity ?? 0,
1275
+ },
1276
+ };
1277
+ }
1278
+ function validateSelectedOptionCounts(optionLists, selectionsByGroup) {
1279
+ for (const group of optionLists) {
1280
+ const selectedEntries = selectionsByGroup.get(group.id) ?? [];
1281
+ const selectedCount = selectedEntries.reduce((sum, entry) => sum + entry.quantity, 0);
1282
+ if (selectedCount < group.minNumOptions) {
1283
+ throw new Error(`Missing required selections for ${group.name}. Need at least ${group.minNumOptions}.`);
1284
+ }
1285
+ if (group.maxNumOptions > 0 && selectedCount > group.maxNumOptions) {
1286
+ throw new Error(`Too many selections for ${group.name}. Maximum is ${group.maxNumOptions}.`);
1287
+ }
1288
+ }
1289
+ }
1290
+ function isStandaloneRecommendedGroup(group) {
1291
+ return group.id.startsWith("recommended_option_");
1292
+ }
1293
+ function parseRequestedOptionSelection(entry, label) {
1294
+ const object = asObject(entry);
1295
+ const groupId = typeof object.groupId === "string" ? object.groupId.trim() : "";
1296
+ const optionId = typeof object.optionId === "string" ? object.optionId.trim() : "";
1297
+ const quantity = object.quantity == null ? undefined : Number.parseInt(String(object.quantity), 10);
1298
+ const childrenRaw = object.children;
1299
+ if (!groupId || !optionId) {
1300
+ throw new Error(`Invalid option selection at ${label}. Each entry must include string groupId and optionId fields.`);
1301
+ }
1302
+ if (quantity !== undefined && (!Number.isInteger(quantity) || quantity < 1)) {
1303
+ throw new Error(`Invalid option quantity at ${label}: ${object.quantity}`);
1304
+ }
1305
+ if (childrenRaw !== undefined && !Array.isArray(childrenRaw)) {
1306
+ throw new Error(`Invalid option children at ${label}. children must be an array when provided.`);
1307
+ }
1308
+ const children = Array.isArray(childrenRaw)
1309
+ ? childrenRaw.map((child, index) => parseRequestedOptionSelection(child, `${label}.children[${index}]`))
1310
+ : undefined;
1311
+ return {
1312
+ groupId,
1313
+ optionId,
1314
+ ...(quantity === undefined ? {} : { quantity }),
1315
+ ...(children === undefined ? {} : { children }),
1316
+ };
1317
+ }
1318
+ function normalizeRequestedOptionSelections(selections) {
1319
+ const aggregated = new Map();
1320
+ for (const selection of selections) {
1321
+ const groupId = selection.groupId.trim();
1322
+ const optionId = selection.optionId.trim();
1323
+ const quantity = selection.quantity ?? 1;
1324
+ const children = selection.children ? normalizeRequestedOptionSelections(selection.children) : undefined;
1325
+ if (!groupId || !optionId) {
1326
+ throw new Error("Option selections must include non-empty groupId and optionId values.");
1327
+ }
1328
+ if (!Number.isInteger(quantity) || quantity < 1) {
1329
+ throw new Error(`Invalid option quantity for ${groupId}/${optionId}: ${selection.quantity}`);
1330
+ }
1331
+ const key = `${groupId}:${optionId}`;
1332
+ const previous = aggregated.get(key);
1333
+ if (previous) {
1334
+ if ((previous.children && previous.children.length > 0) || (children && children.length > 0)) {
1335
+ throw new Error(`Duplicate option selections for ${groupId}/${optionId} are only supported when no nested child selections are attached.`);
1336
+ }
1337
+ aggregated.set(key, { ...previous, quantity: (previous.quantity ?? 1) + quantity });
1338
+ }
1339
+ else {
1340
+ aggregated.set(key, { groupId, optionId, quantity, ...(children ? { children } : {}) });
1341
+ }
1342
+ }
1343
+ return [...aggregated.values()];
1344
+ }
1345
+ async function fetchNestedOptionListsDirect(input) {
1346
+ if (!input.option.nextCursor) {
1347
+ return [];
1348
+ }
1349
+ const data = await session.graphql("itemPage", ITEM_QUERY, {
1350
+ storeId: input.restaurantId,
1351
+ itemId: input.option.id,
1352
+ consumerId: input.consumerId,
1353
+ isMerchantPreview: false,
1354
+ isNested: true,
1355
+ fulfillmentType: "Delivery",
1356
+ cursorContext: {
1357
+ itemCursor: input.option.nextCursor,
1358
+ },
1359
+ });
1360
+ const root = asObject(data.itemPage);
1361
+ const optionLists = Array.isArray(root.optionLists) ? root.optionLists : [];
1362
+ return optionLists.map(parseOptionList);
1363
+ }
1364
+ async function getAvailableAddressesDirect() {
1365
+ const data = await session.graphql("getAvailableAddresses", GET_AVAILABLE_ADDRESSES_QUERY, {});
1366
+ return Array.isArray(data.getAvailableAddresses) ? data.getAvailableAddresses : [];
1367
+ }
1368
+ async function autocompleteAddressDirect(inputAddress) {
1369
+ const params = new URLSearchParams({
1370
+ input_address: inputAddress,
1371
+ autocomplete_type: "AUTOCOMPLETE_TYPE_V2_UNSPECIFIED",
1372
+ });
1373
+ const response = await session.requestJson({
1374
+ url: `${BASE_URL}/unified-gateway/geo-intelligence/v2/address/autocomplete?${params.toString()}`,
1375
+ method: "GET",
1376
+ headers: { accept: "application/json" },
1377
+ operationName: "address autocomplete",
1378
+ });
1379
+ return Array.isArray(response.predictions) ? response.predictions : [];
1380
+ }
1381
+ async function getOrCreateAddressDirect(prediction) {
1382
+ const sourcePlaceId = prediction.source_place_id?.trim();
1383
+ if (!sourcePlaceId) {
1384
+ throw new Error("DoorDash autocomplete did not return a source_place_id for the selected address.");
1385
+ }
1386
+ const response = await session.requestJson({
1387
+ url: `${BASE_URL}/unified-gateway/geo-intelligence/v2/address/get-or-create`,
1388
+ method: "POST",
1389
+ headers: {
1390
+ accept: "application/json",
1391
+ "content-type": "application/json",
1392
+ },
1393
+ body: JSON.stringify({
1394
+ address_identifier: {
1395
+ _type: "source_place_id_request",
1396
+ source_place_id: sourcePlaceId,
1397
+ },
1398
+ }),
1399
+ operationName: "address get-or-create",
1400
+ });
1401
+ return response.address ?? null;
1402
+ }
1403
+ export function buildAddConsumerAddressPayload(input) {
1404
+ const createdAddress = input.createdAddress;
1405
+ const lat = typeof createdAddress?.lat === "number" ? createdAddress.lat : input.prediction.lat;
1406
+ const lng = typeof createdAddress?.lng === "number" ? createdAddress.lng : input.prediction.lng;
1407
+ const city = firstNonEmptyString(createdAddress?.locality, input.prediction.locality);
1408
+ const state = firstNonEmptyString(createdAddress?.administrative_area_level1, input.prediction.administrative_area_level1);
1409
+ const zipCode = firstNonEmptyString(createdAddress?.postal_code, combinePostalCode(input.prediction));
1410
+ const printableAddress = firstNonEmptyString(createdAddress?.formatted_address, input.prediction.formatted_address, input.requestedAddress);
1411
+ const shortname = firstNonEmptyString(createdAddress?.formatted_address_short, input.prediction.formatted_address_short, input.requestedAddress);
1412
+ const googlePlaceId = firstNonEmptyString(input.prediction.source_place_id);
1413
+ if (typeof lat !== "number" || typeof lng !== "number") {
1414
+ throw new Error(`DoorDash did not return stable coordinates for "${input.requestedAddress}".`);
1415
+ }
1416
+ if (!city || !state || !zipCode || !printableAddress || !shortname || !googlePlaceId) {
1417
+ throw new Error(`DoorDash resolved "${input.requestedAddress}", but the addConsumerAddressV2 payload was incomplete. city=${city ?? ""} state=${state ?? ""} zip=${zipCode ?? ""} shortname=${shortname ?? ""} googlePlaceId=${googlePlaceId ? "present" : "missing"}`);
1418
+ }
1419
+ return {
1420
+ lat,
1421
+ lng,
1422
+ city,
1423
+ state,
1424
+ zipCode,
1425
+ printableAddress,
1426
+ shortname,
1427
+ googlePlaceId,
1428
+ subpremise: null,
1429
+ driverInstructions: null,
1430
+ dropoffOptionId: null,
1431
+ manualLat: null,
1432
+ manualLng: null,
1433
+ addressLinkType: "ADDRESS_LINK_TYPE_UNSPECIFIED",
1434
+ buildingName: null,
1435
+ entryCode: null,
1436
+ personalAddressLabel: null,
1437
+ };
1438
+ }
1439
+ async function addConsumerAddressDirect(requestedAddress, payload) {
1440
+ const data = await session.graphql("addConsumerAddressV2", ADD_CONSUMER_ADDRESS_MUTATION, payload);
1441
+ const defaultAddress = data.addConsumerAddressV2?.defaultAddress ?? null;
1442
+ const matchedAddressId = typeof defaultAddress?.id === "string" ? defaultAddress.id : "";
1443
+ if (!matchedAddressId) {
1444
+ throw new Error(`DoorDash accepted addConsumerAddressV2 for "${requestedAddress}", but it did not return a saved defaultAddress id. The CLI is refusing to guess follow-up address state.`);
1445
+ }
1446
+ await session.saveState();
1447
+ return {
1448
+ success: true,
1449
+ mode: "direct-added-address",
1450
+ requestedAddress,
1451
+ matchedAddressId,
1452
+ matchedAddressSource: "add-consumer-address",
1453
+ printableAddress: defaultAddress?.printableAddress ?? payload.printableAddress,
1454
+ };
1455
+ }
1456
+ async function updateConsumerDefaultAddressDirect(requestedAddress, match) {
1457
+ const data = await session.graphql("updateConsumerDefaultAddressV2", UPDATE_CONSUMER_DEFAULT_ADDRESS_MUTATION, { defaultAddressId: match.id });
1458
+ await session.saveState();
1459
+ return {
1460
+ success: true,
1461
+ mode: "direct-saved-address",
1462
+ requestedAddress,
1463
+ matchedAddressId: match.id,
1464
+ matchedAddressSource: match.source,
1465
+ printableAddress: data.updateConsumerDefaultAddressV2?.defaultAddress?.printableAddress ?? match.printableAddress ?? null,
1466
+ };
1467
+ }
1468
+ export function resolveAvailableAddressMatch(input) {
1469
+ const addressIds = [input.prediction?.geo_address_id, input.createdAddress?.id]
1470
+ .filter((value) => typeof value === "string" && value.trim().length > 0)
1471
+ .map((value) => value.trim());
1472
+ for (const availableAddress of input.availableAddresses) {
1473
+ const defaultAddressId = typeof availableAddress.id === "string" ? availableAddress.id.trim() : "";
1474
+ if (!defaultAddressId) {
1475
+ continue;
1476
+ }
1477
+ if (typeof availableAddress.addressId === "string" && addressIds.includes(availableAddress.addressId.trim())) {
1478
+ return {
1479
+ id: defaultAddressId,
1480
+ printableAddress: availableAddress.printableAddress ?? null,
1481
+ source: "autocomplete-address-id",
1482
+ };
1483
+ }
1484
+ }
1485
+ const normalizedCandidates = dedupeBy([
1486
+ input.input,
1487
+ input.prediction?.formatted_address ?? null,
1488
+ input.prediction?.formatted_address_short ?? null,
1489
+ input.createdAddress?.formatted_address ?? null,
1490
+ input.createdAddress?.formatted_address_short ?? null,
1491
+ ]
1492
+ .filter((value) => typeof value === "string" && value.trim().length > 0)
1493
+ .map(normalizeAddressText), (value) => value);
1494
+ for (const availableAddress of input.availableAddresses) {
1495
+ const defaultAddressId = typeof availableAddress.id === "string" ? availableAddress.id.trim() : "";
1496
+ if (!defaultAddressId) {
1497
+ continue;
1498
+ }
1499
+ const printableAddress = normalizeAddressText(availableAddress.printableAddress ?? "");
1500
+ const shortname = normalizeAddressText(availableAddress.shortname ?? "");
1501
+ const matchesText = normalizedCandidates.some((candidate) => candidate === printableAddress ||
1502
+ candidate === shortname ||
1503
+ (shortname.length > 0 && candidate.includes(shortname)) ||
1504
+ (printableAddress.length > 0 && printableAddress.includes(candidate)));
1505
+ if (matchesText) {
1506
+ return {
1507
+ id: defaultAddressId,
1508
+ printableAddress: availableAddress.printableAddress ?? null,
1509
+ source: input.prediction || input.createdAddress ? "autocomplete-text" : "saved-address",
1510
+ };
1511
+ }
1512
+ }
1513
+ return null;
1514
+ }
1515
+ function combinePostalCode(prediction) {
1516
+ const postalCode = typeof prediction.postal_code === "string" ? prediction.postal_code.trim() : "";
1517
+ const suffix = typeof prediction.postal_code_suffix === "string" ? prediction.postal_code_suffix.trim() : "";
1518
+ if (!postalCode) {
1519
+ return null;
1520
+ }
1521
+ return suffix ? `${postalCode}-${suffix}` : postalCode;
1522
+ }
1523
+ function firstNonEmptyString(...values) {
1524
+ for (const value of values) {
1525
+ if (typeof value === "string" && value.trim()) {
1526
+ return value.trim();
1527
+ }
1528
+ }
1529
+ return null;
1530
+ }
1531
+ function normalizeAddressText(value) {
1532
+ return value
1533
+ .trim()
1534
+ .toLowerCase()
1535
+ .replace(/[.,]/g, "")
1536
+ .replace(/\s+/g, " ");
1537
+ }
1538
+ async function importManagedBrowserSessionIfAvailable() {
1539
+ for (const cdpUrl of await getManagedBrowserCdpCandidates()) {
1540
+ if (!(await isCdpEndpointReachable(cdpUrl))) {
1541
+ continue;
1542
+ }
1543
+ let browser = null;
1544
+ let tempPage = null;
1545
+ try {
1546
+ browser = await chromium.connectOverCDP(cdpUrl);
1547
+ const context = browser.contexts()[0];
1548
+ if (!context) {
1549
+ continue;
1550
+ }
1551
+ let page = context.pages().find((candidate) => candidate.url().includes("doordash.com")) ?? null;
1552
+ if (!page) {
1553
+ tempPage = await context.newPage();
1554
+ page = tempPage;
1555
+ await page.goto(`${BASE_URL}/home`, { waitUntil: "domcontentloaded", timeout: 90_000 }).catch(() => { });
1556
+ await page.waitForTimeout(1_000);
1557
+ }
1558
+ const consumerData = await fetchConsumerViaPage(page);
1559
+ const consumer = consumerData.consumer ?? null;
1560
+ if (!consumer || consumer.isGuest !== false) {
1561
+ continue;
1562
+ }
1563
+ await saveContextState(context);
1564
+ return true;
1565
+ }
1566
+ catch {
1567
+ continue;
1568
+ }
1569
+ finally {
1570
+ await tempPage?.close().catch(() => { });
1571
+ await browser?.close().catch(() => { });
1572
+ }
1573
+ }
1574
+ return false;
1575
+ }
1576
+ async function fetchConsumerViaPage(page) {
1577
+ const raw = await page.evaluate(async ({ query, headers, url }) => {
1578
+ const response = await fetch(url, {
1579
+ method: "POST",
1580
+ headers,
1581
+ body: JSON.stringify({ operationName: "consumer", variables: {}, query }),
1582
+ });
1583
+ return { status: response.status, text: await response.text() };
1584
+ }, {
1585
+ query: CONSUMER_QUERY,
1586
+ headers: GRAPHQL_HEADERS,
1587
+ url: `${BASE_URL}/graphql/consumer?operation=consumer`,
1588
+ });
1589
+ return parseGraphQlResponse("managedBrowserConsumerImport", raw.status, raw.text);
1590
+ }
1591
+ async function saveContextState(context) {
1592
+ const storageStatePath = getStorageStatePath();
1593
+ await ensureConfigDir();
1594
+ await context.storageState({ path: storageStatePath });
1595
+ const cookies = await context.cookies();
1596
+ await writeFile(getCookiesPath(), JSON.stringify(cookies, null, 2));
1597
+ }
1598
+ async function getManagedBrowserCdpCandidates() {
1599
+ const candidates = new Set();
1600
+ for (const value of [
1601
+ process.env.DOORDASH_MANAGED_BROWSER_CDP_URL,
1602
+ process.env.OPENCLAW_BROWSER_CDP_URL,
1603
+ process.env.OPENCLAW_OPENCLAW_CDP_URL,
1604
+ ]) {
1605
+ if (typeof value === "string" && value.trim().length > 0) {
1606
+ candidates.add(value.trim().replace(/\/$/, ""));
1607
+ }
1608
+ }
1609
+ for (const value of await readOpenClawBrowserConfigCandidates()) {
1610
+ candidates.add(value.replace(/\/$/, ""));
1611
+ }
1612
+ candidates.add("http://127.0.0.1:18800");
1613
+ return [...candidates];
1614
+ }
1615
+ async function readOpenClawBrowserConfigCandidates() {
1616
+ try {
1617
+ const raw = await readFile(join(homedir(), ".openclaw", "openclaw.json"), "utf8");
1618
+ const parsed = safeJsonParse(raw);
1619
+ const browserConfig = asObject(parsed?.browser);
1620
+ const candidates = [];
1621
+ const pushCandidate = (value) => {
1622
+ const object = asObject(value);
1623
+ if (typeof object.cdpUrl === "string" && object.cdpUrl.trim()) {
1624
+ candidates.push(object.cdpUrl.trim());
1625
+ }
1626
+ else if (typeof object.cdpPort === "number" && Number.isInteger(object.cdpPort)) {
1627
+ candidates.push(`http://127.0.0.1:${object.cdpPort}`);
1628
+ }
1629
+ };
1630
+ pushCandidate(browserConfig);
1631
+ pushCandidate(browserConfig.openclaw);
1632
+ pushCandidate(asObject(browserConfig.profiles).openclaw);
1633
+ return dedupeBy(candidates, (value) => value);
1634
+ }
1635
+ catch {
1636
+ return [];
1637
+ }
1638
+ }
1639
+ async function isCdpEndpointReachable(cdpUrl) {
1640
+ try {
1641
+ const response = await fetch(`${cdpUrl.replace(/\/$/, "")}/json/version`);
1642
+ return response.ok;
1643
+ }
1644
+ catch {
1645
+ return false;
1646
+ }
1647
+ }
1648
+ export function parseSearchRestaurants(body) {
1649
+ const results = [];
1650
+ for (const section of body) {
1651
+ const entries = asObject(section).body;
1652
+ if (!Array.isArray(entries)) {
1653
+ continue;
1654
+ }
1655
+ for (const entry of entries) {
1656
+ const parsed = parseSearchRestaurantRow(entry);
1657
+ if (parsed) {
1658
+ results.push(parsed);
1659
+ }
1660
+ }
1661
+ }
1662
+ return dedupeBy(results, (row) => row.id);
1663
+ }
1664
+ export function parseSearchRestaurantRow(entry) {
1665
+ const object = asObject(entry);
1666
+ const componentId = asObject(object.component).id;
1667
+ if (componentId !== "row.store") {
1668
+ return null;
1669
+ }
1670
+ const title = asObject(object.text).title;
1671
+ if (typeof title !== "string" || title.trim().length === 0) {
1672
+ return null;
1673
+ }
1674
+ const customPairs = Array.isArray(asObject(object.text).custom) ? asObject(object.text).custom : [];
1675
+ const custom = new Map();
1676
+ for (const pair of customPairs) {
1677
+ const pairObject = asObject(pair);
1678
+ if (typeof pairObject.key === "string" && typeof pairObject.value === "string") {
1679
+ custom.set(pairObject.key, pairObject.value);
1680
+ }
1681
+ }
1682
+ const clickData = safeJsonParse(asObject(asObject(object.events).click).data);
1683
+ const url = clickData?.uri ? `${BASE_URL}/${clickData.uri.replace(/^\//, "")}` : null;
1684
+ const id = extractStoreId(url) ?? extractIdFromFacetId(typeof object.id === "string" ? object.id : "") ?? title.trim();
1685
+ const description = typeof asObject(object.text).description === "string" ? asObject(object.text).description : null;
1686
+ return {
1687
+ id,
1688
+ name: title.trim(),
1689
+ description,
1690
+ cuisines: parseCuisineDescription(description),
1691
+ isRetail: custom.get("is_retail") === "true",
1692
+ eta: custom.get("eta_display_string") ?? custom.get("cc_eta_string") ?? null,
1693
+ deliveryFee: custom.get("delivery_fee_string") ?? custom.get("modality_display_string") ?? null,
1694
+ imageUrl: asObject(asObject(object.images).main).uri ?? null,
1695
+ url,
1696
+ };
1697
+ }
1698
+ function parseMenuResponse(storepageFeed, requestedRestaurantId) {
1699
+ const root = asObject(storepageFeed);
1700
+ const storeHeader = asObject(root.storeHeader);
1701
+ const menuBook = asObject(root.menuBook);
1702
+ const itemLists = Array.isArray(root.itemLists) ? root.itemLists : [];
1703
+ const categories = itemLists.map((list) => {
1704
+ const object = asObject(list);
1705
+ const items = Array.isArray(object.items) ? object.items : [];
1706
+ return {
1707
+ id: typeof object.id === "string" ? object.id : "",
1708
+ name: typeof object.name === "string" ? object.name : "",
1709
+ description: typeof object.description === "string" ? object.description : null,
1710
+ itemCount: items.length,
1711
+ items: items.map(parseMenuItem).filter((item) => item !== null),
1712
+ };
1713
+ });
1714
+ return {
1715
+ success: true,
1716
+ restaurant: {
1717
+ id: typeof storeHeader.id === "string" ? storeHeader.id : requestedRestaurantId,
1718
+ name: typeof storeHeader.name === "string" ? storeHeader.name : null,
1719
+ description: typeof storeHeader.description === "string" ? storeHeader.description : null,
1720
+ businessName: asObject(storeHeader.business).name ?? null,
1721
+ displayAddress: asObject(storeHeader.address).displayAddress ?? null,
1722
+ averageRating: typeof asObject(storeHeader.ratings).averageRating === "number"
1723
+ ? asObject(storeHeader.ratings).averageRating
1724
+ : null,
1725
+ numRatingsDisplayString: asObject(storeHeader.ratings).numRatingsDisplayString ?? null,
1726
+ coverImgUrl: typeof storeHeader.coverImgUrl === "string" ? storeHeader.coverImgUrl : null,
1727
+ },
1728
+ menu: {
1729
+ id: typeof menuBook.id === "string" ? menuBook.id : null,
1730
+ name: typeof menuBook.name === "string" ? menuBook.name : null,
1731
+ },
1732
+ categoryCount: categories.length,
1733
+ itemCount: categories.reduce((sum, category) => sum + category.items.length, 0),
1734
+ categories,
1735
+ };
1736
+ }
1737
+ function parseItemResponse(itemPage, restaurantId) {
1738
+ const root = asObject(itemPage);
1739
+ const itemHeader = asObject(root.itemHeader);
1740
+ const optionLists = Array.isArray(root.optionLists) ? root.optionLists : [];
1741
+ const parsedOptionLists = optionLists.map(parseOptionList);
1742
+ return {
1743
+ success: true,
1744
+ restaurantId,
1745
+ item: {
1746
+ id: typeof itemHeader.id === "string" ? itemHeader.id : null,
1747
+ name: typeof itemHeader.name === "string" ? itemHeader.name : null,
1748
+ description: typeof itemHeader.description === "string" ? itemHeader.description : null,
1749
+ displayPrice: typeof itemHeader.displayString === "string" ? itemHeader.displayString : null,
1750
+ unitAmount: typeof itemHeader.unitAmount === "number" ? itemHeader.unitAmount : null,
1751
+ currency: typeof itemHeader.currency === "string" ? itemHeader.currency : null,
1752
+ decimalPlaces: typeof itemHeader.decimalPlaces === "number" ? itemHeader.decimalPlaces : null,
1753
+ menuId: typeof itemHeader.menuId === "string" ? itemHeader.menuId : null,
1754
+ specialInstructionsMaxLength: typeof itemHeader.specialInstructionsMaxLength === "number" ? itemHeader.specialInstructionsMaxLength : null,
1755
+ dietaryTags: Array.isArray(itemHeader.dietaryTagsList)
1756
+ ? itemHeader.dietaryTagsList.map((tag) => {
1757
+ const object = asObject(tag);
1758
+ return {
1759
+ type: typeof object.type === "string" ? object.type : null,
1760
+ abbreviatedTagDisplayString: typeof object.abbreviatedTagDisplayString === "string" ? object.abbreviatedTagDisplayString : null,
1761
+ fullTagDisplayString: typeof object.fullTagDisplayString === "string" ? object.fullTagDisplayString : null,
1762
+ };
1763
+ })
1764
+ : [],
1765
+ reviewData: root.itemHeader
1766
+ ? {
1767
+ ratingDisplayString: asObject(itemHeader.reviewData).ratingDisplayString ?? null,
1768
+ reviewCount: asObject(itemHeader.reviewData).reviewCount ?? null,
1769
+ itemReviewRankingCount: typeof asObject(itemHeader.reviewData).itemReviewRankingCount === "number"
1770
+ ? asObject(itemHeader.reviewData).itemReviewRankingCount
1771
+ : null,
1772
+ }
1773
+ : null,
1774
+ requiredOptionLists: parsedOptionLists.filter((group) => group.minNumOptions > 0 && !group.isOptional),
1775
+ optionLists: parsedOptionLists,
1776
+ preferences: Array.isArray(root.itemPreferences) ? root.itemPreferences : [],
1777
+ },
1778
+ };
1779
+ }
1780
+ function parseCartResponse(cartRoot) {
1781
+ const cart = cartRoot ? asObject(cartRoot) : {};
1782
+ const orders = Array.isArray(cart.orders) ? cart.orders : [];
1783
+ const items = orders.flatMap((order) => {
1784
+ const orderItems = Array.isArray(asObject(order).orderItems) ? asObject(order).orderItems : [];
1785
+ return orderItems.map((item) => {
1786
+ const object = asObject(item);
1787
+ return {
1788
+ cartItemId: typeof object.id === "string" ? object.id : "",
1789
+ itemId: asObject(object.item).id ?? null,
1790
+ name: asObject(object.item).name ?? null,
1791
+ quantity: typeof object.quantity === "number" ? object.quantity : 0,
1792
+ specialInstructions: typeof object.specialInstructions === "string" ? object.specialInstructions : object.specialInstructions ?? null,
1793
+ priceDisplayString: typeof object.priceDisplayString === "string" ? object.priceDisplayString : null,
1794
+ singlePrice: typeof object.singlePrice === "number" ? object.singlePrice : null,
1795
+ totalPrice: typeof object.priceOfTotalQuantity === "number" ? object.priceOfTotalQuantity : null,
1796
+ status: typeof object.cartItemStatusType === "string" ? object.cartItemStatusType : null,
1797
+ options: Array.isArray(object.options)
1798
+ ? object.options.map((option) => {
1799
+ const optionObject = asObject(option);
1800
+ return {
1801
+ id: typeof optionObject.id === "string" ? optionObject.id : "",
1802
+ name: typeof optionObject.name === "string" ? optionObject.name : null,
1803
+ quantity: typeof optionObject.quantity === "number" ? optionObject.quantity : null,
1804
+ };
1805
+ })
1806
+ : [],
1807
+ };
1808
+ });
1809
+ });
1810
+ return {
1811
+ success: true,
1812
+ cartId: typeof cart.id === "string" ? cart.id : null,
1813
+ subtotal: typeof cart.subtotal === "number" ? cart.subtotal : null,
1814
+ total: typeof cart.total === "number" ? cart.total : null,
1815
+ currencyCode: typeof cart.currencyCode === "string" ? cart.currencyCode : null,
1816
+ restaurant: cart.restaurant
1817
+ ? {
1818
+ id: asObject(cart.restaurant).id ?? null,
1819
+ name: asObject(cart.restaurant).name ?? null,
1820
+ slug: asObject(cart.restaurant).slug ?? null,
1821
+ businessName: asObject(asObject(cart.restaurant).business).name ?? null,
1822
+ }
1823
+ : null,
1824
+ menu: cart.menu
1825
+ ? {
1826
+ id: asObject(cart.menu).id ?? null,
1827
+ name: asObject(cart.menu).name ?? null,
1828
+ }
1829
+ : null,
1830
+ itemCount: items.length,
1831
+ items,
1832
+ };
1833
+ }
1834
+ export function parseExistingOrderLifecycleStatus(orderRoot) {
1835
+ const order = asObject(orderRoot);
1836
+ if (typeof order.cancelledAt === "string" && order.cancelledAt.length > 0) {
1837
+ return "cancelled";
1838
+ }
1839
+ if (typeof order.fulfilledAt === "string" && order.fulfilledAt.length > 0) {
1840
+ return "fulfilled";
1841
+ }
1842
+ if (typeof order.pollingInterval === "number" && order.pollingInterval > 0) {
1843
+ return "in-progress";
1844
+ }
1845
+ if (typeof order.submittedAt === "string" && order.submittedAt.length > 0) {
1846
+ return "submitted";
1847
+ }
1848
+ if (typeof order.createdAt === "string" && order.createdAt.length > 0) {
1849
+ return "draft";
1850
+ }
1851
+ return "unknown";
1852
+ }
1853
+ export function parseExistingOrdersResponse(orderRoots) {
1854
+ return orderRoots
1855
+ .map((order) => parseExistingOrder(order))
1856
+ .sort((left, right) => compareIsoDateDesc(left.createdAt, right.createdAt));
1857
+ }
1858
+ export function extractExistingOrdersFromApolloCache(cache) {
1859
+ if (!cache) {
1860
+ return [];
1861
+ }
1862
+ const rootQuery = asObject(cache.ROOT_QUERY);
1863
+ const key = Object.keys(rootQuery)
1864
+ .filter((entry) => entry.startsWith("getConsumerOrdersWithDetails("))
1865
+ .sort()[0];
1866
+ if (!key) {
1867
+ return [];
1868
+ }
1869
+ const values = Array.isArray(rootQuery[key]) ? rootQuery[key] : [];
1870
+ return values.map((value) => parseExistingOrder(value, cache)).sort((left, right) => compareIsoDateDesc(left.createdAt, right.createdAt));
1871
+ }
1872
+ function parseExistingOrder(orderRoot, cache = null) {
1873
+ const order = asObject(resolveApolloCacheValue(cache, orderRoot));
1874
+ const creator = order.creator ? asObject(resolveApolloCacheValue(cache, order.creator)) : null;
1875
+ const deliveryAddress = order.deliveryAddress ? asObject(resolveApolloCacheValue(cache, order.deliveryAddress)) : null;
1876
+ const store = order.store ? asObject(resolveApolloCacheValue(cache, order.store)) : null;
1877
+ const lifecycleStatus = parseExistingOrderLifecycleStatus(order);
1878
+ const items = parseExistingOrderItems(order.orders, cache);
1879
+ return {
1880
+ id: typeof order.id === "string" ? order.id : null,
1881
+ orderUuid: typeof order.orderUuid === "string" ? order.orderUuid : null,
1882
+ deliveryUuid: typeof order.deliveryUuid === "string" ? order.deliveryUuid : null,
1883
+ createdAt: typeof order.createdAt === "string" ? order.createdAt : null,
1884
+ submittedAt: typeof order.submittedAt === "string" ? order.submittedAt : null,
1885
+ cancelledAt: typeof order.cancelledAt === "string" ? order.cancelledAt : null,
1886
+ fulfilledAt: typeof order.fulfilledAt === "string" ? order.fulfilledAt : null,
1887
+ lifecycleStatus,
1888
+ isActive: lifecycleStatus === "submitted" || lifecycleStatus === "in-progress",
1889
+ hasLiveTracking: typeof order.pollingInterval === "number" && order.pollingInterval > 0,
1890
+ pollingIntervalSeconds: typeof order.pollingInterval === "number" ? order.pollingInterval : null,
1891
+ specialInstructions: typeof order.specialInstructions === "string" ? order.specialInstructions : null,
1892
+ isReorderable: Boolean(order.isReorderable),
1893
+ isGift: Boolean(order.isGift),
1894
+ isPickup: Boolean(order.isPickup),
1895
+ isRetail: Boolean(order.isRetail),
1896
+ isMerchantShipping: Boolean(order.isMerchantShipping),
1897
+ containsAlcohol: Boolean(order.containsAlcohol),
1898
+ fulfillmentType: typeof order.fulfillmentType === "string" ? order.fulfillmentType : null,
1899
+ shoppingProtocol: typeof order.shoppingProtocol === "string" ? order.shoppingProtocol : null,
1900
+ orderFilterType: typeof order.orderFilterType === "string" ? order.orderFilterType : null,
1901
+ creator: creator
1902
+ ? {
1903
+ id: typeof creator.id === "string" ? creator.id : null,
1904
+ firstName: typeof creator.firstName === "string" ? creator.firstName : null,
1905
+ lastName: typeof creator.lastName === "string" ? creator.lastName : null,
1906
+ }
1907
+ : null,
1908
+ deliveryAddress: deliveryAddress
1909
+ ? {
1910
+ id: typeof deliveryAddress.id === "string" ? deliveryAddress.id : null,
1911
+ formattedAddress: typeof deliveryAddress.formattedAddress === "string" ? deliveryAddress.formattedAddress : null,
1912
+ }
1913
+ : null,
1914
+ store: store
1915
+ ? {
1916
+ id: typeof store.id === "string" ? store.id : null,
1917
+ name: typeof store.name === "string" ? store.name : null,
1918
+ businessName: typeof asObject(resolveApolloCacheValue(cache, store.business)).name === "string"
1919
+ ? asObject(resolveApolloCacheValue(cache, store.business)).name
1920
+ : null,
1921
+ phoneNumber: typeof store.phoneNumber === "string" ? store.phoneNumber : null,
1922
+ fulfillsOwnDeliveries: typeof store.fulfillsOwnDeliveries === "boolean" ? store.fulfillsOwnDeliveries : null,
1923
+ customerArrivedPickupInstructions: typeof store.customerArrivedPickupInstructions === "string" ? store.customerArrivedPickupInstructions : null,
1924
+ rerouteStoreId: typeof store.rerouteStoreId === "string" ? store.rerouteStoreId : null,
1925
+ }
1926
+ : null,
1927
+ grandTotal: parseExistingOrderMoney(order.grandTotal, cache),
1928
+ itemCount: items.length,
1929
+ items,
1930
+ likelyOutOfStockItems: Array.isArray(order.likelyOosItems)
1931
+ ? order.likelyOosItems.map((item) => {
1932
+ const object = asObject(resolveApolloCacheValue(cache, item));
1933
+ return {
1934
+ menuItemId: typeof object.menuItemId === "string" ? object.menuItemId : null,
1935
+ name: typeof object.name === "string" ? object.name : null,
1936
+ photoUrl: typeof object.photoUrl === "string" ? object.photoUrl : null,
1937
+ };
1938
+ })
1939
+ : [],
1940
+ recurringOrderDetails: order.recurringOrderDetails
1941
+ ? {
1942
+ itemNames: Array.isArray(asObject(resolveApolloCacheValue(cache, order.recurringOrderDetails)).itemNames)
1943
+ ? asObject(resolveApolloCacheValue(cache, order.recurringOrderDetails)).itemNames.filter((value) => typeof value === "string")
1944
+ : [],
1945
+ consumerId: typeof asObject(resolveApolloCacheValue(cache, order.recurringOrderDetails)).consumerId === "string"
1946
+ ? asObject(resolveApolloCacheValue(cache, order.recurringOrderDetails)).consumerId
1947
+ : null,
1948
+ recurringOrderUpcomingOrderUuid: typeof asObject(resolveApolloCacheValue(cache, order.recurringOrderDetails)).recurringOrderUpcomingOrderUuid === "string"
1949
+ ? asObject(resolveApolloCacheValue(cache, order.recurringOrderDetails)).recurringOrderUpcomingOrderUuid
1950
+ : null,
1951
+ scheduledDeliveryDate: typeof asObject(resolveApolloCacheValue(cache, order.recurringOrderDetails)).scheduledDeliveryDate === "string"
1952
+ ? asObject(resolveApolloCacheValue(cache, order.recurringOrderDetails)).scheduledDeliveryDate
1953
+ : null,
1954
+ arrivalTimeDisplayString: typeof asObject(resolveApolloCacheValue(cache, order.recurringOrderDetails)).arrivalTimeDisplayString === "string"
1955
+ ? asObject(resolveApolloCacheValue(cache, order.recurringOrderDetails)).arrivalTimeDisplayString
1956
+ : null,
1957
+ storeName: typeof asObject(resolveApolloCacheValue(cache, order.recurringOrderDetails)).storeName === "string"
1958
+ ? asObject(resolveApolloCacheValue(cache, order.recurringOrderDetails)).storeName
1959
+ : null,
1960
+ isCancelled: typeof asObject(resolveApolloCacheValue(cache, order.recurringOrderDetails)).isCancelled === "boolean"
1961
+ ? asObject(resolveApolloCacheValue(cache, order.recurringOrderDetails)).isCancelled
1962
+ : null,
1963
+ }
1964
+ : null,
1965
+ bundleOrderInfo: order.bundleOrderInfo
1966
+ ? {
1967
+ primaryBundleOrderUuid: typeof asObject(resolveApolloCacheValue(cache, order.bundleOrderInfo)).primaryBundleOrderUuid === "string"
1968
+ ? asObject(resolveApolloCacheValue(cache, order.bundleOrderInfo)).primaryBundleOrderUuid
1969
+ : null,
1970
+ primaryBundleOrderId: typeof asObject(resolveApolloCacheValue(cache, order.bundleOrderInfo)).primaryBundleOrderId === "string"
1971
+ ? asObject(resolveApolloCacheValue(cache, order.bundleOrderInfo)).primaryBundleOrderId
1972
+ : null,
1973
+ bundleOrderUuids: Array.isArray(asObject(resolveApolloCacheValue(cache, order.bundleOrderInfo)).bundleOrderUuids)
1974
+ ? asObject(resolveApolloCacheValue(cache, order.bundleOrderInfo)).bundleOrderUuids.filter((value) => typeof value === "string")
1975
+ : [],
1976
+ bundleType: typeof asObject(resolveApolloCacheValue(cache, asObject(resolveApolloCacheValue(cache, order.bundleOrderInfo)).bundleOrderConfig)).bundleType === "string"
1977
+ ? asObject(resolveApolloCacheValue(cache, asObject(resolveApolloCacheValue(cache, order.bundleOrderInfo)).bundleOrderConfig)).bundleType
1978
+ : null,
1979
+ bundleOrderRole: typeof asObject(resolveApolloCacheValue(cache, asObject(resolveApolloCacheValue(cache, order.bundleOrderInfo)).bundleOrderConfig)).bundleOrderRole === "string"
1980
+ ? asObject(resolveApolloCacheValue(cache, asObject(resolveApolloCacheValue(cache, order.bundleOrderInfo)).bundleOrderConfig)).bundleOrderRole
1981
+ : null,
1982
+ }
1983
+ : null,
1984
+ cancellationPendingRefundState: typeof asObject(resolveApolloCacheValue(cache, order.cancellationPendingRefundInfo)).state === "string"
1985
+ ? asObject(resolveApolloCacheValue(cache, order.cancellationPendingRefundInfo)).state
1986
+ : null,
1987
+ };
1988
+ }
1989
+ function parseExistingOrderMoney(value, cache) {
1990
+ if (!value) {
1991
+ return null;
1992
+ }
1993
+ const object = asObject(resolveApolloCacheValue(cache, value));
1994
+ return {
1995
+ unitAmount: typeof object.unitAmount === "number" ? object.unitAmount : null,
1996
+ currency: typeof object.currency === "string" ? object.currency : null,
1997
+ decimalPlaces: typeof object.decimalPlaces === "number" ? object.decimalPlaces : null,
1998
+ displayString: typeof object.displayString === "string" ? object.displayString : null,
1999
+ sign: typeof object.sign === "string" ? object.sign : null,
2000
+ };
2001
+ }
2002
+ function parseExistingOrderItems(value, cache) {
2003
+ const groups = Array.isArray(value) ? value : [];
2004
+ return groups.flatMap((group) => {
2005
+ const object = asObject(resolveApolloCacheValue(cache, group));
2006
+ const items = Array.isArray(object.items) ? object.items : [];
2007
+ return items.map((item) => parseExistingOrderItem(item, cache));
2008
+ });
2009
+ }
2010
+ function parseExistingOrderItem(value, cache) {
2011
+ const object = asObject(resolveApolloCacheValue(cache, value));
2012
+ return {
2013
+ id: typeof object.id === "string" ? object.id : null,
2014
+ name: typeof object.name === "string" ? object.name : null,
2015
+ quantity: typeof object.quantity === "number" ? object.quantity : 0,
2016
+ specialInstructions: typeof object.specialInstructions === "string" ? object.specialInstructions : null,
2017
+ substitutionPreferences: typeof object.substitutionPreferences === "string" ? object.substitutionPreferences : null,
2018
+ originalItemPrice: typeof object.originalItemPrice === "number" ? object.originalItemPrice : null,
2019
+ purchaseType: typeof object.purchaseType === "string" ? object.purchaseType : null,
2020
+ purchaseQuantity: parseExistingOrderQuantity(object.purchaseQuantity, cache),
2021
+ fulfillQuantity: parseExistingOrderQuantity(object.fulfillQuantity, cache),
2022
+ extras: Array.isArray(object.orderItemExtras)
2023
+ ? object.orderItemExtras.map((extra) => parseExistingOrderExtra(extra, cache))
2024
+ : [],
2025
+ };
2026
+ }
2027
+ function parseExistingOrderQuantity(value, cache) {
2028
+ if (!value) {
2029
+ return null;
2030
+ }
2031
+ const quantityRoot = asObject(resolveApolloCacheValue(cache, value));
2032
+ const continuous = asObject(resolveApolloCacheValue(cache, quantityRoot.continuousQuantity));
2033
+ const discrete = asObject(resolveApolloCacheValue(cache, quantityRoot.discreteQuantity));
2034
+ const candidate = typeof continuous.quantity === "number" ? continuous : discrete;
2035
+ return {
2036
+ quantity: typeof candidate.quantity === "number" ? candidate.quantity : null,
2037
+ unit: typeof candidate.unit === "string" ? candidate.unit : null,
2038
+ };
2039
+ }
2040
+ function parseExistingOrderExtra(value, cache) {
2041
+ const object = asObject(resolveApolloCacheValue(cache, value));
2042
+ return {
2043
+ menuItemExtraId: typeof object.menuItemExtraId === "string" ? object.menuItemExtraId : null,
2044
+ name: typeof object.name === "string" ? object.name : null,
2045
+ options: Array.isArray(object.orderItemExtraOptions)
2046
+ ? object.orderItemExtraOptions.map((option) => parseExistingOrderExtraOption(option, cache))
2047
+ : [],
2048
+ };
2049
+ }
2050
+ function parseExistingOrderExtraOption(value, cache) {
2051
+ const object = asObject(resolveApolloCacheValue(cache, value));
2052
+ return {
2053
+ menuExtraOptionId: typeof object.menuExtraOptionId === "string" ? object.menuExtraOptionId : null,
2054
+ name: typeof object.name === "string" ? object.name : null,
2055
+ description: typeof object.description === "string" ? object.description : null,
2056
+ price: typeof object.price === "number" ? object.price : null,
2057
+ quantity: typeof object.quantity === "number" ? object.quantity : null,
2058
+ extras: Array.isArray(object.orderItemExtras)
2059
+ ? object.orderItemExtras.map((extra) => parseExistingOrderExtra(extra, cache))
2060
+ : [],
2061
+ };
2062
+ }
2063
+ function parseOptionList(optionList) {
2064
+ const object = asObject(optionList);
2065
+ const options = Array.isArray(object.options) ? object.options : [];
2066
+ return {
2067
+ id: typeof object.id === "string" ? object.id : "",
2068
+ name: typeof object.name === "string" ? object.name : "",
2069
+ subtitle: typeof object.subtitle === "string" ? object.subtitle : null,
2070
+ minNumOptions: typeof object.minNumOptions === "number" ? object.minNumOptions : 0,
2071
+ maxNumOptions: typeof object.maxNumOptions === "number" ? object.maxNumOptions : 0,
2072
+ numFreeOptions: typeof object.numFreeOptions === "number" ? object.numFreeOptions : 0,
2073
+ isOptional: Boolean(object.isOptional),
2074
+ options: options.map((option) => {
2075
+ const optionObject = asObject(option);
2076
+ return {
2077
+ id: typeof optionObject.id === "string" ? optionObject.id : "",
2078
+ name: typeof optionObject.name === "string" ? optionObject.name : "",
2079
+ displayPrice: typeof optionObject.displayString === "string" ? optionObject.displayString : null,
2080
+ unitAmount: typeof optionObject.unitAmount === "number" ? optionObject.unitAmount : null,
2081
+ defaultQuantity: typeof optionObject.defaultQuantity === "number" ? optionObject.defaultQuantity : null,
2082
+ nextCursor: typeof optionObject.nextCursor === "string" ? optionObject.nextCursor : null,
2083
+ };
2084
+ }),
2085
+ };
2086
+ }
2087
+ function parseMenuItem(item) {
2088
+ const object = asObject(item);
2089
+ if (typeof object.id !== "string" || typeof object.name !== "string") {
2090
+ return null;
2091
+ }
2092
+ return {
2093
+ id: object.id,
2094
+ name: object.name,
2095
+ description: typeof object.description === "string" ? object.description : null,
2096
+ displayPrice: typeof object.displayPrice === "string" ? object.displayPrice : null,
2097
+ imageUrl: typeof object.imageUrl === "string" ? object.imageUrl : null,
2098
+ nextCursor: typeof object.nextCursor === "string" ? object.nextCursor : null,
2099
+ storeId: typeof object.storeId === "string" ? object.storeId : null,
2100
+ };
2101
+ }
2102
+ async function resolveMenuItem(restaurantId, itemId, itemName) {
2103
+ const menu = await getMenuDirect(restaurantId);
2104
+ const allItems = dedupeBy(menu.categories.flatMap((category) => category.items), (item) => item.id);
2105
+ const resolved = itemId
2106
+ ? allItems.find((item) => item.id === itemId)
2107
+ : resolveMenuItemByName(allItems, itemName ?? "");
2108
+ if (!resolved) {
2109
+ throw new Error(itemId ? `Could not find item ${itemId} in restaurant ${restaurantId}.` : `Could not find item named \"${itemName}\".`);
2110
+ }
2111
+ const itemDetail = await getItemDirect(restaurantId, resolved.id);
2112
+ return { item: resolved, itemDetail };
2113
+ }
2114
+ function resolveMenuItemByName(items, itemName) {
2115
+ const normalized = normalizeItemName(itemName);
2116
+ const exactMatches = items.filter((item) => normalizeItemName(item.name) === normalized);
2117
+ if (exactMatches.length === 1) {
2118
+ return exactMatches[0];
2119
+ }
2120
+ if (exactMatches.length > 1) {
2121
+ throw new Error(`Multiple items matched \"${itemName}\". Use --item-id instead. Matching item IDs: ${exactMatches.map((item) => item.id).join(", ")}`);
2122
+ }
2123
+ const fuzzyMatches = items.filter((item) => normalizeItemName(item.name).includes(normalized));
2124
+ if (fuzzyMatches.length === 1) {
2125
+ return fuzzyMatches[0];
2126
+ }
2127
+ if (fuzzyMatches.length > 1) {
2128
+ throw new Error(`Multiple items partially matched \"${itemName}\". Use --item-id instead. Matching item IDs: ${fuzzyMatches.map((item) => item.id).join(", ")}`);
2129
+ }
2130
+ return undefined;
2131
+ }
2132
+ function parseGraphQlResponse(operationName, status, text) {
2133
+ const parsed = safeJsonParse(text);
2134
+ if (!parsed) {
2135
+ throw new Error(`DoorDash ${operationName} returned HTTP ${status} with a non-JSON response. This usually means the stored session or anti-bot state needs to be refreshed. Response snippet: ${truncate(text, 240)}`);
2136
+ }
2137
+ if (Array.isArray(parsed.errors) && parsed.errors.length > 0) {
2138
+ const message = parsed.errors.map((error) => error.message ?? "Unknown GraphQL error").join("; ");
2139
+ throw new Error(`DoorDash ${operationName} failed: ${message}`);
2140
+ }
2141
+ if (!parsed.data) {
2142
+ throw new Error(`DoorDash ${operationName} returned no data.`);
2143
+ }
2144
+ return parsed.data;
2145
+ }
2146
+ function asObject(value) {
2147
+ return value && typeof value === "object" ? value : {};
2148
+ }
2149
+ async function fetchExistingOrdersGraphql(params) {
2150
+ const requestedLimit = params.limit;
2151
+ const orders = [];
2152
+ let offset = 0;
2153
+ while (true) {
2154
+ const pageSize = requestedLimit == null ? 25 : Math.min(25, Math.max(requestedLimit - orders.length, 0));
2155
+ if (requestedLimit != null && pageSize === 0) {
2156
+ break;
2157
+ }
2158
+ const data = await session.graphql("getConsumerOrdersWithDetails", EXISTING_ORDERS_QUERY, {
2159
+ offset,
2160
+ limit: pageSize === 0 ? 25 : pageSize,
2161
+ includeCancelled: true,
2162
+ });
2163
+ const batch = parseExistingOrdersResponse(Array.isArray(data.getConsumerOrdersWithDetails) ? data.getConsumerOrdersWithDetails : []);
2164
+ if (batch.length === 0) {
2165
+ break;
2166
+ }
2167
+ orders.push(...batch);
2168
+ offset += batch.length;
2169
+ if (batch.length < (pageSize === 0 ? 25 : pageSize)) {
2170
+ break;
2171
+ }
2172
+ }
2173
+ return requestedLimit == null ? orders : orders.slice(0, requestedLimit);
2174
+ }
2175
+ function buildOrdersResult(input) {
2176
+ const filtered = input.activeOnly ? input.orders.filter((order) => order.isActive) : input.orders;
2177
+ const limited = input.requestedLimit == null ? filtered : filtered.slice(0, input.requestedLimit);
2178
+ return {
2179
+ success: true,
2180
+ source: input.source,
2181
+ warning: input.warning,
2182
+ count: limited.length,
2183
+ activeCount: limited.filter((order) => order.isActive).length,
2184
+ orders: limited,
2185
+ };
2186
+ }
2187
+ function findExistingOrderByIdentifier(orders, orderId) {
2188
+ for (const field of ["orderUuid", "deliveryUuid", "id"]) {
2189
+ const match = orders.find((order) => order[field] === orderId);
2190
+ if (match) {
2191
+ return { matchedField: field, order: match };
2192
+ }
2193
+ }
2194
+ return null;
2195
+ }
2196
+ function resolveApolloCacheValue(cache, value) {
2197
+ if (!cache) {
2198
+ return value;
2199
+ }
2200
+ if (typeof value === "string" && value in cache) {
2201
+ return cache[value];
2202
+ }
2203
+ const object = asObject(value);
2204
+ if (typeof object.__ref === "string" && object.__ref in cache) {
2205
+ return cache[object.__ref];
2206
+ }
2207
+ return value;
2208
+ }
2209
+ function compareIsoDateDesc(left, right) {
2210
+ const leftValue = left ? Date.parse(left) : Number.NEGATIVE_INFINITY;
2211
+ const rightValue = right ? Date.parse(right) : Number.NEGATIVE_INFINITY;
2212
+ return rightValue - leftValue;
2213
+ }
2214
+ function isOrderHistoryChallengeError(error) {
2215
+ if (!(error instanceof Error)) {
2216
+ return false;
2217
+ }
2218
+ return /non-JSON response|Checking if the site connection is secured|cf-mitigated|DoorDash getConsumerOrdersWithDetails returned HTTP 403/i.test(error.message);
2219
+ }
2220
+ function isRetryablePageEvaluateError(error) {
2221
+ if (!(error instanceof Error)) {
2222
+ return false;
2223
+ }
2224
+ return /Execution context was destroyed|Cannot find context with specified id|Target page, context or browser has been closed/i.test(error.message);
2225
+ }
2226
+ function safeJsonParse(value) {
2227
+ if (!value) {
2228
+ return null;
2229
+ }
2230
+ try {
2231
+ return JSON.parse(value);
2232
+ }
2233
+ catch {
2234
+ return null;
2235
+ }
2236
+ }
2237
+ function extractStoreId(url) {
2238
+ if (!url) {
2239
+ return null;
2240
+ }
2241
+ const match = url.match(/\/(?:convenience\/)?store\/(\d+)(?:\/|\?|$)/);
2242
+ return match?.[1] ?? null;
2243
+ }
2244
+ function extractIdFromFacetId(value) {
2245
+ const match = value.match(/row\.store:(?:ad_)?(\d+):/);
2246
+ return match?.[1] ?? null;
2247
+ }
2248
+ function parseCuisineDescription(description) {
2249
+ if (!description) {
2250
+ return [];
2251
+ }
2252
+ return description
2253
+ .split("•")
2254
+ .map((part) => part.trim())
2255
+ .filter((part) => part.length > 0 && !/^\$+$/.test(part));
2256
+ }
2257
+ function dedupeBy(values, keyFn) {
2258
+ const seen = new Set();
2259
+ const deduped = [];
2260
+ for (const value of values) {
2261
+ const key = keyFn(value);
2262
+ if (seen.has(key)) {
2263
+ continue;
2264
+ }
2265
+ seen.add(key);
2266
+ deduped.push(value);
2267
+ }
2268
+ return deduped;
2269
+ }
2270
+ function truncate(value, length) {
2271
+ return value.length <= length ? value : `${value.slice(0, length)}...`;
2272
+ }
2273
+ async function ensureConfigDir() {
2274
+ await mkdir(dirname(getCookiesPath()), { recursive: true });
2275
+ }
2276
+ async function hasStorageState() {
2277
+ try {
2278
+ await readFile(getStorageStatePath(), "utf8");
2279
+ return true;
2280
+ }
2281
+ catch {
2282
+ return false;
2283
+ }
2284
+ }
2285
+ async function readStoredCookies() {
2286
+ try {
2287
+ const raw = await readFile(getCookiesPath(), "utf8");
2288
+ const parsed = JSON.parse(raw);
2289
+ return Array.isArray(parsed) ? parsed : [];
2290
+ }
2291
+ catch {
2292
+ return [];
2293
+ }
2294
+ }
2295
+ export function getStorageStatePath() {
2296
+ return join(dirname(getCookiesPath()), "storage-state.json");
2297
+ }