@yuants/app-virtual-exchange 0.8.0 → 0.8.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.
@@ -1,35 +1,43 @@
1
1
  import { createCache } from '@yuants/cache';
2
2
  import { getCredentialId } from '@yuants/exchange';
3
3
  import { Terminal } from '@yuants/protocol';
4
- import { readSecret, writeSecret } from '@yuants/secret';
4
+ import { listSecrets, readSecret, writeSecret } from '@yuants/secret';
5
5
  import { escapeSQL, requestSQL } from '@yuants/sql';
6
6
  import { newError } from '@yuants/utils';
7
7
  import { defer, firstValueFrom, map, repeat, retry, shareReplay } from 'rxjs';
8
8
  const terminal = Terminal.fromNodeEnv();
9
9
  const credentialReader = process.env.NODE_UNIT_PUBLIC_KEY || terminal.keyPair.public_key;
10
+ /**
11
+ * 根据 secret sign 解析出对应的 exchange credential
12
+ * 此处可以做缓存,因为同一个 secret sign 对应的 credential 信息永远不会变化,可以节约解密和后续 SQL 查询的开销
13
+ * 得到 credential 后,此 credential 不一定是有效的,因为可能凭证信息已经过期或被撤销
14
+ */
15
+ const secretSignToCredentialIdCache = createCache(async (sign) => {
16
+ const sql = `SELECT * FROM secret WHERE sign = ${escapeSQL(sign)} LIMIT 1;`;
17
+ const res = await requestSQL(terminal, sql);
18
+ if (res.length === 0)
19
+ throw newError('SECRET_NOT_FOUND', { sign });
20
+ const secret = res[0];
21
+ const decrypted = await readSecret(terminal, secret);
22
+ const credential = JSON.parse(new TextDecoder().decode(decrypted));
23
+ return credential;
24
+ });
25
+ /**
26
+ * 根据 credential 信息解析出对应的 credential ID
27
+ * 此处可以做缓存,因为同一个 credential 永远对应同一个 credential ID,可以节约后续 SQL 查询的开销
28
+ * 但是需要注意的是,credential ID 可能会因为凭证被撤销而失效,但是可以在下游调用其他服务时感知到,因此可以永久缓存
29
+ */
10
30
  const credentialIdCache = createCache(async (credentialKey) => {
11
31
  const credential = JSON.parse(credentialKey);
12
32
  const res = await getCredentialId(terminal, credential);
13
33
  return res.data;
14
34
  });
