@dcl/sdk-commands 7.31.1-40606969412.commit-24c05c3 → 7.31.1-41472004640.commit-3147448

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.
Files changed (33) hide show
  1. package/README.md +15 -0
  2. package/dist/commands/scene-insights/actions.d.ts +63 -0
  3. package/dist/commands/scene-insights/actions.js +106 -0
  4. package/dist/commands/scene-insights/actions.js.map +1 -0
  5. package/dist/commands/scene-insights/client.d.ts +51 -0
  6. package/dist/commands/scene-insights/client.js +294 -0
  7. package/dist/commands/scene-insights/client.js.map +1 -0
  8. package/dist/commands/scene-insights/format.d.ts +23 -0
  9. package/dist/commands/scene-insights/format.js +143 -0
  10. package/dist/commands/scene-insights/format.js.map +1 -0
  11. package/dist/commands/scene-insights/index.d.ts +51 -0
  12. package/dist/commands/scene-insights/index.js +312 -0
  13. package/dist/commands/scene-insights/index.js.map +1 -0
  14. package/dist/commands/scene-insights/inputs.d.ts +49 -0
  15. package/dist/commands/scene-insights/inputs.js +166 -0
  16. package/dist/commands/scene-insights/inputs.js.map +1 -0
  17. package/dist/commands/scene-insights/session-store.d.ts +42 -0
  18. package/dist/commands/scene-insights/session-store.js +131 -0
  19. package/dist/commands/scene-insights/session-store.js.map +1 -0
  20. package/dist/commands/scene-insights/session.d.ts +42 -0
  21. package/dist/commands/scene-insights/session.js +97 -0
  22. package/dist/commands/scene-insights/session.js.map +1 -0
  23. package/dist/commands/scene-insights/sign-in.d.ts +48 -0
  24. package/dist/commands/scene-insights/sign-in.js +116 -0
  25. package/dist/commands/scene-insights/sign-in.js.map +1 -0
  26. package/dist/commands/scene-insights/types.d.ts +61 -0
  27. package/dist/commands/scene-insights/types.js +4 -0
  28. package/dist/commands/scene-insights/types.js.map +1 -0
  29. package/dist/logic/error.d.ts +1 -1
  30. package/dist/logic/error.js.map +1 -1
  31. package/dist/run-command.js +3 -2
  32. package/dist/run-command.js.map +1 -1
  33. package/package.json +5 -5
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LOG_LEVELS = void 0;
4
+ exports.normalizeWorldName = normalizeWorldName;
5
+ exports.normalizeParcel = normalizeParcel;
6
+ exports.buildSceneTarget = buildSceneTarget;
7
+ exports.parseTime = parseTime;
8
+ exports.parseDuration = parseDuration;
9
+ exports.parseTarget = parseTarget;
10
+ exports.parseLevel = parseLevel;
11
+ exports.parseInteger = parseInteger;
12
+ exports.normalizeMetricName = normalizeMetricName;
13
+ const error_1 = require("../../logic/error");
14
+ exports.LOG_LEVELS = ['debug', 'info', 'warn', 'error'];
15
+ /** The service refuses ranges longer than its 7-day retention. */
16
+ const MAX_RANGE_MS = 7 * 24 * 60 * 60 * 1000;
17
+ /**
18
+ * Room for the time between computing `from` here and the service resolving its own "now": a
19
+ * duration of exactly 7 days would otherwise always arrive a few milliseconds over the limit.
20
+ */
21
+ const RANGE_SLACK_MS = 60 * 1000;
22
+ const DURATION_UNITS_MS = {
23
+ s: 1000,
24
+ m: 60 * 1000,
25
+ h: 60 * 60 * 1000,
26
+ d: 24 * 60 * 60 * 1000
27
+ };
28
+ /** setTimeout fires at once past 2^31-1 ms (about 24.8 days). */
29
+ const MAX_DURATION_DAYS = 24;
30
+ /** The hosts a session is meant for; the signature doesn't cover the host, so anywhere else could replay it. */
31
+ const TRUSTED_TARGET_DOMAINS = ['decentraland.org', 'decentraland.zone'];
32
+ /**
33
+ * Normalizes a World name the way creators type it: `foo`, `foo.dcl.eth` or any other ENS name
34
+ * (`foo.eth`). Names without an `.eth` suffix are DCL names.
35
+ */
36
+ function normalizeWorldName(raw) {
37
+ const name = raw.trim().toLowerCase();
38
+ if (!/^[a-z0-9][a-z0-9.-]*$/.test(name)) {
39
+ throw new error_1.CliError('SCENE_INSIGHTS_INVALID_ARGUMENT', `Invalid --world "${raw}"`);
40
+ }
41
+ return name.endsWith('.eth') ? name : `${name}.dcl.eth`;
42
+ }
43
+ /** Normalizes `x,y` parcel coordinates (spaces and leading zeros removed). */
44
+ function normalizeParcel(raw) {
45
+ const match = raw.trim().match(/^(-?\d+)\s*,\s*(-?\d+)$/);
46
+ if (!match) {
47
+ throw new error_1.CliError('SCENE_INSIGHTS_INVALID_ARGUMENT', `Invalid --position "${raw}"; expected "x,y" with integer coordinates`);
48
+ }
49
+ return `${parseInt(match[1], 10)},${parseInt(match[2], 10)}`;
50
+ }
51
+ /**
52
+ * Builds the scene target. A World is addressed by name (`sceneId`), plus the parcel of one of its
53
+ * scenes when it has several; a Genesis City scene by any of its parcels.
54
+ */
55
+ function buildSceneTarget(world, parcel) {
56
+ if (world) {
57
+ return {
58
+ label: parcel ? `${world} ${parcel}` : world,
59
+ metadata: JSON.stringify({ sceneId: world, realmName: world, ...(parcel && { parcel }) }),
60
+ world,
61
+ parcel
62
+ };
63
+ }
64
+ if (!parcel) {
65
+ throw new error_1.CliError('SCENE_INSIGHTS_MISSING_SCENE', 'Provide --world (with an optional --position) for a World, or --position for a Genesis City scene');
66
+ }
67
+ return {
68
+ label: `Genesis City ${parcel}`,
69
+ metadata: JSON.stringify({ parcel, realmName: 'main' }),
70
+ parcel
71
+ };
72
+ }
73
+ /**
74
+ * Parses a `--from`/`--to` value into the ISO-8601 form the service expects. Accepts `now`, a
75
+ * duration back from now (`30s`, `15m`, `2h`, `7d`), epoch milliseconds (13 digits) or ISO-8601
76
+ * with an explicit offset (`2026-10-01T12:00:00Z`, `2026-10-01T09:00:00-03:00`). A duration of 7
77
+ * days or more means the whole retention window, ending a minute short of it.
78
+ */
79
+ function parseTime(raw, flag, now = Date.now()) {
80
+ const value = raw.trim();
81
+ if (value === 'now') {
82
+ return new Date(now).toISOString();
83
+ }
84
+ const duration = value.match(/^(\d+)([smhd])$/);
85
+ if (duration) {
86
+ const ms = parseInt(duration[1], 10) * DURATION_UNITS_MS[duration[2]];
87
+ return new Date(now - Math.min(ms, MAX_RANGE_MS - RANGE_SLACK_MS)).toISOString();
88
+ }
89
+ if (/^\d{13}$/.test(value)) {
90
+ return new Date(parseInt(value, 10)).toISOString();
91
+ }
92
+ const hasOffset = /(Z|[+-]\d{2}:?\d{2})$/i.test(value);
93
+ const parsed = Date.parse(value);
94
+ if (!hasOffset || Number.isNaN(parsed)) {
95
+ throw new error_1.CliError('SCENE_INSIGHTS_INVALID_ARGUMENT', `Invalid ${flag} "${raw}"; use a duration back from now (15m, 2h, 7d), epoch milliseconds or ISO-8601 with an offset (2026-10-01T12:00:00Z)`);
96
+ }
97
+ return new Date(parsed).toISOString();
98
+ }
99
+ /** Parses a duration (`30s`, `5m`, `2h`, `1d`, up to 24 days) into milliseconds. */
100
+ function parseDuration(raw, flag) {
101
+ const duration = raw.trim().match(/^(\d+)([smhd])$/);
102
+ const ms = duration ? parseInt(duration[1], 10) * DURATION_UNITS_MS[duration[2]] : 0;
103
+ if (!(ms > 0)) {
104
+ throw new error_1.CliError('SCENE_INSIGHTS_INVALID_ARGUMENT', `Invalid ${flag} "${raw}"; use a duration such as 30s, 5m or 2h`);
105
+ }
106
+ if (ms > MAX_DURATION_DAYS * DURATION_UNITS_MS.d) {
107
+ throw new error_1.CliError('SCENE_INSIGHTS_INVALID_ARGUMENT', `${flag} must be at most ${MAX_DURATION_DAYS}d`);
108
+ }
109
+ return ms;
110
+ }
111
+ /**
112
+ * Validates a `--target` URL. Without `trustAny`, only https Decentraland hosts are accepted: the
113
+ * signed headers don't name the host, so another server receiving them could replay them.
114
+ */
115
+ function parseTarget(raw, trustAny) {
116
+ let url;
117
+ try {
118
+ url = new URL(raw);
119
+ }
120
+ catch {
121
+ url = undefined;
122
+ }
123
+ if (!url || !['http:', 'https:'].includes(url.protocol) || url.search || url.hash || url.username || url.password) {
124
+ throw new error_1.CliError('SCENE_INSIGHTS_INVALID_ARGUMENT', `Invalid --target "${raw}"; expected an http(s) URL`);
125
+ }
126
+ const hostname = url.hostname;
127
+ const trusted = url.protocol === 'https:' &&
128
+ TRUSTED_TARGET_DOMAINS.some((domain) => hostname === domain || hostname.endsWith(`.${domain}`));
129
+ if (!trusted && !trustAny) {
130
+ throw new error_1.CliError('SCENE_INSIGHTS_INVALID_ARGUMENT', `--target "${raw}" is not an https host under ${TRUSTED_TARGET_DOMAINS.join(' or ')}. It would receive signed ` +
131
+ 'headers it could replay against the real service; pass --trust-target to send them anyway');
132
+ }
133
+ return raw;
134
+ }
135
+ /** Validates a `--level` value. */
136
+ function parseLevel(raw) {
137
+ const level = raw.trim().toLowerCase();
138
+ if (!exports.LOG_LEVELS.includes(level)) {
139
+ throw new error_1.CliError('SCENE_INSIGHTS_INVALID_ARGUMENT', `Invalid --level "${raw}"; use ${exports.LOG_LEVELS.join(', ')}`);
140
+ }
141
+ return level;
142
+ }
143
+ /** Validates an integer flag inside [min, max]. */
144
+ function parseInteger(value, flag, min, max) {
145
+ if (!Number.isInteger(value) || value < min || value > max) {
146
+ throw new error_1.CliError('SCENE_INSIGHTS_INVALID_ARGUMENT', `${flag} must be an integer between ${min} and ${max}`);
147
+ }
148
+ return value;
149
+ }
150
+ /** The metric names the insights service serves: `dcl.scene.<group>.<field>`. */
151
+ const METRIC_NAME_PATTERN = /^dcl\.scene\.[a-z0-9_]+\.[a-z0-9_]+$/;
152
+ /**
153
+ * Expands a metric name, so `cpu.run_ms` can be typed for `dcl.scene.cpu.run_ms`.
154
+ *
155
+ * @throws {CliError} SCENE_INSIGHTS_INVALID_ARGUMENT when the result is not `dcl.scene.<group>.<field>`
156
+ */
157
+ function normalizeMetricName(raw) {
158
+ const trimmed = raw.trim();
159
+ const name = trimmed.startsWith('dcl.scene.') ? trimmed : `dcl.scene.${trimmed}`;
160
+ // checked here because the name becomes a path segment: `..` or `/` would change the path
161
+ if (!METRIC_NAME_PATTERN.test(name)) {
162
+ throw new error_1.CliError('SCENE_INSIGHTS_INVALID_ARGUMENT', `Invalid metric "${raw}": expected <group>.<field> (lowercase letters, digits and _), e.g. cpu.run_ms`);
163
+ }
164
+ return name;
165
+ }
166
+ //# sourceMappingURL=inputs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inputs.js","sourceRoot":"","sources":["../../../src/commands/scene-insights/inputs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import { AuthChain, AuthIdentity } from '@dcl/crypto';
2
+ import { CliComponents } from '../../components';
3
+ import { InsightsSession } from './session';
4
+ /** Environment variable naming the folder whose .dcl/ holds the session, e.g. a sandbox an agent can read. */
5
+ export declare const SESSION_DIR_ENV = "DCL_SCENE_INSIGHTS_SESSION_DIR";
6
+ /**
7
+ * What is saved after a browser signature: the ephemeral key the wallet delegated to, and the
8
+ * delegation. Until it expires it signs requests as the wallet, so it is written readable only by
9
+ * the user and removed by `scene-insights logout`.
10
+ */
11
+ export interface SavedSession {
12
+ version: 1;
13
+ address: string;
14
+ expiration: string;
15
+ ephemeralIdentity: AuthIdentity['ephemeralIdentity'];
16
+ authChain: AuthChain;
17
+ }
18
+ export declare function getSessionFilePath(base?: string): string;
19
+ /**
20
+ * Reads the saved session. Returns undefined when there is none, it is malformed, it expires within
21
+ * a minute, or its delegation does not verify (tampered, or not a delegation to its key).
22
+ */
23
+ export declare function loadSession(components: Pick<CliComponents, 'fs'>, file: string, now?: number): Promise<{
24
+ session: InsightsSession;
25
+ saved: SavedSession;
26
+ } | undefined>;
27
+ /**
28
+ * Refuses a session file git already tracks, e.g. a .dcl folder committed before it ignored
29
+ * itself: its .gitignore doesn't stop a tracked file being committed again.
30
+ *
31
+ * @throws CliError `SCENE_INSIGHTS_UNSAFE_SESSION_FILE` when git tracks it.
32
+ */
33
+ export declare function assertSessionFileIsNotTracked(file: string): Promise<void>;
34
+ /**
35
+ * Saves the session readable only by the user (0600, in a 0700 directory), replacing any other.
36
+ * Its folder's .gitignore ignores everything in it, so git never commits it.
37
+ *
38
+ * @throws CliError `SCENE_INSIGHTS_UNSAFE_SESSION_FILE` when git tracks the file.
39
+ */
40
+ export declare function saveSession(components: Pick<CliComponents, 'fs'>, file: string, ephemeralIdentity: AuthIdentity['ephemeralIdentity'], expiration: Date, authChain: AuthChain): Promise<void>;
41
+ /** Removes the saved session. Returns whether there was one. */
42
+ export declare function deleteSession(components: Pick<CliComponents, 'fs'>, file: string): Promise<boolean>;
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SESSION_DIR_ENV = void 0;
7
+ exports.getSessionFilePath = getSessionFilePath;
8
+ exports.loadSession = loadSession;
9
+ exports.assertSessionFileIsNotTracked = assertSessionFileIsNotTracked;
10
+ exports.saveSession = saveSession;
11
+ exports.deleteSession = deleteSession;
12
+ const os_1 = require("os");
13
+ const path_1 = __importDefault(require("path"));
14
+ const crypto_1 = require("crypto");
15
+ const child_process_1 = require("child_process");
16
+ const crypto_2 = require("@dcl/crypto");
17
+ const error_1 = require("../../logic/error");
18
+ const session_1 = require("./session");
19
+ /** Environment variable naming the folder whose .dcl/ holds the session, e.g. a sandbox an agent can read. */
20
+ exports.SESSION_DIR_ENV = 'DCL_SCENE_INSIGHTS_SESSION_DIR';
21
+ /**
22
+ * Sessions are only saved in a .dcl folder: a dot folder is never deployed (whatever .dclignore
23
+ * says), and its own `*` .gitignore keeps git from adding anything in it.
24
+ */
25
+ const SESSION_FOLDER = '.dcl';
26
+ /** A saved session is not used in its last minute, so a request never races its expiry. */
27
+ const EXPIRY_MARGIN_MS = 60 * 1000;
28
+ function getSessionFilePath(base = process.env[exports.SESSION_DIR_ENV] || (0, os_1.homedir)()) {
29
+ return path_1.default.resolve(base, SESSION_FOLDER, 'scene-insights-session.json');
30
+ }
31
+ /**
32
+ * Reads the saved session. Returns undefined when there is none, it is malformed, it expires within
33
+ * a minute, or its delegation does not verify (tampered, or not a delegation to its key).
34
+ */
35
+ async function loadSession(components, file, now = Date.now()) {
36
+ const { fs } = components;
37
+ if (!(await fs.fileExists(file)))
38
+ return undefined;
39
+ let saved;
40
+ try {
41
+ saved = JSON.parse(await fs.readFile(file, 'utf8'));
42
+ }
43
+ catch {
44
+ return undefined;
45
+ }
46
+ if (saved?.version !== 1 ||
47
+ typeof saved.expiration !== 'string' ||
48
+ typeof saved.ephemeralIdentity?.privateKey !== 'string' ||
49
+ typeof saved.ephemeralIdentity?.address !== 'string' ||
50
+ !Array.isArray(saved.authChain)) {
51
+ return undefined;
52
+ }
53
+ const expiration = new Date(saved.expiration);
54
+ if (!(expiration.getTime() - EXPIRY_MARGIN_MS > now))
55
+ return undefined;
56
+ const check = await crypto_2.Authenticator.validateSignature(saved.ephemeralIdentity.address.toLowerCase(), saved.authChain, null, now, { service: session_1.INSIGHTS_SERVICE });
57
+ if (!check.ok ||
58
+ !check.scope ||
59
+ !session_1.INSIGHTS_OPERATIONS.every((operation) => (0, crypto_2.isOperationGranted)(check.scope, operation)))
60
+ return undefined;
61
+ expiration.setTime(Math.min(expiration.getTime(), check.scope.expiration));
62
+ if (!(expiration.getTime() - EXPIRY_MARGIN_MS > now))
63
+ return undefined;
64
+ return { session: (0, session_1.createSessionFromChain)(saved.ephemeralIdentity, expiration, saved.authChain), saved };
65
+ }
66
+ /** Whether git tracks the file, so a .gitignore no longer keeps it out of commits. */
67
+ function isTrackedByGit(file) {
68
+ // exits 0 only for a tracked file; untracked, outside a repository or without git, it fails
69
+ return new Promise((resolve) => {
70
+ (0, child_process_1.execFile)('git', ['ls-files', '--error-unmatch', '--', path_1.default.basename(file)], { cwd: path_1.default.dirname(file) }, (error) => resolve(!error));
71
+ });
72
+ }
73
+ /**
74
+ * Refuses a session file git already tracks, e.g. a .dcl folder committed before it ignored
75
+ * itself: its .gitignore doesn't stop a tracked file being committed again.
76
+ *
77
+ * @throws CliError `SCENE_INSIGHTS_UNSAFE_SESSION_FILE` when git tracks it.
78
+ */
79
+ async function assertSessionFileIsNotTracked(file) {
80
+ const resolved = path_1.default.resolve(file);
81
+ if (await isTrackedByGit(resolved)) {
82
+ throw new error_1.CliError('SCENE_INSIGHTS_UNSAFE_SESSION_FILE', `The session file ${resolved} is tracked by git, so it would be committed. Remove it from the repository ` +
83
+ `(git rm --cached) or set ${exports.SESSION_DIR_ENV} to a folder outside it`);
84
+ }
85
+ }
86
+ /** Makes the session's folder ignore everything in it, so git never adds the session or its temporary copies. */
87
+ async function ignoreFolderInGit(components, folder) {
88
+ const { fs } = components;
89
+ const gitignore = path_1.default.join(folder, '.gitignore');
90
+ const existing = (await fs.fileExists(gitignore)) ? await fs.readFile(gitignore, 'utf8') : '';
91
+ if (existing.split(/\r?\n/).includes('*'))
92
+ return;
93
+ const separator = existing && !existing.endsWith('\n') ? '\n' : '';
94
+ await fs.appendFile(gitignore, separator + '*\n');
95
+ }
96
+ /**
97
+ * Saves the session readable only by the user (0600, in a 0700 directory), replacing any other.
98
+ * Its folder's .gitignore ignores everything in it, so git never commits it.
99
+ *
100
+ * @throws CliError `SCENE_INSIGHTS_UNSAFE_SESSION_FILE` when git tracks the file.
101
+ */
102
+ async function saveSession(components, file, ephemeralIdentity, expiration, authChain) {
103
+ const { fs } = components;
104
+ const saved = {
105
+ version: 1,
106
+ address: crypto_2.Authenticator.ownerAddress(authChain),
107
+ expiration: expiration.toISOString(),
108
+ ephemeralIdentity: {
109
+ address: ephemeralIdentity.address,
110
+ publicKey: '0x',
111
+ privateKey: ephemeralIdentity.privateKey
112
+ },
113
+ authChain
114
+ };
115
+ await assertSessionFileIsNotTracked(file);
116
+ await fs.mkdir(path_1.default.dirname(file), { recursive: true, mode: 0o700 });
117
+ await ignoreFolderInGit(components, path_1.default.dirname(file));
118
+ // a fresh file always gets the mode, unlike rewriting an existing one; rename makes it atomic
119
+ const temporary = `${file}.${(0, crypto_1.randomBytes)(6).toString('hex')}.tmp`;
120
+ await fs.writeFile(temporary, JSON.stringify(saved, null, 2), { mode: 0o600 });
121
+ await fs.rename(temporary, file);
122
+ }
123
+ /** Removes the saved session. Returns whether there was one. */
124
+ async function deleteSession(components, file) {
125
+ const { fs } = components;
126
+ if (!(await fs.fileExists(file)))
127
+ return false;
128
+ await fs.unlink(file);
129
+ return true;
130
+ }
131
+ //# sourceMappingURL=session-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-store.js","sourceRoot":"","sources":["../../../src/commands/scene-insights/session-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import { AuthChain, AuthIdentity } from '@dcl/crypto';
2
+ /** How long one signature lasts; after that, `scene-insights login` signs a new session. */
3
+ export declare const SESSION_MINUTES: number;
4
+ export declare const INSIGHTS_SERVICE = "insights";
5
+ export declare const INSIGHTS_OPERATIONS: readonly ["logs-read", "metrics-read", "executions-read"];
6
+ export declare function getInsightsSessionMessage(address: string, expiration: Date): string;
7
+ /**
8
+ * Signs any number of requests after a single wallet signature.
9
+ *
10
+ * Every insights request is a signed fetch over `method:path:timestamp:metadata`, so paging, tail
11
+ * reconnects and repeated commands need many signatures. Instead of asking the wallet for each one,
12
+ * the wallet signs once to delegate to an ephemeral key held by the CLI, and that key signs each
13
+ * request (session-store.ts keeps it between commands).
14
+ */
15
+ export interface InsightsSession {
16
+ /** The wallet address the requests are made as */
17
+ address: string;
18
+ /** When the delegation stops being accepted */
19
+ expiration: Date;
20
+ /** Signed-fetch headers for one request */
21
+ sign(method: string, pathname: string, metadata: string): Record<string, string>;
22
+ }
23
+ /** The payload a signed fetch signs (crypto-middleware 6): method, path and timestamp lowercased, metadata verbatim. */
24
+ export declare function buildSignedFetchPayload(method: string, pathname: string, timestamp: string, metadata: string): string;
25
+ /** A fresh ephemeral key from a secure random source. */
26
+ export declare function createEphemeralIdentity(): AuthIdentity['ephemeralIdentity'];
27
+ /** Builds a session from a delegation chain whose final authority is the ephemeral key. */
28
+ export declare function createSessionFromChain(ephemeralIdentity: AuthIdentity['ephemeralIdentity'], expiration: Date, authChain: AuthChain): InsightsSession;
29
+ /** A delegation signed directly with a private key (`DCL_PRIVATE_KEY`). */
30
+ export declare function createSessionFromPrivateKey(privateKey: string, now?: number): InsightsSession;
31
+ /**
32
+ * Turns what the linker dApp returns into a delegation to our ephemeral key.
33
+ *
34
+ * The dApp signs whatever it is given with its own browser-side ephemeral key, closing the chain
35
+ * with an `ECDSA_SIGNED_ENTITY` link. Handing it our ephemeral key's login message makes that last
36
+ * link a signature of the message by the dApp's key, which is exactly a scoped delegation, so
37
+ * it is relabeled as one: wallet → dApp key → our key.
38
+ *
39
+ * @throws CliError when the chain does not sign the message, or comes from a contract wallet
40
+ * (scoped contract-wallet chains require a provider rather than Catalyst validation).
41
+ */
42
+ export declare function delegationFromLinkerChain(authChain: AuthChain, ephemeralAddress: string, message: string, now?: number): Promise<AuthChain>;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.INSIGHTS_OPERATIONS = exports.INSIGHTS_SERVICE = exports.SESSION_MINUTES = void 0;
4
+ exports.getInsightsSessionMessage = getInsightsSessionMessage;
5
+ exports.buildSignedFetchPayload = buildSignedFetchPayload;
6
+ exports.createEphemeralIdentity = createEphemeralIdentity;
7
+ exports.createSessionFromChain = createSessionFromChain;
8
+ exports.createSessionFromPrivateKey = createSessionFromPrivateKey;
9
+ exports.delegationFromLinkerChain = delegationFromLinkerChain;
10
+ const crypto_1 = require("crypto");
11
+ const crypto_2 = require("@dcl/crypto");
12
+ const crypto_3 = require("@dcl/crypto/dist/crypto");
13
+ const eth_connect_1 = require("eth-connect");
14
+ const error_1 = require("../../logic/error");
15
+ const account_1 = require("../../logic/account");
16
+ const auth_chain_headers_1 = require("../../logic/auth-chain-headers");
17
+ /** How long one signature lasts; after that, `scene-insights login` signs a new session. */
18
+ exports.SESSION_MINUTES = 12 * 60;
19
+ exports.INSIGHTS_SERVICE = 'insights';
20
+ exports.INSIGHTS_OPERATIONS = ['logs-read', 'metrics-read', 'executions-read'];
21
+ function getInsightsSessionMessage(address, expiration) {
22
+ return (0, crypto_2.getScopedEphemeralMessage)(address, expiration, exports.INSIGHTS_OPERATIONS.map((operation) => ({ service: exports.INSIGHTS_SERVICE, operation })));
23
+ }
24
+ /** The payload a signed fetch signs (crypto-middleware 6): method, path and timestamp lowercased, metadata verbatim. */
25
+ function buildSignedFetchPayload(method, pathname, timestamp, metadata) {
26
+ return [method.toLowerCase(), pathname.toLowerCase(), timestamp, metadata].join(':');
27
+ }
28
+ /** A fresh ephemeral key from a secure random source. */
29
+ function createEphemeralIdentity() {
30
+ const privateKey = `0x${(0, crypto_1.randomBytes)(32).toString('hex')}`;
31
+ const { address } = (0, account_1.createWallet)(privateKey);
32
+ return { privateKey, publicKey: '0x', address };
33
+ }
34
+ /** Builds a session from a delegation chain whose final authority is the ephemeral key. */
35
+ function createSessionFromChain(ephemeralIdentity, expiration, authChain) {
36
+ const identity = { ephemeralIdentity, expiration, authChain };
37
+ return {
38
+ address: crypto_2.Authenticator.ownerAddress(authChain),
39
+ expiration,
40
+ sign(method, pathname, metadata) {
41
+ const timestamp = String(Date.now());
42
+ const requestChain = crypto_2.Authenticator.signPayload(identity, buildSignedFetchPayload(method, pathname, timestamp, metadata));
43
+ return (0, auth_chain_headers_1.createAuthChainHeaders)(requestChain, timestamp, metadata);
44
+ }
45
+ };
46
+ }
47
+ /** A delegation signed directly with a private key (`DCL_PRIVATE_KEY`). */
48
+ function createSessionFromPrivateKey(privateKey, now = Date.now()) {
49
+ const wallet = (0, account_1.createWallet)(privateKey);
50
+ const ephemeralIdentity = createEphemeralIdentity();
51
+ const expiration = new Date(now + exports.SESSION_MINUTES * 60 * 1000);
52
+ const message = getInsightsSessionMessage(ephemeralIdentity.address, expiration);
53
+ const authChain = [
54
+ { type: crypto_2.AuthLinkType.SIGNER, payload: wallet.address, signature: '' },
55
+ {
56
+ type: crypto_2.ScopedAuthLinkType.ECDSA_PERSONAL_SCOPED_EPHEMERAL,
57
+ payload: message,
58
+ signature: (0, crypto_3.ethSign)((0, eth_connect_1.hexToBytes)(wallet.privateKey), message)
59
+ }
60
+ ];
61
+ return createSessionFromChain(ephemeralIdentity, expiration, authChain);
62
+ }
63
+ /**
64
+ * Turns what the linker dApp returns into a delegation to our ephemeral key.
65
+ *
66
+ * The dApp signs whatever it is given with its own browser-side ephemeral key, closing the chain
67
+ * with an `ECDSA_SIGNED_ENTITY` link. Handing it our ephemeral key's login message makes that last
68
+ * link a signature of the message by the dApp's key, which is exactly a scoped delegation, so
69
+ * it is relabeled as one: wallet → dApp key → our key.
70
+ *
71
+ * @throws CliError when the chain does not sign the message, or comes from a contract wallet
72
+ * (scoped contract-wallet chains require a provider rather than Catalyst validation).
73
+ */
74
+ async function delegationFromLinkerChain(authChain, ephemeralAddress, message, now = Date.now()) {
75
+ if (authChain.some((link) => link.type === crypto_2.AuthLinkType.ECDSA_EIP_1654_EPHEMERAL)) {
76
+ throw new error_1.CliError('SCENE_INSIGHTS_UNSUPPORTED_WALLET', 'Smart contract wallets cannot sign insights sessions in the browser; set DCL_PRIVATE_KEY to an address with access instead');
77
+ }
78
+ const last = authChain[authChain.length - 1];
79
+ if (authChain.length < 2 ||
80
+ !last ||
81
+ last.type !== crypto_2.AuthLinkType.ECDSA_PERSONAL_SIGNED_ENTITY ||
82
+ last.payload !== message) {
83
+ throw new error_1.CliError('SCENE_INSIGHTS_INVALID_SIGNATURE', 'The wallet did not sign the insights session');
84
+ }
85
+ const delegation = [
86
+ ...authChain.slice(0, -1),
87
+ { ...last, type: crypto_2.ScopedAuthLinkType.ECDSA_PERSONAL_SCOPED_EPHEMERAL }
88
+ ];
89
+ const result = await crypto_2.Authenticator.validateSignature(ephemeralAddress.toLowerCase(), delegation, null, now, {
90
+ service: exports.INSIGHTS_SERVICE
91
+ });
92
+ if (!result.ok) {
93
+ throw new error_1.CliError('SCENE_INSIGHTS_INVALID_SIGNATURE', `The insights session signature is invalid: ${result.message}`);
94
+ }
95
+ return delegation;
96
+ }
97
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../../src/commands/scene-insights/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ import { AuthChain } from '@dcl/crypto';
2
+ import { Router } from '@well-known-components/http-server';
3
+ import { CliComponents } from '../../components';
4
+ import { dAppOptions } from '../../run-dapp';
5
+ import { InsightsSession } from './session';
6
+ /** What the linker dApp shows while signing. */
7
+ export interface SignInLabel {
8
+ label: string;
9
+ world?: string;
10
+ parcel?: string;
11
+ }
12
+ export interface SessionOptions {
13
+ /** Where the session is saved */
14
+ file: string;
15
+ /** The service URL, shown in the linker dApp */
16
+ targetUrl: string;
17
+ linkOptions: Omit<dAppOptions, 'uri'>;
18
+ /** Whether a person can answer the browser prompt; without one, a missing session is an error */
19
+ interactive: boolean;
20
+ /** Sign a new session even when a saved one is still valid (`login`) */
21
+ forceNew?: boolean;
22
+ }
23
+ export type SessionSource = 'private-key' | 'saved' | 'signed';
24
+ /**
25
+ * Gets a session to sign requests with, in order:
26
+ * 1. `DCL_PRIVATE_KEY`, signed locally and never saved;
27
+ * 2. the saved session, while it is valid (unless `forceNew`);
28
+ * 3. a new browser signature through the linker dApp, which is then saved.
29
+ *
30
+ * @throws CliError `SCENE_INSIGHTS_SESSION_REQUIRED` when it would have to open the browser but
31
+ * nobody is there to sign (an agent or script): `scene-insights login` has to be run first.
32
+ * @throws CliError `SCENE_INSIGHTS_UNSAFE_SESSION_FILE` when git tracks the session file, before
33
+ * asking for a signature.
34
+ */
35
+ export declare function getSession(components: Pick<CliComponents, 'fs' | 'logger' | 'fetch' | 'config'>, label: SignInLabel, options: SessionOptions): Promise<{
36
+ session: InsightsSession;
37
+ source: SessionSource;
38
+ }>;
39
+ /**
40
+ * Whether a request comes from the linker dApp's own page. The browser sets Origin and Host, so
41
+ * another page open in it (or a rebinding domain) can't pass for the dApp.
42
+ */
43
+ export declare function isLinkerOrigin(origin: string | null, host: string | null): boolean;
44
+ /**
45
+ * The linker dApp posts `view-logs` signatures to `/api/logs`. Only its own page is accepted: any
46
+ * other page could sign the message with its own wallet and sign the CLI in as that address.
47
+ */
48
+ export declare function setSignInRoutes(router: Router<object>, ephemeralAddress: string, message: string, onSigned: (delegation: AuthChain) => void): Router<object>;