File size: 3,842 Bytes
5d0a52f
 
 
 
 
 
 
 
 
85aec43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347f81b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5d0a52f
 
 
 
 
 
85aec43
5d0a52f
85aec43
5d0a52f
 
85aec43
5d0a52f
85aec43
5d0a52f
347f81b
 
 
 
85aec43
5d0a52f
85aec43
5d0a52f
 
 
 
 
 
85aec43
347f81b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85aec43
347f81b
 
85aec43
5d0a52f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/**
 * Fingerprint manager — builds headers that mimic the Codex Desktop client.
 *
 * Based on Codex source: applyDesktopAuthHeaders / buildDesktopUserAgent
 */

import { getConfig, getFingerprint } from "../config.js";
import { extractChatGptAccountId } from "../auth/jwt-utils.js";

/**
 * Reorder headers according to the fingerprint header_order config.
 * Keys not in the order list are appended at the end.
 */
function orderHeaders(
  headers: Record<string, string>,
  order: string[],
): Record<string, string> {
  const ordered: Record<string, string> = {};
  for (const key of order) {
    if (key in headers) {
      ordered[key] = headers[key];
    }
  }
  for (const key of Object.keys(headers)) {
    if (!(key in ordered)) {
      ordered[key] = headers[key];
    }
  }
  return ordered;
}

/**
 * Build the dynamic sec-ch-ua value based on chromium_version from config.
 */
function buildSecChUa(): string {
  const cv = getConfig().client.chromium_version;
  return `"Chromium";v="${cv}", "Not:A-Brand";v="24"`;
}

/**
 * Build the User-Agent string from config + fingerprint template.
 */
function buildUserAgent(): string {
  const config = getConfig();
  const fp = getFingerprint();
  return fp.user_agent_template
    .replace("{version}", config.client.app_version)
    .replace("{platform}", config.client.platform)
    .replace("{arch}", config.client.arch);
}

/**
 * Build raw headers (unordered) with all fingerprint fields.
 * Does NOT include Authorization, ChatGPT-Account-Id, Content-Type, or Accept.
 */
function buildRawDefaultHeaders(): Record<string, string> {
  const fp = getFingerprint();
  const raw: Record<string, string> = {};

  raw["User-Agent"] = buildUserAgent();
  raw["sec-ch-ua"] = buildSecChUa();

  // Add static default headers (Accept-Encoding, Accept-Language, sec-fetch-*, etc.)
  if (fp.default_headers) {
    for (const [key, value] of Object.entries(fp.default_headers)) {
      raw[key] = value;
    }
  }

  return raw;
}

/**
 * Build anonymous headers for non-authenticated requests (OAuth, appcast, etc.).
 * Contains User-Agent, sec-ch-ua, Accept-Encoding, Accept-Language, sec-fetch-*
 * but NOT Authorization, Cookie, or ChatGPT-Account-Id.
 * Headers are ordered per fingerprint config.
 */
export function buildAnonymousHeaders(): Record<string, string> {
  const fp = getFingerprint();
  const raw = buildRawDefaultHeaders();
  return orderHeaders(raw, fp.header_order);
}

export function buildHeaders(
  token: string,
  accountId?: string | null,
): Record<string, string> {
  const config = getConfig();
  const fp = getFingerprint();
  const raw: Record<string, string> = {};

  raw["Authorization"] = `Bearer ${token}`;

  const acctId = accountId ?? extractChatGptAccountId(token);
  if (acctId) raw["ChatGPT-Account-Id"] = acctId;

  raw["originator"] = config.client.originator;

  // Merge default headers (User-Agent, sec-ch-ua, Accept-Encoding, etc.)
  const defaults = buildRawDefaultHeaders();
  for (const [key, value] of Object.entries(defaults)) {
    raw[key] = value;
  }

  return orderHeaders(raw, fp.header_order);
}

export function buildHeadersWithContentType(
  token: string,
  accountId?: string | null,
): Record<string, string> {
  const fp = getFingerprint();
  const config = getConfig();
  const raw: Record<string, string> = {};

  raw["Authorization"] = `Bearer ${token}`;

  const acctId = accountId ?? extractChatGptAccountId(token);
  if (acctId) raw["ChatGPT-Account-Id"] = acctId;

  raw["originator"] = config.client.originator;

  // Merge default headers
  const defaults = buildRawDefaultHeaders();
  for (const [key, value] of Object.entries(defaults)) {
    raw[key] = value;
  }

  raw["Content-Type"] = "application/json";

  // Single orderHeaders call (no double-sorting)
  return orderHeaders(raw, fp.header_order);
}