@undefineds.co/linx 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -0
- package/dist/index.js +578 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/account-api.js +100 -0
- package/dist/lib/account-api.js.map +1 -0
- package/dist/lib/account-session.js +30 -0
- package/dist/lib/account-session.js.map +1 -0
- package/dist/lib/ai-command.js +411 -0
- package/dist/lib/ai-command.js.map +1 -0
- package/dist/lib/chat-api.js +122 -0
- package/dist/lib/chat-api.js.map +1 -0
- package/dist/lib/codex-plugin/bridge.js +109 -0
- package/dist/lib/codex-plugin/bridge.js.map +1 -0
- package/dist/lib/codex-plugin/codex-native-proxy.js +358 -0
- package/dist/lib/codex-plugin/codex-native-proxy.js.map +1 -0
- package/dist/lib/codex-plugin/index.js +4 -0
- package/dist/lib/codex-plugin/index.js.map +1 -0
- package/dist/lib/codex-plugin/runner.js +38 -0
- package/dist/lib/codex-plugin/runner.js.map +1 -0
- package/dist/lib/credentials-store.js +74 -0
- package/dist/lib/credentials-store.js.map +1 -0
- package/dist/lib/default-model.js +8 -0
- package/dist/lib/default-model.js.map +1 -0
- package/dist/lib/login-command.js +51 -0
- package/dist/lib/login-command.js.map +1 -0
- package/dist/lib/models.js +6 -0
- package/dist/lib/models.js.map +1 -0
- package/dist/lib/oidc-auth.js +451 -0
- package/dist/lib/oidc-auth.js.map +1 -0
- package/dist/lib/oidc-session-storage.js +37 -0
- package/dist/lib/oidc-session-storage.js.map +1 -0
- package/dist/lib/pi-adapter/auth.js +38 -0
- package/dist/lib/pi-adapter/auth.js.map +1 -0
- package/dist/lib/pi-adapter/branding.js +239 -0
- package/dist/lib/pi-adapter/branding.js.map +1 -0
- package/dist/lib/pi-adapter/index.js +4 -0
- package/dist/lib/pi-adapter/index.js.map +1 -0
- package/dist/lib/pi-adapter/interactive.js +63 -0
- package/dist/lib/pi-adapter/interactive.js.map +1 -0
- package/dist/lib/pi-adapter/runtime.js +271 -0
- package/dist/lib/pi-adapter/runtime.js.map +1 -0
- package/dist/lib/pi-adapter/stream.js +314 -0
- package/dist/lib/pi-adapter/stream.js.map +1 -0
- package/dist/lib/pi-adapter/theme.js +84 -0
- package/dist/lib/pi-adapter/theme.js.map +1 -0
- package/dist/lib/pod-chat-store.js +200 -0
- package/dist/lib/pod-chat-store.js.map +1 -0
- package/dist/lib/profile-identity.js +116 -0
- package/dist/lib/profile-identity.js.map +1 -0
- package/dist/lib/prompt.js +82 -0
- package/dist/lib/prompt.js.map +1 -0
- package/dist/lib/runtime-target.js +12 -0
- package/dist/lib/runtime-target.js.map +1 -0
- package/dist/lib/solid-auth.js +55 -0
- package/dist/lib/solid-auth.js.map +1 -0
- package/dist/lib/thread-utils.js +12 -0
- package/dist/lib/thread-utils.js.map +1 -0
- package/dist/lib/watch/archive.js +110 -0
- package/dist/lib/watch/archive.js.map +1 -0
- package/dist/lib/watch/auth.js +56 -0
- package/dist/lib/watch/auth.js.map +1 -0
- package/dist/lib/watch/codex-composer.js +219 -0
- package/dist/lib/watch/codex-composer.js.map +1 -0
- package/dist/lib/watch/codex-footer.js +88 -0
- package/dist/lib/watch/codex-footer.js.map +1 -0
- package/dist/lib/watch/codex-overlay.js +154 -0
- package/dist/lib/watch/codex-overlay.js.map +1 -0
- package/dist/lib/watch/codex-request-form.js +341 -0
- package/dist/lib/watch/codex-request-form.js.map +1 -0
- package/dist/lib/watch/codex-request-input.js +187 -0
- package/dist/lib/watch/codex-request-input.js.map +1 -0
- package/dist/lib/watch/display.js +1514 -0
- package/dist/lib/watch/display.js.map +1 -0
- package/dist/lib/watch/format.js +161 -0
- package/dist/lib/watch/format.js.map +1 -0
- package/dist/lib/watch/hooks/claude.js +12 -0
- package/dist/lib/watch/hooks/claude.js.map +1 -0
- package/dist/lib/watch/hooks/codebuddy.js +18 -0
- package/dist/lib/watch/hooks/codebuddy.js.map +1 -0
- package/dist/lib/watch/hooks/codex.js +13 -0
- package/dist/lib/watch/hooks/codex.js.map +1 -0
- package/dist/lib/watch/hooks/index.js +24 -0
- package/dist/lib/watch/hooks/index.js.map +1 -0
- package/dist/lib/watch/hooks/shared.js +19 -0
- package/dist/lib/watch/hooks/shared.js.map +1 -0
- package/dist/lib/watch/index.js +10 -0
- package/dist/lib/watch/index.js.map +1 -0
- package/dist/lib/watch/pod-ai.js +168 -0
- package/dist/lib/watch/pod-ai.js.map +1 -0
- package/dist/lib/watch/pod-approval.js +578 -0
- package/dist/lib/watch/pod-approval.js.map +1 -0
- package/dist/lib/watch/pod-persistence.js +226 -0
- package/dist/lib/watch/pod-persistence.js.map +1 -0
- package/dist/lib/watch/runner.js +1124 -0
- package/dist/lib/watch/runner.js.map +1 -0
- package/dist/lib/watch/types.js +2 -0
- package/dist/lib/watch/types.js.map +1 -0
- package/dist/watch-cli.js +142 -0
- package/dist/watch-cli.js.map +1 -0
- package/package.json +38 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline';
|
|
2
|
+
import { createCodexAttachBridge, createCodexAttachSessionRecord, } from './bridge.js';
|
|
3
|
+
export function createCodexAttachRunner(options) {
|
|
4
|
+
const record = createCodexAttachSessionRecord({
|
|
5
|
+
workspacePath: options.workspacePath,
|
|
6
|
+
cwd: options.cwd,
|
|
7
|
+
backendSessionId: options.backendSessionId,
|
|
8
|
+
model: options.model,
|
|
9
|
+
prompt: options.prompt,
|
|
10
|
+
});
|
|
11
|
+
const bridge = createCodexAttachBridge(record, options.runtime);
|
|
12
|
+
const output = options.output ?? process.stdout;
|
|
13
|
+
const log = options.log ?? process.stderr;
|
|
14
|
+
const input = options.input ?? process.stdin;
|
|
15
|
+
async function handleLine(line) {
|
|
16
|
+
const responses = await bridge.handleCodexRpcLine(line);
|
|
17
|
+
for (const response of responses) {
|
|
18
|
+
output.write(`${JSON.stringify(response)}\n`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
record,
|
|
23
|
+
bridge,
|
|
24
|
+
async run() {
|
|
25
|
+
log.write(`[linx] background codex attach active: codex=${options.backendSessionId} linx=${record.id} backend=xpod\n`);
|
|
26
|
+
const rl = createInterface({
|
|
27
|
+
input,
|
|
28
|
+
crlfDelay: Infinity,
|
|
29
|
+
});
|
|
30
|
+
for await (const line of rl) {
|
|
31
|
+
await handleLine(line);
|
|
32
|
+
}
|
|
33
|
+
return 0;
|
|
34
|
+
},
|
|
35
|
+
handleLine,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../../src/lib/codex-plugin/runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC/C,OAAO,EACL,uBAAuB,EACvB,8BAA8B,GAG/B,MAAM,aAAa,CAAA;AA0BpB,MAAM,UAAU,uBAAuB,CAAC,OAAiC;IACvE,MAAM,MAAM,GAAG,8BAA8B,CAAC;QAC5C,aAAa,EAAE,OAAO,CAAC,aAAa;QACpC,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,gBAAgB,EAAE,OAAO,CAAC,gBAAgB;QAC1C,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,MAAM,EAAE,OAAO,CAAC,MAAM;KACvB,CAAC,CAAA;IACF,MAAM,MAAM,GAAG,uBAAuB,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;IAC/D,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAA;IAC/C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,MAAM,CAAA;IACzC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,CAAA;IAE5C,KAAK,UAAU,UAAU,CAAC,IAAY;QACpC,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAA;QACvD,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QAC/C,CAAC;IACH,CAAC;IAED,OAAO;QACL,MAAM;QACN,MAAM;QACN,KAAK,CAAC,GAAG;YACP,GAAG,CAAC,KAAK,CACP,gDAAgD,OAAO,CAAC,gBAAgB,SAAS,MAAM,CAAC,EAAE,iBAAiB,CAC5G,CAAA;YAED,MAAM,EAAE,GAAG,eAAe,CAAC;gBACzB,KAAK;gBACL,SAAS,EAAE,QAAQ;aACpB,CAAC,CAAA;YAEF,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,EAAE,EAAE,CAAC;gBAC5B,MAAM,UAAU,CAAC,IAAI,CAAC,CAAA;YACxB,CAAC;YAED,OAAO,CAAC,CAAA;QACV,CAAC;QACD,UAAU;KACX,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { isLinxClientCredentialsSecrets, LINX_CONFIG_FILE_NAME, LINX_HOME_DIRNAME, LINX_SECRETS_FILE_NAME, parseLinxClientConfig, parseLinxClientSecrets, } from '@undefineds.co/models/client';
|
|
5
|
+
function linxDir() {
|
|
6
|
+
return join(homedir(), LINX_HOME_DIRNAME);
|
|
7
|
+
}
|
|
8
|
+
export function getConfigPath() {
|
|
9
|
+
return join(linxDir(), LINX_CONFIG_FILE_NAME);
|
|
10
|
+
}
|
|
11
|
+
export function getSecretsPath() {
|
|
12
|
+
return join(linxDir(), LINX_SECRETS_FILE_NAME);
|
|
13
|
+
}
|
|
14
|
+
function readJson(filePath) {
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(readFileSync(filePath, 'utf-8'));
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function credentialDirs() {
|
|
23
|
+
return [linxDir()];
|
|
24
|
+
}
|
|
25
|
+
export function saveCredentials(creds) {
|
|
26
|
+
const dir = linxDir();
|
|
27
|
+
mkdirSync(dir, { recursive: true });
|
|
28
|
+
writeFileSync(getConfigPath(), `${JSON.stringify({ url: creds.url, webId: creds.webId, authType: creds.authType }, null, 2)}\n`, 'utf-8');
|
|
29
|
+
chmodSync(getConfigPath(), 0o644);
|
|
30
|
+
writeFileSync(getSecretsPath(), `${JSON.stringify(creds.secrets, null, 2)}\n`, 'utf-8');
|
|
31
|
+
chmodSync(getSecretsPath(), 0o600);
|
|
32
|
+
}
|
|
33
|
+
export function clearCredentials() {
|
|
34
|
+
for (const path of [getConfigPath(), getSecretsPath()]) {
|
|
35
|
+
if (existsSync(path)) {
|
|
36
|
+
unlinkSync(path);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export function isClientCredentials(secrets) {
|
|
41
|
+
return isLinxClientCredentialsSecrets(secrets);
|
|
42
|
+
}
|
|
43
|
+
export function getClientCredentials(creds) {
|
|
44
|
+
return isClientCredentials(creds.secrets) ? creds.secrets : null;
|
|
45
|
+
}
|
|
46
|
+
export function getOidcOAuthSecrets(creds) {
|
|
47
|
+
const secrets = creds.secrets;
|
|
48
|
+
return 'oidcRefreshToken' in secrets && 'oidcAccessToken' in secrets && 'oidcExpiresAt' in secrets
|
|
49
|
+
? secrets
|
|
50
|
+
: null;
|
|
51
|
+
}
|
|
52
|
+
export function loadCredentials() {
|
|
53
|
+
for (const sourceDir of credentialDirs()) {
|
|
54
|
+
const configPath = join(sourceDir, LINX_CONFIG_FILE_NAME);
|
|
55
|
+
const secretsPath = join(sourceDir, LINX_SECRETS_FILE_NAME);
|
|
56
|
+
if (!existsSync(configPath) || !existsSync(secretsPath)) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const config = parseLinxClientConfig(readJson(configPath));
|
|
60
|
+
const secrets = parseLinxClientSecrets(readJson(secretsPath));
|
|
61
|
+
if (!config || !secrets) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
url: config.url,
|
|
66
|
+
webId: config.webId,
|
|
67
|
+
authType: config.authType,
|
|
68
|
+
sourceDir,
|
|
69
|
+
secrets,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=credentials-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials-store.js","sourceRoot":"","sources":["../../src/lib/credentials-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const DEFAULT_LINX_CLOUD_MODEL_ID = 'linx-lite';
|
|
2
|
+
export function resolvePreferredLinxCloudModelId(models, fallback = DEFAULT_LINX_CLOUD_MODEL_ID) {
|
|
3
|
+
const ids = models
|
|
4
|
+
.map((model) => model.id?.trim())
|
|
5
|
+
.filter((id) => Boolean(id));
|
|
6
|
+
return ids.find((id) => id === DEFAULT_LINX_CLOUD_MODEL_ID) ?? ids[0] ?? fallback;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=default-model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"default-model.js","sourceRoot":"","sources":["../../src/lib/default-model.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,2BAA2B,GAAG,WAAW,CAAA;AAEtD,MAAM,UAAU,gCAAgC,CAC9C,MAA6B,EAC7B,QAAQ,GAAG,2BAA2B;IAEtC,MAAM,GAAG,GAAG,MAAM;SACf,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC;SAChC,MAAM,CAAC,CAAC,EAAE,EAAgB,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAA;IAE5C,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,2BAA2B,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAA;AACnF,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { listLinxWhoAmIFields, } from '@undefineds.co/models/client';
|
|
2
|
+
import { clearAccountSession, loadAccountSession } from './account-session.js';
|
|
3
|
+
import { clearCredentials } from './credentials-store.js';
|
|
4
|
+
import { ensureBrowserConsentLogin } from './oidc-auth.js';
|
|
5
|
+
import { resolveAccountBaseUrl } from './account-api.js';
|
|
6
|
+
export const loginCommand = {
|
|
7
|
+
command: 'login',
|
|
8
|
+
describe: 'Login to LinX cloud in the browser and persist the local OIDC session',
|
|
9
|
+
builder: (yargs) => yargs
|
|
10
|
+
.option('url', {
|
|
11
|
+
alias: 'u',
|
|
12
|
+
type: 'string',
|
|
13
|
+
default: resolveAccountBaseUrl(),
|
|
14
|
+
description: 'Solid / account issuer URL',
|
|
15
|
+
}),
|
|
16
|
+
handler: async (argv) => {
|
|
17
|
+
const result = await ensureBrowserConsentLogin({
|
|
18
|
+
issuerUrl: argv.url,
|
|
19
|
+
});
|
|
20
|
+
process.stdout.write('LinX login successful.\n');
|
|
21
|
+
process.stdout.write(`server: ${result.url}\n`);
|
|
22
|
+
process.stdout.write(`webId: ${result.webId}\n`);
|
|
23
|
+
process.stdout.write('auth: oidc_oauth\n');
|
|
24
|
+
process.stdout.write(`session: ${result.reusedExistingSession ? 'reused' : 'browser-consent'}\n`);
|
|
25
|
+
process.exit(0);
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
export const logoutCommand = {
|
|
29
|
+
command: 'logout',
|
|
30
|
+
describe: 'Remove LinX cloud session and local credentials',
|
|
31
|
+
handler: async () => {
|
|
32
|
+
clearAccountSession();
|
|
33
|
+
clearCredentials();
|
|
34
|
+
process.stdout.write('Logged out. Local LinX credentials removed.\n');
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
export const whoamiCommand = {
|
|
38
|
+
command: 'whoami',
|
|
39
|
+
describe: 'Show the current LinX login identity',
|
|
40
|
+
builder: (yargs) => yargs.option('verbose', { type: 'boolean', default: false }),
|
|
41
|
+
handler: async (argv) => {
|
|
42
|
+
const account = loadAccountSession();
|
|
43
|
+
if (!account) {
|
|
44
|
+
throw new Error('Not logged in. Run `linx login` first.');
|
|
45
|
+
}
|
|
46
|
+
for (const field of listLinxWhoAmIFields(account, { verbose: argv.verbose })) {
|
|
47
|
+
process.stdout.write(`${field.key}: ${field.value}\n`);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=login-command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login-command.js","sourceRoot":"","sources":["../../src/lib/login-command.ts"],"names":[],"mappings":"AACA,OAAO,EACL,oBAAoB,GACrB,MAAM,8BAA8B,CAAA;AACrC,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AACzD,OAAO,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAA;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAA;AAUxD,MAAM,CAAC,MAAM,YAAY,GAAqC;IAC5D,OAAO,EAAE,OAAO;IAChB,QAAQ,EAAE,uEAAuE;IACjF,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CACjB,KAAK;SACF,MAAM,CAAC,KAAK,EAAE;QACb,KAAK,EAAE,GAAG;QACV,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,qBAAqB,EAAE;QAChC,WAAW,EAAE,4BAA4B;KAC1C,CAAC;IACN,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QACtB,MAAM,MAAM,GAAG,MAAM,yBAAyB,CAAC;YAC7C,SAAS,EAAE,IAAI,CAAC,GAAG;SACpB,CAAC,CAAA;QAEF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAA;QAChD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,MAAM,CAAC,GAAG,IAAI,CAAC,CAAA;QAC/C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,MAAM,CAAC,KAAK,IAAI,CAAC,CAAA;QAChD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAA;QAC1C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,iBAAiB,IAAI,CAAC,CAAA;QACjG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;CACF,CAAA;AAED,MAAM,CAAC,MAAM,aAAa,GAAkB;IAC1C,OAAO,EAAE,QAAQ;IACjB,QAAQ,EAAE,iDAAiD;IAC3D,OAAO,EAAE,KAAK,IAAI,EAAE;QAClB,mBAAmB,EAAE,CAAA;QACrB,gBAAgB,EAAE,CAAA;QAClB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAA;IACvE,CAAC;CACF,CAAA;AAED,MAAM,CAAC,MAAM,aAAa,GAAsC;IAC9D,OAAO,EAAE,QAAQ;IACjB,QAAQ,EAAE,sCAAsC;IAChD,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAChF,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QACtB,MAAM,OAAO,GAAG,kBAAkB,EAAE,CAAA;QACpC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAA;QAC3D,CAAC;QAED,KAAK,MAAM,KAAK,IAAI,oBAAoB,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;YAC7E,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,GAAG,KAAK,KAAK,CAAC,KAAK,IAAI,CAAC,CAAA;QACxD,CAAC;IACH,CAAC;CACF,CAAA"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { XPOD_AI, XPOD_CREDENTIAL, aiConfigModelUri, aiConfigProviderUri, aiModelTable, aiProviderTable, buildAIConfigMutationPlan, buildAIConfigProviderStateMap, buildWatchThreadMetadata, buildWatchTranscriptMessages, credentialTable, ContactType, agentTable, chatTable, contactTable, drizzle, eq, getAIConfigProviderFamilyIds, getAIConfigProviderMetadata, getDefaultAIConfigCredentialId, getBuiltinModels as getSharedBuiltinModels, findPodRowByStorageId, initSolidTables, whereByPodStorageId, solidSchema, messageTable, sessionTable, normalizeAIConfigProviderId, normalizeAIConfigResourceId, sameAIConfigProviderFamily, sessionRepository, threadTable, } from '@undefineds.co/models';
|
|
2
|
+
export { ContactType, agentTable, aiConfigModelUri, aiConfigProviderUri, aiModelTable, aiProviderTable, buildAIConfigMutationPlan, buildAIConfigProviderStateMap, buildWatchThreadMetadata, buildWatchTranscriptMessages, chatTable, contactTable, credentialTable, drizzle, eq, getAIConfigProviderFamilyIds, getAIConfigProviderMetadata, getDefaultAIConfigCredentialId, findPodRowByStorageId, solidSchema, initSolidTables, whereByPodStorageId, messageTable, sessionTable, normalizeAIConfigProviderId, normalizeAIConfigResourceId, sameAIConfigProviderFamily, sessionRepository, threadTable, XPOD_AI, XPOD_CREDENTIAL, };
|
|
3
|
+
export function getBuiltinModels() {
|
|
4
|
+
return getSharedBuiltinModels();
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=models.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.js","sourceRoot":"","sources":["../../src/lib/models.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,YAAY,EACZ,eAAe,EACf,yBAAyB,EACzB,6BAA6B,EAC7B,wBAAwB,EACxB,4BAA4B,EAC5B,eAAe,EACf,WAAW,EACX,UAAU,EACV,SAAS,EACT,YAAY,EACZ,OAAO,EACP,EAAE,EACF,4BAA4B,EAC5B,2BAA2B,EAC3B,8BAA8B,EAC9B,gBAAgB,IAAI,sBAAsB,EAC1C,qBAAqB,EACrB,eAAe,EACf,mBAAmB,EACnB,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,2BAA2B,EAC3B,2BAA2B,EAC3B,0BAA0B,EAC1B,iBAAiB,EACjB,WAAW,GAmBZ,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EACL,WAAW,EACX,UAAU,EACV,gBAAgB,EAChB,mBAAmB,EACnB,YAAY,EACZ,eAAe,EACf,yBAAyB,EACzB,6BAA6B,EAC7B,wBAAwB,EACxB,4BAA4B,EAC5B,SAAS,EACT,YAAY,EACZ,eAAe,EACf,OAAO,EACP,EAAE,EACF,4BAA4B,EAC5B,2BAA2B,EAC3B,8BAA8B,EAC9B,qBAAqB,EACrB,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,YAAY,EACZ,YAAY,EACZ,2BAA2B,EAC3B,2BAA2B,EAC3B,0BAA0B,EAC1B,iBAAiB,EACjB,WAAW,EACX,OAAO,EACP,eAAe,GAChB,CAAA;AAsBD,MAAM,UAAU,gBAAgB;IAC9B,OAAO,sBAAsB,EAAE,CAAA;AACjC,CAAC"}
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
import { EVENTS, getSessionFromStorage, refreshSession, Session, } from '@inrupt/solid-client-authn-node';
|
|
5
|
+
import { isLinxOidcOAuthSecrets, resolveLinxCloudAccountBaseUrl } from '@undefineds.co/models/client';
|
|
6
|
+
import { saveAccountSession } from './account-session.js';
|
|
7
|
+
import { loadCredentials, saveCredentials } from './credentials-store.js';
|
|
8
|
+
import { createOidcSessionStorage } from './oidc-session-storage.js';
|
|
9
|
+
const execFileAsync = promisify(execFile);
|
|
10
|
+
const DEFAULT_CALLBACK_HOST = '127.0.0.1';
|
|
11
|
+
const DEFAULT_CALLBACK_PATH = '/auth/callback';
|
|
12
|
+
const DEFAULT_CLIENT_NAME = 'LinX CLI';
|
|
13
|
+
export async function ensureBrowserConsentLogin(options = {}) {
|
|
14
|
+
const reused = await reuseExistingBrowserConsentLogin(options).catch(() => null);
|
|
15
|
+
if (reused) {
|
|
16
|
+
return {
|
|
17
|
+
...reused,
|
|
18
|
+
reusedExistingSession: true,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const fresh = await loginWithBrowserConsent(options);
|
|
22
|
+
return {
|
|
23
|
+
...fresh,
|
|
24
|
+
reusedExistingSession: false,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export async function loginWithBrowserConsent(options = {}) {
|
|
28
|
+
const storage = createOidcSessionStorage();
|
|
29
|
+
const session = new Session({
|
|
30
|
+
storage,
|
|
31
|
+
keepAlive: false,
|
|
32
|
+
}, `linx-cli-oidc-${Date.now()}`);
|
|
33
|
+
const baseUrl = resolveLinxCloudAccountBaseUrl(options.issuerUrl);
|
|
34
|
+
let latestTokenSet = null;
|
|
35
|
+
session.events.on(EVENTS.NEW_TOKENS, (tokenSet) => {
|
|
36
|
+
latestTokenSet = tokenSet;
|
|
37
|
+
});
|
|
38
|
+
await withCallbackServer(options.callbackHost ?? DEFAULT_CALLBACK_HOST, options.callbackPath ?? DEFAULT_CALLBACK_PATH, async (callbackUrl) => {
|
|
39
|
+
let redirectSeen = false;
|
|
40
|
+
await session.login({
|
|
41
|
+
oidcIssuer: baseUrl.replace(/\/$/, ''),
|
|
42
|
+
redirectUrl: callbackUrl,
|
|
43
|
+
clientName: options.clientName ?? DEFAULT_CLIENT_NAME,
|
|
44
|
+
handleRedirect: async (url) => {
|
|
45
|
+
redirectSeen = true;
|
|
46
|
+
options.onAuthUrl?.(url);
|
|
47
|
+
if (options.openBrowser) {
|
|
48
|
+
await options.openBrowser(url);
|
|
49
|
+
}
|
|
50
|
+
else if (!options.onAuthUrl) {
|
|
51
|
+
await openBrowser(url);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
tokenType: 'Bearer',
|
|
55
|
+
});
|
|
56
|
+
if (!redirectSeen) {
|
|
57
|
+
throw new Error('OIDC login did not produce a browser redirect URL');
|
|
58
|
+
}
|
|
59
|
+
}, async (requestUrl) => {
|
|
60
|
+
await session.handleIncomingRedirect(requestUrl);
|
|
61
|
+
}, options.manualRedirectUrl);
|
|
62
|
+
if (!session.info.isLoggedIn || !session.info.webId) {
|
|
63
|
+
throw new Error('Login did not complete successfully');
|
|
64
|
+
}
|
|
65
|
+
const tokenSet = latestTokenSet;
|
|
66
|
+
if (!tokenSet?.refreshToken) {
|
|
67
|
+
throw new Error('OIDC login completed without a refresh token');
|
|
68
|
+
}
|
|
69
|
+
const result = {
|
|
70
|
+
url: baseUrl,
|
|
71
|
+
webId: session.info.webId,
|
|
72
|
+
tokenSet,
|
|
73
|
+
credentialsToSave: serializeOidcCredentials(baseUrl, session.info.webId, tokenSet),
|
|
74
|
+
};
|
|
75
|
+
persistBrowserLoginResult(result);
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
export async function getOidcAccessToken(credentials) {
|
|
79
|
+
if (credentials.authType !== 'oidc_oauth' || !isLinxOidcOAuthSecrets(credentials.secrets)) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const secrets = credentials.secrets;
|
|
83
|
+
if (!secrets.oidcAccessToken) {
|
|
84
|
+
return refreshStoredOidcSession(credentials, secrets);
|
|
85
|
+
}
|
|
86
|
+
const expiresAt = secrets.oidcExpiresAt ? new Date(secrets.oidcExpiresAt).getTime() : 0;
|
|
87
|
+
if (!Number.isFinite(expiresAt) || expiresAt <= Date.now() + 60_000) {
|
|
88
|
+
return refreshStoredOidcSession(credentials, secrets);
|
|
89
|
+
}
|
|
90
|
+
return secrets.oidcAccessToken;
|
|
91
|
+
}
|
|
92
|
+
export function serializeOidcCredentials(url, webId, tokenSet) {
|
|
93
|
+
return {
|
|
94
|
+
url,
|
|
95
|
+
webId,
|
|
96
|
+
authType: 'oidc_oauth',
|
|
97
|
+
secrets: {
|
|
98
|
+
oidcRefreshToken: tokenSet.refreshToken ?? '',
|
|
99
|
+
oidcAccessToken: tokenSet.accessToken ?? '',
|
|
100
|
+
oidcExpiresAt: tokenSet.expiresAt
|
|
101
|
+
? new Date(tokenSet.expiresAt * 1000).toISOString()
|
|
102
|
+
: new Date().toISOString(),
|
|
103
|
+
oidcClientId: tokenSet.clientId,
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
async function withCallbackServer(host, pathname, startLogin, onCallback, manualRedirectUrl) {
|
|
108
|
+
const server = createServer();
|
|
109
|
+
const sockets = new Set();
|
|
110
|
+
server.on('connection', (socket) => {
|
|
111
|
+
sockets.add(socket);
|
|
112
|
+
socket.on('close', () => {
|
|
113
|
+
sockets.delete(socket);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
const callbackPromise = new Promise((resolve, reject) => {
|
|
117
|
+
server.on('request', (req, res) => {
|
|
118
|
+
const currentUrl = new URL(req.url ?? '/', `http://${host}`);
|
|
119
|
+
if (currentUrl.pathname !== pathname) {
|
|
120
|
+
res.statusCode = 404;
|
|
121
|
+
res.end('Not found');
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const address = server.address();
|
|
125
|
+
if (!address || typeof address === 'string') {
|
|
126
|
+
reject(new Error('Local callback server is not bound to a TCP port'));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const absoluteUrl = `http://${host}:${address.port}${currentUrl.pathname}${currentUrl.search}`;
|
|
130
|
+
onCallback(absoluteUrl)
|
|
131
|
+
.then(() => {
|
|
132
|
+
res.statusCode = 200;
|
|
133
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
134
|
+
res.setHeader('Connection', 'close');
|
|
135
|
+
res.end(renderCallbackPage({
|
|
136
|
+
title: 'LinX Cloud connected',
|
|
137
|
+
description: 'Authentication is complete. You can return to your terminal.',
|
|
138
|
+
tone: 'success',
|
|
139
|
+
}));
|
|
140
|
+
server.closeIdleConnections?.();
|
|
141
|
+
server.closeAllConnections?.();
|
|
142
|
+
resolve();
|
|
143
|
+
})
|
|
144
|
+
.catch((error) => {
|
|
145
|
+
res.statusCode = 500;
|
|
146
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
147
|
+
res.setHeader('Connection', 'close');
|
|
148
|
+
res.end(renderCallbackPage({
|
|
149
|
+
title: 'LinX login failed',
|
|
150
|
+
description: error instanceof Error ? error.message : String(error),
|
|
151
|
+
tone: 'error',
|
|
152
|
+
}));
|
|
153
|
+
server.closeIdleConnections?.();
|
|
154
|
+
server.closeAllConnections?.();
|
|
155
|
+
reject(error);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
await new Promise((resolve, reject) => {
|
|
160
|
+
server.once('error', reject);
|
|
161
|
+
server.listen(0, host, () => resolve());
|
|
162
|
+
});
|
|
163
|
+
try {
|
|
164
|
+
const address = server.address();
|
|
165
|
+
if (!address || typeof address === 'string') {
|
|
166
|
+
throw new Error('Failed to allocate local callback server port');
|
|
167
|
+
}
|
|
168
|
+
const callbackUrl = `http://${host}:${address.port}${pathname}`;
|
|
169
|
+
await startLogin(callbackUrl);
|
|
170
|
+
await Promise.race([
|
|
171
|
+
callbackPromise,
|
|
172
|
+
waitForManualRedirect(onCallback, manualRedirectUrl),
|
|
173
|
+
]);
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
server.closeIdleConnections?.();
|
|
177
|
+
server.closeAllConnections?.();
|
|
178
|
+
for (const socket of sockets) {
|
|
179
|
+
try {
|
|
180
|
+
socket.destroy();
|
|
181
|
+
}
|
|
182
|
+
catch { }
|
|
183
|
+
}
|
|
184
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function renderCallbackPage(input) {
|
|
188
|
+
const escapedTitle = escapeHtml(input.title);
|
|
189
|
+
const escapedDescription = escapeHtml(input.description);
|
|
190
|
+
const accent = input.tone === 'success' ? '#0f8f63' : '#b42318';
|
|
191
|
+
const softAccent = input.tone === 'success' ? '#e8f7f0' : '#fff1f0';
|
|
192
|
+
const icon = input.tone === 'success' ? '✓' : '!';
|
|
193
|
+
return `<!doctype html>
|
|
194
|
+
<html lang="en">
|
|
195
|
+
<head>
|
|
196
|
+
<meta charset="utf-8" />
|
|
197
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
198
|
+
<title>${escapedTitle}</title>
|
|
199
|
+
<style>
|
|
200
|
+
:root {
|
|
201
|
+
color-scheme: light;
|
|
202
|
+
--bg: #f7f3eb;
|
|
203
|
+
--card: rgba(255, 255, 255, 0.82);
|
|
204
|
+
--ink: #1d211f;
|
|
205
|
+
--muted: #68706b;
|
|
206
|
+
--line: rgba(44, 58, 49, 0.14);
|
|
207
|
+
--accent: ${accent};
|
|
208
|
+
--soft-accent: ${softAccent};
|
|
209
|
+
}
|
|
210
|
+
* { box-sizing: border-box; }
|
|
211
|
+
body {
|
|
212
|
+
margin: 0;
|
|
213
|
+
min-height: 100vh;
|
|
214
|
+
display: grid;
|
|
215
|
+
place-items: center;
|
|
216
|
+
padding: 32px;
|
|
217
|
+
color: var(--ink);
|
|
218
|
+
font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
219
|
+
background:
|
|
220
|
+
radial-gradient(circle at 20% 20%, rgba(15, 143, 99, 0.16), transparent 34%),
|
|
221
|
+
radial-gradient(circle at 80% 10%, rgba(217, 164, 65, 0.22), transparent 30%),
|
|
222
|
+
linear-gradient(135deg, #fbf7ef 0%, #edf4ef 100%);
|
|
223
|
+
}
|
|
224
|
+
.card {
|
|
225
|
+
width: min(520px, 100%);
|
|
226
|
+
padding: 34px;
|
|
227
|
+
border: 1px solid var(--line);
|
|
228
|
+
border-radius: 28px;
|
|
229
|
+
background: var(--card);
|
|
230
|
+
box-shadow: 0 24px 80px rgba(33, 41, 37, 0.16);
|
|
231
|
+
backdrop-filter: blur(14px);
|
|
232
|
+
}
|
|
233
|
+
.brand {
|
|
234
|
+
display: flex;
|
|
235
|
+
align-items: center;
|
|
236
|
+
gap: 12px;
|
|
237
|
+
margin-bottom: 28px;
|
|
238
|
+
color: var(--muted);
|
|
239
|
+
font-size: 13px;
|
|
240
|
+
font-weight: 700;
|
|
241
|
+
letter-spacing: 0.12em;
|
|
242
|
+
text-transform: uppercase;
|
|
243
|
+
}
|
|
244
|
+
.mark {
|
|
245
|
+
width: 36px;
|
|
246
|
+
height: 36px;
|
|
247
|
+
display: grid;
|
|
248
|
+
place-items: center;
|
|
249
|
+
border-radius: 12px;
|
|
250
|
+
color: white;
|
|
251
|
+
background: #24362c;
|
|
252
|
+
letter-spacing: 0;
|
|
253
|
+
font-size: 15px;
|
|
254
|
+
}
|
|
255
|
+
.status {
|
|
256
|
+
width: 56px;
|
|
257
|
+
height: 56px;
|
|
258
|
+
display: grid;
|
|
259
|
+
place-items: center;
|
|
260
|
+
border-radius: 20px;
|
|
261
|
+
margin-bottom: 20px;
|
|
262
|
+
color: var(--accent);
|
|
263
|
+
background: var(--soft-accent);
|
|
264
|
+
font-size: 28px;
|
|
265
|
+
font-weight: 800;
|
|
266
|
+
}
|
|
267
|
+
h1 {
|
|
268
|
+
margin: 0;
|
|
269
|
+
font-size: clamp(28px, 5vw, 40px);
|
|
270
|
+
line-height: 1.05;
|
|
271
|
+
letter-spacing: -0.04em;
|
|
272
|
+
}
|
|
273
|
+
p {
|
|
274
|
+
margin: 14px 0 0;
|
|
275
|
+
color: var(--muted);
|
|
276
|
+
font-size: 16px;
|
|
277
|
+
line-height: 1.6;
|
|
278
|
+
}
|
|
279
|
+
.hint {
|
|
280
|
+
margin-top: 28px;
|
|
281
|
+
padding: 14px 16px;
|
|
282
|
+
border-radius: 16px;
|
|
283
|
+
background: rgba(36, 54, 44, 0.06);
|
|
284
|
+
color: #3d4942;
|
|
285
|
+
font-size: 13px;
|
|
286
|
+
}
|
|
287
|
+
</style>
|
|
288
|
+
</head>
|
|
289
|
+
<body>
|
|
290
|
+
<main class="card">
|
|
291
|
+
<div class="brand"><div class="mark">Lx</div><span>LinX Cloud</span></div>
|
|
292
|
+
<div class="status">${icon}</div>
|
|
293
|
+
<h1>${escapedTitle}</h1>
|
|
294
|
+
<p>${escapedDescription}</p>
|
|
295
|
+
<div class="hint">This local callback page is safe to close. Your terminal session will continue automatically.</div>
|
|
296
|
+
</main>
|
|
297
|
+
<script>
|
|
298
|
+
setTimeout(() => { window.close(); }, 1200);
|
|
299
|
+
</script>
|
|
300
|
+
</body>
|
|
301
|
+
</html>`;
|
|
302
|
+
}
|
|
303
|
+
async function reuseExistingBrowserConsentLogin(options) {
|
|
304
|
+
const stored = loadCredentials();
|
|
305
|
+
if (!stored || stored.authType !== 'oidc_oauth') {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
if (!isLinxOidcOAuthSecrets(stored.secrets) || !stored.secrets.oidcRefreshToken) {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
const requestedIssuer = resolveLinxCloudAccountBaseUrl(options.issuerUrl);
|
|
312
|
+
const storedIssuer = resolveLinxCloudAccountBaseUrl(stored.url);
|
|
313
|
+
if (requestedIssuer !== storedIssuer) {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
const accessToken = await getOidcAccessToken(stored);
|
|
317
|
+
if (!accessToken || !isLinxOidcOAuthSecrets(stored.secrets)) {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
return {
|
|
321
|
+
url: stored.url,
|
|
322
|
+
webId: stored.webId,
|
|
323
|
+
tokenSet: {
|
|
324
|
+
issuer: stored.url.replace(/\/$/, ''),
|
|
325
|
+
clientId: stored.secrets.oidcClientId ?? DEFAULT_CLIENT_NAME,
|
|
326
|
+
refreshToken: stored.secrets.oidcRefreshToken,
|
|
327
|
+
accessToken,
|
|
328
|
+
webId: stored.webId,
|
|
329
|
+
expiresAt: stored.secrets.oidcExpiresAt
|
|
330
|
+
? Math.floor(new Date(stored.secrets.oidcExpiresAt).getTime() / 1000)
|
|
331
|
+
: undefined,
|
|
332
|
+
},
|
|
333
|
+
credentialsToSave: stored,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
function persistBrowserLoginResult(result) {
|
|
337
|
+
saveCredentials(result.credentialsToSave);
|
|
338
|
+
saveAccountSession({
|
|
339
|
+
url: result.url,
|
|
340
|
+
email: 'browser-consent',
|
|
341
|
+
token: 'oidc-session',
|
|
342
|
+
webId: result.webId,
|
|
343
|
+
createdAt: new Date().toISOString(),
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
async function refreshStoredOidcSession(credentials, secrets) {
|
|
347
|
+
const storage = createOidcSessionStorage();
|
|
348
|
+
const sessionId = await resolveStoredOidcSessionId(storage, credentials.webId, credentials.url);
|
|
349
|
+
if (!sessionId) {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
const session = await getSessionFromStorage(sessionId, {
|
|
353
|
+
storage,
|
|
354
|
+
refreshSession: false,
|
|
355
|
+
});
|
|
356
|
+
if (!session) {
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
let refreshedTokenSet = null;
|
|
360
|
+
session.events.on(EVENTS.NEW_TOKENS, (tokenSet) => {
|
|
361
|
+
refreshedTokenSet = tokenSet;
|
|
362
|
+
});
|
|
363
|
+
await refreshSession(session, { storage });
|
|
364
|
+
const nextTokenSet = refreshedTokenSet;
|
|
365
|
+
if (!nextTokenSet?.accessToken) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
secrets.oidcRefreshToken = nextTokenSet.refreshToken ?? secrets.oidcRefreshToken;
|
|
369
|
+
secrets.oidcAccessToken = nextTokenSet.accessToken;
|
|
370
|
+
secrets.oidcExpiresAt = nextTokenSet.expiresAt
|
|
371
|
+
? new Date(nextTokenSet.expiresAt * 1000).toISOString()
|
|
372
|
+
: secrets.oidcExpiresAt;
|
|
373
|
+
secrets.oidcClientId = nextTokenSet.clientId ?? secrets.oidcClientId;
|
|
374
|
+
saveCredentials({
|
|
375
|
+
url: credentials.url,
|
|
376
|
+
webId: session.info.webId ?? credentials.webId,
|
|
377
|
+
authType: 'oidc_oauth',
|
|
378
|
+
secrets,
|
|
379
|
+
});
|
|
380
|
+
saveAccountSession({
|
|
381
|
+
url: credentials.url,
|
|
382
|
+
email: 'browser-consent',
|
|
383
|
+
token: 'oidc-session',
|
|
384
|
+
webId: session.info.webId ?? credentials.webId,
|
|
385
|
+
createdAt: new Date().toISOString(),
|
|
386
|
+
});
|
|
387
|
+
return nextTokenSet.accessToken;
|
|
388
|
+
}
|
|
389
|
+
async function resolveStoredOidcSessionId(storage, webId, issuerUrl) {
|
|
390
|
+
const raw = await storage.get('solidClientAuthn:registeredSessions');
|
|
391
|
+
if (!raw) {
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
let sessionIds = [];
|
|
395
|
+
try {
|
|
396
|
+
const parsed = JSON.parse(raw);
|
|
397
|
+
if (Array.isArray(parsed)) {
|
|
398
|
+
sessionIds = parsed.filter((value) => typeof value === 'string');
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
catch {
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
const normalizedIssuer = issuerUrl.replace(/\/+$/, '');
|
|
405
|
+
for (const sessionId of [...sessionIds].reverse()) {
|
|
406
|
+
const stored = await storage.get(`solidClientAuthenticationUser:${sessionId}`);
|
|
407
|
+
if (!stored) {
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
try {
|
|
411
|
+
const parsed = JSON.parse(stored);
|
|
412
|
+
const sessionWebId = typeof parsed.webId === 'string' ? parsed.webId : null;
|
|
413
|
+
const sessionIssuer = typeof parsed.issuer === 'string' ? parsed.issuer.replace(/\/+$/, '') : null;
|
|
414
|
+
if (sessionWebId === webId && sessionIssuer === normalizedIssuer) {
|
|
415
|
+
return sessionId;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
catch {
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
function escapeHtml(value) {
|
|
425
|
+
return value
|
|
426
|
+
.replace(/&/g, '&')
|
|
427
|
+
.replace(/</g, '<')
|
|
428
|
+
.replace(/>/g, '>')
|
|
429
|
+
.replace(/"/g, '"')
|
|
430
|
+
.replace(/'/g, ''');
|
|
431
|
+
}
|
|
432
|
+
async function waitForManualRedirect(onCallback, manualRedirectUrl) {
|
|
433
|
+
if (!manualRedirectUrl) {
|
|
434
|
+
await new Promise(() => undefined);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
const requestUrl = await manualRedirectUrl();
|
|
438
|
+
await onCallback(requestUrl);
|
|
439
|
+
}
|
|
440
|
+
async function openBrowser(url) {
|
|
441
|
+
if (process.platform === 'darwin') {
|
|
442
|
+
await execFileAsync('open', [url]);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (process.platform === 'win32') {
|
|
446
|
+
await execFileAsync('cmd', ['/c', 'start', '', url]);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
await execFileAsync('xdg-open', [url]);
|
|
450
|
+
}
|
|
451
|
+
//# sourceMappingURL=oidc-auth.js.map
|