Complete legacy operator UI and playout migration
This commit is contained in:
521
Web/overseas-workflow.js
Normal file
521
Web/overseas-workflow.js
Normal file
@@ -0,0 +1,521 @@
|
||||
(function (root, factory) {
|
||||
"use strict";
|
||||
|
||||
const api = Object.freeze(factory());
|
||||
if (typeof module === "object" && module.exports) module.exports = api;
|
||||
if (root) root.MbnOverseasWorkflow = api;
|
||||
})(typeof globalThis === "object" ? globalThis : this, function () {
|
||||
"use strict";
|
||||
|
||||
const DEFAULT_FADE_DURATION = 6;
|
||||
const DEFAULT_MAXIMUM_RESULTS = 100;
|
||||
const MAXIMUM_RESULTS = 500;
|
||||
const MAXIMUM_QUERY_LENGTH = 64;
|
||||
const SCHEMA_VERSION = 1;
|
||||
const identifierPattern = /^[A-Za-z0-9_-]{1,128}$/;
|
||||
const symbolPattern = /^[A-Za-z0-9@._$:-]{1,64}$/;
|
||||
const unsafeTextPattern = /[\p{Cc}\p{Cf}\p{Cs}\p{Zl}\p{Zp}\uFFFD]/u;
|
||||
|
||||
const bridgeContracts = Object.freeze({
|
||||
industry: Object.freeze({
|
||||
requestType: "search-overseas-industries",
|
||||
resultType: "overseas-industry-results",
|
||||
errorType: "overseas-industry-error",
|
||||
defaultMaximumResults: DEFAULT_MAXIMUM_RESULTS,
|
||||
maximumResults: MAXIMUM_RESULTS,
|
||||
maximumQueryLength: MAXIMUM_QUERY_LENGTH
|
||||
}),
|
||||
stock: Object.freeze({
|
||||
requestType: "search-world-stocks",
|
||||
resultType: "world-stock-search-results",
|
||||
errorType: "world-stock-search-error",
|
||||
defaultMaximumResults: DEFAULT_MAXIMUM_RESULTS,
|
||||
maximumResults: MAXIMUM_RESULTS,
|
||||
maximumQueryLength: MAXIMUM_QUERY_LENGTH
|
||||
})
|
||||
});
|
||||
|
||||
const sourceContract = Object.freeze({
|
||||
originalControl: "Control/UC5.cs",
|
||||
industry: Object.freeze({
|
||||
fdtc: "0",
|
||||
nationCodes: Object.freeze(["US"]),
|
||||
lookupField: "F_KNAM",
|
||||
currentCutCode: "5001"
|
||||
}),
|
||||
stock: Object.freeze({
|
||||
fdtc: "1",
|
||||
nationCodes: Object.freeze(["US", "TW"]),
|
||||
lookupField: "F_INPUT_NAME",
|
||||
currentCutCode: "5001",
|
||||
candleCutCodes: Object.freeze(["8061", "8040", "8046", "8051", "8056"])
|
||||
})
|
||||
});
|
||||
|
||||
function action(id, label, builderKey, code, kind, periodDays = null) {
|
||||
return Object.freeze({
|
||||
id,
|
||||
label,
|
||||
builderKey,
|
||||
code,
|
||||
aliases: Object.freeze([code]),
|
||||
kind,
|
||||
periodDays
|
||||
});
|
||||
}
|
||||
|
||||
const industryActions = Object.freeze([
|
||||
action("industry-current", "\uBBF8\uAD6D \uD574\uC678\uC5C5\uC885 \uD604\uC7AC\uAC00 1\uC5F4\uD310", "s5001", "5001", "current")
|
||||
]);
|
||||
const stockActions = Object.freeze([
|
||||
action("stock-current", "\uD574\uC678\uC885\uBAA9 \uD604\uC7AC\uAC00 1\uC5F4\uD310", "s5001", "5001", "current"),
|
||||
action("stock-candle-5d", "\uD574\uC678\uC885\uBAA9 \uCE94\uB4E4 5\uC77C", "s8010", "8061", "candle", 5),
|
||||
action("stock-candle-20d", "\uD574\uC678\uC885\uBAA9 \uCE94\uB4E4 20\uC77C", "s8010", "8040", "candle", 20),
|
||||
action("stock-candle-60d", "\uD574\uC678\uC885\uBAA9 \uCE94\uB4E4 60\uC77C", "s8010", "8046", "candle", 60),
|
||||
action("stock-candle-120d", "\uD574\uC678\uC885\uBAA9 \uCE94\uB4E4 120\uC77C", "s8010", "8051", "candle", 120),
|
||||
action("stock-candle-240d", "\uD574\uC678\uC885\uBAA9 \uCE94\uB4E4 240\uC77C", "s8010", "8056", "candle", 240)
|
||||
]);
|
||||
const actionsById = new Map(
|
||||
[...industryActions, ...stockActions].map(value => [value.id, value]));
|
||||
|
||||
function hasExactKeys(value, expected) {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
||||
const actual = Object.keys(value).sort();
|
||||
const required = [...expected].sort();
|
||||
return actual.length === required.length && actual.every((key, index) => key === required[index]);
|
||||
}
|
||||
|
||||
function isSafeCanonicalText(value, maximumLength, allowEmpty = false) {
|
||||
return typeof value === "string" &&
|
||||
value.length <= maximumLength &&
|
||||
(allowEmpty || value.length > 0) &&
|
||||
value === value.trim() &&
|
||||
!unsafeTextPattern.test(value);
|
||||
}
|
||||
|
||||
function normalizeQuery(value) {
|
||||
if (typeof value !== "string" || unsafeTextPattern.test(value)) return null;
|
||||
const query = value.trim();
|
||||
return query.length <= MAXIMUM_QUERY_LENGTH && !unsafeTextPattern.test(query) ? query : null;
|
||||
}
|
||||
|
||||
function normalizeSymbol(value) {
|
||||
return isSafeCanonicalText(value, 64) && symbolPattern.test(value) ? value : null;
|
||||
}
|
||||
|
||||
function normalizeIndustry(value) {
|
||||
if (!hasExactKeys(value, [
|
||||
"koreanName", "inputName", "symbol", "nationCode", "fdtc"
|
||||
])) return null;
|
||||
if (!isSafeCanonicalText(value.koreanName, 200) || value.koreanName.includes("|") ||
|
||||
!isSafeCanonicalText(value.inputName, 200) || value.inputName.includes("|") ||
|
||||
!normalizeSymbol(value.symbol) || value.nationCode !== "US" || value.fdtc !== "0") {
|
||||
return null;
|
||||
}
|
||||
return Object.freeze({
|
||||
koreanName: value.koreanName,
|
||||
inputName: value.inputName,
|
||||
symbol: value.symbol,
|
||||
nationCode: "US",
|
||||
fdtc: "0"
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeStock(value) {
|
||||
if (!hasExactKeys(value, ["inputName", "symbol", "nationCode", "fdtc"])) return null;
|
||||
if (!isSafeCanonicalText(value.inputName, 200) || value.inputName.includes("|") ||
|
||||
!normalizeSymbol(value.symbol) || !["US", "TW"].includes(value.nationCode) ||
|
||||
value.fdtc !== "1") {
|
||||
return null;
|
||||
}
|
||||
return Object.freeze({
|
||||
inputName: value.inputName,
|
||||
symbol: value.symbol,
|
||||
nationCode: value.nationCode,
|
||||
fdtc: "1"
|
||||
});
|
||||
}
|
||||
|
||||
function createSearchRequest(requestId, query, maximumResults) {
|
||||
if (!isSafeCanonicalText(requestId, 128) || !identifierPattern.test(requestId)) {
|
||||
throw new TypeError("A safe overseas search request id is required.");
|
||||
}
|
||||
const normalizedQuery = normalizeQuery(query ?? "");
|
||||
if (normalizedQuery === null) {
|
||||
throw new TypeError(`An overseas search query must contain at most ${MAXIMUM_QUERY_LENGTH} safe characters.`);
|
||||
}
|
||||
if (maximumResults !== undefined &&
|
||||
(!Number.isInteger(maximumResults) || maximumResults < 1 || maximumResults > MAXIMUM_RESULTS)) {
|
||||
throw new RangeError(`Overseas results must be limited from 1 through ${MAXIMUM_RESULTS}.`);
|
||||
}
|
||||
const result = { requestId, query: normalizedQuery };
|
||||
if (maximumResults !== undefined) result.maximumResults = maximumResults;
|
||||
return Object.freeze(result);
|
||||
}
|
||||
|
||||
function createIndustrySearchRequest(requestId, query = "", maximumResults) {
|
||||
return createSearchRequest(requestId, query, maximumResults);
|
||||
}
|
||||
|
||||
function createStockSearchRequest(requestId, query = "", maximumResults) {
|
||||
return createSearchRequest(requestId, query, maximumResults);
|
||||
}
|
||||
|
||||
function compareUpper(left, right) {
|
||||
const upperLeft = left.toLocaleUpperCase("en-US");
|
||||
const upperRight = right.toLocaleUpperCase("en-US");
|
||||
if (upperLeft < upperRight) return -1;
|
||||
if (upperLeft > upperRight) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
function compareOrdinal(left, right) {
|
||||
if (left < right) return -1;
|
||||
if (left > right) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
function compareIndustries(left, right) {
|
||||
return compareUpper(left.koreanName, right.koreanName) ||
|
||||
compareOrdinal(left.inputName, right.inputName) ||
|
||||
compareOrdinal(left.symbol, right.symbol);
|
||||
}
|
||||
|
||||
function compareStocks(left, right) {
|
||||
return compareUpper(left.inputName, right.inputName) ||
|
||||
compareOrdinal(left.symbol, right.symbol) ||
|
||||
compareOrdinal(left.nationCode, right.nationCode);
|
||||
}
|
||||
|
||||
function normalizeSearchResponse(value, expectedRequest, normalizeItem, compare, duplicateKeys) {
|
||||
if (!hasExactKeys(value, [
|
||||
"requestId", "query", "retrievedAt", "totalRowCount", "truncated", "results"
|
||||
]) || !isSafeCanonicalText(value.requestId, 128) || !identifierPattern.test(value.requestId) ||
|
||||
normalizeQuery(value.query) !== value.query || !isSafeCanonicalText(value.retrievedAt, 64) ||
|
||||
!Number.isFinite(Date.parse(value.retrievedAt)) || !Number.isInteger(value.totalRowCount) ||
|
||||
value.totalRowCount < 0 || value.totalRowCount > MAXIMUM_RESULTS ||
|
||||
typeof value.truncated !== "boolean" || !Array.isArray(value.results) ||
|
||||
value.results.length !== value.totalRowCount) {
|
||||
return null;
|
||||
}
|
||||
if (expectedRequest) {
|
||||
let expected;
|
||||
try {
|
||||
expected = createSearchRequest(
|
||||
expectedRequest.requestId,
|
||||
expectedRequest.query,
|
||||
expectedRequest.maximumResults);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
const expectedLimit = expected.maximumResults ?? DEFAULT_MAXIMUM_RESULTS;
|
||||
if (value.requestId !== expected.requestId || value.query !== expected.query ||
|
||||
value.results.length > expectedLimit) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const results = [];
|
||||
const keySets = duplicateKeys.map(() => new Set());
|
||||
for (const raw of value.results) {
|
||||
const item = normalizeItem(raw);
|
||||
if (!item) return null;
|
||||
for (let index = 0; index < duplicateKeys.length; index += 1) {
|
||||
const key = duplicateKeys[index](item);
|
||||
if (keySets[index].has(key)) return null;
|
||||
keySets[index].add(key);
|
||||
}
|
||||
if (results.length && compare(results[results.length - 1], item) > 0) return null;
|
||||
results.push(item);
|
||||
}
|
||||
return Object.freeze({
|
||||
requestId: value.requestId,
|
||||
query: value.query,
|
||||
retrievedAt: value.retrievedAt,
|
||||
totalRowCount: value.totalRowCount,
|
||||
truncated: value.truncated,
|
||||
results: Object.freeze(results)
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeIndustrySearchResponse(value, expectedRequest) {
|
||||
return normalizeSearchResponse(
|
||||
value,
|
||||
expectedRequest,
|
||||
normalizeIndustry,
|
||||
compareIndustries,
|
||||
[item => item.koreanName, item => item.inputName, item => item.symbol]);
|
||||
}
|
||||
|
||||
function normalizeStockSearchResponse(value, expectedRequest) {
|
||||
return normalizeSearchResponse(
|
||||
value,
|
||||
expectedRequest,
|
||||
normalizeStock,
|
||||
compareStocks,
|
||||
[item => item.inputName, item => `${item.inputName}\u0000${item.symbol}\u0000${item.nationCode}`]);
|
||||
}
|
||||
|
||||
function normalizeSearchError(value, expectedRequest) {
|
||||
if (!hasExactKeys(value, ["requestId", "query", "message"]) ||
|
||||
!isSafeCanonicalText(value.requestId, 128) || !identifierPattern.test(value.requestId) ||
|
||||
normalizeQuery(value.query) !== value.query || !isSafeCanonicalText(value.message, 512)) {
|
||||
return null;
|
||||
}
|
||||
if (expectedRequest &&
|
||||
(value.requestId !== expectedRequest.requestId ||
|
||||
value.query !== normalizeQuery(expectedRequest.query))) return null;
|
||||
return Object.freeze({ requestId: value.requestId, query: value.query, message: value.message });
|
||||
}
|
||||
|
||||
function normalizeIndustrySearchError(value, expectedRequest) {
|
||||
return normalizeSearchError(value, expectedRequest);
|
||||
}
|
||||
|
||||
function normalizeStockSearchError(value, expectedRequest) {
|
||||
return normalizeSearchError(value, expectedRequest);
|
||||
}
|
||||
|
||||
function requireOptions(options) {
|
||||
if (!options || typeof options !== "object" || Array.isArray(options) ||
|
||||
!isSafeCanonicalText(options.id, 128) || !identifierPattern.test(options.id)) {
|
||||
throw new TypeError("Safe overseas playlist options are required.");
|
||||
}
|
||||
const fadeDuration = options.fadeDuration === undefined
|
||||
? DEFAULT_FADE_DURATION
|
||||
: options.fadeDuration;
|
||||
const ma5 = options.ma5 === undefined ? false : options.ma5;
|
||||
const ma20 = options.ma20 === undefined ? false : options.ma20;
|
||||
if (!Number.isInteger(fadeDuration) || fadeDuration < 0 || fadeDuration > 60) {
|
||||
throw new RangeError("Fade duration must be an integer from 0 through 60.");
|
||||
}
|
||||
if (typeof ma5 !== "boolean" || typeof ma20 !== "boolean") {
|
||||
throw new TypeError("Moving-average flags must be boolean values.");
|
||||
}
|
||||
return Object.freeze({ id: options.id, fadeDuration, ma5, ma20 });
|
||||
}
|
||||
|
||||
function encodeIndustryIdentity(value) {
|
||||
return `${value.inputName}|${value.symbol}|${value.nationCode}|${value.fdtc}`;
|
||||
}
|
||||
|
||||
function encodeStockIdentity(value) {
|
||||
return `${value.symbol}|${value.nationCode}|${value.fdtc}`;
|
||||
}
|
||||
|
||||
function movingAverageFlags(actionValue, options) {
|
||||
const enabled = actionValue.kind === "candle";
|
||||
const movingAverages = Object.freeze({
|
||||
ma5: enabled && options.ma5,
|
||||
ma20: enabled && options.ma20
|
||||
});
|
||||
const flags = [];
|
||||
if (movingAverages.ma5) flags.push("MA5");
|
||||
if (movingAverages.ma20) flags.push("MA20");
|
||||
return Object.freeze({ movingAverages, graphicType: flags.join(",") });
|
||||
}
|
||||
|
||||
function buildOverseasIndustrySelection(industryValue) {
|
||||
const industry = normalizeIndustry(industryValue);
|
||||
if (!industry) throw new TypeError("A valid US overseas-industry identity is required.");
|
||||
return Object.freeze({
|
||||
builderKey: "s5001",
|
||||
code: "5001",
|
||||
aliases: Object.freeze(["5001"]),
|
||||
selection: Object.freeze({
|
||||
groupCode: "FOREIGN_INDUSTRY",
|
||||
subject: industry.koreanName,
|
||||
graphicType: "\u0031\uC5F4\uD310\uAE30\uBCF8",
|
||||
subtype: "CURRENT",
|
||||
dataCode: encodeIndustryIdentity(industry)
|
||||
}),
|
||||
movingAverages: Object.freeze({ ma5: false, ma20: false })
|
||||
});
|
||||
}
|
||||
|
||||
function buildOverseasStockSelection(stockValue, actionId, options = {}) {
|
||||
const stock = normalizeStock(stockValue);
|
||||
const selectedAction = actionsById.get(actionId);
|
||||
if (!stock) throw new TypeError("A valid US/TW overseas-stock identity is required.");
|
||||
if (!selectedAction || !stockActions.includes(selectedAction)) {
|
||||
throw new RangeError("The overseas-stock action is unknown.");
|
||||
}
|
||||
const ma5 = options.ma5 === undefined ? false : options.ma5;
|
||||
const ma20 = options.ma20 === undefined ? false : options.ma20;
|
||||
if (typeof ma5 !== "boolean" || typeof ma20 !== "boolean") {
|
||||
throw new TypeError("Moving-average flags must be boolean values.");
|
||||
}
|
||||
const flags = movingAverageFlags(selectedAction, { ma5, ma20 });
|
||||
return Object.freeze({
|
||||
builderKey: selectedAction.builderKey,
|
||||
code: selectedAction.code,
|
||||
aliases: selectedAction.aliases,
|
||||
selection: Object.freeze({
|
||||
groupCode: "FOREIGN_STOCK",
|
||||
subject: stock.inputName,
|
||||
graphicType: selectedAction.kind === "current"
|
||||
? "\u0031\uC5F4\uD310\uAE30\uBCF8"
|
||||
: flags.graphicType,
|
||||
subtype: selectedAction.kind === "current" ? "CURRENT" : "PRICE",
|
||||
dataCode: encodeStockIdentity(stock)
|
||||
}),
|
||||
movingAverages: flags.movingAverages
|
||||
});
|
||||
}
|
||||
|
||||
function createOverseasIndustryPlaylistEntry(industryValue, options) {
|
||||
const industry = normalizeIndustry(industryValue);
|
||||
if (!industry) throw new TypeError("A valid US overseas-industry identity is required.");
|
||||
const normalizedOptions = requireOptions(options);
|
||||
const mapping = buildOverseasIndustrySelection(industry);
|
||||
const actionValue = industryActions[0];
|
||||
return {
|
||||
id: normalizedOptions.id,
|
||||
builderKey: mapping.builderKey,
|
||||
code: mapping.code,
|
||||
aliases: [...mapping.aliases],
|
||||
title: industry.koreanName,
|
||||
detail: actionValue.label,
|
||||
category: "plate",
|
||||
market: "overseas",
|
||||
symbol: industry.symbol,
|
||||
nationCode: industry.nationCode,
|
||||
fdtc: industry.fdtc,
|
||||
selection: mapping.selection,
|
||||
enabled: true,
|
||||
fadeDuration: normalizedOptions.fadeDuration,
|
||||
graphicType: mapping.selection.graphicType,
|
||||
subtype: mapping.selection.subtype,
|
||||
operator: Object.freeze({
|
||||
source: "legacy-overseas-workflow",
|
||||
schemaVersion: SCHEMA_VERSION,
|
||||
kind: "industry",
|
||||
actionId: actionValue.id,
|
||||
identity: industry,
|
||||
movingAverages: mapping.movingAverages
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
function createOverseasStockPlaylistEntry(stockValue, actionId, options) {
|
||||
const stock = normalizeStock(stockValue);
|
||||
if (!stock) throw new TypeError("A valid US/TW overseas-stock identity is required.");
|
||||
const selectedAction = actionsById.get(actionId);
|
||||
if (!selectedAction || !stockActions.includes(selectedAction)) {
|
||||
throw new RangeError("The overseas-stock action is unknown.");
|
||||
}
|
||||
const normalizedOptions = requireOptions(options);
|
||||
const mapping = buildOverseasStockSelection(stock, actionId, normalizedOptions);
|
||||
return {
|
||||
id: normalizedOptions.id,
|
||||
builderKey: mapping.builderKey,
|
||||
code: mapping.code,
|
||||
aliases: [...mapping.aliases],
|
||||
title: stock.inputName,
|
||||
detail: selectedAction.label,
|
||||
category: selectedAction.kind === "candle" ? "chart" : "stock",
|
||||
market: "overseas",
|
||||
symbol: stock.symbol,
|
||||
nationCode: stock.nationCode,
|
||||
fdtc: stock.fdtc,
|
||||
selection: mapping.selection,
|
||||
enabled: true,
|
||||
fadeDuration: normalizedOptions.fadeDuration,
|
||||
graphicType: mapping.selection.graphicType,
|
||||
subtype: mapping.selection.subtype,
|
||||
operator: Object.freeze({
|
||||
source: "legacy-overseas-workflow",
|
||||
schemaVersion: SCHEMA_VERSION,
|
||||
kind: "stock",
|
||||
actionId,
|
||||
identity: stock,
|
||||
movingAverages: mapping.movingAverages
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
function sameSelection(left, right) {
|
||||
return hasExactKeys(left, ["groupCode", "subject", "graphicType", "subtype", "dataCode"]) &&
|
||||
hasExactKeys(right, ["groupCode", "subject", "graphicType", "subtype", "dataCode"]) &&
|
||||
["groupCode", "subject", "graphicType", "subtype", "dataCode"]
|
||||
.every(key => left[key] === right[key]);
|
||||
}
|
||||
|
||||
function sameAliases(value, recreated) {
|
||||
return Array.isArray(value.aliases) && value.aliases.length === 1 &&
|
||||
value.aliases[0] === recreated.code;
|
||||
}
|
||||
|
||||
function restoreOverseasPlaylistEntry(value) {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value) ||
|
||||
typeof value.enabled !== "boolean") return null;
|
||||
const operator = value.operator;
|
||||
if (!hasExactKeys(operator, [
|
||||
"source", "schemaVersion", "kind", "actionId", "identity", "movingAverages"
|
||||
]) || operator.source !== "legacy-overseas-workflow" ||
|
||||
operator.schemaVersion !== SCHEMA_VERSION ||
|
||||
!hasExactKeys(operator.movingAverages, ["ma5", "ma20"]) ||
|
||||
typeof operator.movingAverages.ma5 !== "boolean" ||
|
||||
typeof operator.movingAverages.ma20 !== "boolean") return null;
|
||||
|
||||
let recreated;
|
||||
try {
|
||||
recreated = operator.kind === "industry"
|
||||
? createOverseasIndustryPlaylistEntry(operator.identity, {
|
||||
id: value.id,
|
||||
fadeDuration: value.fadeDuration,
|
||||
ma5: operator.movingAverages.ma5,
|
||||
ma20: operator.movingAverages.ma20
|
||||
})
|
||||
: operator.kind === "stock"
|
||||
? createOverseasStockPlaylistEntry(operator.identity, operator.actionId, {
|
||||
id: value.id,
|
||||
fadeDuration: value.fadeDuration,
|
||||
ma5: operator.movingAverages.ma5,
|
||||
ma20: operator.movingAverages.ma20
|
||||
})
|
||||
: null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
if (!recreated || operator.actionId !== recreated.operator.actionId) return null;
|
||||
const scalars = [
|
||||
"id", "builderKey", "code", "title", "detail", "category", "market",
|
||||
"symbol", "nationCode", "fdtc", "fadeDuration", "graphicType", "subtype"
|
||||
];
|
||||
if (!scalars.every(key => value[key] === recreated[key]) ||
|
||||
!sameAliases(value, recreated) || !sameSelection(value.selection, recreated.selection) ||
|
||||
JSON.stringify(operator) !== JSON.stringify(recreated.operator)) return null;
|
||||
return { ...recreated, enabled: value.enabled };
|
||||
}
|
||||
|
||||
function isActionAllowed(kind, actionId, identity) {
|
||||
if (kind === "industry") {
|
||||
return actionId === "industry-current" && Boolean(normalizeIndustry(identity));
|
||||
}
|
||||
return kind === "stock" && Boolean(normalizeStock(identity)) &&
|
||||
stockActions.some(value => value.id === actionId);
|
||||
}
|
||||
|
||||
return {
|
||||
bridgeContracts,
|
||||
sourceContract,
|
||||
industryActions,
|
||||
stockActions,
|
||||
normalizeIndustry,
|
||||
normalizeStock,
|
||||
createIndustrySearchRequest,
|
||||
createStockSearchRequest,
|
||||
normalizeIndustrySearchResponse,
|
||||
normalizeStockSearchResponse,
|
||||
normalizeIndustrySearchError,
|
||||
normalizeStockSearchError,
|
||||
isActionAllowed,
|
||||
buildOverseasIndustrySelection,
|
||||
buildOverseasStockSelection,
|
||||
createOverseasIndustryPlaylistEntry,
|
||||
createOverseasStockPlaylistEntry,
|
||||
restoreOverseasPlaylistEntry,
|
||||
refreshOverseasPlaylistEntry: restoreOverseasPlaylistEntry
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user