relayax-cli 0.1.996 → 0.1.998
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/dist/commands/login.js +32 -12
- package/dist/lib/api.d.ts +1 -0
- package/dist/lib/api.js +23 -3
- package/dist/lib/config.js +4 -2
- package/dist/lib/version-check.js +2 -0
- package/package.json +1 -1
package/dist/commands/login.js
CHANGED
|
@@ -37,17 +37,17 @@ async function verifyToken(token) {
|
|
|
37
37
|
return null;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
-
function
|
|
41
|
-
return new
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
40
|
+
function parseFormBody(body) {
|
|
41
|
+
return new URLSearchParams(body);
|
|
42
|
+
}
|
|
43
|
+
function collectBody(req) {
|
|
44
|
+
return new Promise((resolve) => {
|
|
45
|
+
const chunks = [];
|
|
46
|
+
req.on('data', (chunk) => chunks.push(chunk));
|
|
47
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString()));
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
const SUCCESS_HTML = `<!DOCTYPE html>
|
|
51
51
|
<html><head><title>RelayAX</title></head>
|
|
52
52
|
<body style="font-family:system-ui;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#f6f5f2;color:#111318">
|
|
53
53
|
<div style="text-align:center">
|
|
@@ -55,7 +55,27 @@ function waitForToken(port) {
|
|
|
55
55
|
<p>터미널로 돌아가세요. 이 창은 닫아도 됩니다.</p>
|
|
56
56
|
<script>setTimeout(()=>window.close(),2000)</script>
|
|
57
57
|
</div>
|
|
58
|
-
</body></html
|
|
58
|
+
</body></html>`;
|
|
59
|
+
function waitForToken(port) {
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
const server = http_1.default.createServer(async (req, res) => {
|
|
62
|
+
const url = new URL(req.url ?? '/', `http://localhost:${port}`);
|
|
63
|
+
if (url.pathname === '/callback') {
|
|
64
|
+
// POST body (secure) 또는 GET query params (하위 호환) 모두 지원
|
|
65
|
+
let params;
|
|
66
|
+
if (req.method === 'POST') {
|
|
67
|
+
const body = await collectBody(req);
|
|
68
|
+
params = parseFormBody(body);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
params = url.searchParams;
|
|
72
|
+
}
|
|
73
|
+
const token = params.get('token');
|
|
74
|
+
const refresh_token = params.get('refresh_token') ?? undefined;
|
|
75
|
+
const expires_at_raw = params.get('expires_at');
|
|
76
|
+
const expires_at = expires_at_raw ? Number(expires_at_raw) : undefined;
|
|
77
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
78
|
+
res.end(SUCCESS_HTML);
|
|
59
79
|
server.close();
|
|
60
80
|
if (token) {
|
|
61
81
|
resolve({ token, refresh_token, expires_at });
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -14,4 +14,5 @@ export interface ResolvedSlug {
|
|
|
14
14
|
full: string;
|
|
15
15
|
}
|
|
16
16
|
export declare function resolveSlugFromServer(name: string): Promise<ResolvedSlug[]>;
|
|
17
|
+
export declare function sendUsagePing(slug: string): Promise<void>;
|
|
17
18
|
export declare function followBuilder(username: string): Promise<void>;
|
package/dist/lib/api.js
CHANGED
|
@@ -5,10 +5,12 @@ exports.searchTeams = searchTeams;
|
|
|
5
5
|
exports.fetchTeamVersions = fetchTeamVersions;
|
|
6
6
|
exports.reportInstall = reportInstall;
|
|
7
7
|
exports.resolveSlugFromServer = resolveSlugFromServer;
|
|
8
|
+
exports.sendUsagePing = sendUsagePing;
|
|
8
9
|
exports.followBuilder = followBuilder;
|
|
9
10
|
const config_js_1 = require("./config.js");
|
|
10
11
|
async function fetchTeamInfo(slug) {
|
|
11
|
-
const
|
|
12
|
+
const registrySlug = slug.startsWith('@') ? slug.slice(1) : slug;
|
|
13
|
+
const url = `${config_js_1.API_URL}/api/registry/${registrySlug}`;
|
|
12
14
|
const res = await fetch(url);
|
|
13
15
|
if (!res.ok) {
|
|
14
16
|
const body = await res.text();
|
|
@@ -30,7 +32,8 @@ async function searchTeams(query, tag) {
|
|
|
30
32
|
return data.results;
|
|
31
33
|
}
|
|
32
34
|
async function fetchTeamVersions(slug) {
|
|
33
|
-
const
|
|
35
|
+
const registrySlug = slug.startsWith('@') ? slug.slice(1) : slug;
|
|
36
|
+
const url = `${config_js_1.API_URL}/api/registry/${registrySlug}/versions`;
|
|
34
37
|
const res = await fetch(url);
|
|
35
38
|
if (!res.ok) {
|
|
36
39
|
const body = await res.text();
|
|
@@ -39,7 +42,8 @@ async function fetchTeamVersions(slug) {
|
|
|
39
42
|
return res.json();
|
|
40
43
|
}
|
|
41
44
|
async function reportInstall(slug) {
|
|
42
|
-
const
|
|
45
|
+
const registrySlug = slug.startsWith('@') ? slug.slice(1) : slug;
|
|
46
|
+
const url = `${config_js_1.API_URL}/api/registry/${registrySlug}/install`;
|
|
43
47
|
await fetch(url, { method: 'POST' }).catch(() => {
|
|
44
48
|
// non-critical: ignore errors
|
|
45
49
|
});
|
|
@@ -53,6 +57,22 @@ async function resolveSlugFromServer(name) {
|
|
|
53
57
|
const data = (await res.json());
|
|
54
58
|
return data.results;
|
|
55
59
|
}
|
|
60
|
+
async function sendUsagePing(slug) {
|
|
61
|
+
const registrySlug = slug.startsWith('@') ? slug.slice(1) : slug;
|
|
62
|
+
const { createHash } = await import('crypto');
|
|
63
|
+
const { hostname, userInfo } = await import('os');
|
|
64
|
+
const deviceHash = createHash('sha256')
|
|
65
|
+
.update(`${hostname()}:${userInfo().username}`)
|
|
66
|
+
.digest('hex');
|
|
67
|
+
const url = `${config_js_1.API_URL}/api/registry/${registrySlug}/ping`;
|
|
68
|
+
await fetch(url, {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: { 'Content-Type': 'application/json' },
|
|
71
|
+
body: JSON.stringify({ device_hash: deviceHash }),
|
|
72
|
+
}).catch(() => {
|
|
73
|
+
// fire-and-forget: ignore errors
|
|
74
|
+
});
|
|
75
|
+
}
|
|
56
76
|
async function followBuilder(username) {
|
|
57
77
|
const token = await (0, config_js_1.getValidToken)();
|
|
58
78
|
const headers = {
|
package/dist/lib/config.js
CHANGED
|
@@ -79,11 +79,13 @@ function loadToken() {
|
|
|
79
79
|
}
|
|
80
80
|
function saveTokenData(data) {
|
|
81
81
|
ensureGlobalRelayDir();
|
|
82
|
-
|
|
82
|
+
const tokenFile = path_1.default.join(GLOBAL_RELAY_DIR, 'token');
|
|
83
|
+
fs_1.default.writeFileSync(tokenFile, JSON.stringify(data), { mode: 0o600 });
|
|
83
84
|
}
|
|
84
85
|
function saveToken(token) {
|
|
85
86
|
ensureGlobalRelayDir();
|
|
86
|
-
|
|
87
|
+
const tokenFile = path_1.default.join(GLOBAL_RELAY_DIR, 'token');
|
|
88
|
+
fs_1.default.writeFileSync(tokenFile, JSON.stringify({ access_token: token }), { mode: 0o600 });
|
|
87
89
|
}
|
|
88
90
|
/**
|
|
89
91
|
* 유효한 access_token을 반환한다.
|
|
@@ -42,6 +42,8 @@ async function checkTeamVersion(slug, force) {
|
|
|
42
42
|
}
|
|
43
43
|
const team = await (0, api_js_1.fetchTeamInfo)(slug);
|
|
44
44
|
(0, update_cache_js_1.updateCacheTimestamp)(slug);
|
|
45
|
+
// Fire-and-forget usage ping (only when cache expired = actual API call happened)
|
|
46
|
+
(0, api_js_1.sendUsagePing)(slug);
|
|
45
47
|
if (team.version !== entry.version) {
|
|
46
48
|
return {
|
|
47
49
|
type: 'team',
|