15
- export const listAllCredentials = async () => {
16
- const secrets = await requestSQL(terminal, `select * from secret where tags->>'type' = 'exchange_credential' and reader = ${escapeSQL(credentialReader)}`);
17
- const results = secrets.map((secret) => ({ secret }));
18
- await Promise.all(results.map(async (result) => {
19
- try {
20
- const decrypted = await readSecret(terminal, result.secret);
21
- const credential = JSON.parse(new TextDecoder().decode(decrypted));
22
- result.credential = credential;
23
- const res = await credentialIdCache.query(JSON.stringify(credential));
24
- if (res) {
25
- result.credentialId = res;
26
- }
27
- }
28
- catch (e) {
29
- result.reason = `${e}`;
30
- }
31
- }));
32
- return results;
35
+ const listAllCredentials = async () => {
36
+ const secrets = await listSecrets(terminal, {
37
+ reader: credentialReader,
38
+ tags: { type: 'exchange_credential' },
39
+ });
40
+ return Promise.allSettled(secrets.map((secret) => getCredentialBySecretId(secret.sign)));
33
41
  };
34
42
  terminal.server.provideService('VEX/RegisterExchangeCredential', {
35
43
  type: 'object',
@@ -44,6 +52,7 @@ terminal.server.provideService('VEX/RegisterExchangeCredential', {
44
52
  const secret = await writeSecret(terminal, credentialReader, { type: 'exchange_credential' }, secretData);
45
53
  return { res: { code: 0, message: 'OK', data: secret } };
46
54
  });
55
+ // For Debugging Purpose
47
56
  terminal.server.provideService('VEX/ListExchangeCredential', {}, async () => {
48
57
  return { res: { code: 0, message: 'OK', data: await listAllCredentials() } };
49
58
  });
@@ -51,14 +60,15 @@ terminal.server.provideService('VEX/ListCredentials', {}, async () => {
51
60
  const credentials = await firstValueFrom(validCredentials$);
52
61
  return { res: { code: 0, message: 'OK', data: [...credentials.keys()] } };
53
62
  });
54
- const credentialCache = createCache(() => listAllCredentials(), { swrAfter: 10000, expire: 3600000 });
55
- export const validCredentials$ = defer(() => credentialCache.query('')).pipe(map((x) => {
63
+ export const validCredentials$ = defer(() => listAllCredentials()).pipe(map((x) => {
56
64
  const map = new Map();
57
65
  if (!x)
58
66
  return map;
59
67
  for (const xx of x) {
60
- if (xx.credentialId && xx.credential) {
61
- map.set(xx.credentialId, xx.credential);
68
+ if (xx.status !== 'fulfilled')
69
+ continue;
70
+ if (xx.value.credentialId && xx.value.credential) {
71
+ map.set(xx.value.credentialId, xx.value.credential);
62
72
  }
63
73
  }
64
74
  return map;
@@ -70,26 +80,21 @@ export const validCredentialTypes$ = validCredentials$.pipe(map((credentials) =>
70
80
  });
71
81
  return Array.from(types);
72
82
  }));
73
- export const getCredentialById = async (credential_id) => {
74
- const credentials = await firstValueFrom(validCredentials$);
75
- return credentials.get(credential_id);
76
- };
77
- export const getCredentialBySecretId = async (secret_id) => {
78
- const allCredentials = await credentialCache.query('');
79
- const theCredential = allCredentials === null || allCredentials === void 0 ? void 0 : allCredentials.find((x) => x.secret.sign === secret_id);
80
- if (!theCredential) {
81
- throw newError('CREDENTIAL_NOT_FOUND', { secret_id });
82
- }
83
- if (!theCredential.credential) {
84
- throw newError('CREDENTIAL_NOT_RESOLVED', { secret_id });
85
- }
86
- if (!theCredential.credentialId) {
87
- throw newError('CREDENTIAL_ID_NOT_RESOLVED', { secret_id });
88
- }
89
- return {
90
- secret: theCredential.secret,
91
- credential: theCredential.credential,
92
- credentialId: theCredential.credentialId,
93
- };
83
+ /**
84
+ * 根据 secret sign 解析出对应的 credential 以及 credential ID
85
+ * @param sign - secret sign
86
+ * @returns 解析得到的 credential 以及对应的 credential ID
87
+ * @throws 如果无法解析出对应的 credential credential ID,则抛出异常
88
+ *
89
+ * 不依赖 List Credential 服务,可以及时感知凭证的新增和变更
90
+ */
91
+ export const getCredentialBySecretId = async (sign) => {
92
+ const credential = await secretSignToCredentialIdCache.query(sign);
93
+ if (!credential)
94
+ throw newError('CREDENTIAL_NOT_RESOLVED', { sign });
95
+ const credentialId = await credentialIdCache.query(JSON.stringify(credential));
96
+ if (!credentialId)
97
+ throw newError('CREDENTIAL_ID_NOT_RESOLVED', { sign });
98
+ return { sign, credential, credentialId };
94
99
  };
95
100
  //# sourceMappingURL=credential.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"credential.js","sourceRoot":"","sources":["../src/credential.ts"],"names":[],"mappings":"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","sourcesContent":["import { createCache } from '@yuants/cache';\nimport { getCredentialId } from '@yuants/exchange';\nimport { Terminal } from '@yuants/protocol';\nimport { ISecret, readSecret, writeSecret } from '@yuants/secret';\nimport { escapeSQL, requestSQL } from '@yuants/sql';\nimport { newError } from '@yuants/utils';\nimport { defer, firstValueFrom, map, repeat, retry, shareReplay } from 'rxjs';\n\nexport interface IExchangeCredential {\n type: string;\n payload: any;\n}\n\nconst terminal = Terminal.fromNodeEnv();\n\nconst credentialReader = process.env.NODE_UNIT_PUBLIC_KEY || terminal.keyPair.public_key;\n\ninterface ICredentialResolvedStatus {\n secret: ISecret;\n credential?: IExchangeCredential;\n credentialId?: string;\n reason?: string;\n}\n\nconst credentialIdCache = createCache(async (credentialKey: string) => {\n const credential = JSON.parse(credentialKey) as IExchangeCredential;\n const res = await getCredentialId(terminal, credential);\n return res.data;\n});\n\nexport const listAllCredentials = async () => {\n const secrets = await requestSQL<ISecret[]>(\n terminal,\n `select * from secret where tags->>'type' = 'exchange_credential' and reader = ${escapeSQL(\n credentialReader,\n )}`,\n );\n\n const results = secrets.map((secret): ICredentialResolvedStatus => ({ secret }));\n\n await Promise.all(\n results.map(async (result) => {\n try {\n const decrypted = await readSecret(terminal, result.secret);\n const credential = JSON.parse(new TextDecoder().decode(decrypted)) as IExchangeCredential;\n result.credential = credential;\n\n const res = await credentialIdCache.query(JSON.stringify(credential));\n if (res) {\n result.credentialId = res;\n }\n } catch (e) {\n result.reason = `${e}`;\n }\n }),\n );\n\n return results;\n};\n\nterminal.server.provideService<IExchangeCredential, ISecret>(\n 'VEX/RegisterExchangeCredential',\n {\n type: 'object',\n required: ['type', 'payload'],\n properties: {\n type: { type: 'string' },\n payload: { type: 'object' },\n },\n },\n async (msg) => {\n const credential = msg.req;\n const secretData = new TextEncoder().encode(JSON.stringify(credential));\n const secret = await writeSecret(terminal, credentialReader, { type: 'exchange_credential' }, secretData);\n return { res: { code: 0, message: 'OK', data: secret } };\n },\n);\n\nterminal.server.provideService<void, ICredentialResolvedStatus[]>(\n 'VEX/ListExchangeCredential',\n {},\n async () => {\n return { res: { code: 0, message: 'OK', data: await listAllCredentials() } };\n },\n);\n\nterminal.server.provideService<void, string[]>('VEX/ListCredentials', {}, async () => {\n const credentials = await firstValueFrom(validCredentials$);\n return { res: { code: 0, message: 'OK', data: [...credentials.keys()] } };\n});\n\nconst credentialCache = createCache(() => listAllCredentials(), { swrAfter: 10_000, expire: 3600_000 });\n\nexport const validCredentials$ = defer(() => credentialCache.query('')).pipe(\n map((x) => {\n const map = new Map<string, IExchangeCredential>();\n if (!x) return map;\n for (const xx of x) {\n if (xx.credentialId && xx.credential) {\n map.set(xx.credentialId, xx.credential);\n }\n }\n return map;\n }),\n repeat({ delay: 10000 }),\n retry({ delay: 5000 }),\n shareReplay(1),\n);\n\nexport const validCredentialTypes$ = validCredentials$.pipe(\n map((credentials) => {\n const types = new Set<string>();\n credentials.forEach((credential) => {\n types.add(credential.type);\n });\n return Array.from(types);\n }),\n);\n\nexport const getCredentialById = async (credential_id: string) => {\n const credentials = await firstValueFrom(validCredentials$);\n return credentials.get(credential_id);\n};\n\nexport const getCredentialBySecretId = async (\n secret_id: string,\n): Promise<Required<Pick<ICredentialResolvedStatus, 'secret' | 'credential' | 'credentialId'>>> => {\n const allCredentials = await credentialCache.query('');\n const theCredential = allCredentials?.find((x) => x.secret.sign === secret_id);\n if (!theCredential) {\n throw newError('CREDENTIAL_NOT_FOUND', { secret_id });\n }\n if (!theCredential.credential) {\n throw newError('CREDENTIAL_NOT_RESOLVED', { secret_id });\n }\n if (!theCredential.credentialId) {\n throw newError('CREDENTIAL_ID_NOT_RESOLVED', { secret_id });\n }\n\n return {\n secret: theCredential.secret,\n credential: theCredential.credential,\n credentialId: theCredential.credentialId,\n };\n};\n"]}
1
+ {"version":3,"file":"credential.js","sourceRoot":"","sources":["../src/credential.ts"],"names":[],"mappings":"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","sourcesContent":["import { createCache } from '@yuants/cache';\nimport { getCredentialId } from '@yuants/exchange';\nimport { Terminal } from '@yuants/protocol';\nimport { ISecret, listSecrets, readSecret, writeSecret } from '@yuants/secret';\nimport { escapeSQL, requestSQL } from '@yuants/sql';\nimport { newError } from '@yuants/utils';\nimport { defer, firstValueFrom, map, repeat, retry, shareReplay } from 'rxjs';\n\ninterface IExchangeCredential {\n type: string;\n payload: any;\n}\n\nconst terminal = Terminal.fromNodeEnv();\n\nconst credentialReader = process.env.NODE_UNIT_PUBLIC_KEY || terminal.keyPair.public_key;\n\n/**\n * 根据 secret sign 解析出对应的 exchange credential\n * 此处可以做缓存,因为同一个 secret sign 对应的 credential 信息永远不会变化,可以节约解密和后续 SQL 查询的开销\n * 得到 credential 后,此 credential 不一定是有效的,因为可能凭证信息已经过期或被撤销\n */\nconst secretSignToCredentialIdCache = createCache(async (sign: string) => {\n const sql = `SELECT * FROM secret WHERE sign = ${escapeSQL(sign)} LIMIT 1;`;\n const res = await requestSQL<ISecret[]>(terminal, sql);\n if (res.length === 0) throw newError('SECRET_NOT_FOUND', { sign });\n const secret = res[0];\n const decrypted = await readSecret(terminal, secret);\n const credential = JSON.parse(new TextDecoder().decode(decrypted)) as IExchangeCredential;\n return credential;\n});\n\n/**\n * 根据 credential 信息解析出对应的 credential ID\n * 此处可以做缓存,因为同一个 credential 永远对应同一个 credential ID,可以节约后续 SQL 查询的开销\n * 但是需要注意的是,credential ID 可能会因为凭证被撤销而失效,但是可以在下游调用其他服务时感知到,因此可以永久缓存\n */\nconst credentialIdCache = createCache(async (credentialKey: string) => {\n const credential = JSON.parse(credentialKey) as IExchangeCredential;\n const res = await getCredentialId(terminal, credential);\n return res.data;\n});\n\nconst listAllCredentials = async () => {\n const secrets = await listSecrets(terminal, {\n reader: credentialReader,\n tags: { type: 'exchange_credential' },\n });\n\n return Promise.allSettled(secrets.map((secret) => getCredentialBySecretId(secret.sign)));\n};\n\nterminal.server.provideService<IExchangeCredential, ISecret>(\n 'VEX/RegisterExchangeCredential',\n {\n type: 'object',\n required: ['type', 'payload'],\n properties: {\n type: { type: 'string' },\n payload: { type: 'object' },\n },\n },\n async (msg) => {\n const credential = msg.req;\n const secretData = new TextEncoder().encode(JSON.stringify(credential));\n const secret = await writeSecret(terminal, credentialReader, { type: 'exchange_credential' }, secretData);\n return { res: { code: 0, message: 'OK', data: secret } };\n },\n);\n\n// For Debugging Purpose\nterminal.server.provideService('VEX/ListExchangeCredential', {}, async () => {\n return { res: { code: 0, message: 'OK', data: await listAllCredentials() } };\n});\n\nterminal.server.provideService<void, string[]>('VEX/ListCredentials', {}, async () => {\n const credentials = await firstValueFrom(validCredentials$);\n return { res: { code: 0, message: 'OK', data: [...credentials.keys()] } };\n});\n\nexport const validCredentials$ = defer(() => listAllCredentials()).pipe(\n map((x) => {\n const map = new Map<string, IExchangeCredential>();\n if (!x) return map;\n for (const xx of x) {\n if (xx.status !== 'fulfilled') continue;\n if (xx.value.credentialId && xx.value.credential) {\n map.set(xx.value.credentialId, xx.value.credential);\n }\n }\n return map;\n }),\n repeat({ delay: 10000 }),\n retry({ delay: 5000 }),\n shareReplay(1),\n);\n\nexport const validCredentialTypes$ = validCredentials$.pipe(\n map((credentials) => {\n const types = new Set<string>();\n credentials.forEach((credential) => {\n types.add(credential.type);\n });\n return Array.from(types);\n }),\n);\n\n/**\n * 根据 secret sign 解析出对应的 credential 以及 credential ID\n * @param sign - secret sign\n * @returns 解析得到的 credential 以及对应的 credential ID\n * @throws 如果无法解析出对应的 credential credential ID,则抛出异常\n *\n * 不依赖 List Credential 服务,可以及时感知凭证的新增和变更\n */\nexport const getCredentialBySecretId = async (sign: string) => {\n const credential = await secretSignToCredentialIdCache.query(sign);\n if (!credential) throw newError('CREDENTIAL_NOT_RESOLVED', { sign });\n const credentialId = await credentialIdCache.query(JSON.stringify(credential));\n if (!credentialId) throw newError('CREDENTIAL_ID_NOT_RESOLVED', { sign });\n return { sign, credential, credentialId };\n};\n"]}
@@ -1,18 +1,21 @@
1
- import { ISecret } from '@yuants/secret';
2
- export interface IExchangeCredential {
1
+ interface IExchangeCredential {
3
2
  type: string;
4
3
  payload: any;
5
4
  }
6
- interface ICredentialResolvedStatus {
7
- secret: ISecret;
8
- credential?: IExchangeCredential;
9
- credentialId?: string;
10
- reason?: string;
11
- }
12
- export declare const listAllCredentials: () => Promise<ICredentialResolvedStatus[]>;
13
5
  export declare const validCredentials$: import("rxjs").Observable<Map<string, IExchangeCredential>>;
14
6
  export declare const validCredentialTypes$: import("rxjs").Observable<string[]>;
15
- export declare const getCredentialById: (credential_id: string) => Promise<IExchangeCredential | undefined>;
16
- export declare const getCredentialBySecretId: (secret_id: string) => Promise<Required<Pick<ICredentialResolvedStatus, 'secret' | 'credential' | 'credentialId'>>>;
7
+ /**
8
+ * 根据 secret sign 解析出对应的 credential 以及 credential ID
9
+ * @param sign - secret sign
10
+ * @returns 解析得到的 credential 以及对应的 credential ID
11
+ * @throws 如果无法解析出对应的 credential 或 credential ID,则抛出异常
12
+ *
13
+ * 不依赖 List Credential 服务,可以及时感知凭证的新增和变更
14
+ */
15
+ export declare const getCredentialBySecretId: (sign: string) => Promise<{
16
+ sign: string;
17
+ credential: IExchangeCredential;
18
+ credentialId: string;
19
+ }>;
17
20
  export {};
18
21
  //# sourceMappingURL=credential.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"credential.d.ts","sourceRoot":"","sources":["../src/credential.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAA2B,MAAM,gBAAgB,CAAC;AAKlE,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,GAAG,CAAC;CACd;AAMD,UAAU,yBAAyB;IACjC,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,CAAC,EAAE,mBAAmB,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAQD,eAAO,MAAM,kBAAkB,4CA4B9B,CAAC;AAmCF,eAAO,MAAM,iBAAiB,6DAc7B,CAAC;AAEF,eAAO,MAAM,qBAAqB,qCAQjC,CAAC;AAEF,eAAO,MAAM,iBAAiB,kBAAyB,MAAM,6CAG5D,CAAC;AAEF,eAAO,MAAM,uBAAuB,cACvB,MAAM,KAChB,QAAQ,SAAS,KAAK,yBAAyB,EAAE,QAAQ,GAAG,YAAY,GAAG,cAAc,CAAC,CAAC,CAkB7F,CAAC"}
1
+ {"version":3,"file":"credential.d.ts","sourceRoot":"","sources":["../src/credential.ts"],"names":[],"mappings":"AAQA,UAAU,mBAAmB;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,GAAG,CAAC;CACd;AAqED,eAAO,MAAM,iBAAiB,6DAe7B,CAAC;AAEF,eAAO,MAAM,qBAAqB,qCAQjC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,SAAgB,MAAM;;;;EAMzD,CAAC"}
package/lib/credential.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getCredentialBySecretId = exports.getCredentialById = exports.validCredentialTypes$ = exports.validCredentials$ = exports.listAllCredentials = void 0;
3
+ exports.getCredentialBySecretId = exports.validCredentialTypes$ = exports.validCredentials$ = void 0;
4
4
  const cache_1 = require("@yuants/cache");
5
5
  const exchange_1 = require("@yuants/exchange");
6
6
  const protocol_1 = require("@yuants/protocol");
@@ -10,31 +10,38 @@ const utils_1 = require("@yuants/utils");
10
10
  const rxjs_1 = require("rxjs");
11
11
  const terminal = protocol_1.Terminal.fromNodeEnv();
12
12
  const credentialReader = process.env.NODE_UNIT_PUBLIC_KEY || terminal.keyPair.public_key;
13
+ /**
14
+ * 根据 secret sign 解析出对应的 exchange credential
15
+ * 此处可以做缓存,因为同一个 secret sign 对应的 credential 信息永远不会变化,可以节约解密和后续 SQL 查询的开销
16
+ * 得到 credential 后,此 credential 不一定是有效的,因为可能凭证信息已经过期或被撤销
17
+ */
18
+ const secretSignToCredentialIdCache = (0, cache_1.createCache)(async (sign) => {
19
+ const sql = `SELECT * FROM secret WHERE sign = ${(0, sql_1.escapeSQL)(sign)} LIMIT 1;`;
20
+ const res = await (0, sql_1.requestSQL)(terminal, sql);
21
+ if (res.length === 0)
22
+ throw (0, utils_1.newError)('SECRET_NOT_FOUND', { sign });
23
+ const secret = res[0];
24
+ const decrypted = await (0, secret_1.readSecret)(terminal, secret);
25
+ const credential = JSON.parse(new TextDecoder().decode(decrypted));
26
+ return credential;
27
+ });
28
+ /**
29
+ * 根据 credential 信息解析出对应的 credential ID
30
+ * 此处可以做缓存,因为同一个 credential 永远对应同一个 credential ID,可以节约后续 SQL 查询的开销
31
+ * 但是需要注意的是,credential ID 可能会因为凭证被撤销而失效,但是可以在下游调用其他服务时感知到,因此可以永久缓存
32
+ */
13
33
  const credentialIdCache = (0, cache_1.createCache)(async (credentialKey) => {
14
34
  const credential = JSON.parse(credentialKey);
15
35
  const res = await (0, exchange_1.getCredentialId)(terminal, credential);
16
36
  return res.data;
17
37
  });
18
38
  const listAllCredentials = async () => {
19
- const secrets = await (0, sql_1.requestSQL)(terminal, `select * from secret where tags->>'type' = 'exchange_credential' and reader = ${(0, sql_1.escapeSQL)(credentialReader)}`);
20
- const results = secrets.map((secret) => ({ secret }));
21
- await Promise.all(results.map(async (result) => {
22
- try {
23
- const decrypted = await (0, secret_1.readSecret)(terminal, result.secret);
24
- const credential = JSON.parse(new TextDecoder().decode(decrypted));
25
- result.credential = credential;
26
- const res = await credentialIdCache.query(JSON.stringify(credential));
27
- if (res) {
28
- result.credentialId = res;
29
- }
30
- }
31
- catch (e) {
32
- result.reason = `${e}`;
33
- }
34
- }));
35
- return results;
39
+ const secrets = await (0, secret_1.listSecrets)(terminal, {
40
+ reader: credentialReader,
41
+ tags: { type: 'exchange_credential' },
42
+ });
43
+ return Promise.allSettled(secrets.map((secret) => (0, exports.getCredentialBySecretId)(secret.sign)));
36
44
  };
37
- exports.listAllCredentials = listAllCredentials;
38
45
  terminal.server.provideService('VEX/RegisterExchangeCredential', {
39
46
  type: 'object',
40
47
  required: ['type', 'payload'],
@@ -48,21 +55,23 @@ terminal.server.provideService('VEX/RegisterExchangeCredential', {
48
55
  const secret = await (0, secret_1.writeSecret)(terminal, credentialReader, { type: 'exchange_credential' }, secretData);
49
56
  return { res: { code: 0, message: 'OK', data: secret } };
50
57
  });
58
+ // For Debugging Purpose
51
59
  terminal.server.provideService('VEX/ListExchangeCredential', {}, async () => {
52
- return { res: { code: 0, message: 'OK', data: await (0, exports.listAllCredentials)() } };
60
+ return { res: { code: 0, message: 'OK', data: await listAllCredentials() } };
53
61
  });
54
62
  terminal.server.provideService('VEX/ListCredentials', {}, async () => {
55
63
  const credentials = await (0, rxjs_1.firstValueFrom)(exports.validCredentials$);
56
64
  return { res: { code: 0, message: 'OK', data: [...credentials.keys()] } };
57
65
  });
58
- const credentialCache = (0, cache_1.createCache)(() => (0, exports.listAllCredentials)(), { swrAfter: 10000, expire: 3600000 });
59
- exports.validCredentials$ = (0, rxjs_1.defer)(() => credentialCache.query('')).pipe((0, rxjs_1.map)((x) => {
66
+ exports.validCredentials$ = (0, rxjs_1.defer)(() => listAllCredentials()).pipe((0, rxjs_1.map)((x) => {
60
67
  const map = new Map();
61
68
  if (!x)
62
69
  return map;
63
70
  for (const xx of x) {
64
- if (xx.credentialId && xx.credential) {
65
- map.set(xx.credentialId, xx.credential);
71
+ if (xx.status !== 'fulfilled')
72
+ continue;
73
+ if (xx.value.credentialId && xx.value.credential) {
74
+ map.set(xx.value.credentialId, xx.value.credential);
66
75
  }
67
76
  }
68
77
  return map;
@@ -74,28 +83,22 @@ exports.validCredentialTypes$ = exports.validCredentials$.pipe((0, rxjs_1.map)((
74
83
  });
75
84
  return Array.from(types);
76
85
  }));
77
- const getCredentialById = async (credential_id) => {
78
- const credentials = await (0, rxjs_1.firstValueFrom)(exports.validCredentials$);
79
- return credentials.get(credential_id);
80
- };
81
- exports.getCredentialById = getCredentialById;
82
- const getCredentialBySecretId = async (secret_id) => {
83
- const allCredentials = await credentialCache.query('');
84
- const theCredential = allCredentials === null || allCredentials === void 0 ? void 0 : allCredentials.find((x) => x.secret.sign === secret_id);
85
- if (!theCredential) {
86
- throw (0, utils_1.newError)('CREDENTIAL_NOT_FOUND', { secret_id });
87
- }
88
- if (!theCredential.credential) {
89
- throw (0, utils_1.newError)('CREDENTIAL_NOT_RESOLVED', { secret_id });
90
- }
91
- if (!theCredential.credentialId) {
92
- throw (0, utils_1.newError)('CREDENTIAL_ID_NOT_RESOLVED', { secret_id });
93
- }
94
- return {
95
- secret: theCredential.secret,
96
- credential: theCredential.credential,
97
- credentialId: theCredential.credentialId,
98
- };
86
+ /**
87
+ * 根据 secret sign 解析出对应的 credential 以及 credential ID
88
+ * @param sign - secret sign
89
+ * @returns 解析得到的 credential 以及对应的 credential ID
90
+ * @throws 如果无法解析出对应的 credential 或 credential ID,则抛出异常
91
+ *
92
+ * 不依赖 List Credential 服务,可以及时感知凭证的新增和变更
93
+ */
94
+ const getCredentialBySecretId = async (sign) => {
95
+ const credential = await secretSignToCredentialIdCache.query(sign);
96
+ if (!credential)
97
+ throw (0, utils_1.newError)('CREDENTIAL_NOT_RESOLVED', { sign });
98
+ const credentialId = await credentialIdCache.query(JSON.stringify(credential));
99
+ if (!credentialId)
100
+ throw (0, utils_1.newError)('CREDENTIAL_ID_NOT_RESOLVED', { sign });
101
+ return { sign, credential, credentialId };
99
102
  };
100
103
  exports.getCredentialBySecretId = getCredentialBySecretId;
101
104
  //# sourceMappingURL=credential.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"credential.js","sourceRoot":"","sources":["../src/credential.ts"],"names":[],"mappings":"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","sourcesContent":["import { createCache } from '@yuants/cache';\nimport { getCredentialId } from '@yuants/exchange';\nimport { Terminal } from '@yuants/protocol';\nimport { ISecret, readSecret, writeSecret } from '@yuants/secret';\nimport { escapeSQL, requestSQL } from '@yuants/sql';\nimport { newError } from '@yuants/utils';\nimport { defer, firstValueFrom, map, repeat, retry, shareReplay } from 'rxjs';\n\nexport interface IExchangeCredential {\n type: string;\n payload: any;\n}\n\nconst terminal = Terminal.fromNodeEnv();\n\nconst credentialReader = process.env.NODE_UNIT_PUBLIC_KEY || terminal.keyPair.public_key;\n\ninterface ICredentialResolvedStatus {\n secret: ISecret;\n credential?: IExchangeCredential;\n credentialId?: string;\n reason?: string;\n}\n\nconst credentialIdCache = createCache(async (credentialKey: string) => {\n const credential = JSON.parse(credentialKey) as IExchangeCredential;\n const res = await getCredentialId(terminal, credential);\n return res.data;\n});\n\nexport const listAllCredentials = async () => {\n const secrets = await requestSQL<ISecret[]>(\n terminal,\n `select * from secret where tags->>'type' = 'exchange_credential' and reader = ${escapeSQL(\n credentialReader,\n )}`,\n );\n\n const results = secrets.map((secret): ICredentialResolvedStatus => ({ secret }));\n\n await Promise.all(\n results.map(async (result) => {\n try {\n const decrypted = await readSecret(terminal, result.secret);\n const credential = JSON.parse(new TextDecoder().decode(decrypted)) as IExchangeCredential;\n result.credential = credential;\n\n const res = await credentialIdCache.query(JSON.stringify(credential));\n if (res) {\n result.credentialId = res;\n }\n } catch (e) {\n result.reason = `${e}`;\n }\n }),\n );\n\n return results;\n};\n\nterminal.server.provideService<IExchangeCredential, ISecret>(\n 'VEX/RegisterExchangeCredential',\n {\n type: 'object',\n required: ['type', 'payload'],\n properties: {\n type: { type: 'string' },\n payload: { type: 'object' },\n },\n },\n async (msg) => {\n const credential = msg.req;\n const secretData = new TextEncoder().encode(JSON.stringify(credential));\n const secret = await writeSecret(terminal, credentialReader, { type: 'exchange_credential' }, secretData);\n return { res: { code: 0, message: 'OK', data: secret } };\n },\n);\n\nterminal.server.provideService<void, ICredentialResolvedStatus[]>(\n 'VEX/ListExchangeCredential',\n {},\n async () => {\n return { res: { code: 0, message: 'OK', data: await listAllCredentials() } };\n },\n);\n\nterminal.server.provideService<void, string[]>('VEX/ListCredentials', {}, async () => {\n const credentials = await firstValueFrom(validCredentials$);\n return { res: { code: 0, message: 'OK', data: [...credentials.keys()] } };\n});\n\nconst credentialCache = createCache(() => listAllCredentials(), { swrAfter: 10_000, expire: 3600_000 });\n\nexport const validCredentials$ = defer(() => credentialCache.query('')).pipe(\n map((x) => {\n const map = new Map<string, IExchangeCredential>();\n if (!x) return map;\n for (const xx of x) {\n if (xx.credentialId && xx.credential) {\n map.set(xx.credentialId, xx.credential);\n }\n }\n return map;\n }),\n repeat({ delay: 10000 }),\n retry({ delay: 5000 }),\n shareReplay(1),\n);\n\nexport const validCredentialTypes$ = validCredentials$.pipe(\n map((credentials) => {\n const types = new Set<string>();\n credentials.forEach((credential) => {\n types.add(credential.type);\n });\n return Array.from(types);\n }),\n);\n\nexport const getCredentialById = async (credential_id: string) => {\n const credentials = await firstValueFrom(validCredentials$);\n return credentials.get(credential_id);\n};\n\nexport const getCredentialBySecretId = async (\n secret_id: string,\n): Promise<Required<Pick<ICredentialResolvedStatus, 'secret' | 'credential' | 'credentialId'>>> => {\n const allCredentials = await credentialCache.query('');\n const theCredential = allCredentials?.find((x) => x.secret.sign === secret_id);\n if (!theCredential) {\n throw newError('CREDENTIAL_NOT_FOUND', { secret_id });\n }\n if (!theCredential.credential) {\n throw newError('CREDENTIAL_NOT_RESOLVED', { secret_id });\n }\n if (!theCredential.credentialId) {\n throw newError('CREDENTIAL_ID_NOT_RESOLVED', { secret_id });\n }\n\n return {\n secret: theCredential.secret,\n credential: theCredential.credential,\n credentialId: theCredential.credentialId,\n };\n};\n"]}
1
+ {"version":3,"file":"credential.js","sourceRoot":"","sources":["../src/credential.ts"],"names":[],"mappings":"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","sourcesContent":["import { createCache } from '@yuants/cache';\nimport { getCredentialId } from '@yuants/exchange';\nimport { Terminal } from '@yuants/protocol';\nimport { ISecret, listSecrets, readSecret, writeSecret } from '@yuants/secret';\nimport { escapeSQL, requestSQL } from '@yuants/sql';\nimport { newError } from '@yuants/utils';\nimport { defer, firstValueFrom, map, repeat, retry, shareReplay } from 'rxjs';\n\ninterface IExchangeCredential {\n type: string;\n payload: any;\n}\n\nconst terminal = Terminal.fromNodeEnv();\n\nconst credentialReader = process.env.NODE_UNIT_PUBLIC_KEY || terminal.keyPair.public_key;\n\n/**\n * 根据 secret sign 解析出对应的 exchange credential\n * 此处可以做缓存,因为同一个 secret sign 对应的 credential 信息永远不会变化,可以节约解密和后续 SQL 查询的开销\n * 得到 credential 后,此 credential 不一定是有效的,因为可能凭证信息已经过期或被撤销\n */\nconst secretSignToCredentialIdCache = createCache(async (sign: string) => {\n const sql = `SELECT * FROM secret WHERE sign = ${escapeSQL(sign)} LIMIT 1;`;\n const res = await requestSQL<ISecret[]>(terminal, sql);\n if (res.length === 0) throw newError('SECRET_NOT_FOUND', { sign });\n const secret = res[0];\n const decrypted = await readSecret(terminal, secret);\n const credential = JSON.parse(new TextDecoder().decode(decrypted)) as IExchangeCredential;\n return credential;\n});\n\n/**\n * 根据 credential 信息解析出对应的 credential ID\n * 此处可以做缓存,因为同一个 credential 永远对应同一个 credential ID,可以节约后续 SQL 查询的开销\n * 但是需要注意的是,credential ID 可能会因为凭证被撤销而失效,但是可以在下游调用其他服务时感知到,因此可以永久缓存\n */\nconst credentialIdCache = createCache(async (credentialKey: string) => {\n const credential = JSON.parse(credentialKey) as IExchangeCredential;\n const res = await getCredentialId(terminal, credential);\n return res.data;\n});\n\nconst listAllCredentials = async () => {\n const secrets = await listSecrets(terminal, {\n reader: credentialReader,\n tags: { type: 'exchange_credential' },\n });\n\n return Promise.allSettled(secrets.map((secret) => getCredentialBySecretId(secret.sign)));\n};\n\nterminal.server.provideService<IExchangeCredential, ISecret>(\n 'VEX/RegisterExchangeCredential',\n {\n type: 'object',\n required: ['type', 'payload'],\n properties: {\n type: { type: 'string' },\n payload: { type: 'object' },\n },\n },\n async (msg) => {\n const credential = msg.req;\n const secretData = new TextEncoder().encode(JSON.stringify(credential));\n const secret = await writeSecret(terminal, credentialReader, { type: 'exchange_credential' }, secretData);\n return { res: { code: 0, message: 'OK', data: secret } };\n },\n);\n\n// For Debugging Purpose\nterminal.server.provideService('VEX/ListExchangeCredential', {}, async () => {\n return { res: { code: 0, message: 'OK', data: await listAllCredentials() } };\n});\n\nterminal.server.provideService<void, string[]>('VEX/ListCredentials', {}, async () => {\n const credentials = await firstValueFrom(validCredentials$);\n return { res: { code: 0, message: 'OK', data: [...credentials.keys()] } };\n});\n\nexport const validCredentials$ = defer(() => listAllCredentials()).pipe(\n map((x) => {\n const map = new Map<string, IExchangeCredential>();\n if (!x) return map;\n for (const xx of x) {\n if (xx.status !== 'fulfilled') continue;\n if (xx.value.credentialId && xx.value.credential) {\n map.set(xx.value.credentialId, xx.value.credential);\n }\n }\n return map;\n }),\n repeat({ delay: 10000 }),\n retry({ delay: 5000 }),\n shareReplay(1),\n);\n\nexport const validCredentialTypes$ = validCredentials$.pipe(\n map((credentials) => {\n const types = new Set<string>();\n credentials.forEach((credential) => {\n types.add(credential.type);\n });\n return Array.from(types);\n }),\n);\n\n/**\n * 根据 secret sign 解析出对应的 credential 以及 credential ID\n * @param sign - secret sign\n * @returns 解析得到的 credential 以及对应的 credential ID\n * @throws 如果无法解析出对应的 credential credential ID,则抛出异常\n *\n * 不依赖 List Credential 服务,可以及时感知凭证的新增和变更\n */\nexport const getCredentialBySecretId = async (sign: string) => {\n const credential = await secretSignToCredentialIdCache.query(sign);\n if (!credential) throw newError('CREDENTIAL_NOT_RESOLVED', { sign });\n const credentialId = await credentialIdCache.query(JSON.stringify(credential));\n if (!credentialId) throw newError('CREDENTIAL_ID_NOT_RESOLVED', { sign });\n return { sign, credential, credentialId };\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuants/app-virtual-exchange",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "main": "lib/index.js",
5
5
  "files": [
6
6
  "dist",
@@ -14,7 +14,7 @@
14
14
  "@yuants/data-account": "0.10.1",
15
15
  "@yuants/data-order": "0.7.0",
16
16
  "@yuants/data-quote": "0.2.43",
17
- "@yuants/secret": "0.3.13",
17
+ "@yuants/secret": "0.4.0",
18
18
  "@yuants/sql": "0.9.30",
19
19
  "@yuants/exchange": "0.5.1",
20
20
  "@yuants/cache": "0.3.3",
@@ -1,13 +1,13 @@
1
1
  {
2
- "apps/virtual-exchange/CHANGELOG.json": "2eff996425f4f7ead9024824f7bb4fb7717ddbe5",
3
- "apps/virtual-exchange/CHANGELOG.md": "cd989fdca5e96c0629ad50740eb60c9781b852ad",
2
+ "apps/virtual-exchange/CHANGELOG.json": "df0691c9a901de4e3c328d62f38272a0b3bdae3f",
3
+ "apps/virtual-exchange/CHANGELOG.md": "d6932a16d2c0fb598e42f4d57a79c28b700ccb4e",
4
4
  "apps/virtual-exchange/api-extractor.json": "62f4fd324425b9a235f0c117975967aab09ced0c",
5
5
  "apps/virtual-exchange/config/jest.config.json": "4bb17bde3ee911163a3edb36a6eb71491d80b1bd",
6
6
  "apps/virtual-exchange/config/rig.json": "f6c7b5537dc77a3170ba9f008bae3b6c3ee11956",
7
7
  "apps/virtual-exchange/config/typescript.json": "854907e8a821f2050f6533368db160c649c25348",
8
8
  "apps/virtual-exchange/etc/app-virtual-exchange.api.md": "6cb40ec1fa2d40a31a7b0dd3f02b8b24a4d7c4de",
9
- "apps/virtual-exchange/package.json": "0ed67aa66e97585d7cb5c19344320e1f9b76d917",
10
- "apps/virtual-exchange/src/credential.ts": "f839246750fcbddd9a6a29bf3de54631a92c88b7",
9
+ "apps/virtual-exchange/package.json": "3281548ed4ee4726782a671c76c58d16f115a6e7",
10
+ "apps/virtual-exchange/src/credential.ts": "edf0901b4e1038233bff07b8486d88c777fda3f5",
11
11
  "apps/virtual-exchange/src/general.ts": "b3d0cd8c57975b9711008beaa05ad7f6812bd57e",
12
12
  "apps/virtual-exchange/src/index.ts": "8d7f19a07e6be09c4d8fd4a49ddb3127d3fbf3de",
13
13
  "apps/virtual-exchange/src/legacy-services.ts": "a9f6b6c61b7a0efc909a443e6fde88c2766562bf",
@@ -21,7 +21,7 @@
21
21
  "libraries/data-account/temp/package-deps.json": "e6ba0067a1b68f17266564f15987936ab2672eb9",
22
22
  "libraries/data-order/temp/package-deps.json": "ccdc9819f254f37a3591bb37876a86c703df33a6",
23
23
  "libraries/data-quote/temp/package-deps.json": "34d079eab44d2bf65e07b112ac2099c6e92a015e",
24
- "libraries/secret/temp/package-deps.json": "d7bea10938c76e2e654781b013cc7be5eaa04ede",
24
+ "libraries/secret/temp/package-deps.json": "28f70cf8095162d1adb59070f30bf21f452537df",
25
25
  "libraries/sql/temp/package-deps.json": "4a9a7ec55f04b20459e664e81e76fa74b6c77b39",
26
26
  "libraries/exchange/temp/package-deps.json": "2679f6d65ffd785cbca43cfbe5b6b9a13c7c003d",
27
27
  "libraries/cache/temp/package-deps.json": "a4afa15e6462983f9d3735d31dc1ed8a683fb4dc",