relay-config.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. const fs = require('fs');
  2. const path = require('path');
  3. const os = require('os');
  4. const { PACKAGE_ROOT, outputsRoot } = require('./output-paths');
  5. const RELAY_CONFIG_FILE = path.join(outputsRoot(), 'webhook', 'relay-config.json');
  6. const CROSS_PROJECT_CREDENTIALS_FILE = path.join(os.homedir(), '.qiwe-skill', 'relay-credentials.json');
  7. function ensureDir(filePath) {
  8. const dir = path.dirname(filePath);
  9. if (!fs.existsSync(dir)) {
  10. fs.mkdirSync(dir, { recursive: true });
  11. }
  12. }
  13. function readJsonMaybe(filePath) {
  14. try {
  15. if (!filePath || !fs.existsSync(filePath)) return {};
  16. return JSON.parse(fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, ''));
  17. } catch {
  18. return {};
  19. }
  20. }
  21. function readEnvLocal() {
  22. const candidates = [
  23. path.resolve(process.cwd(), '.env.local'),
  24. path.resolve(process.cwd(), '.env'),
  25. path.join(PACKAGE_ROOT, '.env.local'),
  26. path.join(PACKAGE_ROOT, '.env')
  27. ];
  28. const env = {};
  29. for (const envPath of [...new Set(candidates)]) {
  30. if (!fs.existsSync(envPath)) continue;
  31. const content = fs.readFileSync(envPath, 'utf8').replace(/^\uFEFF/, '');
  32. for (const rawLine of content.split(/\r?\n/)) {
  33. const line = rawLine.trim();
  34. if (!line || line.startsWith('#')) continue;
  35. const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
  36. if (!match || env[match[1]]) continue;
  37. let value = match[2].trim();
  38. if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
  39. value = value.slice(1, -1);
  40. }
  41. env[match[1]] = value;
  42. }
  43. }
  44. return env;
  45. }
  46. function readRelayConfigFile() {
  47. return readJsonMaybe(RELAY_CONFIG_FILE);
  48. }
  49. function writeRelayConfigFile(config) {
  50. ensureDir(RELAY_CONFIG_FILE);
  51. const current = readRelayConfigFile();
  52. fs.writeFileSync(
  53. RELAY_CONFIG_FILE,
  54. JSON.stringify({ ...current, ...config, updatedAt: new Date().toISOString() }, null, 2),
  55. 'utf8'
  56. );
  57. }
  58. function isRelayEnabled() {
  59. const env = readEnvLocal();
  60. const baseUrl = process.env.RELAY_BASE_URL || env.RELAY_BASE_URL || readRelayConfigFile().relayBaseUrl || '';
  61. const apiKey = process.env.TENANT_API_KEY || env.TENANT_API_KEY || '';
  62. const apiSecret = process.env.TENANT_API_SECRET || env.TENANT_API_SECRET || '';
  63. return !!(baseUrl && apiKey && apiSecret);
  64. }
  65. function getRelayBaseUrl() {
  66. const env = readEnvLocal();
  67. return (process.env.RELAY_BASE_URL || env.RELAY_BASE_URL || readRelayConfigFile().relayBaseUrl || 'http://8.138.37.248:4000').replace(/\/$/, '');
  68. }
  69. function getTenantApiKey() {
  70. const env = readEnvLocal();
  71. return process.env.TENANT_API_KEY || env.TENANT_API_KEY || '';
  72. }
  73. function getTenantApiSecret() {
  74. const env = readEnvLocal();
  75. return process.env.TENANT_API_SECRET || env.TENANT_API_SECRET || '';
  76. }
  77. function getTenantId() {
  78. const env = readEnvLocal();
  79. return process.env.TENANT_ID || env.TENANT_ID || readRelayConfigFile().tenantId || '';
  80. }
  81. function getRelayPrivateKey() {
  82. const env = readEnvLocal();
  83. return (process.env.RELAY_PRIVATE_KEY || env.RELAY_PRIVATE_KEY || '').replace(/\\n/g, '\n');
  84. }
  85. function getRelayPublicKey() {
  86. return readRelayConfigFile().publicKey || '';
  87. }
  88. function getRelayDeviceGuid() {
  89. return readRelayConfigFile().deviceGuid || '';
  90. }
  91. function getRelayCredentials() {
  92. return {
  93. relayBaseUrl: getRelayBaseUrl(),
  94. tenantId: getTenantId(),
  95. tenantApiKey: getTenantApiKey(),
  96. tenantApiSecret: getTenantApiSecret(),
  97. privateKey: getRelayPrivateKey(),
  98. publicKey: getRelayPublicKey(),
  99. deviceGuid: getRelayDeviceGuid()
  100. };
  101. }
  102. function normalizePrivateKey(privateKey) {
  103. return privateKey
  104. .replace(/\r\n/g, '\n')
  105. .replace(/\n/g, '\\n');
  106. }
  107. function saveRelayCredentialsToEnv(creds, envPath) {
  108. const targetPath = envPath || path.join(PACKAGE_ROOT, '.env.local');
  109. ensureDir(targetPath);
  110. let content = '';
  111. if (fs.existsSync(targetPath)) {
  112. content = fs.readFileSync(targetPath, 'utf8');
  113. }
  114. const normalizedPrivateKey = creds.privateKey ? normalizePrivateKey(creds.privateKey) : '';
  115. const entries = {
  116. RELAY_BASE_URL: creds.relayBaseUrl || getRelayBaseUrl(),
  117. TENANT_ID: creds.tenantId || '',
  118. TENANT_API_KEY: creds.tenantApiKey || '',
  119. TENANT_API_SECRET: creds.tenantApiSecret || '',
  120. RELAY_PRIVATE_KEY: normalizedPrivateKey
  121. };
  122. for (const [key, value] of Object.entries(entries)) {
  123. if (!value) continue;
  124. const regex = new RegExp(`^${key}\\s*=.*$`, 'm');
  125. const line = `${key}=${value}`;
  126. if (regex.test(content)) {
  127. content = content.replace(regex, line);
  128. } else {
  129. content = `${content.replace(/\n*$/, '')}${content ? '\n' : ''}${line}\n`;
  130. }
  131. }
  132. fs.writeFileSync(targetPath, content.trim() + '\n', 'utf8');
  133. for (const [key, value] of Object.entries(entries)) {
  134. if (value) process.env[key] = value;
  135. }
  136. return targetPath;
  137. }
  138. function saveRelayCredentials(creds) {
  139. const saved = [];
  140. try {
  141. saved.push(saveRelayCredentialsToEnv(creds));
  142. } catch (err) {
  143. console.warn('[RelayConfig] Failed to save .env.local:', err.message);
  144. }
  145. try {
  146. writeRelayConfigFile({
  147. relayBaseUrl: creds.relayBaseUrl || getRelayBaseUrl(),
  148. tenantId: creds.tenantId || '',
  149. publicKey: creds.publicKey || '',
  150. deviceGuid: creds.deviceGuid || ''
  151. });
  152. saved.push(RELAY_CONFIG_FILE);
  153. } catch (err) {
  154. console.warn('[RelayConfig] Failed to save relay-config.json:', err.message);
  155. }
  156. return saved;
  157. }
  158. function saveRelayCredentialsToFile(creds) {
  159. try {
  160. ensureDir(CROSS_PROJECT_CREDENTIALS_FILE);
  161. fs.writeFileSync(
  162. CROSS_PROJECT_CREDENTIALS_FILE,
  163. JSON.stringify({ ...creds, updatedAt: new Date().toISOString() }, null, 2),
  164. 'utf8'
  165. );
  166. return CROSS_PROJECT_CREDENTIALS_FILE;
  167. } catch (err) {
  168. console.warn('[RelayConfig] Failed to save cross-project credentials:', err.message);
  169. return null;
  170. }
  171. }
  172. function loadRelayCredentialsFromFile() {
  173. return readJsonMaybe(CROSS_PROJECT_CREDENTIALS_FILE);
  174. }
  175. function ensureRelayConfigDir() {
  176. ensureDir(RELAY_CONFIG_FILE);
  177. }
  178. module.exports = {
  179. RELAY_CONFIG_FILE,
  180. isRelayEnabled,
  181. getRelayBaseUrl,
  182. getTenantApiKey,
  183. getTenantApiSecret,
  184. getTenantId,
  185. getRelayPrivateKey,
  186. getRelayPublicKey,
  187. getRelayDeviceGuid,
  188. getRelayCredentials,
  189. saveRelayCredentials,
  190. saveRelayCredentialsToEnv,
  191. saveRelayCredentialsToFile,
  192. loadRelayCredentialsFromFile,
  193. writeRelayConfigFile,
  194. readRelayConfigFile,
  195. ensureRelayConfigDir,
  196. normalizePrivateKey
  197. };