@deepdream314/remodex 1.3.8
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/bin/phodex.js +10 -0
- package/bin/remodex.js +314 -0
- package/package.json +32 -0
- package/src/account-status.js +144 -0
- package/src/bridge.js +1259 -0
- package/src/codex-desktop-refresher.js +765 -0
- package/src/codex-transport.js +244 -0
- package/src/daemon-state.js +150 -0
- package/src/desktop-handler.js +390 -0
- package/src/git-handler.js +1267 -0
- package/src/index.js +35 -0
- package/src/macos-launch-agent.js +455 -0
- package/src/notifications-handler.js +129 -0
- package/src/package-version-status.js +188 -0
- package/src/private-defaults.json +4 -0
- package/src/push-notification-completion-dedupe.js +147 -0
- package/src/push-notification-service-client.js +157 -0
- package/src/push-notification-tracker.js +688 -0
- package/src/qr.js +19 -0
- package/src/rollout-live-mirror.js +735 -0
- package/src/rollout-watch.js +840 -0
- package/src/scripts/codex-handoff.applescript +100 -0
- package/src/scripts/codex-refresh.applescript +51 -0
- package/src/secure-device-state.js +394 -0
- package/src/secure-transport.js +738 -0
- package/src/session-state.js +57 -0
- package/src/thread-context-handler.js +80 -0
- package/src/utf8-chunk-decoder.js +36 -0
- package/src/voice-handler.js +321 -0
- package/src/workspace-handler.js +464 -0
package/bin/phodex.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// FILE: phodex.js
|
|
3
|
+
// Purpose: Backward-compatible wrapper that forwards legacy `phodex up` usage to `remodex up`.
|
|
4
|
+
// Layer: CLI binary
|
|
5
|
+
// Exports: none
|
|
6
|
+
// Depends on: ./remodex
|
|
7
|
+
|
|
8
|
+
const { main } = require("./remodex");
|
|
9
|
+
|
|
10
|
+
void main();
|
package/bin/remodex.js
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// FILE: remodex.js
|
|
3
|
+
// Purpose: CLI surface for foreground bridge runs, pairing reset, thread resume, and macOS service control.
|
|
4
|
+
// Layer: CLI binary
|
|
5
|
+
// Exports: none
|
|
6
|
+
// Depends on: ../src
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
getMacOSBridgeServiceStatus,
|
|
10
|
+
printMacOSBridgePairingQr,
|
|
11
|
+
printMacOSBridgeServiceStatus,
|
|
12
|
+
readBridgeConfig,
|
|
13
|
+
resetMacOSBridgePairing,
|
|
14
|
+
runMacOSBridgeService,
|
|
15
|
+
startBridge,
|
|
16
|
+
startMacOSBridgeService,
|
|
17
|
+
stopMacOSBridgeService,
|
|
18
|
+
resetBridgePairing,
|
|
19
|
+
openLastActiveThread,
|
|
20
|
+
watchThreadRollout,
|
|
21
|
+
} = require("../src");
|
|
22
|
+
const { version } = require("../package.json");
|
|
23
|
+
|
|
24
|
+
const defaultDeps = {
|
|
25
|
+
getMacOSBridgeServiceStatus,
|
|
26
|
+
printMacOSBridgePairingQr,
|
|
27
|
+
printMacOSBridgeServiceStatus,
|
|
28
|
+
readBridgeConfig,
|
|
29
|
+
resetMacOSBridgePairing,
|
|
30
|
+
runMacOSBridgeService,
|
|
31
|
+
startBridge,
|
|
32
|
+
startMacOSBridgeService,
|
|
33
|
+
stopMacOSBridgeService,
|
|
34
|
+
resetBridgePairing,
|
|
35
|
+
openLastActiveThread,
|
|
36
|
+
watchThreadRollout,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
if (require.main === module) {
|
|
40
|
+
void main();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ─── ENTRY POINT ─────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
async function main({
|
|
46
|
+
argv = process.argv,
|
|
47
|
+
platform = process.platform,
|
|
48
|
+
consoleImpl = console,
|
|
49
|
+
exitImpl = process.exit,
|
|
50
|
+
deps = defaultDeps,
|
|
51
|
+
} = {}) {
|
|
52
|
+
const { command, jsonOutput, watchThreadId } = parseCliArgs(argv.slice(2));
|
|
53
|
+
|
|
54
|
+
if (isVersionCommand(command)) {
|
|
55
|
+
emitVersion({ jsonOutput, consoleImpl });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (command === "up") {
|
|
60
|
+
if (platform === "darwin") {
|
|
61
|
+
const result = await deps.startMacOSBridgeService({
|
|
62
|
+
waitForPairing: true,
|
|
63
|
+
});
|
|
64
|
+
deps.printMacOSBridgePairingQr({
|
|
65
|
+
pairingSession: result.pairingSession,
|
|
66
|
+
});
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
deps.startBridge();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (command === "run") {
|
|
75
|
+
deps.startBridge();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (command === "run-service") {
|
|
80
|
+
deps.runMacOSBridgeService();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (command === "start") {
|
|
85
|
+
assertMacOSCommand(command, {
|
|
86
|
+
platform,
|
|
87
|
+
consoleImpl,
|
|
88
|
+
exitImpl,
|
|
89
|
+
});
|
|
90
|
+
deps.readBridgeConfig();
|
|
91
|
+
const result = await deps.startMacOSBridgeService({
|
|
92
|
+
waitForPairing: false,
|
|
93
|
+
});
|
|
94
|
+
emitResult({
|
|
95
|
+
payload: {
|
|
96
|
+
ok: true,
|
|
97
|
+
currentVersion: version,
|
|
98
|
+
plistPath: result?.plistPath,
|
|
99
|
+
pairingSession: result?.pairingSession,
|
|
100
|
+
},
|
|
101
|
+
message: "[remodex] macOS bridge service is running.",
|
|
102
|
+
jsonOutput,
|
|
103
|
+
consoleImpl,
|
|
104
|
+
});
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (command === "restart") {
|
|
109
|
+
assertMacOSCommand(command, {
|
|
110
|
+
platform,
|
|
111
|
+
consoleImpl,
|
|
112
|
+
exitImpl,
|
|
113
|
+
});
|
|
114
|
+
deps.readBridgeConfig();
|
|
115
|
+
const result = await deps.startMacOSBridgeService({
|
|
116
|
+
waitForPairing: false,
|
|
117
|
+
});
|
|
118
|
+
emitResult({
|
|
119
|
+
payload: {
|
|
120
|
+
ok: true,
|
|
121
|
+
currentVersion: version,
|
|
122
|
+
plistPath: result?.plistPath,
|
|
123
|
+
pairingSession: result?.pairingSession,
|
|
124
|
+
},
|
|
125
|
+
message: "[remodex] macOS bridge service restarted.",
|
|
126
|
+
jsonOutput,
|
|
127
|
+
consoleImpl,
|
|
128
|
+
});
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (command === "stop") {
|
|
133
|
+
assertMacOSCommand(command, {
|
|
134
|
+
platform,
|
|
135
|
+
consoleImpl,
|
|
136
|
+
exitImpl,
|
|
137
|
+
});
|
|
138
|
+
deps.stopMacOSBridgeService();
|
|
139
|
+
emitResult({
|
|
140
|
+
payload: {
|
|
141
|
+
ok: true,
|
|
142
|
+
currentVersion: version,
|
|
143
|
+
},
|
|
144
|
+
message: "[remodex] macOS bridge service stopped.",
|
|
145
|
+
jsonOutput,
|
|
146
|
+
consoleImpl,
|
|
147
|
+
});
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (command === "status") {
|
|
152
|
+
assertMacOSCommand(command, {
|
|
153
|
+
platform,
|
|
154
|
+
consoleImpl,
|
|
155
|
+
exitImpl,
|
|
156
|
+
});
|
|
157
|
+
if (jsonOutput) {
|
|
158
|
+
emitJson({
|
|
159
|
+
...deps.getMacOSBridgeServiceStatus(),
|
|
160
|
+
currentVersion: version,
|
|
161
|
+
});
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
deps.printMacOSBridgeServiceStatus();
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (command === "reset-pairing") {
|
|
169
|
+
try {
|
|
170
|
+
if (platform === "darwin") {
|
|
171
|
+
deps.resetMacOSBridgePairing();
|
|
172
|
+
emitResult({
|
|
173
|
+
payload: {
|
|
174
|
+
ok: true,
|
|
175
|
+
currentVersion: version,
|
|
176
|
+
platform: "darwin",
|
|
177
|
+
},
|
|
178
|
+
message: "[remodex] Stopped the macOS bridge service and cleared the saved pairing state. Run `remodex up` to pair again.",
|
|
179
|
+
jsonOutput,
|
|
180
|
+
consoleImpl,
|
|
181
|
+
});
|
|
182
|
+
} else {
|
|
183
|
+
deps.resetBridgePairing();
|
|
184
|
+
emitResult({
|
|
185
|
+
payload: {
|
|
186
|
+
ok: true,
|
|
187
|
+
currentVersion: version,
|
|
188
|
+
platform,
|
|
189
|
+
},
|
|
190
|
+
message: "[remodex] Cleared the saved pairing state. Run `remodex up` to pair again.",
|
|
191
|
+
jsonOutput,
|
|
192
|
+
consoleImpl,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
} catch (error) {
|
|
196
|
+
consoleImpl.error(`[remodex] ${(error && error.message) || "Failed to clear the saved pairing state."}`);
|
|
197
|
+
exitImpl(1);
|
|
198
|
+
}
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (command === "resume") {
|
|
203
|
+
try {
|
|
204
|
+
const state = deps.openLastActiveThread();
|
|
205
|
+
emitResult({
|
|
206
|
+
payload: {
|
|
207
|
+
ok: true,
|
|
208
|
+
currentVersion: version,
|
|
209
|
+
threadId: state.threadId,
|
|
210
|
+
source: state.source || "unknown",
|
|
211
|
+
},
|
|
212
|
+
message: `[remodex] Opened last active thread: ${state.threadId} (${state.source || "unknown"})`,
|
|
213
|
+
jsonOutput,
|
|
214
|
+
consoleImpl,
|
|
215
|
+
});
|
|
216
|
+
} catch (error) {
|
|
217
|
+
consoleImpl.error(`[remodex] ${(error && error.message) || "Failed to reopen the last thread."}`);
|
|
218
|
+
exitImpl(1);
|
|
219
|
+
}
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (command === "watch") {
|
|
224
|
+
try {
|
|
225
|
+
deps.watchThreadRollout(watchThreadId);
|
|
226
|
+
} catch (error) {
|
|
227
|
+
consoleImpl.error(`[remodex] ${(error && error.message) || "Failed to watch the thread rollout."}`);
|
|
228
|
+
exitImpl(1);
|
|
229
|
+
}
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
consoleImpl.error(`Unknown command: ${command}`);
|
|
234
|
+
consoleImpl.error(
|
|
235
|
+
"Usage: remodex up | remodex run | remodex start | remodex restart | remodex stop | remodex status | "
|
|
236
|
+
+ "remodex reset-pairing | remodex resume | remodex watch [threadId] | remodex --version | "
|
|
237
|
+
+ "append --json to start/restart/stop/status/reset-pairing/resume for machine-readable output"
|
|
238
|
+
);
|
|
239
|
+
exitImpl(1);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function parseCliArgs(rawArgs) {
|
|
243
|
+
const positionals = [];
|
|
244
|
+
let jsonOutput = false;
|
|
245
|
+
|
|
246
|
+
for (const arg of rawArgs) {
|
|
247
|
+
if (arg === "--json") {
|
|
248
|
+
jsonOutput = true;
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
positionals.push(arg);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
command: positionals[0] || "up",
|
|
257
|
+
jsonOutput,
|
|
258
|
+
watchThreadId: positionals[1] || "",
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function emitVersion({
|
|
263
|
+
jsonOutput = false,
|
|
264
|
+
consoleImpl = console,
|
|
265
|
+
} = {}) {
|
|
266
|
+
if (jsonOutput) {
|
|
267
|
+
emitJson({
|
|
268
|
+
currentVersion: version,
|
|
269
|
+
});
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
consoleImpl.log(version);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function emitResult({
|
|
277
|
+
payload,
|
|
278
|
+
message,
|
|
279
|
+
jsonOutput = false,
|
|
280
|
+
consoleImpl = console,
|
|
281
|
+
} = {}) {
|
|
282
|
+
if (jsonOutput) {
|
|
283
|
+
emitJson(payload);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
consoleImpl.log(message);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function emitJson(payload) {
|
|
291
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function assertMacOSCommand(name, {
|
|
295
|
+
platform = process.platform,
|
|
296
|
+
consoleImpl = console,
|
|
297
|
+
exitImpl = process.exit,
|
|
298
|
+
} = {}) {
|
|
299
|
+
if (platform === "darwin") {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
consoleImpl.error(`[remodex] \`${name}\` is only available on macOS. Use \`remodex up\` or \`remodex run\` for the foreground bridge on this OS.`);
|
|
304
|
+
exitImpl(1);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function isVersionCommand(value) {
|
|
308
|
+
return value === "-v" || value === "--v" || value === "-V" || value === "--version" || value === "version";
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
module.exports = {
|
|
312
|
+
isVersionCommand,
|
|
313
|
+
main,
|
|
314
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deepdream314/remodex",
|
|
3
|
+
"version": "1.3.8",
|
|
4
|
+
"description": "Local bridge between Codex and the Remodex mobile app. Run `remodex up` to start.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"remodex": "./bin/remodex.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"src/"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"start": "node ./bin/remodex.js up",
|
|
15
|
+
"test": "node --test ./test/*.test.js",
|
|
16
|
+
"prepack": "node ./scripts/prepare-private-defaults.js",
|
|
17
|
+
"postpack": "node ./scripts/cleanup-private-defaults.js"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"remodex",
|
|
21
|
+
"codex",
|
|
22
|
+
"bridge",
|
|
23
|
+
"cli"
|
|
24
|
+
],
|
|
25
|
+
"author": "Emanuele Di Pietro",
|
|
26
|
+
"license": "ISC",
|
|
27
|
+
"type": "commonjs",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"qrcode-terminal": "^0.12.0",
|
|
30
|
+
"ws": "^8.19.0"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// FILE: account-status.js
|
|
2
|
+
// Purpose: Converts raw codex account/auth responses into a sanitized status payload for the phone UI.
|
|
3
|
+
// Layer: CLI helper
|
|
4
|
+
// Exports: composeAccountStatus, composeSanitizedAuthStatusFromSettledResults, redactAuthStatus
|
|
5
|
+
// Depends on: none
|
|
6
|
+
|
|
7
|
+
const { version: bridgePackageVersion = "" } = require("../package.json");
|
|
8
|
+
|
|
9
|
+
// ─── Status composition ─────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
function composeAccountStatus({
|
|
12
|
+
accountRead = null,
|
|
13
|
+
authStatus = null,
|
|
14
|
+
loginInFlight = false,
|
|
15
|
+
bridgeVersionInfo = null,
|
|
16
|
+
transportMode = null,
|
|
17
|
+
} = {}) {
|
|
18
|
+
const account = accountRead?.account || null;
|
|
19
|
+
const authToken = normalizeString(authStatus?.authToken);
|
|
20
|
+
const hasAccountLogin = hasExplicitAccountLogin(account);
|
|
21
|
+
const authMethod = firstNonEmpty([
|
|
22
|
+
normalizeString(authStatus?.authMethod),
|
|
23
|
+
normalizeString(account?.type),
|
|
24
|
+
]) || null;
|
|
25
|
+
const tokenReady = Boolean(authToken);
|
|
26
|
+
const requiresOpenaiAuth = Boolean(accountRead?.requiresOpenaiAuth || authStatus?.requiresOpenaiAuth);
|
|
27
|
+
const hasPriorLoginContext = hasAccountLogin || Boolean(authMethod);
|
|
28
|
+
const needsReauth = !loginInFlight && requiresOpenaiAuth && hasPriorLoginContext;
|
|
29
|
+
const isAuthenticated = !needsReauth && (tokenReady || hasAccountLogin);
|
|
30
|
+
const status = isAuthenticated
|
|
31
|
+
? "authenticated"
|
|
32
|
+
: (loginInFlight ? "pending_login" : (needsReauth ? "expired" : "not_logged_in"));
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
status,
|
|
36
|
+
authMethod,
|
|
37
|
+
email: normalizeString(account?.email) || null,
|
|
38
|
+
planType: normalizeString(account?.planType) || null,
|
|
39
|
+
loginInFlight: Boolean(loginInFlight),
|
|
40
|
+
needsReauth,
|
|
41
|
+
tokenReady,
|
|
42
|
+
expiresAt: null,
|
|
43
|
+
requiresOpenaiAuth,
|
|
44
|
+
bridgeVersion: firstNonEmpty([
|
|
45
|
+
normalizeString(bridgeVersionInfo?.bridgeVersion),
|
|
46
|
+
normalizeString(bridgePackageVersion),
|
|
47
|
+
]) || null,
|
|
48
|
+
bridgeLatestVersion: normalizeString(bridgeVersionInfo?.bridgeLatestVersion) || null,
|
|
49
|
+
codexTransportMode: normalizeString(transportMode) || null,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Removes any token-bearing fields before the bridge sends auth state to the phone.
|
|
54
|
+
function redactAuthStatus(authStatus = null, extras = {}) {
|
|
55
|
+
const composed = composeAccountStatus({
|
|
56
|
+
accountRead: extras.accountRead || null,
|
|
57
|
+
authStatus,
|
|
58
|
+
loginInFlight: Boolean(extras.loginInFlight),
|
|
59
|
+
bridgeVersionInfo: extras.bridgeVersionInfo || null,
|
|
60
|
+
transportMode: extras.transportMode || null,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
authMethod: composed.authMethod,
|
|
65
|
+
status: composed.status,
|
|
66
|
+
email: composed.email,
|
|
67
|
+
planType: composed.planType,
|
|
68
|
+
loginInFlight: composed.loginInFlight,
|
|
69
|
+
needsReauth: composed.needsReauth,
|
|
70
|
+
tokenReady: composed.tokenReady,
|
|
71
|
+
expiresAt: composed.expiresAt,
|
|
72
|
+
bridgeVersion: composed.bridgeVersion,
|
|
73
|
+
bridgeLatestVersion: composed.bridgeLatestVersion,
|
|
74
|
+
codexTransportMode: composed.codexTransportMode,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ─── Settled snapshot helpers ───────────────────────────────
|
|
79
|
+
|
|
80
|
+
// Collapses settled bridge RPC results into one safe snapshot, even if one side fails.
|
|
81
|
+
// Input: Promise.allSettled-style results → Output: sanitized account status object
|
|
82
|
+
// Throws if both the account read and auth status fail, so the bridge can surface a real error.
|
|
83
|
+
function composeSanitizedAuthStatusFromSettledResults({
|
|
84
|
+
accountReadResult = null,
|
|
85
|
+
authStatusResult = null,
|
|
86
|
+
loginInFlight = false,
|
|
87
|
+
bridgeVersionInfo = null,
|
|
88
|
+
transportMode = null,
|
|
89
|
+
} = {}) {
|
|
90
|
+
const accountRead = accountReadResult?.status === "fulfilled" ? accountReadResult.value : null;
|
|
91
|
+
const authStatus = authStatusResult?.status === "fulfilled" ? authStatusResult.value : null;
|
|
92
|
+
|
|
93
|
+
if (!accountRead && !authStatus) {
|
|
94
|
+
const error = new Error("Unable to read ChatGPT account status from the bridge.");
|
|
95
|
+
error.errorCode = "auth_status_unavailable";
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return redactAuthStatus(authStatus, {
|
|
100
|
+
accountRead,
|
|
101
|
+
loginInFlight: Boolean(loginInFlight),
|
|
102
|
+
bridgeVersionInfo,
|
|
103
|
+
transportMode,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Treat explicit account signals as authenticated when the token read is temporarily unavailable.
|
|
108
|
+
function hasExplicitAccountLogin(account) {
|
|
109
|
+
if (!account || typeof account !== "object") {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (parseBoolean(account.loggedIn) || parseBoolean(account.logged_in) || parseBoolean(account.isLoggedIn)) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return Boolean(normalizeString(account.email));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Normalizes empty strings and picks the first meaningful value from a short list.
|
|
121
|
+
function firstNonEmpty(values) {
|
|
122
|
+
for (const value of values) {
|
|
123
|
+
const normalized = normalizeString(value);
|
|
124
|
+
if (normalized) {
|
|
125
|
+
return normalized;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return "";
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Keeps auth/account fields compact and consistent in the status snapshot.
|
|
132
|
+
function normalizeString(value) {
|
|
133
|
+
return typeof value === "string" ? value.trim() : "";
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function parseBoolean(value) {
|
|
137
|
+
return value === true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
module.exports = {
|
|
141
|
+
composeAccountStatus,
|
|
142
|
+
composeSanitizedAuthStatusFromSettledResults,
|
|
143
|
+
redactAuthStatus,
|
|
144
|
+
};
|