From e50d56a4d06130afa2aa7bb1e80ed04daa8f00dd Mon Sep 17 00:00:00 2001 From: janherich Date: Sat, 4 Nov 2017 13:59:19 +0100 Subject: [PATCH] Refactored jail loading + commands/responses --- env/dev/env/ios/main.cljs | 4 +- resources/default_contacts.json | 36 +- resources/js/bots/browse/bot.js | 2 +- resources/js/bots/console/bot.js | 9 +- resources/js/bots/mailman/bot.js | 4 +- .../{transactor_group => transactor}/bot.js | 461 ++++++++------ .../translations.js | 0 resources/js/bots/transactor_personal/bot.js | 562 ------------------ .../bots/transactor_personal/translations.js | 448 -------------- resources/js/status.js | 29 +- src/status_im/bots/events.cljs | 14 +- src/status_im/bots/subs.cljs | 17 +- src/status_im/chat/constants.cljs | 6 + src/status_im/chat/events.cljs | 131 ++-- src/status_im/chat/events/commands.cljs | 24 +- src/status_im/chat/events/console.cljs | 8 +- src/status_im/chat/events/input.cljs | 63 +- .../chat/events/receive_message.cljs | 70 ++- src/status_im/chat/events/requests.cljs | 43 ++ src/status_im/chat/events/sign_up.cljs | 3 +- src/status_im/chat/handlers.cljs | 18 +- src/status_im/chat/handlers/requests.cljs | 46 -- src/status_im/chat/handlers/send_message.cljs | 30 +- src/status_im/chat/models/commands.cljs | 140 ++--- src/status_im/chat/models/input.cljs | 57 +- src/status_im/chat/screen.cljs | 3 +- src/status_im/chat/sign_up.cljs | 72 ++- src/status_im/chat/specs.cljs | 8 +- src/status_im/chat/subs.cljs | 97 +-- src/status_im/chat/utils.cljs | 5 - src/status_im/chat/views/input/input.cljs | 4 +- .../chat/views/input/suggestions.cljs | 30 +- src/status_im/chat/views/message/message.cljs | 11 +- .../chat/views/message/request_message.cljs | 24 +- src/status_im/commands/events/loading.cljs | 158 +++++ src/status_im/commands/handlers/debug.cljs | 12 +- src/status_im/commands/handlers/loading.cljs | 206 ------- src/status_im/commands/specs.cljs | 4 + src/status_im/commands/subs.cljs | 13 + src/status_im/data_store/chats.cljs | 6 +- src/status_im/data_store/contacts.cljs | 2 +- src/status_im/data_store/realm/requests.cljs | 35 +- .../realm/schemas/account/core.cljs | 4 + .../realm/schemas/account/v19/contact.cljs | 30 + .../realm/schemas/account/v19/core.cljs | 100 ++++ .../realm/schemas/account/v19/request.cljs | 8 + src/status_im/data_store/requests.cljs | 16 +- src/status_im/protocol/handlers.cljs | 3 +- .../ui/components/list_selection.cljs | 4 +- src/status_im/ui/screens/accounts/events.cljs | 115 ++-- .../ui/screens/accounts/login/events.cljs | 26 +- .../ui/screens/accounts/recover/events.cljs | 12 +- src/status_im/ui/screens/accounts/subs.cljs | 9 +- src/status_im/ui/screens/contacts/db.cljs | 27 +- src/status_im/ui/screens/contacts/events.cljs | 91 +-- src/status_im/ui/screens/contacts/subs.cljs | 4 +- src/status_im/ui/screens/db.cljs | 12 +- src/status_im/ui/screens/events.cljs | 189 +++--- src/status_im/ui/screens/profile/events.cljs | 30 +- src/status_im/ui/screens/subs.cljs | 16 +- .../ui/screens/wallet/request/events.cljs | 22 +- src/status_im/utils/js_resources.cljs | 15 +- test/cljs/status_im/test/bots/events.cljs | 12 +- test/cljs/status_im/test/chat/events.cljs | 6 +- .../status_im/test/chat/models/input.cljs | 107 ++-- 65 files changed, 1417 insertions(+), 2356 deletions(-) rename resources/js/bots/{transactor_group => transactor}/bot.js (69%) rename resources/js/bots/{transactor_group => transactor}/translations.js (100%) delete mode 100644 resources/js/bots/transactor_personal/bot.js delete mode 100644 resources/js/bots/transactor_personal/translations.js create mode 100644 src/status_im/chat/events/requests.cljs delete mode 100644 src/status_im/chat/handlers/requests.cljs create mode 100644 src/status_im/commands/events/loading.cljs delete mode 100644 src/status_im/commands/handlers/loading.cljs create mode 100644 src/status_im/commands/specs.cljs create mode 100644 src/status_im/commands/subs.cljs create mode 100644 src/status_im/data_store/realm/schemas/account/v19/contact.cljs create mode 100644 src/status_im/data_store/realm/schemas/account/v19/core.cljs create mode 100644 src/status_im/data_store/realm/schemas/account/v19/request.cljs diff --git a/env/dev/env/ios/main.cljs b/env/dev/env/ios/main.cljs index 15c9273607..19f0a6d10f 100644 --- a/env/dev/env/ios/main.cljs +++ b/env/dev/env/ios/main.cljs @@ -12,8 +12,8 @@ (def root-el (r/as-element [reloader])) (figwheel/watch-and-reload - :websocket-url "ws://10.0.1.15:3449/figwheel-ws" + :websocket-url "ws://localhost:3449/figwheel-ws" :heads-up-display false :jsload-callback #(swap! cnt inc)) -(rr/enable-re-frisk-remote! {:host "localhost:4567" :on-init core/init :pre-send (fn [db] (update db :chats #(into {} %)))}) \ No newline at end of file +(rr/enable-re-frisk-remote! {:host "localhost:4567" :on-init core/init :pre-send (fn [db] (update db :chats #(into {} %)))}) diff --git a/resources/default_contacts.json b/resources/default_contacts.json index c4d407df87..934f0743e0 100644 --- a/resources/default_contacts.json +++ b/resources/default_contacts.json @@ -6,21 +6,8 @@ "en": "Browse" }, "dapp?": true, - "bot-url": "local://browse-bot", - "global-command": { - "description":"Launch the browser", - "color": "#ffa500", - "name": "global", - "params": [{ - "name": "url", - "placeholder":"URL", - "type":"text" - }], - "title":"Browser", - "registered-only":true, - "has-handler": false, - "fullscreen":true - }, + "hide-contact?": true, + "bot-url": "local://browse-bot", "unremovable?": true }, @@ -32,30 +19,19 @@ "ru": "Печкин" }, "dapp?": true, - "mixable?": true, + "hide-contact?": true, "bot-url": "local://mailman-bot" }, - "transactor-group": + "transactor": { "name": { "en": "Transactor" }, "dapp?": true, - "mixable?": true, - "bot-url": "local://transactor-group-bot" - }, - - "transactor-personal": - { - "name": - { - "en": "Transactor" - }, - "dapp?": true, - "mixable?": true, - "bot-url": "local://transactor-personal-bot" + "hide-contact?": true, + "bot-url": "local://transactor-bot" }, "demo-bot": diff --git a/resources/js/bots/browse/bot.js b/resources/js/bots/browse/bot.js index 3979bc3749..5aa1a7f54a 100644 --- a/resources/js/bots/browse/bot.js +++ b/resources/js/bots/browse/bot.js @@ -36,7 +36,7 @@ function browse(params, context) { status.command({ name: "browse", title: I18n.t('browse_title'), - scope: ["global", "registered-only", "group-chats", "personal-chats", "can-use-for-dapps"], + scope: ["global", "personal-chats", "group-chats", "public-chats", "registered", "dapps", "humans"], description: I18n.t('browse_description'), color: "#ffa500", fullscreen: true, diff --git a/resources/js/bots/console/bot.js b/resources/js/bots/console/bot.js index 15d31112a1..84b28b61e3 100644 --- a/resources/js/bots/console/bot.js +++ b/resources/js/bots/console/bot.js @@ -458,6 +458,7 @@ var phoneConfig = { icon: "phone_white", color: "#5bb2a2", title: I18n.t('phone_title'), + scope: ["personal-chats", "registered", "dapps"], description: I18n.t('phone_description'), sequentialParams: true, validator: function (params) { @@ -489,6 +490,7 @@ var phoneConfig = { }; } }; +status.command(phoneConfig); status.response(phoneConfig); var ropstenNetworkId = 3; @@ -556,7 +558,7 @@ var faucetCommandConfig ={ title: I18n.t('faucet_title'), description: I18n.t('faucet_description'), color: "#7099e6", - scope: ["registered-only", "group-chats", "personal-chats", "can-use-for-dapps"], + scope: ["personal-chats", "registered", "dapps"], params: [{ name: "url", type: status.types.TEXT, @@ -632,7 +634,7 @@ status.command({ title: I18n.t('debug_mode_title'), description: I18n.t('debug_mode_description'), color: "#7099e6", - scope: ["registered-only", "group-chats", "personal-chats", "can-use-for-dapps"], + scope: ["personal-chats", "registered", "dapps"], params: [{ name: "mode", suggestions: debugSuggestions, @@ -659,6 +661,7 @@ status.command({ status.response({ name: "confirmation-code", color: "#7099e6", + scope: ["personal-chats", "registered", "dapps"], description: I18n.t('confirm_description'), sequentialParams: true, params: [{ @@ -696,6 +699,7 @@ status.response({ status.response({ name: "password", color: "#7099e6", + scope: ["personal-chats", "anonymous", "dapps"], description: I18n.t('password_description'), icon: "lock_white", sequentialParams: true, @@ -754,6 +758,7 @@ status.response({ status.response({ name: "grant-permissions", + scope: ["personal-chats", "anonymous", "registered", "dapps"], color: "#7099e6", description: "Grant permissions", icon: "lock_white", diff --git a/resources/js/bots/mailman/bot.js b/resources/js/bots/mailman/bot.js index ac599ddecf..d6ac3b1ca4 100644 --- a/resources/js/bots/mailman/bot.js +++ b/resources/js/bots/mailman/bot.js @@ -32,7 +32,7 @@ function locationsSuggestions (params) { status.command({ name: "location", title: I18n.t('location_title'), - scope: ["registered-only", "group-chats", "personal-chats"], + scope: ["global", "personal-chats", "group-chats", "public-chats", "registered", "humans"], description: I18n.t('location_description'), sequentialParams: true, hideSendButton: true, @@ -96,4 +96,4 @@ status.command({ ) }; } -}); \ No newline at end of file +}); diff --git a/resources/js/bots/transactor_group/bot.js b/resources/js/bots/transactor/bot.js similarity index 69% rename from resources/js/bots/transactor_group/bot.js rename to resources/js/bots/transactor/bot.js index b7c450e843..aa9a4ef106 100644 --- a/resources/js/bots/transactor_group/bot.js +++ b/resources/js/bots/transactor/bot.js @@ -1,3 +1,5 @@ +// Send command/response + function calculateFee(n, tx) { var estimatedGas = 21000; if (tx !== null) { @@ -52,26 +54,30 @@ status.defineSubscription( function(params) { return getFeeExplanation(params.value); } -) - -function amountParameterBox(params, context) { +); +function amountParameterBox(groupChat, params, context) { if (!params["bot-db"]) { params["bot-db"] = {}; } var contactAddress; - if (params["bot-db"]["public"] && params["bot-db"]["public"]["recipient"]) { - contactAddress = params["bot-db"]["public"]["recipient"]["address"]; + if (groupChat) { + if (params["bot-db"]["public"] && params["bot-db"]["public"]["recipient"]) { + contactAddress = params["bot-db"]["public"]["recipient"]["address"]; + } else { + contactAddress = null; + } } else { - contactAddress = null; + contactAddress = context.to; } - + var txData; var amount; - + var amountIndex = groupChat ? 1 : 0; + try { - amount = params.args[1] || "0"; + amount = params.args[amountIndex]; txData = { to: contactAddress, value: web3.toWei(amount) || 0 @@ -82,7 +88,7 @@ function amountParameterBox(params, context) { to: contactAddress, value: 0 }; - } + } var sliderValue = params["bot-db"]["sliderValue"] || 0; @@ -104,7 +110,7 @@ function amountParameterBox(params, context) { sliderValue: sliderValue }); } - + return { title: I18n.t('send_title'), showBack: true, @@ -275,43 +281,48 @@ function amountParameterBox(params, context) { )] ) }; - } -var paramsSend = [ - { - name: "recipient", - type: status.types.TEXT, - suggestions: function (params) { - return { - title: I18n.t('send_title'), - markup: status.components.chooseContact(I18n.t('send_choose_recipient'), "recipient", 0) - }; - } - }, - { +var recipientSendParam = { + name: "recipient", + type: status.types.TEXT, + suggestions: function (params) { + return { + title: I18n.t('send_title'), + markup: status.components.chooseContact(I18n.t('send_choose_recipient'), "recipient", 0) + }; + } +}; + +function amountSendParam(groupChat) { + return { name: "amount", type: status.types.NUMBER, - suggestions: amountParameterBox - } -]; + suggestions: amountParameterBox.bind(this, groupChat) + }; +} -function validateSend(params, context) { +var paramsPersonalSend = [amountSendParam(false)]; +var paramsGroupSend = [recipientSendParam, amountSendParam(true)]; + +function validateSend(validateRecipient, params, context) { if (!params["bot-db"]) { params["bot-db"] = {}; } - if (!params["bot-db"]["public"] - || !params["bot-db"]["public"]["recipient"] - || !params["bot-db"]["public"]["recipient"]["address"]) { - return { - markup: status.components.validationMessage( - "Wrong address", - "Recipient address must be specified" - ) - }; + if (validateRecipient) { + if (!params["bot-db"]["public"] + || !params["bot-db"]["public"]["recipient"] + || !params["bot-db"]["public"]["recipient"]["address"]) { + return { + markup: status.components.validationMessage( + "Wrong address", + "Recipient address must be specified" + ) + }; + } } - + if (!params["amount"]) { return { markup: status.components.validationMessage( @@ -369,10 +380,11 @@ function validateSend(params, context) { }; } + var fee = calculateFee( params["bot-db"]["sliderValue"], { - to: params["bot-db"]["public"]["recipient"]["address"], + to: context.to, value: val } ); @@ -389,16 +401,21 @@ function validateSend(params, context) { } } -function handleSend(params, context) { +function handleSend(groupChat, params, context) { var val = web3.toWei(params["amount"].replace(",", "."), "ether"); var gasPrice = calculateGasPrice(params["bot-db"]["sliderValue"]); var data = { - from: context.from, - to: params["bot-db"]["public"]["recipient"]["address"], + from: context.from, value: val }; + if (groupChat) { + data.to = params["bot-db"]["public"]["recipient"]["address"]; + } else { + data.to = context.to; + } + if (gasPrice) { data.gasPrice = gasPrice; } @@ -417,7 +434,7 @@ function handleSend(params, context) { // async handler, so we don't return anything immediately } -function previewSend(params, context) { +function previewSend(showRecipient, params, context) { var amountStyle = { fontSize: 36, color: "#000000", @@ -461,7 +478,7 @@ function previewSend(params, context) { )] ); - var firstRow = status.components.view( + var amountRow = status.components.view( { style: { flexDirection: "row", @@ -473,12 +490,14 @@ function previewSend(params, context) { [amount, currency] ); - var markup; - if (params["bot-db"] + var markup = [amountRow]; + + if (showRecipient + && params["bot-db"] && params["bot-db"]["public"] && params["bot-db"]["public"]["recipient"] && context["chat"]["group-chat"] === true) { - var secondRow = status.components.text( + var recipientRow = status.components.text( { style: { color: "#9199a0", @@ -488,11 +507,9 @@ function previewSend(params, context) { }, I18n.t('send_sending_to') + " " + params["bot-db"]["public"]["recipient"]["name"] ); - markup = [firstRow, secondRow]; - } else { - markup = [firstRow]; + markup.push(recipientRow); } - + return { markup: status.components.view( { @@ -516,124 +533,157 @@ function shortPreviewSend(params, context) { }; } -var send = { +var personalSend = { name: "send", - scope: ["group-chats"], + scope: ["global", "personal-chats", "registered", "humans"], icon: "money_white", color: "#5fc48d", title: I18n.t('send_title'), description: I18n.t('send_description'), - params: paramsSend, - validator: validateSend, - handler: handleSend, + params: paramsPersonalSend, + validator: validateSend.bind(this, false), + handler: handleSend.bind(this, false), asyncHandler: true, - preview: previewSend, + preview: previewSend.bind(this, false), shortPreview: shortPreviewSend }; -status.command(send); -status.response(send); - -var paramsRequest = [ - { - name: "recipient", - type: status.types.TEXT, - suggestions: function (params) { - return { - title: I18n.t('request_title'), - markup: status.components.chooseContact(I18n.t('send_choose_recipient'), "recipient", 0) - }; - } - }, - { - name: "amount", - type: status.types.NUMBER - } -]; - -status.command({ - name: "request", - scope: ["group-chats"], +var groupSend = { + name: "send", + scope: ["global", "group-chats", "registered", "humans"], + icon: "money_white", color: "#5fc48d", - title: I18n.t('request_title'), - description: I18n.t('request_description'), - params: paramsRequest, - handler: function (params, context) { - var val = params["amount"].replace(",", "."); + title: I18n.t('send_title'), + description: I18n.t('send_description'), + params: paramsGroupSend, + validator: validateSend.bind(this, true), + handler: handleSend.bind(this, true), + asyncHandler: true, + preview: previewSend.bind(this, true), + shortPreview: shortPreviewSend +}; +status.command(personalSend); +status.response(personalSend); + +status.command(groupSend); +status.response(groupSend); + +// Request command + +var recipientRequestParam = { + name: "recipient", + type: status.types.TEXT, + suggestions: function (params) { return { - event: "request", - request: { - command: "send", - params: { - recipient: context["current-account"]["name"], - amount: val - }, - prefill: [context["current-account"]["name"], val], - prefillBotDb: { - public: { - recipient: context["current-account"] - } + title: I18n.t('request_title'), + markup: status.components.chooseContact(I18n.t('send_choose_recipient'), "recipient", 0) + }; + } +}; + +var amountRequestParam = { + name: "amount", + type: status.types.NUMBER +}; + +var paramsPersonalRequest = [amountRequestParam]; +var paramsGroupRequest = [recipientRequestParam, amountRequestParam]; + +function handlePersonalRequest(params, context) { + var val = params["amount"].replace(",", "."); + + return { + event: "request", + request: { + command: "send", + params: { + amount: val + }, + prefill: [val] + } + }; +} + +function handleGroupRequest(params, context) { + var val = params["amount"].replace(",", "."); + + return { + event: "request", + request: { + command: "send", + params: { + recipient: context["current-account"]["name"], + amount: val + }, + prefill: [context["current-account"]["name"], val], + prefillBotDb: { + public: { + recipient: context["current-account"] } } - }; - }, - preview: function (params, context) { - var firstRow = status.components.text( + } + }; +} + +function previewRequest(showRecipient, params, context) { + var amountRow = status.components.text( + {}, + I18n.t('request_requesting') + " " + + status.localizeNumber(params.amount, context.delimiter, context.separator) + + " ETH" + ); + + var markup = [amountRow]; + + if (showRecipient + && params["bot-db"] + && params["bot-db"]["public"] + && params["bot-db"]["public"]["recipient"] + && context["chat"]["group-chat"] === true) { + + var recipientRow = status.components.text( + { + style: { + color: "#9199a0", + fontSize: 14, + lineHeight: 18 + } + }, + I18n.t('request_requesting_from') + " " + params["bot-db"]["public"]["recipient"]["name"] + ); + markup.push(recipientRow); + } + + return { + markup: status.components.view( + { + style: { + flexDirection: "column" + } + }, + markup + ) + }; +} + +function shortPreviewRequest(params, context) { + return { + markup: status.components.text( {}, I18n.t('request_requesting') + " " + status.localizeNumber(params.amount, context.delimiter, context.separator) + " ETH" - ); + ) + }; +} - var markup; - - if (params["bot-db"] - && params["bot-db"]["public"] - && params["bot-db"]["public"]["recipient"] - && context["chat"]["group-chat"] === true) { - - var secondRow = status.components.text( - { - style: { - color: "#9199a0", - fontSize: 14, - lineHeight: 18 - } - }, - I18n.t('request_requesting_from') + " " + params["bot-db"]["public"]["recipient"]["name"] - ); - markup = [firstRow, secondRow]; - } else { - markup = [firstRow]; - } - - return { - markup: status.components.view( - { - style: { - flexDirection: "column" - } - }, - markup - ) - }; - }, - shortPreview: function (params, context) { - return { - markup: status.components.text( - {}, - I18n.t('request_requesting') + " " - + status.localizeNumber(params.amount, context.delimiter, context.separator) - + " ETH" - ) - }; - }, - validator: function (params) { - if (!params["bot-db"]) { - params["bot-db"] = {}; - } +function validateRequest(validateRecipient, params) { + if (!params["bot-db"]) { + params["bot-db"] = {}; + } + if (validateRecipient) { if (!params["bot-db"]["public"] || !params["bot-db"]["public"]["recipient"] || !params["bot-db"]["public"]["recipient"]["address"]) { return { markup: status.components.validationMessage( @@ -642,47 +692,76 @@ status.command({ ) }; } - if (!params["amount"]) { - return { - markup: status.components.validationMessage( - I18n.t('validation_title'), - I18n.t('validation_amount_specified') - ) - }; - } - - var amount = params.amount.replace(",", "."); - var amountSplitted = amount.split("."); - if (amountSplitted.length === 2 && amountSplitted[1].length > 18) { - return { - markup: status.components.validationMessage( - I18n.t('validation_title'), - I18n.t('validation_amount_is_too_small') - ) - }; - } - - if (isNaN(parseFloat(params.amount.replace(",", ".")))) { - return { - markup: status.components.validationMessage( - I18n.t('validation_title'), - I18n.t('validation_invalid_number') - ) - }; - } - - try { - var val = web3.toWei(amount, "ether"); - if (val < 0) { - throw new Error(); - } - } catch (err) { - return { - markup: status.components.validationMessage( - I18n.t('validation_title'), - I18n.t('validation_invalid_number') - ) - }; - } } + + if (!params["amount"]) { + return { + markup: status.components.validationMessage( + I18n.t('validation_title'), + I18n.t('validation_amount_specified') + ) + }; + } + + var amount = params.amount.replace(",", "."); + var amountSplitted = amount.split("."); + if (amountSplitted.length === 2 && amountSplitted[1].length > 18) { + return { + markup: status.components.validationMessage( + I18n.t('validation_title'), + I18n.t('validation_amount_is_too_small') + ) + }; + } + + if (isNaN(parseFloat(params.amount.replace(",", ".")))) { + return { + markup: status.components.validationMessage( + I18n.t('validation_title'), + I18n.t('validation_invalid_number') + ) + }; + } + + try { + var val = web3.toWei(amount, "ether"); + if (val < 0) { + throw new Error(); + } + } catch (err) { + return { + markup: status.components.validationMessage( + I18n.t('validation_title'), + I18n.t('validation_invalid_number') + ) + }; + } +} + +status.command({ + name: "request", + scope: ["global", "personal-chats", "registered", "humans"], + icon: "money_white", + color: "#5fc48d", + title: I18n.t('request_title'), + description: I18n.t('request_description'), + params: paramsPersonalRequest, + handler: handlePersonalRequest, + preview: previewRequest.bind(null, false), + shortPreview: shortPreviewRequest, + validator: validateRequest.bind(null, false) +}); + +status.command({ + name: "request", + scope: ["global", "group-chats", "registered", "humans"], + icon: "money_white", + color: "#5fc48d", + title: I18n.t('request_title'), + description: I18n.t('request_description'), + params: paramsGroupRequest, + handler: handleGroupRequest, + preview: previewRequest.bind(null, true), + shortPreview: shortPreviewRequest, + validator: validateRequest.bind(null, true) }); diff --git a/resources/js/bots/transactor_group/translations.js b/resources/js/bots/transactor/translations.js similarity index 100% rename from resources/js/bots/transactor_group/translations.js rename to resources/js/bots/transactor/translations.js diff --git a/resources/js/bots/transactor_personal/bot.js b/resources/js/bots/transactor_personal/bot.js deleted file mode 100644 index 1860b07d60..0000000000 --- a/resources/js/bots/transactor_personal/bot.js +++ /dev/null @@ -1,562 +0,0 @@ -function calculateFee(n, tx) { - var estimatedGas = 21000; - if (tx !== null) { - estimatedGas = web3.eth.estimateGas(tx); - } - - var gasMultiplicator = Math.pow(1.4, n).toFixed(3); - var weiFee = web3.eth.gasPrice * gasMultiplicator * estimatedGas; - // force fee in eth to be of BigNumber type - var ethFee = web3.toBigNumber(web3.fromWei(weiFee, "ether")); - // always display 7 decimal places - return ethFee.toFixed(7); -} - -function calculateGasPrice(n) { - var gasMultiplicator = Math.pow(1.4, n).toFixed(3); - return web3.eth.gasPrice * gasMultiplicator; -} - -status.defineSubscription( - "calculatedFee", - {value: ["sliderValue"], tx: ["transaction"]}, - function (params) { - return calculateFee(params.value, params.tx); - } -); - -function getFeeExplanation(n) { - return I18n.t('send_explanation') + I18n.t('send_explanation_' + (n + 2)); -} - -status.defineSubscription( - "feeExplanation", - {value: ["sliderValue"]}, - function(params) { - return getFeeExplanation(params.value); - } -); - -function amountParameterBox(params, context) { - if (!params["bot-db"]) { - params["bot-db"] = {}; - } - - var contactAddress = context.to; - - var txData; - var amount; - try { - amount = params.args[0]; - txData = { - to: contactAddress, - value: web3.toWei(amount) || 0 - }; - } catch (err) { - amount = null; - txData = { - to: contactAddress, - value: 0 - }; - } - - var sliderValue = params["bot-db"]["sliderValue"] || 0; - - try { - - status.setDefaultDb({ - transaction: txData, - calculatedFee: calculateFee(sliderValue, txData), - feeExplanation: getFeeExplanation(sliderValue), - sliderValue: sliderValue - }); - - } catch (err) { - - status.setDefaultDb({ - transaction: txData, - calculatedFee: "0", - feeExplanation: "", - sliderValue: sliderValue - }); - } - - return { - title: I18n.t('send_title'), - showBack: true, - markup: status.components.scrollView( - { - keyboardShouldPersistTaps: "always" - }, - [status.components.view( - { - flex: 1 - }, - [ - status.components.text( - { - style: { - fontSize: 14, - color: "rgb(147, 155, 161)", - paddingTop: 12, - paddingLeft: 16, - paddingRight: 16, - paddingBottom: 20 - } - }, - I18n.t('send_specify_amount') - ), - status.components.touchable( - { - onPress: status.components.dispatch([status.events.FOCUS_INPUT, []]) - }, - status.components.view( - { - flexDirection: "row", - alignItems: "center", - textAlign: "center", - justifyContent: "center" - }, - [ - status.components.text( - { - font: "light", - numberOfLines: 1, - ellipsizeMode: "tail", - style: { - maxWidth: 250, - fontSize: 38, - marginLeft: 8, - color: "black" - } - }, - amount || "0.00" - ), - status.components.text( - { - font: "light", - style: { - fontSize: 38, - marginLeft: 8, - color: "rgb(147, 155, 161)" - } - }, - I18n.t('eth') - ), - ] - ) - ), - status.components.text( - { - style: { - fontSize: 14, - color: "rgb(147, 155, 161)", - paddingTop: 14, - paddingLeft: 16, - paddingRight: 16, - paddingBottom: 5 - } - }, - I18n.t('send_fee') - ), - status.components.view( - { - flexDirection: "row" - }, - [ - status.components.text( - { - style: { - fontSize: 17, - color: "black", - paddingLeft: 16 - } - }, - [status.components.subscribe(["calculatedFee"])] - ), - status.components.text( - { - style: { - fontSize: 17, - color: "rgb(147, 155, 161)", - paddingLeft: 4, - paddingRight: 4 - } - }, - I18n.t('eth') - ) - ] - ), - status.components.slider( - { - maximumValue: 2, - minimumValue: -2, - onSlidingComplete: status.components.dispatch( - [status.events.UPDATE_DB, "sliderValue"] - ), - step: 1, - style: { - marginLeft: 16, - marginRight: 16 - } - } - ), - status.components.view( - { - flexDirection: "row" - }, - [ - status.components.text( - { - style: { - flex: 1, - fontSize: 14, - color: "rgb(147, 155, 161)", - paddingLeft: 16, - alignSelf: "flex-start" - } - }, - I18n.t('send_cheaper') - ), - status.components.text( - { - style: { - flex: 1, - fontSize: 14, - color: "rgb(147, 155, 161)", - paddingRight: 16, - alignSelf: "flex-end", - textAlign: "right" - } - }, - I18n.t('send_faster') - ) - ] - ), - status.components.text( - { - style: { - fontSize: 14, - color: "black", - paddingTop: 16, - paddingLeft: 16, - paddingRight: 16, - paddingBottom: 16, - lineHeight: 24 - } - }, - [status.components.subscribe(["feeExplanation"])] - ) - ] - )] - ) - }; -} - -var paramsSend = [ - { - name: "amount", - type: status.types.NUMBER, - suggestions: amountParameterBox - } -]; - -function validateSend(params, context) { - if (!params["bot-db"]) { - params["bot-db"] = {}; - } - - if (!params["amount"]) { - return { - markup: status.components.validationMessage( - I18n.t('validation_title'), - I18n.t('validation_amount_specified') - ) - }; - } - - var amount = params["amount"].replace(",", "."); - var amountSplitted = amount.split("."); - if (amountSplitted.length === 2 && amountSplitted[1].length > 18) { - return { - markup: status.components.validationMessage( - I18n.t('validation_title'), - I18n.t('validation_amount_is_too_small') - ) - }; - } - - if (isNaN(parseFloat(params.amount.replace(",", ".")))) { - return { - markup: status.components.validationMessage( - I18n.t('validation_title'), - I18n.t('validation_invalid_number') - ) - }; - } - - try { - var val = web3.toWei(amount, "ether"); - if (val < 0) { - throw new Error(); - } - } catch (err) { - return { - markup: status.components.validationMessage( - I18n.t('validation_title'), - I18n.t('validation_invalid_number') - ) - }; - } - - var balance = web3.eth.getBalance(context.from); - var fee = calculateFee( - params["bot-db"]["sliderValue"], - { - to: context.to, - value: val - } - ); - - if (bn(val).plus(bn(web3.toWei(fee, "ether"))).greaterThan(bn(balance))) { - return { - markup: status.components.validationMessage( - I18n.t('validation_title'), - I18n.t('validation_insufficient_amount') - + web3.fromWei(balance, "ether") - + " ETH)" - ) - }; - } -} - -function handleSend(params, context) { - var val = web3.toWei(params["amount"].replace(",", "."), "ether"); - - var gasPrice = calculateGasPrice(params["bot-db"]["sliderValue"]); - var data = { - from: context.from, - to: context.to, - value: val - }; - - if (gasPrice) { - data.gasPrice = gasPrice; - } - - web3.eth.sendTransaction(data, function(error, hash) { - if (error) { - // Do nothing, as error handling will be done as response to transaction.failed event from go - } else { - status.sendSignal("handler-result", { - status: "success", - hash: hash, - origParams: context["orig-params"] - }); - } - }); - // async handler, so we don't return anything immediately -} - -function previewSend(params, context) { - var amountStyle = { - fontSize: 36, - color: "#000000", - height: 40 - }; - - var amount = status.components.view( - { - flexDirection: "column", - alignItems: "flex-end", - maxWidth: 250 - }, - [status.components.text( - { - style: amountStyle, - numberOfLines: 1, - ellipsizeMode: "tail", - font: "light" - }, - status.localizeNumber(params.amount, context.delimiter, context.separator) - )]); - - var currency = status.components.view( - { - style: { - flexDirection: "column", - justifyContent: "flex-end", - paddingBottom: 0 - } - }, - [status.components.text( - { - style: { - color: "#9199a0", - fontSize: 16, - lineHeight: 18, - marginLeft: 7.5 - } - }, - I18n.t('eth') - )] - ); - - var row = status.components.view( - { - style: { - flexDirection: "row", - justifyContent: "space-between", - marginTop: 8, - marginBottom: 8 - } - }, - [amount, currency] - ); - - return { - markup: status.components.view( - { - style: { - flexDirection: "column" - } - }, - [row] - ) - }; -} - -function shortPreviewSend(params, context) { - return { - markup: status.components.text( - {}, - I18n.t('send_title') + ": " - + status.localizeNumber(params.amount, context.delimiter, context.separator) - + " ETH" - ) - }; -} - -var send = { - name: "send", - scope: ["personal-chats"], - icon: "money_white", - color: "#5fc48d", - title: I18n.t('send_title'), - description: I18n.t('send_description'), - params: paramsSend, - validator: validateSend, - handler: handleSend, - asyncHandler: true, - preview: previewSend, - shortPreview: shortPreviewSend -}; - -status.command(send); -status.response(send); - -var paramsRequest = [ - { - name: "amount", - type: status.types.NUMBER - } -]; - -status.command({ - name: "request", - scope: ["personal-chats"], - color: "#5fc48d", - title: I18n.t('request_title'), - description: I18n.t('request_description'), - params: paramsRequest, - handler: function (params, context) { - var val = params["amount"].replace(",", "."); - - return { - event: "request", - request: { - command: "send", - params: { - amount: val - }, - prefill: [val] - } - }; - }, - preview: function (params, context) { - var row = status.components.text( - {}, - I18n.t('request_requesting') + " " - + status.localizeNumber(params.amount, context.delimiter, context.separator) - + " ETH" - ); - return { - markup: status.components.view( - { - style: { - flexDirection: "column" - } - }, - [row] - ) - }; - }, - shortPreview: function (params, context) { - return { - markup: status.components.text( - {}, - I18n.t('request_requesting') + " " - + status.localizeNumber(params.amount, context.delimiter, context.separator) - + " ETH" - ) - }; - }, - validator: function (params) { - if (!params["bot-db"]) { - params["bot-db"] = {}; - } - - if (!params["amount"]) { - return { - markup: status.components.validationMessage( - I18n.t('validation_title'), - I18n.t('validation_amount_specified') - ) - }; - } - - var amount = params.amount.replace(",", "."); - var amountSplitted = amount.split("."); - if (amountSplitted.length === 2 && amountSplitted[1].length > 18) { - return { - markup: status.components.validationMessage( - I18n.t('validation_title'), - I18n.t('validation_amount_is_too_small') - ) - }; - } - - if (isNaN(parseFloat(params.amount.replace(",", ".")))) { - return { - markup: status.components.validationMessage( - I18n.t('validation_title'), - I18n.t('validation_invalid_number') - ) - }; - } - - try { - var val = web3.toWei(amount, "ether"); - if (val < 0) { - throw new Error(); - } - } catch (err) { - return { - markup: status.components.validationMessage( - I18n.t('validation_title'), - I18n.t('validation_invalid_number') - ) - }; - } - } -}); diff --git a/resources/js/bots/transactor_personal/translations.js b/resources/js/bots/transactor_personal/translations.js deleted file mode 100644 index 4e72ce27fb..0000000000 --- a/resources/js/bots/transactor_personal/translations.js +++ /dev/null @@ -1,448 +0,0 @@ -I18n.translations = { - en: { - send_title: 'Send transaction', - send_description: 'Send a payment', - send_choose_recipient: 'Choose recipient', - send_specify_amount: 'Specify amount', - send_fee: 'Fee', - send_cheaper: 'Cheaper', - send_faster: 'Faster', - send_explanation: 'This is the most amount of money that might be used to process this transaction. Your transaction will be mined ', - send_explanation_0: 'in a few minutes or more.', - send_explanation_1: 'likely within a few minutes.', - send_explanation_2: 'usually within a minute.', - send_explanation_3: 'probably within 30 seconds.', - send_explanation_4: 'probably within a few seconds.', - send_sending_to: 'to ', - - eth: 'ETH', - - request_title: 'Request ETH', - request_description: 'Request a payment', - request_requesting: 'Requesting ', - request_requesting_from: 'from ', - - validation_title: 'Amount', - validation_amount_specified: 'Amount must be specified', - validation_invalid_number: 'Amount is not valid number', - validation_amount_is_too_small: 'Amount is too precise. The smallest unit you can send is 1 Wei (1x10^-18 ETH)', - validation_insufficient_amount: 'Insufficient funds for gas * price + value (balance ' - }, - ru: { - send_title: 'Отправить транзакцию', - send_description: 'Отправить платеж', - - request_title: 'Запросить ETH', - request_description: 'Запросить платеж', - request_requesting: 'Запрос ', - - validation_title: 'Сумма', - validation_amount_specified: 'Необходимо указать сумму', - validation_invalid_number: 'Сумма не является действительным числом', - validation_amount_is_too_small: 'Сумма излишне точная. Минимальная единица, которую можно отправить — 1 Wei (1x10^-18 ETH)', - validation_insufficient_amount: 'Недостаточно ETH на балансе (' - }, - af: { - send_title: 'Stuur ETH', - send_description: 'Stuur \'n betaling', - - request_title: 'Versoek ETH', - request_description: 'Versoek \'n betaling', - request_requesting: 'Besig met versoek ', - - validation_title: 'Bedrag', - validation_amount_specified: 'Bedrag moet gespesifiseer word', - validation_invalid_number: 'Bedrag is nie \'n geldige syfer nie', - validation_insufficient_amount: 'Nie genoeg ETH in rekening nie (' - }, - ar: { - send_title: 'إرسال ETH', - send_description: 'إرسال مدفوعات', - - request_title: 'طلب ETH', - request_description: 'طلب مدفوعات', - request_requesting: 'مُطَالَبَة ', - - validation_title: 'المبلغ', - validation_amount_specified: 'يجب تحديد المبلغ', - validation_invalid_number: 'المبلغ المحدد غير صحيح', - validation_insufficient_amount: 'لا يوجد ETH كافي بالحساب (' - }, - 'zh-hant': { - send_title: '發送 ETH', - send_description: '發送一筆付款', - - request_title: '請求 ETH', - request_description: '請求一筆付款', - request_requesting: '正在請求 ', - - validation_title: '金額', - validation_amount_specified: 'ي未指定金額', - validation_invalid_number: '金額數字無效', - validation_insufficient_amount: '餘額中 ETH 不足 (' - }, - 'zh-hans': { - send_title: '发送ETH', - send_description: '付款', - - request_title: '请求ETH', - request_description: '要求付款', - request_requesting: '正在请求 ', - - validation_title: '金額', - validation_amount_specified: '必须指定金额', - validation_invalid_number: '金额不是有效数字', - validation_insufficient_amount: 'ETH余额不足 (' - }, - 'zh-yue': { - send_title: '發送ETH', - send_description: '發送付款', - - request_title: '徵求ETH', - request_description: '徵求付款', - request_requesting: '徵求中 ', - - validation_title: '金額', - validation_amount_specified: '必須指定金額', - validation_invalid_number: '指定金額並非有效數字', - validation_insufficient_amount: '沒有足夠ETH餘額 (' - }, - 'zh-wuu': { - send_title: '发送ETH', - send_description: '发送付款', - - request_title: '请求ETH', - request_description: '请求付款', - request_requesting: '请求中 ', - - validation_title: '金额', - validation_amount_specified: '金额必须明确', - validation_invalid_number: '金额不是一个有效数字', - validation_insufficient_amount: 'ETH余额不足 (' - }, - nl: { - send_title: 'Stuur ETH', - send_description: 'Stuur een betaling', - - request_title: 'Vraag ETH aan', - request_description: 'Vraag om een betaling', - request_requesting: 'Wordt aangevraagd ', - - validation_title: 'Bedrag', - validation_amount_specified: 'Bedrag moet worden opgegeven', - validation_invalid_number: 'Bedrag is geen geldig nummer', - validation_insufficient_amount: 'Niet genoeg ETH op saldo (' - }, - fr: { - send_title: 'Envoyer l\'ETH', - send_description: 'Envoyer un paiement', - - request_title: 'Demander l\'ETH', - request_description: 'Demander un paiement', - request_requesting: 'Demande en cours: ', - - validation_title: 'Montant', - validation_amount_specified: 'Le montant doit être spécifié', - validation_invalid_number: 'Le montant n\'est pas un nombre valide', - validation_insufficient_amount: 'Pas assez d\'ETH sur le solde (' - }, - de: { - send_title: 'ETH abschicken', - send_description: 'Zahlung senden', - - request_title: 'ETH anfragen', - request_description: 'Zahlung anfragen', - request_requesting: 'Anfrage: ', - - validation_title: 'Betrag', - validation_amount_specified: 'Betrag muss angegeben werden', - validation_invalid_number: 'Betrag ist keine gültige Zahl', - validation_insufficient_amount: 'Nicht genügend ETH auf dem Konto (' - }, - hi: { - send_title: 'ETH भेजें', - send_description: 'भुगतान भेजें', - - request_title: 'ETH का अनुरोध करें', - request_description: 'भुगतान का अनुरोध करें', - request_requesting: 'अनुरोध किया जा रहा है ', - - validation_title: 'राशि', - validation_amount_specified: 'राशि निर्दिष्ट की जानी चाहिए', - validation_invalid_number: 'राशि वैध संख्या नहीं है', - validation_insufficient_amount: 'बैलेंस पर पर्याप्त ETH नहीं है (' - }, - hu: { - send_title: 'ETH küldése', - send_description: 'Kifizetés küldése', - - request_title: 'ETH igénylése', - request_description: 'Fizetés igénylése', - request_requesting: 'Igénylés ', - - validation_title: 'Összeg', - validation_amount_specified: 'Az összeget meg kell határozni', - validation_invalid_number: 'Az összeg nem egy elfogadott szám', - validation_insufficient_amount: 'Nincs elég ETH a számlán (' - }, - it: { - send_title: 'Invia ETH', - send_description: 'Invia un pagamento', - - request_title: 'Richiedi ETH', - request_description: 'Richiedi un pagamento', - request_requesting: 'Richiesta in corso: ', - - validation_title: 'Ammontare', - validation_amount_specified: 'L\'ammontare deve essere specificato', - validation_invalid_number: 'L\'ammontare non è un numero valido', - validation_insufficient_amount: 'ETH insufficiente sul bilancio (' - }, - ja: { - send_title: 'ETHを送信', - send_description: '支払いを送信', - - request_title: 'ETHをリクエスト', - request_description: '支払いをリクエスト', - request_requesting: 'リクエスト中 ', - - validation_title: '金額', - validation_amount_specified: '金額を特定する必要があります', - validation_invalid_number: '金額は有効な数字ではありません', - validation_insufficient_amount: '残高に十分なETHがありません(' - }, - ko: { - send_title: 'ETH 보내기', - send_description: '지불금 보내기', - - request_title: 'ETH 요청', - request_description: '지불금 요청', - request_requesting: '요청 중 ', - - validation_title: '금액', - validation_amount_specified: '금액을 지정해야 합니다', - validation_invalid_number: '금액이 유효한 숫자가 아닙니다', - validation_insufficient_amount: 'ETH 잔고가 부족합니다 (' - }, - pl: { - send_title: 'Wyślij ETH', - send_description: 'Wyślij płatność', - - request_title: 'Poproś o ETH', - request_description: 'Poproś o płatność', - request_requesting: 'Przesyłanie prośby ', - - validation_title: 'Kwota', - validation_amount_specified: 'Należy określić kwotę', - validation_invalid_number: 'Kwota nie jest prawidłową liczbą', - validation_insufficient_amount: 'Brak wystarczającej liczby ETH na koncie (' - }, - 'pt-br': { - send_title: 'Enviar ETH', - send_description: 'Enviar um pagamento', - - request_title: 'Solicitar ETH', - request_description: 'Solicitar um pagamento', - request_requesting: 'Solicitando ', - - validation_title: 'Quantia', - validation_amount_specified: 'É necessário especificar a quantia', - validation_invalid_number: 'A quantia não é um número válido', - validation_insufficient_amount: 'ETH insuficiente no saldo (' - }, - 'pt-pt': { - send_title: 'Enviar ETH', - send_description: 'Enviar um pagamento', - - request_title: 'Solicitar ETH', - request_description: 'Solicitar um pagamento', - request_requesting: 'A solicitar ', - - validation_title: 'Montante', - validation_amount_specified: 'O montante deve ser especificado', - validation_invalid_number: 'O montante não é um número válido', - validation_insufficient_amount: 'Não há ETH suficiente no saldo (' - }, - ro: { - send_title: 'Trimite ETH', - send_description: 'Trimite o plată', - - request_title: 'Solicită ETH', - request_description: 'Solicită o plată', - request_requesting: 'Se solicită ', - - validation_title: 'Sumă', - validation_amount_specified: 'Trebuie menționată o sumă', - validation_invalid_number: 'Suma nu are forma unui număr valid', - validation_insufficient_amount: 'Sold ETH insuficient (' - }, - sl: { - send_title: 'Pošlji ETH', - send_description: 'Pošlji plačilo', - - request_title: 'Zahtevaj ETH', - request_description: 'Zahtevaj plačilo', - request_requesting: 'Zahtevam ', - - validation_title: 'Vsota', - validation_amount_specified: 'Vsota mora biti izrecno navedena', - validation_invalid_number: 'Vsota ni veljavna številka', - validation_insufficient_amount: 'Stanje ETH na računu je prenizko (' - }, - es: { - send_title: 'Enviar ETH ', - send_description: 'Enviar un pago', - - request_title: 'Solicitar ETH', - request_description: 'Solicitar un pago', - request_requesting: 'Solicitando ', - - validation_title: 'Cantidad', - validation_amount_specified: 'Hay que especificar la cantidad', - validation_invalid_number: 'La cantidad no es un número válido', - validation_insufficient_amount: 'No hay suficiente ETH en conjunto (' - }, - 'es-ar': { - send_title: 'Enviar ETH', - send_description: 'Enviar un pago', - - request_title: 'Solicitar ETH', - request_description: 'Solicitar un pago', - request_requesting: 'Solicitando ', - - validation_title: 'Monto', - validation_amount_specified: 'Debes especificar el monto', - validation_invalid_number: 'El monto no es un número válido', - validation_insufficient_amount: 'No tienes suficiente ETH en tu saldo (' - }, - sw: { - send_title: 'Tuma ETH', - send_description: 'Tuma malipo', - - request_title: 'Omba ETH', - request_description: 'Omba malipo', - request_requesting: 'Kuomba ', - - validation_title: 'Kiasi', - validation_amount_specified: 'Kiasi lazima kifafanuliwe', - validation_invalid_number: 'Kiasi si nambari halali', - validation_insufficient_amount: 'ETH haitoshi kwenye salio (' - }, - sv: { - send_title: 'Skicka ETH', - send_description: 'Skicka en betalning', - - request_title: 'Begär ETH', - request_description: 'Begär en betalning', - request_requesting: 'Begär ', - - validation_title: 'Belopp', - validation_amount_specified: 'Beloppet måste anges', - validation_invalid_number: 'Beloppet är inte ett giltigt nummer', - validation_insufficient_amount: 'Inte tillräcklig ETH på balansen (' - }, - 'fr-ch': { - send_title: 'Envoyer des ETH', - send_description: 'Envoyer un paiement', - - request_title: 'Demander des ETH', - request_description: 'Demander un paiement', - request_requesting: 'Demande ', - - validation_title: 'Montant', - validation_amount_specified: 'Le montant doit être spécifié', - validation_invalid_number: 'Le montant n\'est pas un nombre valable', - validation_insufficient_amount: 'Pas assez d\'ETH sur le solde (' - }, - 'de-ch': { - send_title: 'Sende ETH', - send_description: 'Sende eine Zahlung', - - request_title: 'Fordere ETH an', - request_description: 'Eine Zahlung anfordern', - request_requesting: 'Anfordern ', - - validation_title: 'Betrag', - validation_amount_specified: 'Der Betrag muss angegeben werden', - validation_invalid_number: 'Der Betrag ist nicht gültig', - validation_insufficient_amount: 'Nicht genug ETH vorhanden (' - }, - 'it-ch': { - send_title: 'Invia ETH', - send_description: 'Invia un pagamento', - - request_title: 'Richiedi ETH', - request_description: 'Richiedi un pagamento', - request_requesting: 'Richiesta in corso: ', - - validation_title: 'Importo', - validation_amount_specified: 'Specificare l\'importo', - validation_invalid_number: 'Importo inserito non valido', - validation_insufficient_amount: 'Saldo ETH non sufficiente (' - }, - th: { - send_title: 'ส่ง ETH', - send_description: 'ส่งการชำระเงิน', - - request_title: 'ร้องขอ ETH', - request_description: 'ร้องขอการชำระเงิน', - request_requesting: 'กำลังร้องขอ ', - - validation_title: 'จำนวน', - validation_amount_specified: 'จำเป็นต้องระบุจำนวน', - validation_invalid_number: 'จำนวนไม่ใช่หมายเลขที่ถูกต้อง', - validation_insufficient_amount: 'มี ETH ไม่เพียงพอในยอดคงเหลือ (' - }, - tr: { - send_title: 'ETH gönder', - send_description: 'Bir ödeme gönder', - - request_title: 'ETH iste', - request_description: 'Bir ödeme iste', - request_requesting: 'İsteniyor ', - - validation_title: 'Miktar', - validation_amount_specified: 'Miktar belirtilmelidir', - validation_invalid_number: 'Miktar geçerli bir sayı değil', - validation_insufficient_amount: 'Yeterli ETH bakiyesi yok (' - }, - uk: { - send_title: 'Надіслати ETH', - send_description: 'Надіслати платіж', - - request_title: 'Запит ETH', - request_description: 'Запит платежу', - request_requesting: 'Запит ', - - validation_title: 'Сума', - validation_amount_specified: 'Сума повинна бути вказана', - validation_invalid_number: 'Сума не дійсне число', - validation_insufficient_amount: 'Не вистачає ETH на балансі (' - }, - ur: { - send_title: 'ETH بھیجیں', - send_description: 'ادائیگی کریں', - - request_title: 'ETH کی درخواست دیں', - request_description: 'ادائیگی کی درخواست دیں', - request_requesting: 'درخواست کی جارہی ہے ', - - validation_title: 'رقم', - validation_amount_specified: 'رقم درج کی جانی چاہیے۔ ', - validation_invalid_number: 'رقیم درست ہندسے نہیں ہیں', - validation_insufficient_amount: 'ETH میں کافی بیلنس نہیں ہے (' - }, - vi: { - send_title: 'Gửi ETH', - send_description: 'Gửi một khoản thanh toán', - - request_title: 'Yêu cầu ETH', - request_description: 'Yêu cầu một khoản thanh toán', - request_requesting: 'Đang yêu cầu ', - - validation_title: 'Số tiền', - validation_amount_specified: 'Số tiền phải được xác định', - validation_invalid_number: 'Số tiền không phải là một số hợp lệ', - validation_insufficient_amount: 'Không đủ ETH trong số dư (' - } -}; diff --git a/resources/js/status.js b/resources/js/status.js index 4ef618ac57..2fcc1ae980 100644 --- a/resources/js/status.js +++ b/resources/js/status.js @@ -7,13 +7,15 @@ var _status_catalog = { status = {}; function scopeToBitMask(scope) { - // this function transforms scopes map to a single integer by generating a bit mask - // this similar method also exists on clojure side: status-im.chat.models.commands/scope->bit-mask - return (scope["global?"] ? 1 : 0) | - (scope["registered-only?"] ? 2 : 0) | - (scope["personal-chats?"] ? 4 : 0) | - (scope["group-chats?"] ? 8 : 0) | - (scope["can-use-for-dapps?"] ? 16 : 0); + // this function transforms scopes array to a single integer by generating a bit mask + return ((scope != null && scope.indexOf("global") > -1) ? 1 : 0) | + ((scope != null && scope.indexOf("personal-chats") > -1) ? 2 : 0) | + ((scope != null && scope.indexOf("group-chats") > -1) ? 4 : 0) | + ((scope != null && scope.indexOf("anonymous") > -1) ? 8 : 0) | + ((scope != null && scope.indexOf("registered") > -1) ? 16 : 0) | + ((scope != null && scope.indexOf("dapps") > -1) ? 32 : 0) | + ((scope != null && scope.indexOf("humans") > -1) ? 64 : 0) | + ((scope != null && scope.indexOf("public-chats") > -1) ? 128 : 0); } function Command() { @@ -22,7 +24,7 @@ function Response() { } Command.prototype.addToCatalog = function () { - _status_catalog.commands[[this.name, this.scope.bitmask]] = this; + _status_catalog.commands[[this.name, this["scope-bitmask"]]] = this; }; Command.prototype.param = function (parameter) { @@ -53,13 +55,8 @@ Command.prototype.create = function (com) { this["hide-send-button"] = com.hideSendButton; // scopes - this["scope"] = {}; - this["scope"]["global?"] = com["scope"] != null && com["scope"].indexOf("global") > -1; - this["scope"]["registered-only?"] = com["scope"] != null && com["scope"].indexOf("registered-only") > -1; - this["scope"]["personal-chats?"] = com["scope"] == null || com["scope"].indexOf("personal-chats") > -1; - this["scope"]["group-chats?"] = com["scope"] == null || com["scope"].indexOf("group-chats") > -1; - this["scope"]["can-use-for-dapps?"] = com["scope"] == null || com["scope"].indexOf("can-use-for-dapps") > -1; - this["scope"]["bitmask"] = scopeToBitMask(this["scope"]); + this.scope = com.scope; + this["scope-bitmask"] = scopeToBitMask(this["scope"]); this.addToCatalog(); @@ -69,7 +66,7 @@ Command.prototype.create = function (com) { Response.prototype = Object.create(Command.prototype); Response.prototype.addToCatalog = function () { - _status_catalog.responses[[this.name, 0]] = this; + _status_catalog.responses[[this.name, this["scope-bitmask"]]] = this; }; Response.prototype.onReceiveResponse = function (handler) { this.onReceive = handler; diff --git a/src/status_im/bots/events.cljs b/src/status_im/bots/events.cljs index e343abb8e7..91d82a42dd 100644 --- a/src/status_im/bots/events.cljs +++ b/src/status_im/bots/events.cljs @@ -14,8 +14,8 @@ sub-params)) (defn- check-subscriptions-fx - [{:keys [bot-db bot-subscriptions] :as app-db} {:keys [bot path]}] - (when-let [subscriptions (and bot (get-in bot-subscriptions (concat [bot] [path])))] + [{:keys [bot-db] :contacts/keys [contacts] :as app-db} {:keys [bot path]}] + (when-let [subscriptions (and bot (get-in contacts (concat [bot :subscriptions] [path])))] {:call-jail-function-n (for [[sub-name sub-params] subscriptions] {:chat-id bot @@ -47,7 +47,7 @@ (def ^:private keywordize-vector (partial mapv keyword)) -(defn- transform-bot-subscriptions +(defn transform-bot-subscriptions "Transforms bot subscriptions as returned from jail in the following format: `{:calculatedFee {:subscriptions {:value [\"sliderValue\"] @@ -78,14 +78,6 @@ {} bot-subscriptions)) -(defn add-active-bot-subscriptions - "Add subscriptions for selected bot identities into app-db" - [app-db bot-identities] - (assoc app-db :bot-subscriptions (-> app-db - :contacts/contacts - (select-keys bot-identities) - (utils/map-values (comp transform-bot-subscriptions :subscriptions))))) - (defn calculated-subscription [db {:keys [bot path] {:keys [error result]} :result}] diff --git a/src/status_im/bots/subs.cljs b/src/status_im/bots/subs.cljs index 1389b28644..83bbbdd1db 100644 --- a/src/status_im/bots/subs.cljs +++ b/src/status_im/bots/subs.cljs @@ -3,11 +3,14 @@ [status-im.chat.models.input :as input-model])) (re-frame/reg-sub - :current-bot-db + :bot-db (fn [db] - (let [current-chat-id (re-frame/subscribe [:get-current-chat-id]) - command-owner (-> db - (input-model/selected-chat-command @current-chat-id) - :command - :owner-id)] - [command-owner (get-in db [:bot-db command-owner])]))) + (:bot-db db))) + +(re-frame/reg-sub + :current-bot-db + :<- [:bot-db] + :<- [:selected-chat-command] + (fn [[bot-db command]] + (let [command-owner (get-in command [:command :owner-id])] + [command-owner (get bot-db command-owner)]))) diff --git a/src/status_im/chat/constants.cljs b/src/status_im/chat/constants.cljs index 189e257050..422f7f538c 100644 --- a/src/status_im/chat/constants.cljs +++ b/src/status_im/chat/constants.cljs @@ -15,3 +15,9 @@ (def signing-phrase-message-id "signing-phrase-message") (def intro-status-message-id "intro-status") (def intro-message1-id "intro-message1") + +;; TODO(janherich): figure out something better then this +(def browse-command-ref ["browse" :command 247 "browse"]) +(def send-command-ref ["transactor" :command 83 "send"]) +(def request-command-ref ["transactor" :command 83 "request"]) +(def phone-command-ref ["console" :command 50 "phone"]) diff --git a/src/status_im/chat/events.cljs b/src/status_im/chat/events.cljs index a69486b5a4..500d73dd34 100644 --- a/src/status_im/chat/events.cljs +++ b/src/status_im/chat/events.cljs @@ -15,8 +15,9 @@ [status-im.protocol.core :as protocol] [status-im.constants :as const] [status-im.ui.components.list-selection :as list-selection] - status-im.chat.events.input + [status-im.chat.events.input :as input-events] status-im.chat.events.commands + status-im.chat.events.requests status-im.chat.events.animation status-im.chat.events.receive-message status-im.chat.events.sign-up @@ -157,8 +158,8 @@ message)) messages))))) -(defn- init-console-chat - [{:keys [chats] :accounts/keys [current-account-id] :as db} existing-account?] +(defn init-console-chat + [{:keys [chats] :accounts/keys [current-account-id] :as db}] (if (chats const/console-chat-id) {:db db} (cond-> {:db (-> db @@ -169,53 +170,49 @@ :save-all-contacts [sign-up/console-contact]} (not current-account-id) - (update :dispatch-n concat sign-up/intro-events) - - existing-account? - (update :dispatch-n concat sign-up/start-signup-events)))) + (update :dispatch-n concat sign-up/intro-events)))) (handlers/register-handler-fx :init-console-chat (fn [{:keys [db]} _] - (init-console-chat db false))) + (init-console-chat db))) (handlers/register-handler-fx :initialize-chats [(re-frame/inject-cofx :all-stored-chats) (re-frame/inject-cofx :stored-unviewed-messages) + (re-frame/inject-cofx :get-stored-unanswered-requests) (re-frame/inject-cofx :get-last-stored-message) (re-frame/inject-cofx :get-message-previews)] - (fn [{:keys [db all-stored-chats stored-unviewed-messages get-last-stored-message message-previews]} _] - (let [{:accounts/keys [account-creation?]} db + (fn [{:keys [db + all-stored-chats + stored-unanswered-requests + stored-unviewed-messages + get-last-stored-message + message-previews]} _] + (let [{:accounts/keys [account-creation?] :contacts/keys [contacts]} db new-db (unviewed-messages-model/load-unviewed-messages db stored-unviewed-messages) event [:load-default-contacts!]] (if account-creation? {:db new-db - :dispatch-n [event]} - (let [chats (->> all-stored-chats + :dispatch event} + (let [chat->message-id->request (reduce (fn [acc {:keys [chat-id message-id] :as request}] + (assoc-in acc [chat-id message-id] request)) + {} + stored-unanswered-requests) + chats (->> all-stored-chats (map (fn [{:keys [chat-id] :as chat}] - [chat-id (assoc chat :last-message (get-last-stored-message chat-id))])) + [chat-id (assoc chat + :last-message (get-last-stored-message chat-id) + :requests (get chat->message-id->request chat-id))])) (into {}))] (-> new-db (assoc-in [:message-data :preview] message-previews) (assoc :handler-data (handler-data/get-all)) (assoc :chats chats) - (init-console-chat true) + init-console-chat (update :dispatch-n conj event))))))) -(handlers/register-handler-fx - :reload-chats - [(re-frame/inject-cofx :all-stored-chats) (re-frame/inject-cofx :get-last-stored-message)] - (fn [{:keys [db all-stored-chats get-last-stored-message]} _] - (let [updated-chats (->> all-stored-chats - (map (fn [{:keys [chat-id] :as chat}] - (let [prev-chat (get (:chats db) chat-id) - updated-chat (assoc chat :last-message (get-last-stored-message chat-id))] - [chat-id (merge prev-chat updated-chat)]))) - (into {}))] - (-> (assoc db :chats updated-chats) - (init-console-chat true))))) - (handlers/register-handler-fx :send-seen! [re-frame/trim-v] @@ -260,64 +257,35 @@ (handlers/register-handler-fx :browse-link-from-message - (fn [{{:keys [global-commands]} :db} [_ link]] - {:browse [(:browse global-commands) link]})) - -(handlers/register-handler-fx - :init-chat - [(re-frame/inject-cofx :get-stored-messages)] - (fn [{:keys [db get-stored-messages]} _] - (let [current-chat-id (:current-chat-id db)] - {:db (assoc-in db [:chats current-chat-id :messages] (get-stored-messages current-chat-id)) - ;; TODO(janherich): make this dispatch into fn call once commands loading is refactored - :dispatch [:load-commands! current-chat-id]}))) - -(defn- jail-init-callback - [{:keys [db] :as fx} chat-id] - (let [bot-url (get-in db [:contacts/contacts chat-id :bot-url]) - was-opened? (get-in db [:chats chat-id :was-opened?])] - (if (and (not was-opened?) bot-url) - (assoc fx :call-jail-function {:chat-id chat-id - :function :init - :context {:from (:accounts/current-account-id db)}}) - fx))) + (fn [{{:contacts/keys [contacts]} :db} [_ link]] + {:browse [(get-in contacts chat-const/browse-command-ref) link]})) (defn preload-chat-data "Takes coeffects map and chat-id, returns effects necessary when navigating to chat" [{:keys [db get-stored-messages]} chat-id] (let [messages (get-in db [:chats chat-id :messages]) chat-loaded-event (get-in db [:chats chat-id :chat-loaded-event]) - commands-loaded? (get-in db [:contacts/contacts chat-id :commands-loaded?])] + jail-loaded? (get-in db [:contacts/contacts chat-id :jail-loaded?])] (cond-> {:db (-> db (assoc :current-chat-id chat-id) (assoc-in [:chats chat-id :was-opened?] true) (model/set-chat-ui-props {:validation-messages nil}) - (update-in [:chats chat-id] dissoc :chat-loaded-event)) - :dispatch-n [[:load-requests! chat-id]]} - (not commands-loaded?) - (update :dispatch-n conj [:load-commands! chat-id #(re-frame/dispatch [::jail-init-callback chat-id])]) + (update-in [:chats chat-id] dissoc :chat-loaded-event))} - commands-loaded? - (jail-init-callback chat-id) - ;; TODO(janherich): what's the purpose of the second term in AND ? - (and (seq messages) - (not= (count messages) 1)) + (empty? messages) (assoc-in [:db :chats chat-id :messages] (get-stored-messages chat-id)) chat-loaded-event - (update :dispatch-n conj chat-loaded-event)))) - -(handlers/register-handler-db - :add-chat-loaded-event - [re-frame/trim-v] - (fn [db [chat-id event]] - (assoc-in db [:chats chat-id :chat-loaded-event] event))) + (assoc :dispatch chat-loaded-event)))) (handlers/register-handler-fx - ::jail-init-callback + :add-chat-loaded-event [re-frame/trim-v] - (fn [{:keys [db]} [chat-id]] - (jail-init-callback {:db db} chat-id))) + (fn [{:keys [db] :as cofx} [chat-id event]] + (if (get (:chats db) chat-id) + {:db (assoc-in db [:chats chat-id :chat-loaded-event] event)} + (-> (model/add-chat cofx chat-id) ; chat not created yet, we have to create it + (assoc-in [:db :chats chat-id :chat-loaded-event] event))))) ;; TODO(janherich): remove this unnecessary event in the future (only model function `add-chat` will stay) (handlers/register-handler-fx @@ -326,14 +294,16 @@ (fn [cofx [chat-id chat-props]] (model/add-chat cofx chat-id chat-props))) -(defn- navigate-to-chat - [cofx chat-id navigation-replace?] - (let [nav-fn (if navigation-replace? - #(navigation/navigate-to % :chat) - #(navigation/replace-view % :chat))] - (-> cofx - (preload-chat-data chat-id) - (update :db nav-fn)))) +(defn navigate-to-chat + "Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data" + ([cofx chat-id] + (navigate-to-chat cofx chat-id false)) + ([cofx chat-id navigation-replace?] + (let [nav-fn (if navigation-replace? + #(navigation/navigate-to % :chat) + #(navigation/replace-view % :chat))] + (-> (preload-chat-data cofx chat-id) + (update :db nav-fn))))) (handlers/register-handler-fx :navigate-to-chat @@ -370,3 +340,12 @@ (fn [cofx [chat]] (model/upsert-chat cofx chat))) +(handlers/register-handler-fx + :check-and-open-dapp! + (fn [{{:keys [current-chat-id] + :contacts/keys [contacts] :as db} :db} _] + (when-let [dapp-url (get-in contacts [current-chat-id :dapp-url])] + (-> db + (input-events/select-chat-input-command + (assoc (get-in contacts chat-const/browse-command-ref) :prefill [dapp-url]) nil true) + (assoc :dispatch [:send-current-message]))))) diff --git a/src/status_im/chat/events/commands.cljs b/src/status_im/chat/events/commands.cljs index 322c166e2c..9643cf79ff 100644 --- a/src/status_im/chat/events/commands.cljs +++ b/src/status_im/chat/events/commands.cljs @@ -5,8 +5,7 @@ [taoensso.timbre :as log] [status-im.utils.handlers :as handlers] [status-im.i18n :as i18n] - [status-im.utils.platform :as platform] - [status-im.chat.models.commands :as commands-model])) + [status-im.utils.platform :as platform])) ;;;; Helper fns @@ -26,24 +25,18 @@ [db {{command-name :command content-command-name :content-command - :keys [content-command-scope scope params type bot]} :content + :keys [content-command-scope-bitmask scope-bitmask params type bot]} :content :keys [chat-id jail-id group-id] :as message} data-type] (let [{:keys [chats] :accounts/keys [current-account-id] :contacts/keys [contacts]} db jail-id (or bot jail-id chat-id) - jail-command-name (or content-command-name command-name) - ;; here we're trying to use the default scope if there is no other scope provided - default-command-scope (-> (get-in contacts [jail-id :commands (keyword jail-command-name)]) - first - :scope)] - (if (get-in contacts [jail-id :commands-loaded?]) + jail-command-name (or content-command-name command-name)] + (if (get-in contacts [jail-id :jail-loaded?]) (let [path [(if (= :response (keyword type)) :responses :commands) [jail-command-name - (commands-model/scope->bit-mask (or scope - content-command-scope - default-command-scope))] + (or scope-bitmask content-command-scope-bitmask)] data-type] to (get-in contacts [chat-id :address]) jail-params {:parameters params @@ -54,9 +47,8 @@ :callback-events-creator (fn [jail-response] [[::jail-command-data-response jail-response message data-type]])}}) - {:dispatch-n [[:add-commands-loading-callback jail-id - #(re-frame/dispatch [:request-command-message-data message data-type])] - [:load-commands! jail-id]]}))) + {:db (update-in db [:contacts/contacts jail-id :jail-loaded-events] + conj [:request-command-message-data message data-type])}))) ;;;; Handlers @@ -76,7 +68,7 @@ (handlers/register-handler-fx :request-command-message-data - [re-frame/trim-v] + [re-frame/trim-v (re-frame/inject-cofx :get-local-storage-data)] (fn [{:keys [db]} [message data-type]] (request-command-message-data db message data-type))) diff --git a/src/status_im/chat/events/console.cljs b/src/status_im/chat/events/console.cljs index 8261e0cfc5..14037fddbd 100644 --- a/src/status_im/chat/events/console.cljs +++ b/src/status_im/chat/events/console.cljs @@ -60,12 +60,8 @@ (accounts-events/create-account db (:password params))) "phone" - (fn [{:keys [db]} {:keys [params id]}] - (-> db - (sign-up-events/sign-up (:phone params) id) - (as-> fx - (assoc fx :dispatch-n [(:dispatch fx)])) - (dissoc :dispatch))) + (fn [{:keys [db]} {:keys [params id]}] + (sign-up-events/sign-up db (:phone params) id)) "confirmation-code" (fn [{:keys [db]} {:keys [params id]}] diff --git a/src/status_im/chat/events/input.cljs b/src/status_im/chat/events/input.cljs index 254a181f13..3e5891eee6 100644 --- a/src/status_im/chat/events/input.cljs +++ b/src/status_im/chat/events/input.cljs @@ -46,33 +46,6 @@ ;;;; Helper functions -(defn- extract-command-request-owners [commands requests] - [commands requests] - (into #{} (keep :owner-id) (concat commands requests))) - -(defn update-suggestions - "Update suggestions for current chat input, takes db as the only argument - and returns new db with up-to date suggestions" - [{:keys [chats current-chat-id] :as db}] - (let [chat-text (str/trim (or (get-in chats [current-chat-id :input-text]) "")) - requests (->> (commands-model/get-possible-requests db) - (remove (fn [{:keys [type]}] - (= type :grant-permissions)))) - commands (commands-model/commands-for-chat db current-chat-id) - {:keys [dapp?]} (get-in db [:contacts/contacts current-chat-id]) - ;; TODO(janherich) surely there is a better place to merge in possible commands/request/subscriptions into current chat - ;; then in `:update-suggestions` which is called whenever commands for chat are loaded, chat view is opened - ;; or new message is received from network - it's unnecessary to call it as a response to last two events - new-db (cond-> (-> db - (update-in [:chats current-chat-id] merge {:possible-commands commands - :possible-requests requests}) - (bots-events/add-active-bot-subscriptions (extract-command-request-owners - commands requests))) - (and dapp? - (str/blank? chat-text)) - (assoc-in [:chats current-chat-id :parameter-boxes :message] nil))] - new-db)) - (defn set-chat-input-text "Set input text for current-chat and updates suggestions relevant to current input. Takes db, input text and `:append?` flag as arguments and returns new db. @@ -141,24 +114,19 @@ (input-model/join-command-args command-args) (when (and move-to-next? (= index (dec (count command-args)))) - const/spacing-char))] - (-> db - (set-chat-input-text input-text) - update-suggestions))))) + const/spacing-char))] + (set-chat-input-text db input-text))))) (defn load-chat-parameter-box "Returns fx for loading chat parameter box for active chat" [{:keys [current-chat-id bot-db] :accounts/keys [current-account-id] :as db} - {:keys [name scope type bot owner-id] :as command}] + {:keys [name scope-bitmask type bot owner-id] :as command}] (let [parameter-index (input-model/argument-position db)] (when (and command (> parameter-index -1)) (let [data (get-in db [:local-storage current-chat-id]) bot-db (get bot-db owner-id) path [(if (= :command type) :commands :responses) - [name - (if (= :command type) - (commands-model/scope->bit-mask scope) - 0)] + [name scope-bitmask] :params parameter-index :suggestions] @@ -301,11 +269,10 @@ request-data {:message-id message-id :chat-id chat-id :jail-id (or owner-id jail-id) - :content {:command (:name command) - :scope (when-not (:to-message-id metadata) - (:scope command)) - :params params - :type (:type command)} + :content {:command (:name command) + :scope-bitmask (:scope-bitmask command) + :params params + :type (:type command)} :on-requested (fn [jail-response] (event-after-creator command-message jail-response))}] (commands-events/request-command-message-data db request-data data-type))) @@ -395,11 +362,6 @@ (when-let [cmp-ref (get-in chat-ui-props [current-chat-id ref])] {::blur-rn-component cmp-ref}))) -(handlers/register-handler-db - :update-suggestions - (fn [db _] - (update-suggestions db))) - (handlers/register-handler-fx :load-chat-parameter-box [re-frame/trim-v] @@ -449,8 +411,7 @@ clear-seq-arguments (set-chat-input-metadata nil) (set-chat-input-text nil) - (model/set-chat-ui-props {:sending-in-progress? false}) - update-suggestions) + (model/set-chat-ui-props {:sending-in-progress? false})) ;; TODO: refactor send-message.cljs to use atomic pure handlers and get rid of this dispatch :dispatch [:check-commands-handlers! {:message (get-in db [:chats current-chat-id :input-text]) :command command-message @@ -504,8 +465,7 @@ {:db db} {:db (-> db (set-chat-input-metadata nil) - (set-chat-input-text nil) - update-suggestions) + (set-chat-input-text nil)) ;; TODO: refactor send-message.cljs to use atomic pure handlers and get rid of this dispatch :dispatch [:prepare-message {:message input-text :chat-id current-chat-id @@ -592,5 +552,4 @@ (fn [db _] (-> db (model/toggle-chat-ui-prop :show-suggestions?) - (model/set-chat-ui-props {:validation-messages nil}) - update-suggestions))) + (model/set-chat-ui-props {:validation-messages nil})))) diff --git a/src/status_im/chat/events/receive_message.cljs b/src/status_im/chat/events/receive_message.cljs index d8cb7d89cc..a5b1cd0e25 100644 --- a/src/status_im/chat/events/receive_message.cljs +++ b/src/status_im/chat/events/receive_message.cljs @@ -7,7 +7,9 @@ [status-im.constants :as const] [status-im.chat.utils :as chat-utils] [status-im.chat.models :as model] + [status-im.chat.models.commands :as commands-model] [status-im.chat.models.unviewed-messages :as unviewed-messages-model] + [status-im.chat.events.requests :as requests-events] [status-im.data-store.chats :as chat-store] [status-im.data-store.messages :as msg-store])) @@ -28,53 +30,69 @@ (fn [cofx] (assoc cofx :get-last-clock-value msg-store/get-last-clock-value))) -(defn- get-current-identity +(defn- get-current-account [{:accounts/keys [accounts current-account-id]}] - (get-in accounts [current-account-id :public-key])) + (get accounts current-account-id)) + +(defn- lookup-response-ref + [access-scope->commands-responses account chat contacts response-name] + (let [available-commands-responses (commands-model/commands-responses :response + access-scope->commands-responses + account + chat + contacts)] + (:ref (get available-commands-responses response-name)))) (defn add-message [{:keys [db message-exists? get-last-stored-message pop-up-chat? get-last-clock-value now random-id] :as cofx} - {:keys [from group-id chat-id content-type + {:keys [from group-id chat-id content-type content message-id timestamp clock-value] :as message :or {clock-value 0}}] - (let [chat-identifier (or group-id chat-id from) - current-identity (get-current-identity db)] + (let [{:keys [access-scope->commands-responses] :contacts/keys [contacts]} db + chat-identifier (or group-id chat-id from) + current-account (get-current-account db)] ;; proceed with adding message if message is not already stored in realm, ;; it's not from current user (outgoing message) and it's for relevant chat ;; (either current active chat or new chat not existing yet) (if (and (not (message-exists? message-id)) - (not= from current-identity) + (not= from (:public-key current-account)) (pop-up-chat? chat-identifier)) (let [group-chat? (not (nil? group-id)) - enriched-message (assoc (chat-utils/check-author-direction - (get-last-stored-message chat-identifier) - message) - :chat-id chat-identifier - :timestamp (or timestamp now) - :clock-value (clocks/receive - clock-value - (get-last-clock-value chat-identifier))) + command-request? (= content-type const/content-type-command-request) + command (:command content) fx (model/upsert-chat cofx {:chat-id chat-identifier - :group-chat group-chat?})] + :group-chat group-chat?}) + enriched-message (cond-> (assoc (chat-utils/check-author-direction + (get-last-stored-message chat-identifier) + message) + :chat-id chat-identifier + :timestamp (or timestamp now) + :clock-value (clocks/receive + clock-value + (get-last-clock-value chat-identifier))) + (and command command-request?) + (assoc-in [:content :content-command-ref] + (lookup-response-ref access-scope->commands-responses + current-account + (get-in fx [:db :chats chat-identifier]) + contacts + command)))] (cond-> (-> fx (update :db #(-> % (chat-utils/add-message-to-db chat-identifier chat-identifier enriched-message (:new? enriched-message)) (unviewed-messages-model/add-unviewed-message chat-identifier message-id) - (assoc-in [:chats chat-identifier :last-message] message))) - (assoc :dispatch-n [[:request-command-message-data enriched-message :short-preview]] - :save-message (dissoc enriched-message :new?))) + (assoc-in [:chats chat-identifier :last-message] enriched-message))) + (assoc :save-message (dissoc enriched-message :new?))) - (get-in enriched-message [:content :command]) - (update :dispatch-n conj [:request-command-preview enriched-message]) + command + (update :dispatch-n concat [[:request-command-message-data enriched-message :short-preview] + [:request-command-preview enriched-message]]) - (= (:content-type enriched-message) const/content-type-command-request) - (update :dispatch-n conj [:add-request chat-identifier enriched-message]) - ;; TODO(janherich) this shouldn't be dispatch, but plain function call, refactor after adding requests is refactored - true - (update :dispatch-n conj [:update-suggestions]))) + command-request? + (requests-events/add-request chat-identifier enriched-message))) {:db db}))) (def ^:private receive-interceptors @@ -102,6 +120,6 @@ receive-interceptors (fn [{:keys [db] :as cofx} [chat-id message]] (if (and (:status-node-started? db) - (get-in db [:contacts/contacts chat-id :commands-loaded?])) + (get-in db [:contacts/contacts chat-id :jail-loaded?])) (add-message cofx message) {:dispatch-later [{:ms 400 :dispatch [:received-message-when-commands-loaded chat-id message]}]}))) diff --git a/src/status_im/chat/events/requests.cljs b/src/status_im/chat/events/requests.cljs new file mode 100644 index 0000000000..72d64aeb98 --- /dev/null +++ b/src/status_im/chat/events/requests.cljs @@ -0,0 +1,43 @@ +(ns status-im.chat.events.requests + (:require [re-frame.core :as re-frame] + [status-im.utils.handlers :as handlers] + [status-im.data-store.requests :as requests-store])) + +;; Coeffects + +(re-frame/reg-cofx + :get-stored-unanswered-requests + (fn [cofx _] + (assoc cofx :stored-unanswered-requests (requests-store/get-all-unanswered)))) + +;; Effects +(re-frame/reg-fx + ::request-answered + (fn [{:keys [chat-id message-id]}] + (requests-store/mark-as-answered chat-id message-id))) + +(re-frame/reg-fx + ::save-request + (fn [request] + (requests-store/save request))) + +;; Handlers + +(handlers/register-handler-fx + :request-answered + [re-frame/trim-v] + (fn [{:keys [db]} [chat-id message-id]] + {:db (update-in db [:chats chat-id :requests] dissoc message-id) + ::request-answered {:chat-id chat-id + :message-id message-id}})) + +(defn add-request + "Takes fx, chat-id and message, updates fx with necessary data for adding new request" + [fx chat-id {:keys [message-id content]}] + (let [request {:chat-id chat-id + :message-id message-id + :response (:command content) + :status "open"}] + (-> fx + (assoc-in [:db :chats chat-id :requests message-id] request) + (assoc ::save-request request)))) diff --git a/src/status_im/chat/events/sign_up.cljs b/src/status_im/chat/events/sign_up.cljs index 81af62f948..36b612e709 100644 --- a/src/status_im/chat/events/sign_up.cljs +++ b/src/status_im/chat/events/sign_up.cljs @@ -140,8 +140,7 @@ [re-frame/trim-v (re-frame/inject-cofx :random-id)] (fn [{:keys [db random-id now] :as cofx} [contacts]] (-> {:db db} - (accounts-events/account-update {:signed-up? true - :last-updated now}) + (accounts-events/account-update {:signed-up? true :last-updated now}) (assoc :dispatch (sign-up/contacts-synchronised-event random-id))))) (handlers/register-handler-fx diff --git a/src/status_im/chat/handlers.cljs b/src/status_im/chat/handlers.cljs index 9747bae312..d06c26e7a8 100644 --- a/src/status_im/chat/handlers.cljs +++ b/src/status_im/chat/handlers.cljs @@ -15,8 +15,7 @@ console-chat-id]] [status-im.utils.random :as random] [status-im.utils.handlers :refer [register-handler register-handler-fx] :as u] - status-im.chat.events - status-im.chat.handlers.requests + status-im.chat.events status-im.chat.handlers.send-message status-im.chat.handlers.webview-bridge)) @@ -73,21 +72,6 @@ remove-pending-messages! delete-chat!)) -(register-handler - :check-and-open-dapp! - (u/side-effect! - (fn [{:keys [current-chat-id global-commands] - :contacts/keys [contacts]}] - (let [dapp-url (get-in contacts [current-chat-id :dapp-url])] - (when dapp-url - (am/go - (dispatch [:select-chat-input-command - (assoc (first (:browse global-commands)) :prefill [dapp-url]) - nil - true]) - (a/ db - (update-in [:chats chat-id :requests] conj request') - (assoc :new-request request)))) - -(defn load-requests! - [{:keys [current-chat-id] :as db} [_ chat-id]] - (let [chat-id' (or chat-id current-chat-id) - ;; todo maybe limit is needed - requests (requests/get-available-by-chat-id chat-id') - requests' (map #(update % :type keyword) requests)] - (assoc-in db [:chats chat-id' :requests] requests'))) - -(defn mark-request-as-answered! - [_ [_ chat-id message-id]] - (requests/mark-as-answered chat-id message-id)) - -(register-handler :add-request - (after store-request!) - add-request) - -(register-handler :load-requests! load-requests!) - -(register-handler :request-answered! - (after (fn [_ [_ chat-id]] - (dispatch [:load-requests! chat-id]))) - (u/side-effect! mark-request-as-answered!)) diff --git a/src/status_im/chat/handlers/send_message.cljs b/src/status_im/chat/handlers/send_message.cljs index f29e4e8312..572ef66504 100644 --- a/src/status_im/chat/handlers/send_message.cljs +++ b/src/status_im/chat/handlers/send_message.cljs @@ -38,7 +38,8 @@ content' (assoc content :handler-data handler-data :type (name (:type command)) :content-command (:name command) - :content-command-scope (:scope command) + :content-command-scope-bitmask (:scope-bitmask command) + :content-command-ref (:ref command) :bot (or (:bot command) (:owner-id command)))] {:message-id id @@ -126,7 +127,7 @@ (u/side-effect! (fn [_ [_ {{:keys [to-message]} :command :keys [chat-id]}]] (when to-message - (dispatch [:request-answered! chat-id to-message]))))) + (dispatch [:request-answered chat-id to-message]))))) (register-handler ::invoke-command-handlers! (u/side-effect! @@ -138,7 +139,7 @@ id]} :command :keys [chat-id address] :as orig-params}]] - (let [{:keys [type name scope bot owner-id]} command + (let [{:keys [type name scope-bitmask bot owner-id]} command handler-type (if (= :command type) :commands :responses) to (get-in contacts [chat-id :address]) identity (or owner-id bot chat-id) @@ -151,19 +152,16 @@ :current-account (get accounts current-account-id) :message-id id} (:async-handler command) - (assoc :orig-params orig-params))}] - (dispatch - [:check-and-load-commands! - identity - #(status/call-jail - {:jail-id identity - :path [handler-type [name (commands-model/scope->bit-mask scope)] :handler] - :params jail-params - :callback (if (:async-handler command) ; async handler, we ignore return value - (fn [_] - (log/debug "Async command handler called")) - (fn [res] - (dispatch [:command-handler! chat-id orig-params res])))})]))))) + (assoc :orig-params orig-params))}] + (status/call-jail + {:jail-id identity + :path [handler-type [name scope-bitmask] :handler] + :params jail-params + :callback (if (:async-handler command) ; async handler, we ignore return value + (fn [_] + (log/debug "Async command handler called")) + (fn [res] + (dispatch [:command-handler! chat-id orig-params res])))}))))) (register-handler :prepare-message (u/side-effect! diff --git a/src/status_im/chat/models/commands.cljs b/src/status_im/chat/models/commands.cljs index 6937ee5242..34505b76ff 100644 --- a/src/status_im/chat/models/commands.cljs +++ b/src/status_im/chat/models/commands.cljs @@ -4,111 +4,41 @@ [clojure.string :as str] [taoensso.timbre :as log])) -(defn scope->bit-mask - "Transforms scope map to a single integer value by generating a bit mask." - [{:keys [global? registered-only? personal-chats? group-chats? can-use-for-dapps?]}] - (bit-or (when global? 1) - (when registered-only? 2) - (when personal-chats? 4) - (when group-chats? 8) - (when can-use-for-dapps? 16))) +(defn- resolve-references + [contacts name->ref] + (reduce-kv (fn [acc name ref] + (assoc acc name (get-in contacts ref))) + {} + name->ref)) -(defn get-mixable-commands - "Returns all commands of mixable contacts." - [{:contacts/keys [contacts]}] - (->> contacts - (vals) - (filter :mixable?) - (mapv :commands) - (mapcat #(into [] %)) - (reduce (fn [acc [k v]] (update acc k #(into % v))) {}))) +(defn- is-dapp? [all-contacts {:keys [identity]}] + (get-in all-contacts [identity :dapp?])) -(defn get-mixable-identities - "Returns a lazy-seq of all mixable contacts. Each contact contains only one key `:identity`." - [{:contacts/keys [contacts]}] - (->> contacts - (vals) - (filter :mixable?) - (map (fn [{:keys [whisper-identity]}] - {:identity whisper-identity})))) +(defn commands-responses + "Returns map of commands/responses eligible for current chat." + [type access-scope->commands-responses {:keys [address]} {:keys [contacts group-chat public?]} all-contacts] + (let [dapps? (some (partial is-dapp? all-contacts) contacts) + humans? (some (comp not (partial is-dapp? all-contacts)) contacts) + basic-access-scope (cond-> #{} + group-chat (conj :group-chats) + (not group-chat) (conj :personal-chats) + address (conj :registered) + (not address) (conj :anonymous) + dapps? (conj :dapps) + humans? (conj :humans) + public? (conj :public-chats)) + global-access-scope (conj basic-access-scope :global) + member-access-scopes (into #{} (map (comp (partial conj basic-access-scope) :identity)) + contacts)] + (reduce (fn [acc access-scope] + (merge acc (resolve-references all-contacts + (get-in access-scope->commands-responses [access-scope type])))) + {} + (cons global-access-scope member-access-scopes)))) -(defn- transform-commands-map - "Transforms a map of commands to a flat list." - [commands] - (->> commands - (map val) - (remove empty?) - (flatten))) - -(defn get-possible-requests - "Returns a list of all possible requests for current chat." - [{:keys [current-chat-id] :as db}] - (let [requests (->> (get-in db [:chats current-chat-id :requests]) - (map (fn [{:keys [type chat-id bot] :as req}] - [type (map (fn [resp] - (assoc resp :request req)) - (get-in db [:contacts/contacts (or bot chat-id) :responses type]))])) - (remove (fn [[_ items]] (empty? items))) - (into {}))] - (transform-commands-map requests))) - -(defn get-possible-commands - "Returns a list of all possible commands for current chat." - [{:keys [current-chat-id] :as db}] - (->> (get-in db [:chats current-chat-id :contacts]) - (into (get-mixable-identities db)) - (map (fn [{:keys [identity]}] - (let [commands (get-in db [:contacts/contacts identity :commands])] - (transform-commands-map commands)))) - (flatten))) - -(defn get-possible-global-commands - "Returns a list of all possible global commands for current chat." - [{:keys [global-commands] :as db}] - (transform-commands-map global-commands)) - -(defn commands-for-chat - "Returns a list of filtered commands for current chat. - Uses scopes to filter commands." - [{:keys [global-commands chats] - :contacts/keys [contacts] - :accounts/keys [accounts current-account-id] - :as db} chat-id] - (let [global-commands (get-possible-global-commands db) - commands (get-possible-commands db) - account (get accounts current-account-id) - commands (-> (into [] global-commands) - (into commands)) - {chat-contacts :contacts - group-chat :group-chat} (get chats chat-id)] - (remove (fn [{:keys [scope]}] - (or - (and (:registered-only? scope) - (not (:address account))) - (and (not (:personal-chats? scope)) - (not group-chat)) - (and (not (:group-chats? scope)) - group-chat) - (and (not (:can-use-for-dapps? scope)) - (every? (fn [{:keys [identity]}] - (get-in contacts [identity :dapp?])) - chat-contacts)))) - commands))) - -(defn- commands-list->map [commands] - (->> commands - (map #(vector (:name %) %)) - (into {}))) - -(defn replace-name-with-request - "Sets the information about command for a specified request." - ([{:keys [content] :as message} commands requests] - (if (map? content) - (let [{:keys [command content-command]} content - commands (commands-list->map commands) - requests (commands-list->map requests)] - (assoc content :command (or (get requests (or content-command command)) - (get commands command)))) - content)) - ([message commands] - (replace-name-with-request message commands []))) \ No newline at end of file +(defn requested-responses + "Returns map of requested command responses eligible for current chat." + [access-scope->commands-responses account chat contacts requests] + (let [requested-responses (map :response requests) + responses-map (commands-responses :response access-scope->commands-responses account chat contacts)] + (select-keys responses-map requested-responses))) diff --git a/src/status_im/chat/models/input.cljs b/src/status_im/chat/models/input.cljs index 6e20058185..1791588020 100644 --- a/src/status_im/chat/models/input.cljs +++ b/src/status_im/chat/models/input.cljs @@ -56,7 +56,7 @@ (str/replace (str/trim command-text) #" +" " ") command-text) splitted (cond-> (str/split command-text-normalized const/spacing-char) - space? (drop-last))] + space? (drop-last))] (->> splitted (reduce (fn [[list command-started?] arg] (let [quotes-count (count (filter #(= % const/arg-wrapping-char) arg)) @@ -104,28 +104,35 @@ For instance, we can add a `:to-message-id` key to this map, and this key will allow us to identity the request we're responding to. * `:args` contains all arguments provided by user." - ([{:keys [current-chat-id] :as db} chat-id input-text] + ([{:keys [current-chat-id access-scope->commands-responses] + :contacts/keys [contacts] + :accounts/keys [accounts current-account-id] :as db} chat-id input-text] (let [chat-id (or chat-id current-chat-id) - {:keys [input-metadata - seq-arguments - possible-requests - possible-commands]} (get-in db [:chats chat-id]) + chat (get-in db [:chats chat-id]) + {:keys [input-metadata seq-arguments requests]} chat command-args (split-command-args input-text) command-name (first command-args)] (when (starts-as-command? (or command-name "")) - (when-let [{{:keys [message-id]} :request :as command} - (->> (into possible-requests possible-commands) - (filter (fn [{:keys [name]}] - (= name (subs command-name 1)))) - (first))] - {:command command - :metadata (if (and (nil? (:to-message-id input-metadata)) message-id) - (assoc input-metadata :to-message-id message-id) - input-metadata) - :args (->> (if (empty? seq-arguments) - (rest command-args) - seq-arguments) - (into []))})))) + (let [account (get accounts current-account-id) + available-commands-responses (merge (commands-model/commands-responses :command + access-scope->commands-responses + account + chat + contacts) + (commands-model/requested-responses access-scope->commands-responses + account + chat + contacts + (vals requests)))] + (when-let [{{:keys [message-id]} :request :as command} (get available-commands-responses (subs command-name 1))] + {:command command + :metadata (if (and (nil? (:to-message-id input-metadata)) message-id) + (assoc input-metadata :to-message-id message-id) + input-metadata) + :args (->> (if (empty? seq-arguments) + (rest command-args) + seq-arguments) + (into []))}))))) ([{:keys [current-chat-id] :as db} chat-id] (selected-chat-command db chat-id (get-in db [:chats chat-id :input-text])))) @@ -226,12 +233,12 @@ prev-command (get-in db [:chat-ui-props current-chat-id :prev-command])] (if command (cond-> db - ;; clear the bot db - (not= prev-command (-> command :command :name)) - (assoc-in [:bot-db (or (:bot command) current-chat-id)] nil) - ;; clear the chat's validation messages - true - (assoc-in [:chat-ui-props current-chat-id :validation-messages] nil)) + ;; clear the bot db + (not= prev-command (-> command :command :name)) + (assoc-in [:bot-db (or (:bot command) current-chat-id)] nil) + ;; clear the chat's validation messages + true + (assoc-in [:chat-ui-props current-chat-id :validation-messages] nil)) (-> db ;; clear input metadata (assoc-in [:chats current-chat-id :input-metadata] nil) diff --git a/src/status_im/chat/screen.cljs b/src/status_im/chat/screen.cljs index a66d626df3..af8cd2aa0f 100644 --- a/src/status_im/chat/screen.cljs +++ b/src/status_im/chat/screen.cljs @@ -180,8 +180,7 @@ show-emoji? [:chat-ui-props :show-emoji?] layout-height [:get :layout-height] input-text [:chat :input-text]] - {:component-did-mount #(do (dispatch [:check-and-open-dapp!]) - (dispatch [:update-suggestions])) + {:component-did-mount #(dispatch [:check-and-open-dapp!]) :component-will-unmount #(dispatch [:set-chat-ui-props {:show-emoji? false}])} [view {:style st/chat-view :on-layout (fn [event] diff --git a/src/status_im/chat/sign_up.cljs b/src/status_im/chat/sign_up.cljs index dd25a87634..3caea29728 100644 --- a/src/status_im/chat/sign_up.cljs +++ b/src/status_im/chat/sign_up.cljs @@ -85,41 +85,40 @@ :to "me"}]) (defn passphrase-messages-events [mnemonic signing-phrase crazy-math-message?] - (into [[:received-message - {:message-id chat-const/passphrase-message-id - :content (if crazy-math-message? - (label :t/phew-here-is-your-passphrase) - (label :t/here-is-your-passphrase)) - :content-type const/text-content-type - :outgoing false - :chat-id const/console-chat-id - :from const/console-chat-id - :to "me"}] - [:received-message - {:message-id (random/id) - :content mnemonic - :content-type const/text-content-type - :outgoing false - :chat-id const/console-chat-id - :from const/console-chat-id - :to "me"}] - [:received-message - {:message-id chat-const/signing-phrase-message-id - :content (label :t/here-is-your-signing-phrase) - :content-type const/text-content-type - :outgoing false - :chat-id const/console-chat-id - :from const/console-chat-id - :to "me"}] - [:received-message - {:message-id (random/id) - :content signing-phrase - :content-type const/text-content-type - :outgoing false - :chat-id const/console-chat-id - :from const/console-chat-id - :to "me"}]] - start-signup-events)) + [[:received-message + {:message-id chat-const/passphrase-message-id + :content (if crazy-math-message? + (label :t/phew-here-is-your-passphrase) + (label :t/here-is-your-passphrase)) + :content-type const/text-content-type + :outgoing false + :chat-id const/console-chat-id + :from const/console-chat-id + :to "me"}] + [:received-message + {:message-id (random/id) + :content mnemonic + :content-type const/text-content-type + :outgoing false + :chat-id const/console-chat-id + :from const/console-chat-id + :to "me"}] + [:received-message + {:message-id chat-const/signing-phrase-message-id + :content (label :t/here-is-your-signing-phrase) + :content-type const/text-content-type + :outgoing false + :chat-id const/console-chat-id + :from const/console-chat-id + :to "me"}] + [:received-message + {:message-id (random/id) + :content signing-phrase + :content-type const/text-content-type + :outgoing false + :chat-id const/console-chat-id + :from const/console-chat-id + :to "me"}]]) (def intro-status {:message-id chat-const/intro-status-message-id @@ -162,5 +161,4 @@ :photo-path const/console-chat-id :dapp? true :unremovable? true - :bot-url "local://console-bot" - :dapp-hash 858845357}) + :bot-url "local://console-bot"}) diff --git a/src/status_im/chat/specs.cljs b/src/status_im/chat/specs.cljs index 06c443b2ea..07efd604c3 100644 --- a/src/status_im/chat/specs.cljs +++ b/src/status_im/chat/specs.cljs @@ -10,7 +10,6 @@ (s/def :chat/chat-list-ui-props (s/nilable map?)) (s/def :chat/layout-height (s/nilable number?)) ;;height of chat's view layout (s/def :chat/expandable-view-height-to-value (s/nilable number?)) -(s/def :chat/global-commands (s/nilable map?)) ; {key (keyword) command (map)} atm used for browse command (s/def :chat/loading-allowed (s/nilable boolean?)) ;;allow to load more messages (s/def :chat/handler-data (s/nilable map?)) (s/def :chat/message-data (s/nilable map?)) @@ -18,15 +17,12 @@ (s/def :chat/message-status (s/nilable map?)) (s/def :chat/unviewed-messages (s/nilable map?)) (s/def :chat/selected-participants (s/nilable set?)) -(s/def :chat/chat-loaded-callbacks (s/nilable map?)) -(s/def :chat/commands-callbacks (s/nilable map?)) +(s/def :chat/chat-loaded-callbacks (s/nilable map?)) (s/def :chat/command-hash-valid? (s/nilable boolean?)) (s/def :chat/public-group-topic (s/nilable string?)) (s/def :chat/confirmation-code-sms-listener (s/nilable any?)) ; .addListener result object (s/def :chat/messages (s/nilable seq?)) -(s/def :chat/loaded-chats (s/nilable seq?)) -(s/def :chat/bot-subscriptions (s/nilable map?)) -(s/def :chat/new-request (s/nilable map?)) +(s/def :chat/loaded-chats (s/nilable seq?)) (s/def :chat/raw-unviewed-messages (s/nilable vector?)) (s/def :chat/bot-db (s/nilable map?)) (s/def :chat/geolocation (s/nilable map?)) diff --git a/src/status_im/chat/subs.cljs b/src/status_im/chat/subs.cljs index 3d19e63e25..4ad9efda4f 100644 --- a/src/status_im/chat/subs.cljs +++ b/src/status_im/chat/subs.cljs @@ -38,13 +38,11 @@ (:chats db))) (reg-sub - :chat-actions + :get-current-chat :<- [:chats] :<- [:get-current-chat-id] - :<- [:chat :input-text] - (fn [[chats current-chat-id text] [_ type chat-id]] - (->> (get-in chats [(or chat-id current-chat-id) type]) - (filter #(or (str/includes? (chat-utils/command-name %) (or text ""))))))) + (fn [[chats id]] + (get chats id))) (reg-sub :chat @@ -53,18 +51,50 @@ (fn [[chats id] [_ k chat-id]] (get-in chats [(or chat-id id) k]))) -(defn get-suggested-commands - [[commands requests]] - (let [vec->map-by-name #(into {} (map vector (map :name %) %)) - commands-map (vec->map-by-name commands) - requests-map (vec->map-by-name requests)] - (vals (merge commands-map requests-map)))) +(reg-sub + :get-commands-for-chat + :<- [:get-commands-responses-by-access-scope] + :<- [:get-current-account] + :<- [:get-current-chat] + :<- [:get-contacts] + (fn [[commands-responses account chat contacts]] + (commands-model/commands-responses :command commands-responses account chat contacts))) (reg-sub - :get-suggested-commands - :<- [:chat :possible-commands] - :<- [:chat :possible-requests] - get-suggested-commands) + :get-responses-for-chat + :<- [:get-commands-responses-by-access-scope] + :<- [:get-current-account] + :<- [:get-current-chat] + :<- [:get-contacts] + :<- [:chat :requests] + (fn [[commands-responses account chat contacts requests]] + (commands-model/requested-responses commands-responses account chat contacts (vals requests)))) + +(def ^:private map->sorted-seq (comp (partial map second) (partial sort-by first))) + +(defn- available-commands-responses [[commands-responses input-text]] + (->> commands-responses + map->sorted-seq + (filter #(str/includes? (chat-utils/command-name %) (or input-text ""))))) + +(reg-sub + :get-available-commands + :<- [:get-commands-for-chat] + :<- [:chat :input-text] + available-commands-responses) + +(reg-sub + :get-available-responses + :<- [:get-responses-for-chat] + :<- [:chat :input-text] + available-commands-responses) + +(reg-sub + :get-available-commands-responses + :<- [:get-commands-for-chat] + :<- [:get-responses-for-chat] + (fn [[commands responses]] + (map->sorted-seq (merge commands responses)))) (reg-sub :get-current-chat-id @@ -76,10 +106,6 @@ (fn [_ [_ chat-id]] (chats/get-by-id chat-id))) -(reg-sub :get-commands-for-chat - (fn [db [_ chat-id]] - (commands-model/commands-for-chat db chat-id))) - (reg-sub :selected-chat-command (fn [db [_ chat-id]] @@ -89,12 +115,12 @@ (reg-sub :current-chat-argument-position - (fn [db] - (let [command (subscribe [:selected-chat-command]) - input-text (subscribe [:chat :input-text]) - seq-arguments (subscribe [:chat :seq-arguments]) - selection (subscribe [:chat-ui-props :selection])] - (input-model/current-chat-argument-position @command @input-text @selection @seq-arguments)))) + :<- [:selected-chat-command] + :<- [:chat :input-text] + :<- [:chat :seq-arguments] + :<- [:chat-ui-props :selection] + (fn [[command input-text seq-arguments selection]] + (input-model/current-chat-argument-position command input-text selection seq-arguments))) (reg-sub :chat-parameter-box @@ -132,21 +158,14 @@ (reg-sub :show-suggestions? (fn [db [_ chat-id]] - (let [chat-id (or chat-id (db :current-chat-id)) - show-suggestions? (subscribe [:chat-ui-props :show-suggestions? chat-id]) - input-text (subscribe [:chat :input-text chat-id]) - selected-command (subscribe [:selected-chat-command chat-id]) - requests (subscribe [:chat :possible-requests chat-id]) - commands (subscribe [:chat :possible-commands chat-id])] + (let [chat-id (or chat-id (db :current-chat-id)) + show-suggestions? (subscribe [:chat-ui-props :show-suggestions? chat-id]) + input-text (subscribe [:chat :input-text chat-id]) + selected-command (subscribe [:selected-chat-command chat-id]) + commands-responses (subscribe [:get-available-commands-responses])] (and (or @show-suggestions? (input-model/starts-as-command? (str/trim (or @input-text "")))) (not (:command @selected-command)) - (or (not-empty @requests) - (not-empty @commands)))))) - -(reg-sub :get-current-chat - (fn [db] - (let [current-chat-id (:current-chat-id db)] - (get-in db [:chats current-chat-id])))) + (seq @commands-responses))))) (reg-sub :get-chat (fn [db [_ chat-id]] @@ -160,7 +179,7 @@ (reg-sub :is-request-answered? :<- [:chat :requests] (fn [requests [_ message-id]] - (not-any? #(= message-id (:message-id %)) requests))) + (not= "open" (get-in requests [message-id :status])))) (reg-sub :unviewed-messages-count (fn [db [_ chat-id]] diff --git a/src/status_im/chat/utils.cljs b/src/status_im/chat/utils.cljs index 1e9da77fc2..e8b83698fc 100644 --- a/src/status_im/chat/utils.cljs +++ b/src/status_im/chat/utils.cljs @@ -38,11 +38,6 @@ (let [previous-message (first (get-in db [:chats chat-id :messages]))] (check-message previous-message message)))) -(defn command-valid? [message validator] - (if validator - (validator message) - (pos? (count message)))) - (defn command-name [{:keys [bot name scope]}] (cond (:global? scope) diff --git a/src/status_im/chat/views/input/input.cljs b/src/status_im/chat/views/input/input.cljs index 23a749ebf5..93612ef38e 100644 --- a/src/status_im/chat/views/input/input.cljs +++ b/src/status_im/chat/views/input/input.cljs @@ -35,7 +35,7 @@ (chat-utils/command-name command)]]]) (defview commands-view [] - [all-commands [:get-suggested-commands] + [all-commands-responses [:get-available-commands-responses] show-suggestions? [:show-suggestions?]] [view style/commands-root [view style/command-list-icon-container @@ -48,7 +48,7 @@ :showsHorizontalScrollIndicator false :keyboardShouldPersistTaps :always} [view style/commands - (for [[index command] (map-indexed vector all-commands)] + (for [[index command] (map-indexed vector all-commands-responses)] ^{:key (str "command-" index)} [command-view (= index 0) command])]]]) diff --git a/src/status_im/chat/views/input/suggestions.cljs b/src/status_im/chat/views/input/suggestions.cljs index a555838048..08b28fc67d 100644 --- a/src/status_im/chat/views/input/suggestions.cljs +++ b/src/status_im/chat/views/input/suggestions.cljs @@ -2,10 +2,10 @@ (:require-macros [status-im.utils.views :refer [defview]]) (:require [re-frame.core :refer [subscribe dispatch]] [status-im.ui.components.react :refer [view - scroll-view - touchable-highlight - text - icon]] + scroll-view + touchable-highlight + text + icon]] [status-im.data-store.messages :as messages] [status-im.chat.styles.input.suggestions :as style] [status-im.chat.constants :as const] @@ -25,8 +25,8 @@ :number-of-lines 2} description]]]) -(defview request-item [{:keys [name description] - {:keys [type message-id]} :request :as command} last?] +(defview response-item [{:keys [name description] + {:keys [type message-id]} :request :as command} last?] [{:keys [chat-id]} [:get-current-chat]] [suggestion-item {:on-press #(let [{:keys [params]} (messages/get-message-content-by-id message-id) @@ -50,25 +50,23 @@ (defview suggestions-view [] [show-suggestions? [:show-suggestions?] - requests [:chat-actions :possible-requests] - commands [:chat-actions :possible-commands]] + responses [:get-available-responses] + commands [:get-available-commands]] (when show-suggestions? [expandable-view {:key :suggestions :draggable? false :height 212} [view {:flex 1} [scroll-view {:keyboardShouldPersistTaps :always} - (when (seq requests) + (when (seq responses) [view [item-title false (label :t/suggestions-requests)] - (for [[i {{:keys [chat-id message-id]} :request :as request}] (map-indexed vector requests)] - ^{:key [chat-id message-id]} - [request-item request (= i (dec (count requests)))])]) + (for [[i response] (map-indexed vector responses)] + ^{:key i} + [response-item response (= i (dec (count responses)))])]) (when (seq commands) [view - [item-title (seq requests) (label :t/suggestions-commands)] - (for [[i command] (->> commands - (remove #(nil? (:title %))) - (map-indexed vector))] + [item-title (seq responses) (label :t/suggestions-commands)] + (for [[i command] (map-indexed vector commands)] ^{:key i} [command-item command (= i (dec (count commands)))])])]]])) diff --git a/src/status_im/chat/views/message/message.cljs b/src/status_im/chat/views/message/message.cljs index 72df03f361..78438801c5 100644 --- a/src/status_im/chat/views/message/message.cljs +++ b/src/status_im/chat/views/message/message.cljs @@ -121,12 +121,11 @@ (defview message-content-command [{:keys [message-id content content-type chat-id to from outgoing] :as message}] - (letsubs [commands [:get-commands-for-chat chat-id] + (letsubs [command [:get-command (:content-command-ref content)] current-chat-id [:get-current-chat-id] contact-chat [:get-in [:chats (if outgoing to from)]] preview [:get-message-preview message-id]] - (let [{:keys [command params]} (commands/replace-name-with-request message commands) - {:keys [name type] + (let [{:keys [name type] icon-path :icon} command] [view st/content-command-view (when (:color command) @@ -134,13 +133,13 @@ [view (pill-st/pill command) [text {:style pill-st/pill-text :font :default} - (str (if (= :command type) chat-consts/command-char "?") name)]]]) + (str chat-consts/command-char name)]]]) (when icon-path [view st/command-image-view [icon icon-path st/command-image]]) [command-preview {:command (:name command) :content-type content-type - :params params + :params (:params content) :outgoing? outgoing :preview preview :contact-chat contact-chat @@ -285,7 +284,7 @@ (defview message-delivery-status [{:keys [message-id chat-id message-status user-statuses content]}] [app-db-message-status-value [:get-in [:message-data :statuses message-id :status]]] - (let [delivery-status (get-in user-statuses [chat-id :status]) + (let [delivery-status (get-in user-statuses [chat-id :status]) status (cond (and (not (console/commands-with-delivery-status (:command content))) (cu/console? chat-id)) :seen diff --git a/src/status_im/chat/views/message/request_message.cljs b/src/status_im/chat/views/message/request_message.cljs index f86807dd9d..a42425e581 100644 --- a/src/status_im/chat/views/message/request_message.cljs +++ b/src/status_im/chat/views/message/request_message.cljs @@ -3,11 +3,11 @@ (:require [re-frame.core :refer [subscribe dispatch]] [reagent.core :as r] [status-im.ui.components.react :refer [view - animated-view - text - image - icon - touchable-highlight]] + animated-view + text + image + icon + touchable-highlight]] [status-im.chat.styles.message.message :as st] [status-im.chat.models.commands :as commands] [status-im.ui.components.animation :as anim] @@ -25,9 +25,9 @@ (defn button-animation [val to-value loop? answered?] (anim/anim-sequence [(anim/anim-delay - (if (and @loop? (not @answered?)) - request-message-icon-scale-delay - 0)) + (if (and @loop? (not @answered?)) + request-message-icon-scale-delay + 0)) (anim/spring val {:toValue to-value})])) (defn request-button-animation-logic @@ -73,14 +73,12 @@ (defview message-content-command-request [{:keys [message-id chat-id content from incoming-group] :as message}] - (letsubs [commands [:get-commands-for-chat chat-id] - requests [:chat-actions :possible-requests] + (letsubs [command [:get-command (:content-command-ref content)] answered? [:is-request-answered? message-id] status-initialized? [:get :status-module-initialized?] markup [:get-message-preview message-id]] (let [{:keys [prefill prefill-bot-db prefillBotDb params] - text-content :text} content - {:keys [command content]} (commands/replace-name-with-request message commands requests) + text-content :text} content command (if (and params command) (merge command {:prefill prefill :prefill-bot-db (or prefill-bot-db prefillBotDb)}) @@ -98,7 +96,7 @@ [view markup] [text {:style st/style-message-text :font :default} - (or text-content markup content)])]] + (or text-content markup (:content content))])]] (when (:request-text command) [view st/command-request-text-view [text {:style st/style-sub-text diff --git a/src/status_im/commands/events/loading.cljs b/src/status_im/commands/events/loading.cljs new file mode 100644 index 0000000000..65fe860619 --- /dev/null +++ b/src/status_im/commands/events/loading.cljs @@ -0,0 +1,158 @@ +(ns status-im.commands.events.loading + (:require [clojure.string :as string] + [clojure.set :as s] + [re-frame.core :as re-frame] + [status-im.utils.handlers :as handlers] + [status-im.utils.js-resources :as js-resources] + [status-im.utils.types :as types] + [status-im.utils.utils :as utils] + [status-im.native-module.core :as status] + [status-im.data-store.local-storage :as local-storage] + [status-im.bots.events :as bots-events] + [taoensso.timbre :as log])) + +;; COFX +(re-frame/reg-cofx + :get-local-storage-data + (fn [cofx] + (assoc cofx :get-local-storage-data local-storage/get-data))) + +;; FX +(re-frame/reg-fx + ::evaluate-jail-n + (fn [jail-data] + (doseq [{:keys [jail-id jail-resource]} jail-data] + (status/parse-jail + jail-id jail-resource + (fn [jail-response] + (re-frame/dispatch [::proceed-loading jail-id (types/json->clj jail-response)])))))) + +(re-frame/reg-fx + ::show-popup + (fn [{:keys [title msg]}] + (utils/show-popup title msg))) + +;; Handlers +(defn- valid-network-resource? + [response] + (some-> (.. response -headers) + (get "Content-type") + (string/includes? "application/javascript"))) + +(defn- evaluate-commands-in-jail + [{:keys [db get-local-storage-data]} commands-resource whisper-identity] + (let [data (get-local-storage-data whisper-identity) + local-storage-snippet (js-resources/local-storage-data data) + network-id (get-in db [:networks/networks (:network db) :raw-config :NetworkId]) + ethereum-id-snippet (js-resources/network-id network-id) + commands-snippet (str local-storage-snippet ethereum-id-snippet commands-resource)] + {::evaluate-jail-n [{:jail-id whisper-identity + :jail-resource commands-snippet}]})) + +(defn load-commands + "This function takes coeffects, effects and contact and adds effects + for loading all commands/responses/subscriptions. + + It's currently working only for bots, eq we are not evaluating + dapp resources in jail at all." + [cofx fx {:keys [whisper-identity bot-url]}] + (if bot-url + (if-let [commands-resource (js-resources/get-resource bot-url)] + (merge-with into fx (evaluate-commands-in-jail cofx commands-resource whisper-identity)) + (update fx :http-get-n conj {:url bot-url + :response-validator valid-network-resource? + :success-event-creator (fn [commands-resource] + [::evaluate-commands-in-jail commands-resource whisper-identity]) + :failure-event-creator (fn [error-response] + [::proceed-loading whisper-identity {:error error-response}])})) + fx)) + +(defn- add-exclusive-choices [initial-scope exclusive-choices] + (reduce (fn [scopes-set exclusive-choices] + (reduce (fn [scopes-set scope] + (let [exclusive-match (s/intersection scope exclusive-choices)] + (if (seq exclusive-match) + (reduce conj + (disj scopes-set scope) + (map (partial conj (s/difference scope exclusive-match)) + exclusive-match)) + scopes-set))) + scopes-set + scopes-set)) + #{initial-scope} + exclusive-choices)) + +(defn- create-access-scopes + "Based on command owner and command scope, create set of access-scopes which can be used to directly + look up any commands/subscriptions relevant for actual context (type of chat opened, registered user + or not, etc.)" + [jail-id scope] + (let [member-scope (cond-> scope + (not (scope :global)) (conj jail-id))] + (add-exclusive-choices member-scope [#{:personal-chats :group-chats} + #{:anonymous :registered} + #{:dapps :humans :public-chats}]))) + +(defn- index-by-access-scope-type + [init jail-id items] + (reduce (fn [acc {:keys [scope name type ref]}] + (let [access-scopes (create-access-scopes jail-id scope)] + (reduce (fn [acc access-scope] + (assoc-in acc [access-scope type name] ref)) + acc + access-scopes))) + init + items)) + +(defn- enrich + [jail-id type [_ {:keys [scope-bitmask scope name] :as props}]] + (-> props + (assoc :scope (into #{} (map keyword) scope) + :owner-id jail-id + :bot jail-id + :type type + :ref [jail-id type scope-bitmask name]))) + +(defn add-jail-result + "This function add commands/responses/subscriptions from jail-evaluated resource + into the database" + [db jail-id {:keys [commands responses subscriptions]}] + (let [enriched-commands (map (partial enrich jail-id :command) commands) + enriched-responses (map (partial enrich jail-id :response) responses) + new-db (reduce (fn [acc {:keys [ref] :as props}] + (assoc-in acc (into [:contacts/contacts] ref) props)) + db + (concat enriched-commands enriched-responses))] + (-> new-db + (update :access-scope->commands-responses (fn [acc] + (-> (or acc {}) + (index-by-access-scope-type jail-id enriched-commands) + (index-by-access-scope-type jail-id enriched-responses)))) + (update-in [:contacts/contacts jail-id] assoc + :subscriptions (bots-events/transform-bot-subscriptions subscriptions) + :jail-loaded? true)))) + +(handlers/register-handler-fx + ::evaluate-commands-in-jail + [re-frame/trim-v (re-frame/inject-cofx :get-local-storage-data)] + (fn [cofx [commands-resource whisper-identity]] + (evaluate-commands-in-jail cofx commands-resource whisper-identity))) + +(handlers/register-handler-fx + ::proceed-loading + [re-frame/trim-v] + (fn [{:keys [db]} [jail-id {:keys [error result]}]] + (if error + (let [message (string/join "\n" ["bot.js loading failed" + jail-id + error])] + {::show-popup {:title "Error" + :msg message}}) + (let [jail-loaded-events (get-in db [:contacts/contacts jail-id :jail-loaded-events])] + (cond-> {:db (add-jail-result db jail-id result) + :call-jail-function {:chat-id jail-id + :function :init :context + {:from (:accounts/current-account-id db)}}} + (seq jail-loaded-events) + (-> (assoc :dispatch-n jail-loaded-events) + (update-in [:db :contacts/contacts jail-id] dissoc :jail-loaded-events))))))) diff --git a/src/status_im/commands/handlers/debug.cljs b/src/status_im/commands/handlers/debug.cljs index fd2e4abed6..d8a5eb1c14 100644 --- a/src/status_im/commands/handlers/debug.cljs +++ b/src/status_im/commands/handlers/debug.cljs @@ -1,6 +1,7 @@ (ns status-im.commands.handlers.debug (:require [re-frame.core :as re-frame] [status-im.ui.components.react :as react] + [status-im.commands.events.loading :as loading-events] [status-im.data-store.accounts :as accounts] [status-im.data-store.messages :as messages] [status-im.utils.handlers :as handlers] @@ -84,8 +85,9 @@ (when (and (= current-chat-id whisper-identity) webview-bridge) (.reload webview-bridge)) - (when (get-in contacts [whisper-identity :bot-url]) - (re-frame/dispatch [:load-commands! whisper-identity]))) + (when-let [bot-url (get-in contacts [whisper-identity :bot-url])] + (re-frame/dispatch [::load-commands! {:whisper-identity whisper-identity + :bot-url bot-url}]))) (respond {:type :ok :text "Command has been executed."})) @@ -172,4 +174,10 @@ (fn [{:keys [db]} [url post-data]] {::process-request-fx [db url post-data]})) +;; TODO(janherich) once `contact-changed` fn is refactored, get rid of this unnecessary event +(handlers/register-handler-fx + ::load-commands + [re-frame/trim-v (re-frame/inject-cofx :get-local-storage-data)] + (fn [cofx [contact]] + (loading-events/load-commands cofx {} contact))) diff --git a/src/status_im/commands/handlers/loading.cljs b/src/status_im/commands/handlers/loading.cljs deleted file mode 100644 index 78e2772a28..0000000000 --- a/src/status_im/commands/handlers/loading.cljs +++ /dev/null @@ -1,206 +0,0 @@ -(ns status-im.commands.handlers.loading - (:require [re-frame.core :refer [path after dispatch subscribe trim-v debug]] - [status-im.utils.handlers :as u] - [status-im.utils.utils :refer [http-get show-popup]] - [clojure.string :as s] - [status-im.data-store.contacts :as contacts] - [status-im.data-store.local-storage :as local-storage] - [status-im.commands.utils :refer [reg-handler]] - [status-im.constants :refer [console-chat-id]] - [status-im.chat.models.commands :as commands-model] - [status-im.native-module.core :as status] - [status-im.utils.homoglyph :as h] - [status-im.utils.js-resources :as js-res] - [status-im.utils.types :as types] - [taoensso.timbre :as log])) - - -(defn load-commands! - [{:keys [current-chat-id chats] - :contacts/keys [contacts]} [jail-id callback]] - (let [identity (or jail-id current-chat-id) - contact-ids (if (get contacts identity) - [identity] - (->> (get-in chats [identity :contacts]) - (map :identity) - (into [])))] - (when (seq contacts) - (doseq [contact-id contact-ids] - (when-let [contact (get contacts contact-id)] - (dispatch [::fetch-commands! {:contact contact - :callback callback}])))))) - -(defn http-get-commands [params url] - (http-get url - (fn [response] - (when-let [content-type (.. response -headers (get "Content-Type"))] - (s/includes? content-type "application/javascript"))) - #(dispatch [::validate-hash params %]) - #(log/debug (str "command.js wasn't found at " url)))) - - -(defn fetch-commands! - [_ [{{:keys [whisper-identity - dapp-url - bot-url - dapp?]} :contact - callback :callback - :as params}]] - (if bot-url - (if-let [resource (js-res/get-resource bot-url)] - (dispatch [::validate-hash params resource]) - (http-get-commands params bot-url)) - (when callback (callback)))) - -(defn dispatch-loaded! - [db [{{:keys [whisper-identity]} :contact - :as params} file]] - (if (:command-hash-valid? db) - (dispatch [::parse-commands! params file]) - (dispatch [::loading-failed! whisper-identity ::wrong-hash]))) - -(defn get-hash-by-identity - [db identity] - (get-in db [:contacts/contacts identity :dapp-hash])) - -(defn get-hash-by-file - [file] - ;; todo tbd hashing algorithm - (hash file)) - -(defn parse-commands! - [{:networks/keys [networks] - :keys [network] - :as db} - [{{:keys [whisper-identity]} :contact - :keys [callback]} - file]] - (let [data (local-storage/get-data whisper-identity) - local-storage-js (js-res/local-storage-data data) - network-id (get-in networks [network :raw-config :NetworkId]) - ethereum-id-js (js-res/network-id network-id)] - (status/parse-jail - whisper-identity (str local-storage-js ethereum-id-js file) - (fn [result] - (let [{:keys [error result]} (types/json->clj result)] - (log/debug "Parsing commands results: " error result) - (if error - (dispatch [::loading-failed! whisper-identity ::error-in-jail error]) - (do - (dispatch [::add-commands whisper-identity file result]) - (when callback (callback))))))))) - -(defn validate-hash - [db [_ file]] - (let [valid? true - ;; todo check - #_(= (get-hash-by-identity db identity) - (get-hash-by-file file))] - (assoc db :command-hash-valid? valid?))) - -(defn each-merge [with coll] - (->> coll - (map (fn [[k v]] [k (merge v with)])) - (into {}))) - -(defn extract-commands [{:keys [contacts]} commands] - (->> commands - (remove (fn [[_ {:keys [name]}]] - (and (= (count contacts) 1) - (not= console-chat-id (get (first contacts) :identity)) - (h/matches name "password")))) - (map (fn [[k {:keys [name scope] :as v}]] - [[name scope] v])) - (into {}))) - -(defn extract-global-commands [commands chat-id] - (->> commands - (filter (fn [[_ {:keys [scope]}]] - (:global? scope))) - (map (fn [[k {:keys [name scope] :as v}]] - [[name scope] (assoc v :bot chat-id :type :command)])) - (into {}))) - -(defn transform-commands [commands] - (reduce (fn [m [_ {:keys [name scope] :as v}]] - (update m (keyword name) conj v)) - {} - commands)) - -(defn add-commands - [{:keys [current-account-id accounts chats] :as db} - [id _ {:keys [commands responses subscriptions]}]] - (let [account (get accounts current-account-id) - chat (get chats id) - global-commands (extract-global-commands commands id) - mixable-commands (when-not (get-in db [:contacts/contacts id :mixable?]) - (commands-model/get-mixable-commands db)) - commands' (->> (apply dissoc commands (keys global-commands)) - (extract-commands chat) - (each-merge {:type :command - :owner-id id}) - (transform-commands) - (merge mixable-commands)) - responses' (->> (extract-commands chat responses) - (each-merge {:type :response - :owner-id id}) - (transform-commands)) - new-db (-> db - (update-in [:contacts/contacts id] assoc - :commands-loaded? true - :commands commands' - :responses responses' - :subscriptions subscriptions) - (update :global-commands merge (transform-commands global-commands)))] - new-db)) - -(defn loading-failed! - [db [id reason details]] - (let [url (get-in db [:chats id :dapp-url])] - (let [m (s/join "\n" ["commands.js loading failed" - url - id - (name reason) - details])] - (show-popup "Error" m) - (log/debug m)))) - -(reg-handler :check-and-load-commands! - (u/side-effect! - (fn [{:contacts/keys [contacts]} [identity callback]] - (if (get-in contacts [identity :commands-loaded?]) - (callback) - (dispatch [:load-commands! identity callback]))))) - -(reg-handler :load-commands! (u/side-effect! load-commands!)) -(reg-handler ::fetch-commands! (u/side-effect! fetch-commands!)) - -(reg-handler ::validate-hash - (after dispatch-loaded!) - validate-hash) - -(reg-handler ::parse-commands! (u/side-effect! parse-commands!)) - -(reg-handler ::add-commands - (after (fn [_ [id]] - (dispatch [:invoke-commands-loading-callbacks id]) - (dispatch [:update-suggestions]))) - add-commands) - -(reg-handler ::loading-failed! (u/side-effect! loading-failed!)) - -(reg-handler :add-commands-loading-callback - (fn [db [chat-id callback]] - (update-in db [:commands-callbacks chat-id] conj callback))) - -(reg-handler :invoke-commands-loading-callbacks - (u/side-effect! - (fn [db [chat-id]] - (let [callbacks (get-in db [:commands-callbacks chat-id])] - (doseq [callback callbacks] - (callback)) - (dispatch [::clear-commands-callbacks chat-id]))))) - -(reg-handler ::clear-commands-callbacks - (fn [db [chat-id]] - (assoc-in db [:commands-callbacks chat-id] nil))) diff --git a/src/status_im/commands/specs.cljs b/src/status_im/commands/specs.cljs new file mode 100644 index 0000000000..ea136ed1c3 --- /dev/null +++ b/src/status_im/commands/specs.cljs @@ -0,0 +1,4 @@ +(ns status-im.commands.specs + (:require [cljs.spec.alpha :as spec])) + +(spec/def :commands/access-scope->commands-responses (spec/nilable map?)) diff --git a/src/status_im/commands/subs.cljs b/src/status_im/commands/subs.cljs new file mode 100644 index 0000000000..c6c695ace2 --- /dev/null +++ b/src/status_im/commands/subs.cljs @@ -0,0 +1,13 @@ +(ns status-im.commands.subs + (:require [re-frame.core :refer [reg-sub]])) + +(reg-sub + :get-commands-responses-by-access-scope + (fn [db _] + (:access-scope->commands-responses db))) + +(reg-sub + :get-command + :<- [:get-contacts] + (fn [contacts [_ ref]] + (some->> ref (get-in contacts)))) diff --git a/src/status_im/data_store/chats.cljs b/src/status_im/data_store/chats.cljs index c34fd88f1a..12199203cb 100644 --- a/src/status_im/data_store/chats.cljs +++ b/src/status_im/data_store/chats.cljs @@ -1,6 +1,5 @@ (ns status-im.data-store.chats - (:require [status-im.data-store.realm.chats :as data-store] - [re-frame.core :refer [dispatch]]) + (:require [status-im.data-store.realm.chats :as data-store]) (:refer-clojure :exclude [exists?])) (defn- normalize-contacts @@ -43,8 +42,7 @@ (defn add-contacts [chat-id identities] - (data-store/add-contacts chat-id identities) - (dispatch [:reload-chats])) + (data-store/add-contacts chat-id identities)) (defn remove-contacts [chat-id identities] diff --git a/src/status_im/data_store/contacts.cljs b/src/status_im/data_store/contacts.cljs index 514791064b..28239deb04 100644 --- a/src/status_im/data_store/contacts.cljs +++ b/src/status_im/data_store/contacts.cljs @@ -18,7 +18,7 @@ (assoc :pending? (boolean (if contact-db (if (nil? pending?) pending-db? pending?) pending?))) - (dissoc :commands :responses :global-command))] + (dissoc :command :response :subscriptions :jail-loaded-events))] (data-store/save contact' (boolean contact-db)))) (defn save-all diff --git a/src/status_im/data_store/realm/requests.cljs b/src/status_im/data_store/realm/requests.cljs index b014a388fb..4346562c1f 100644 --- a/src/status_im/data_store/realm/requests.cljs +++ b/src/status_im/data_store/realm/requests.cljs @@ -1,45 +1,16 @@ (ns status-im.data-store.realm.requests (:require [status-im.data-store.realm.core :as realm])) -(defn get-all +(defn get-all-unanswered [] - (realm/get-all @realm/account-realm :request)) - -(defn get-all-as-list - [] - (realm/js-object->clj (get-all))) - -(defn get-open-by-chat-id - [chat-id] - (-> (realm/get-by-fields @realm/account-realm :request :and [[:chat-id chat-id] - [:status "open"]]) - (realm/sorted :added :desc) + (-> @realm/account-realm + (realm/get-by-field :request :status "open") realm/js-object->clj)) -;; NOTE(oskarth): phone command in Console can be answered again, so we want to list this -;; TODO(oskarth): Refactor this, either by extending and/or query or changing status of message -(defn- get-reanswerable-by-chat-id - [chat-id] - (-> (realm/get-by-fields @realm/account-realm :request :and [[:chat-id chat-id] - [:type "phone"] - [:status "answered"]]) - (realm/sorted :added :desc) - realm/js-object->clj)) - -(defn get-available-by-chat-id - [chat-id] - (-> ((juxt get-open-by-chat-id get-reanswerable-by-chat-id) chat-id) - flatten - vec)) - (defn save [request] (realm/save @realm/account-realm :request request true)) -(defn save-all - [requests] - (realm/save-all @realm/account-realm :request requests true)) - (defn- get-by-message-id [chat-id message-id] (-> @realm/account-realm diff --git a/src/status_im/data_store/realm/schemas/account/core.cljs b/src/status_im/data_store/realm/schemas/account/core.cljs index 23c1be959c..a26e5134e0 100644 --- a/src/status_im/data_store/realm/schemas/account/core.cljs +++ b/src/status_im/data_store/realm/schemas/account/core.cljs @@ -17,6 +17,7 @@ [status-im.data-store.realm.schemas.account.v16.core :as v16] [status-im.data-store.realm.schemas.account.v17.core :as v17] [status-im.data-store.realm.schemas.account.v18.core :as v18] + [status-im.data-store.realm.schemas.account.v19.core :as v19] )) ;; TODO(oskarth): Add failing test if directory vXX exists but isn't in schemas. @@ -76,4 +77,7 @@ {:schema v18/schema :schemaVersion 18 :migration v18/migration} + {:schema v19/schema + :schemaVersion 19 + :migration v19/migration} ]) diff --git a/src/status_im/data_store/realm/schemas/account/v19/contact.cljs b/src/status_im/data_store/realm/schemas/account/v19/contact.cljs new file mode 100644 index 0000000000..4c240afc5e --- /dev/null +++ b/src/status_im/data_store/realm/schemas/account/v19/contact.cljs @@ -0,0 +1,30 @@ +(ns status-im.data-store.realm.schemas.account.v19.contact + (:require [taoensso.timbre :as log])) + +(def schema {:name :contact + :primaryKey :whisper-identity + :properties {:address {:type :string :optional true} + :whisper-identity :string + :name {:type :string :optional true} + :photo-path {:type :string :optional true} + :last-updated {:type :int :default 0} + :last-online {:type :int :default 0} + :pending? {:type :bool :default false} + :hide-contact? {:type :bool :default false} + :status {:type :string :optional true} + :fcm-token {:type :string :optional true} + :description {:type :string :optional true} + :public-key {:type :string + :optional true} + :private-key {:type :string + :optional true} + :dapp? {:type :bool + :default false} + :dapp-url {:type :string + :optional true} + :bot-url {:type :string + :optional true} + :dapp-hash {:type :int + :optional true} + :debug? {:type :bool + :default false}}}) diff --git a/src/status_im/data_store/realm/schemas/account/v19/core.cljs b/src/status_im/data_store/realm/schemas/account/v19/core.cljs new file mode 100644 index 0000000000..6940802ef7 --- /dev/null +++ b/src/status_im/data_store/realm/schemas/account/v19/core.cljs @@ -0,0 +1,100 @@ +(ns status-im.data-store.realm.schemas.account.v19.core + (:require [status-im.data-store.realm.schemas.account.v11.chat :as chat] + [status-im.data-store.realm.schemas.account.v1.chat-contact :as chat-contact] + [status-im.data-store.realm.schemas.account.v6.command :as command] + [status-im.data-store.realm.schemas.account.v9.command-parameter :as command-parameter] + [status-im.data-store.realm.schemas.account.v19.contact :as contact] + [status-im.data-store.realm.schemas.account.v1.discover :as discover] + [status-im.data-store.realm.schemas.account.v1.kv-store :as kv-store] + [status-im.data-store.realm.schemas.account.v10.message :as message] + [status-im.data-store.realm.schemas.account.v12.pending-message :as pending-message] + [status-im.data-store.realm.schemas.account.v1.processed-message :as processed-message] + [status-im.data-store.realm.schemas.account.v19.request :as request] + [status-im.data-store.realm.schemas.account.v1.tag :as tag] + [status-im.data-store.realm.schemas.account.v1.user-status :as user-status] + [status-im.data-store.realm.schemas.account.v5.contact-group :as contact-group] + [status-im.data-store.realm.schemas.account.v5.group-contact :as group-contact] + [status-im.data-store.realm.schemas.account.v8.local-storage :as local-storage] + [status-im.data-store.realm.schemas.account.v13.handler-data :as handler-data] + [taoensso.timbre :as log] + [cljs.reader :as reader])) + +(def schema [chat/schema + chat-contact/schema + command/schema + command-parameter/schema + contact/schema + discover/schema + kv-store/schema + message/schema + pending-message/schema + processed-message/schema + request/schema + tag/schema + user-status/schema + contact-group/schema + group-contact/schema + local-storage/schema + handler-data/schema]) + +(defn remove-contact! [new-realm whisper-identity] + (when-let [contact (some-> new-realm + (.objects "contact") + (.filtered (str "whisper-identity = \"" whisper-identity "\"")) + (aget 0))] + (log/debug "v19 Removing contact" (pr-str contact)) + (.delete new-realm contact))) + +(def owner-command->new-props + {;; console commands + ["console" "password"] {:content-command-ref ["console" :response 42 "password"] + :content-command-scope-bitmask 42} + ["console" "debug"] {:content-command-ref ["console" :command 50 "debug"] + :content-command-scope-bitmask 50} + ["console" "phone"] {:content-command-ref ["console" :response 50 "phone"] + :content-command-scope-bitmask 50} + ["console" "confirmation-code"] {:content-command-ref ["console" :response 50 "confirmation-code"] + :content-command-scope-bitmask 50} + ["console" "faucet"] {:content-command-ref ["console" :command 50 "faucet"] + :content-command-scope-bitmask 50} + ;; mailman commands + ["mailman" "location"] {:content-command-ref ["mailman" :command 215 "location"] + :content-command-scope-bitmask 215} + ;; transactor personal + ["transactor-personal" "send"] {:content-command-ref ["transactor" :command 83 "send"] + :content-command-scope-bitmask 83 + :bot "transactor"} + ["transactor-personal" "request"] {:content-command-ref ["transactor" :command 83 "request"] + :content-command-scope-bitmask 83 + :bot "transactor"} + ;; transactor group + ["transactor-group" "send"] {:content-command-ref ["transactor" :command 85 "send"] + :content-command-scope-bitmask 85 + :bot "transactor"} + ["transactor-group" "request"] {:content-command-ref ["transactor" :command 85 "request"] + :content-command-scope-bitmask 85 + :bot "transactor"}}) + +(def console-requests->new-props + {;; console + ["password"] {:content-command-ref ["console" :response 42 "password"]} + ["phone"] {:content-command-ref ["console" :response 50 "phone"]} + ["confirmation-code"] {:content-command-ref ["console" :response 50 "confirmation-code"]}}) + +(defn update-commands [selector mapping new-realm content-type] + (some-> new-realm + (.objects "message") + (.filtered (str "content-type = \"" content-type "\"")) + (.map (fn [object _ _] + (let [content (reader/read-string (aget object "content")) + new-props (get owner-command->new-props (selector content)) + new-content (merge content new-props)] + (log/debug "migrating v19 command/request database, updating: " content " with: " new-props) + (aset object "content" (pr-str new-content))))))) + +(defn migration [old-realm new-realm] + (log/debug "migrating v19 account database: " old-realm new-realm) + (remove-contact! new-realm "transactor-personal") + (remove-contact! new-realm "transactor-group") + (update-commands owner-command->new-props (juxt :bot :command) new-realm "command") + (update-commands console-requests->new-props (juxt :command) new-realm "command-request")) diff --git a/src/status_im/data_store/realm/schemas/account/v19/request.cljs b/src/status_im/data_store/realm/schemas/account/v19/request.cljs new file mode 100644 index 0000000000..32990b53af --- /dev/null +++ b/src/status_im/data_store/realm/schemas/account/v19/request.cljs @@ -0,0 +1,8 @@ +(ns status-im.data-store.realm.schemas.account.v19.request) + +(def schema {:name :request + :properties {:message-id :string + :chat-id :string + :response :string + :status {:type :string + :default "open"}}}) diff --git a/src/status_im/data_store/requests.cljs b/src/status_im/data_store/requests.cljs index 6902385e36..6582b0afb3 100644 --- a/src/status_im/data_store/requests.cljs +++ b/src/status_im/data_store/requests.cljs @@ -1,26 +1,14 @@ (ns status-im.data-store.requests (:require [status-im.data-store.realm.requests :as data-store])) -(defn get-all +(defn get-all-unanswered [] - (data-store/get-all-as-list)) - -(defn get-open-by-chat-id - [chat-id] - (data-store/get-open-by-chat-id chat-id)) - -(defn get-available-by-chat-id - [chat-id] - (data-store/get-available-by-chat-id chat-id)) + (data-store/get-all-unanswered)) (defn save [request] (data-store/save request)) -(defn save-all - [requests] - (data-store/save-all requests)) - (defn mark-as-answered [chat-id message-id] (data-store/mark-as-answered chat-id message-id)) diff --git a/src/status_im/protocol/handlers.cljs b/src/status_im/protocol/handlers.cljs index 10f1824d9d..dc4fb012a7 100644 --- a/src/status_im/protocol/handlers.cljs +++ b/src/status_im/protocol/handlers.cljs @@ -398,8 +398,7 @@ ios-error? (re-find (re-pattern "Could not connect to the server.") message) android-error? (re-find (re-pattern "Failed to connect") message)] (when (or ios-error? android-error?) - (when android-error? (status/init-jail)) - (dispatch [:load-commands!])))))) + (when android-error? (status/init-jail))))))) (register-handler :load-processed-messages diff --git a/src/status_im/ui/components/list_selection.cljs b/src/status_im/ui/components/list_selection.cljs index 390fc405a4..c612656c0f 100644 --- a/src/status_im/ui/components/list_selection.cljs +++ b/src/status_im/ui/components/list_selection.cljs @@ -28,7 +28,7 @@ :default)) :cancel-text (label :t/sharing-cancel)}))) -(defn browse [command link] +(defn browse [browse-command link] (let [list-selection-fn (:list-selection-fn platform-specific)] (list-selection-fn {:title (label :t/browsing-title) :options [{:text "@browse"} @@ -37,7 +37,7 @@ (case index 0 (do (dispatch [:select-chat-input-command - (assoc (first command) :prefill [link]) + (assoc browse-command :prefill [link]) nil true]) (js/setTimeout #(dispatch [:send-current-message]) 100)) diff --git a/src/status_im/ui/screens/accounts/events.cljs b/src/status_im/ui/screens/accounts/events.cljs index 9cab6c38f4..26130a58b2 100644 --- a/src/status_im/ui/screens/accounts/events.cljs +++ b/src/status_im/ui/screens/accounts/events.cljs @@ -1,23 +1,19 @@ (ns status-im.ui.screens.accounts.events - (:require - status-im.ui.screens.accounts.login.events - status-im.ui.screens.accounts.recover.events - - [status-im.data-store.accounts :as accounts-store] - [re-frame.core :as re-frame] - [taoensso.timbre :as log] - [status-im.protocol.core :as protocol] - [status-im.native-module.core :as status] - [status-im.utils.types :refer [json->clj]] - [status-im.utils.identicon :refer [identicon]] - [status-im.utils.random :as random] - [clojure.string :as str] - [status-im.utils.datetime :as time] - [status-im.utils.handlers :as handlers] - [status-im.ui.screens.accounts.statuses :as statuses] - [status-im.utils.signing-phrase.core :as signing-phrase] - [status-im.utils.gfycat.core :refer [generate-gfy]] - [status-im.utils.hex :as utils.hex])) + (:require [status-im.data-store.accounts :as accounts-store] + [re-frame.core :as re-frame] + [taoensso.timbre :as log] + [status-im.protocol.core :as protocol] + [status-im.native-module.core :as status] + [status-im.utils.types :refer [json->clj]] + [status-im.utils.identicon :refer [identicon]] + [status-im.utils.random :as random] + [clojure.string :as str] + [status-im.utils.datetime :as time] + [status-im.utils.handlers :as handlers] + [status-im.ui.screens.accounts.statuses :as statuses] + [status-im.utils.signing-phrase.core :as signing-phrase] + [status-im.utils.gfycat.core :refer [generate-gfy]] + [status-im.utils.hex :as utils.hex])) ;;;; Helper fns @@ -26,6 +22,7 @@ [db password] {:db (assoc db :accounts/creating-account? true) ::create-account password + ;; TODO(janherich): get rid of this shitty delayed dispatch once sending commands/msgs is refactored :dispatch-later [{:ms 400 :dispatch [:account-generation-message]}]}) ;;;; COFX @@ -40,6 +37,16 @@ (fn [coeffects _] (assoc coeffects :all-accounts (accounts-store/get-all)))) +(re-frame/reg-cofx + ::get-signing-phrase + (fn [coeffects _] + (assoc coeffects :signing-phrase (signing-phrase/generate)))) + +(re-frame/reg-cofx + ::get-status + (fn [coeffects _] + (assoc coeffects :status (rand-nth statuses/data)))) + ;;;; FX (re-frame/reg-fx @@ -47,33 +54,12 @@ (fn [account] (accounts-store/save account true))) -(defn account-created [result password] - (let [data (json->clj result) - public-key (:pubkey data) - address (:address data) - mnemonic (:mnemonic data) - phrase (signing-phrase/generate) - {:keys [public private]} (protocol/new-keypair!) - account {:public-key public-key - :address address - :name (generate-gfy public-key) - :status (rand-nth statuses/data) - :signed-up? true - :updates-public-key public - :updates-private-key private - :photo-path (identicon public-key) - :signing-phrase phrase}] - (log/debug "account-created") - (when-not (str/blank? public-key) - (re-frame/dispatch [:show-mnemonic mnemonic phrase]) - (re-frame/dispatch [:add-account account password])))) - (re-frame/reg-fx ::create-account (fn [password] (status/create-account password - #(account-created % password)))) + #(re-frame/dispatch [::account-created (json->clj %) password])))) (re-frame/reg-fx ::broadcast-account-update @@ -104,21 +90,42 @@ :private updates-private-key}}}})))) ;;;; Handlers +(defn add-account + "Takes db and new account, creates map of effects describing adding account to database and realm" + [{:keys [network] :networks/keys [networks] :as db} {:keys [address] :as account}] + (let [enriched-account (assoc account + :network network + :networks networks + :address address)] + {:db (assoc-in db [:accounts/accounts address] enriched-account) + ::save-account enriched-account})) + +;; TODO(janherich) we have this handler here only because of the tests, refactor/improve tests ASAP (handlers/register-handler-fx :add-account - (fn [{{:keys [network] - :networks/keys [networks] - :as db} :db} [_ {:keys [address] :as account} password]] - (let [address (utils.hex/normalize-hex address) - account' (assoc account - :network network - :networks networks - :address address)] - (merge - {:db (assoc-in db [:accounts/accounts address] account') - ::save-account account'} - (when password - {:dispatch-later [{:ms 400 :dispatch [:login-account address password true]}]}))))) + (fn [{:keys [db]} [_ new-account]] + (add-account db new-account))) + +(handlers/register-handler-fx + ::account-created + [re-frame/trim-v (re-frame/inject-cofx :get-new-keypair!) + (re-frame/inject-cofx ::get-signing-phrase) (re-frame/inject-cofx ::get-status)] + (fn [{:keys [keypair signing-phrase status db] :as cofx} [{:keys [pubkey address mnemonic]} password]] + (let [normalized-address (utils.hex/normalize-hex address) + account {:public-key pubkey + :address normalized-address + :name (generate-gfy pubkey) + :status status + :signed-up? true + :updates-public-key (:public keypair) + :updates-private-key (:private keypair) + :photo-path (identicon pubkey) + :signing-phrase signing-phrase}] + (log/debug "account-created") + (when-not (str/blank? pubkey) + (-> (add-account db account) + (assoc :dispatch-n [[:show-mnemonic mnemonic signing-phrase] + [:login-account normalized-address password true]])))))) (handlers/register-handler-fx :create-new-account-handler diff --git a/src/status_im/ui/screens/accounts/login/events.cljs b/src/status_im/ui/screens/accounts/login/events.cljs index 469c097b0e..66d0336744 100644 --- a/src/status_im/ui/screens/accounts/login/events.cljs +++ b/src/status_im/ui/screens/accounts/login/events.cljs @@ -1,10 +1,11 @@ (ns status-im.ui.screens.accounts.login.events (:require status-im.ui.screens.accounts.login.navigation - + [re-frame.core :refer [dispatch reg-fx]] [status-im.utils.handlers :refer [register-handler-db register-handler-fx]] [taoensso.timbre :as log] + [status-im.chat.sign-up :as sign-up] [status-im.utils.types :refer [json->clj]] [status-im.data-store.core :as data-store] [status-im.native-module.core :as status] @@ -65,7 +66,7 @@ (let [{:keys [network config]} (get-network-by-address db address)] {:initialize-geth-fx config :db (assoc db :network network - :node/after-start [::login-account address password])})) + :node/after-start [::login-account address password])})) (register-handler-fx ::start-node @@ -116,12 +117,15 @@ (register-handler-fx :change-account-handler (fn [{db :db} [_ error address new-account?]] - (if (nil? error) - {:db (dissoc db :accounts/login) - :dispatch-n [[:stop-debugging] - [:initialize-account address] - [:navigate-to-clean :chat-list] - (if new-account? - [:navigate-to-chat console-chat-id] - [:navigate-to :chat-list])]} - (log/debug "Error changing acount: " error)))) + (let [recover-in-progress? (:accounts/recover db)] + (if (nil? error) + {:db (dissoc db :accounts/login) + :dispatch-n [[:stop-debugging] + [:initialize-account address (when (or new-account? + recover-in-progress?) + sign-up/start-signup-events)] + [:navigate-to-clean :chat-list] + (if new-account? + [:navigate-to-chat console-chat-id] + [:navigate-to :chat-list])]} + (log/debug "Error changing acount: " error))))) diff --git a/src/status_im/ui/screens/accounts/recover/events.cljs b/src/status_im/ui/screens/accounts/recover/events.cljs index 5714dc705f..20c06879a4 100644 --- a/src/status_im/ui/screens/accounts/recover/events.cljs +++ b/src/status_im/ui/screens/accounts/recover/events.cljs @@ -4,13 +4,15 @@ [re-frame.core :refer [reg-fx inject-cofx dispatch]] [status-im.native-module.core :as status] + [status-im.ui.screens.accounts.events :as accounts-events] [status-im.utils.types :refer [json->clj]] [status-im.utils.identicon :refer [identicon]] [taoensso.timbre :as log] [clojure.string :as str] [status-im.utils.handlers :refer [register-handler-fx]] [status-im.utils.gfycat.core :refer [generate-gfy]] - [status-im.utils.signing-phrase.core :as signing-phrase])) + [status-im.utils.signing-phrase.core :as signing-phrase] + [status-im.utils.hex :as utils.hex])) ;;;; FX @@ -30,7 +32,7 @@ (fn [{:keys [db keypair]} [_ result]] (let [data (json->clj result) public-key (:pubkey data) - address (:address data) + address (-> data :address utils.hex/normalize-hex) phrase (signing-phrase/generate) {:keys [public private]} keypair account {:public-key public-key @@ -43,9 +45,9 @@ :signing-phrase phrase}] (log/debug "account-recovered") (when-not (str/blank? public-key) - {:db (update db :accounts/recover assoc :passphrase "" :password "") - :dispatch-n [[:add-account account] - [:navigate-to-clean :accounts]]})))) + (-> db + (accounts-events/add-account account) + (assoc :dispatch [:navigate-to-clean :accounts])))))) (register-handler-fx :recover-account diff --git a/src/status_im/ui/screens/accounts/subs.cljs b/src/status_im/ui/screens/accounts/subs.cljs index 836872e517..6ba1ce588a 100644 --- a/src/status_im/ui/screens/accounts/subs.cljs +++ b/src/status_im/ui/screens/accounts/subs.cljs @@ -4,4 +4,11 @@ (reg-sub :get-accounts (fn [db _] - (get db :accounts/accounts))) \ No newline at end of file + (get db :accounts/accounts))) + +(reg-sub + :get-current-account + :<- [:get :accounts/current-account-id] + :<- [:get-accounts] + (fn [[account-id accounts]] + (some-> accounts (get account-id)))) diff --git a/src/status_im/ui/screens/contacts/db.cljs b/src/status_im/ui/screens/contacts/db.cljs index 61c8a28474..60c9084cc5 100644 --- a/src/status_im/ui/screens/contacts/db.cljs +++ b/src/status_im/ui/screens/contacts/db.cljs @@ -28,17 +28,17 @@ (spec/def :contact/last-updated (spec/nilable int?)) (spec/def :contact/last-online (spec/nilable int?)) (spec/def :contact/pending? boolean?) -(spec/def :contact/mixable? boolean?) (spec/def :contact/unremovable? boolean?) +(spec/def :contact/hide-contact? boolean?) (spec/def :contact/dapp? boolean?) (spec/def :contact/dapp-url (spec/nilable string?)) (spec/def :contact/dapp-hash (spec/nilable int?)) -(spec/def :contact/bot-url (spec/nilable string?)) -(spec/def :contact/global-command (spec/nilable map?)) -(spec/def :contact/commands (spec/nilable (spec/map-of keyword? seq?))) -(spec/def :contact/responses (spec/nilable (spec/map-of keyword? seq?))) -(spec/def :contact/commands-loaded? (spec/nilable boolean?)) +(spec/def :contact/bot-url (spec/nilable string?)) +(spec/def :contact/command (spec/nilable (spec/map-of int? map?))) +(spec/def :contact/response (spec/nilable (spec/map-of int? map?))) +(spec/def :contact/jail-loaded? (spec/nilable boolean?)) +(spec/def :contact/jail-loaded-events (spec/nilable seq?)) (spec/def :contact/subscriptions (spec/nilable map?)) ;true when contact added using status-dev-cli (spec/def :contact/debug? boolean?) @@ -54,18 +54,17 @@ :contact/status :contact/last-updated :contact/last-online - :contact/pending? - :contact/mixable? - :contact/scope + :contact/pending? + :contact/hide-contact? :contact/unremovable? :contact/dapp? :contact/dapp-url :contact/dapp-hash - :contact/bot-url - :contact/global-command - :contact/commands-loaded? - :contact/commands - :contact/responses + :contact/bot-url + :contact/jail-loaded? + :contact/jail-loaded-events + :contact/command + :contact/response :contact/debug? :contact/subscriptions :contact/fcm-token diff --git a/src/status_im/ui/screens/contacts/events.cljs b/src/status_im/ui/screens/contacts/events.cljs index 94665b5bbf..e64d32cb7e 100644 --- a/src/status_im/ui/screens/contacts/events.cljs +++ b/src/status_im/ui/screens/contacts/events.cljs @@ -7,7 +7,7 @@ [status-im.protocol.core :as protocol] [status-im.utils.utils :refer [http-post]] [status-im.utils.phone-number :refer [format-phone-number]] - [status-im.utils.random :as random] + [status-im.utils.random :as random] [taoensso.timbre :as log] [cljs.reader :refer [read-string]] [status-im.utils.js-resources :as js-res] @@ -17,6 +17,8 @@ [status-im.utils.gfycat.core :refer [generate-gfy]] [status-im.i18n :refer [label]] [status-im.ui.screens.contacts.navigation] + [status-im.chat.sign-up :as sign-up] + [status-im.commands.events.loading :as loading-events] [cljs.spec.alpha :as spec])) ;;;; COFX @@ -29,8 +31,9 @@ (reg-cofx ::get-default-contacts-and-groups (fn [coeffects _] - (assoc coeffects :default-contacts js-res/default-contacts - :default-groups js-res/default-contact-groups))) + (assoc coeffects + :default-contacts js-res/default-contacts + :default-groups js-res/default-contact-groups))) ;;;; FX @@ -125,8 +128,9 @@ (defn- add-identity [contacts-by-hash contacts] (map (fn [{:keys [phone-number-hash whisper-identity address]}] (let [contact (contacts-by-hash phone-number-hash)] - (assoc contact :whisper-identity whisper-identity - :address address))) + (assoc contact + :whisper-identity whisper-identity + :address address))) (js->clj contacts))) (reg-fx @@ -213,23 +217,25 @@ ;; NOTE(oskarth): We now overwrite default contacts upon upgrade with default_contacts.json data. (defn- prepare-default-contacts-events [contacts default-contacts] - [[:add-contacts - (for [[id {:keys [name photo-path public-key add-chat? pending? description - dapp? dapp-url dapp-hash bot-url unremovable? mixable?]}] default-contacts - :let [id' (clojure.core/name id)]] - {:whisper-identity id' - :address (public-key->address id') - :name (:en name) - :photo-path photo-path - :public-key public-key - :unremovable? (boolean unremovable?) - :mixable? (boolean mixable?) - :pending? pending? - :dapp? dapp? - :dapp-url (:en dapp-url) - :bot-url bot-url - :description description - :dapp-hash dapp-hash})]]) + (let [default-contacts + (for [[id {:keys [name photo-path public-key add-chat? pending? description + dapp? dapp-url dapp-hash bot-url unremovable? hide-contact?]}] default-contacts + :let [id' (clojure.core/name id)]] + {:whisper-identity id' + :address (public-key->address id') + :name (:en name) + :photo-path photo-path + :public-key public-key + :unremovable? (boolean unremovable?) + :hide-contact? (boolean hide-contact?) + :pending? pending? + :dapp? dapp? + :dapp-url (:en dapp-url) + :bot-url bot-url + :description description + :dapp-hash dapp-hash}) + all-default-contacts (conj default-contacts sign-up/console-contact)] + [[:add-contacts all-default-contacts]])) (defn- prepare-add-chat-events [contacts default-contacts] (for [[id {:keys [name add-chat?]}] default-contacts @@ -237,12 +243,6 @@ :when (and (not (get contacts id')) add-chat?)] [:add-chat id' {:name (:en name)}])) -(defn- prepare-bot-commands-events [contacts default-contacts] - (for [[id {:keys [bot-url]}] default-contacts - :let [id' (clojure.core/name id)] - :when bot-url] - [:load-commands! id'])) - (defn- prepare-add-contacts-to-groups-events [contacts default-contacts] (let [groups (for [[id {:keys [groups]}] default-contacts :let [id' (clojure.core/name id)] @@ -258,14 +258,13 @@ (register-handler-fx :load-default-contacts! [(inject-cofx ::get-default-contacts-and-groups)] - (fn [{:keys [db default-contacts default-groups]} _] + (fn [{:keys [db default-contacts default-groups] :as cofx} _] (let [{:contacts/keys [contacts] :group/keys [contact-groups]} db] {:dispatch-n (concat - (prepare-default-groups-events contact-groups default-groups) - (prepare-default-contacts-events contacts default-contacts) - (prepare-add-chat-events contacts default-contacts) - (prepare-bot-commands-events contacts default-contacts) - (prepare-add-contacts-to-groups-events contacts default-contacts))}))) + (prepare-default-groups-events contact-groups default-groups) + (prepare-default-contacts-events contacts default-contacts) + (prepare-add-chat-events contacts default-contacts) + (prepare-add-contacts-to-groups-events contacts default-contacts))}))) (register-handler-fx :load-contacts @@ -273,7 +272,7 @@ (fn [{:keys [db all-contacts]} _] (let [contacts-list (map #(vector (:whisper-identity %) %) all-contacts) contacts (into {} contacts-list)] - {:db (assoc db :contacts/contacts contacts) + {:db (update db :contacts/contacts #(merge contacts %)) ;; TODO (yenda) this mapv was dispatching useless events, fixed but is it necessary if ;; it was dispatching useless events before with nil ;;:dispatch-n (mapv (fn [[_ contact]] [:watch-contact contact]) contacts) @@ -281,16 +280,21 @@ (register-handler-fx :add-contacts - (fn [{:keys [db]} [_ new-contacts]] + [(inject-cofx :get-local-storage-data)] + (fn [{:keys [db] :as cofx} [_ new-contacts]] (let [{:contacts/keys [contacts]} db new-contacts' (->> new-contacts (map #(update-pending-status contacts %)) ;; NOTE(oskarth): Overwriting default contacts here ;;(remove #(identities (:whisper-identity %))) (map #(vector (:whisper-identity %) %)) - (into {}))] - {:db (update db :contacts/contacts merge new-contacts') - ::save-contacts! (vals new-contacts')}))) + (into {})) + fx {:db (update db :contacts/contacts merge new-contacts') + ::save-contacts! (vals new-contacts')}] + (transduce (map second) + (completing (partial loading-events/load-commands (assoc cofx :db (:db fx)))) + fx + new-contacts')))) (register-handler-db :remove-contacts-click-handler @@ -335,8 +339,9 @@ contact (if-let [contact-info (get-in chats [chat-or-whisper-id :contact-info])] (read-string contact-info) (get contacts chat-or-whisper-id)) - contact' (assoc contact :address (public-key->address chat-or-whisper-id) - :pending? false)] + contact' (assoc contact + :address (public-key->address chat-or-whisper-id) + :pending? false)] {:dispatch-n [[::add-new-contact contact'] [:watch-contact contact'] [:discoveries-send-portions chat-or-whisper-id]]}))) @@ -392,8 +397,8 @@ :hide-contact (fn [{:keys [db]} [_ {:keys [whisper-identity] :as contact}]] {::stop-watching-contact (merge - (select-keys db [:web3]) - (select-keys contact [:whisper-identity])) + (select-keys db [:web3]) + (select-keys contact [:whisper-identity])) :dispatch-n [[:update-contact! {:whisper-identity whisper-identity :pending? true}] [:account-update-keys]]})) diff --git a/src/status_im/ui/screens/contacts/subs.cljs b/src/status_im/ui/screens/contacts/subs.cljs index dfc6cd4ad3..9df9f5551c 100644 --- a/src/status_im/ui/screens/contacts/subs.cljs +++ b/src/status_im/ui/screens/contacts/subs.cljs @@ -24,8 +24,8 @@ :<- [:get-contacts] (fn [contacts] (->> contacts - (remove (fn [[_ {:keys [pending? mixable?]}]] - (or pending? mixable?))) + (remove (fn [[_ {:keys [pending? hide-contact?]}]] + (or pending? hide-contact?))) (sort-contacts)))) (reg-sub :all-added-people-contacts diff --git a/src/status_im/ui/screens/db.cljs b/src/status_im/ui/screens/db.cljs index 28883bbb4d..b5fc899b38 100644 --- a/src/status_im/ui/screens/db.cljs +++ b/src/status_im/ui/screens/db.cljs @@ -9,6 +9,7 @@ status-im.ui.screens.group.db status-im.chat.specs status-im.chat.new-public-chat.db + status-im.commands.specs status-im.ui.screens.profile.db status-im.ui.screens.discover.db status-im.ui.screens.network-settings.db)) @@ -150,27 +151,24 @@ :chat/chat-ui-props :chat/chat-list-ui-props :chat/layout-height - :chat/expandable-view-height-to-value - :chat/global-commands + :chat/expandable-view-height-to-value :chat/loading-allowed :chat/message-data :chat/message-id->transaction-id :chat/message-status :chat/unviewed-messages :chat/selected-participants - :chat/chat-loaded-callbacks - :chat/commands-callbacks + :chat/chat-loaded-callbacks :chat/command-hash-valid? :chat/public-group-topic :chat/confirmation-code-sms-listener :chat/messages :chat/handler-data - :chat/loaded-chats - :chat/bot-subscriptions - :chat/new-request + :chat/loaded-chats :chat/raw-unviewed-messages :chat/bot-db :chat/geolocation + :commands/access-scope->commands-responses :discoveries/discoveries :discoveries/discover-search-tags :discoveries/discover-current-dapp diff --git a/src/status_im/ui/screens/events.cljs b/src/status_im/ui/screens/events.cljs index a953541a99..c92408de28 100644 --- a/src/status_im/ui/screens/events.cljs +++ b/src/status_im/ui/screens/events.cljs @@ -2,11 +2,13 @@ (:require status-im.bots.events status-im.chat.handlers status-im.commands.handlers.jail - status-im.commands.handlers.loading + status-im.commands.events.loading status-im.commands.handlers.debug status-im.network.handlers status-im.protocol.handlers status-im.ui.screens.accounts.events + status-im.ui.screens.accounts.login.events + status-im.ui.screens.accounts.recover.events status-im.ui.screens.contacts.events status-im.ui.screens.discover.events status-im.ui.screens.group.chat-settings.events @@ -60,21 +62,21 @@ ;;;; COFX (reg-cofx - :now - (fn [coeffects _] - (assoc coeffects :now (time/now-ms)))) + :now + (fn [coeffects _] + (assoc coeffects :now (time/now-ms)))) (reg-cofx - :random-id - (fn [coeffects _] - (assoc coeffects :random-id (random/id)))) + :random-id + (fn [coeffects _] + (assoc coeffects :random-id (random/id)))) (reg-cofx - :random-id-seq - (fn [coeffects _] - (assoc coeffects :random-id-seq - ((fn rand-id-seq [] - (cons (random/id) (lazy-seq (rand-id-seq)))))))) + :random-id-seq + (fn [coeffects _] + (assoc coeffects :random-id-seq + ((fn rand-id-seq [] + (cons (random/id) (lazy-seq (rand-id-seq)))))))) ;;;; FX @@ -107,13 +109,26 @@ #(dispatch (success-event-creator %)) #(dispatch (failure-event-creator %))))) -(reg-fx - :http-get - (fn [{:keys [url success-event-creator failure-event-creator]}] +(defn- http-get [{:keys [url response-validator success-event-creator failure-event-creator]}] + (if response-validator + (utils/http-get url + response-validator + #(dispatch (success-event-creator %)) + #(dispatch (failure-event-creator %))) (utils/http-get url #(dispatch (success-event-creator %)) #(dispatch (failure-event-creator %))))) +(reg-fx + :http-get + http-get) + +(reg-fx + :http-get-n + (fn [calls] + (doseq [call calls] + (http-get call)))) + (reg-fx ::init-store (fn [] @@ -123,29 +138,29 @@ ::initialize-crypt-fx (fn [] (crypt/gen-random-bytes - 1024 - (fn [{:keys [error buffer]}] - (if error - (log/error "Failed to generate random bytes to initialize sjcl crypto") - (->> (.toString buffer "hex") - (.toBits (.. dependencies/eccjs -sjcl -codec -hex)) - (.addEntropy (.. dependencies/eccjs -sjcl -random)))))))) + 1024 + (fn [{:keys [error buffer]}] + (if error + (log/error "Failed to generate random bytes to initialize sjcl crypto") + (->> (.toString buffer "hex") + (.toBits (.. dependencies/eccjs -sjcl -codec -hex)) + (.addEntropy (.. dependencies/eccjs -sjcl -random)))))))) (defn move-to-internal-storage [config] (status/move-to-internal-storage - #(status/start-node config))) + #(status/start-node config))) (reg-fx :initialize-geth-fx (fn [config] (status/should-move-to-internal-storage? - (fn [should-move?] - (if should-move? - (dispatch [:request-permissions - [:read-external-storage] - #(move-to-internal-storage config) - #(dispatch [:move-to-internal-failure-message])]) - (status/start-node config)))))) + (fn [should-move?] + (if should-move? + (dispatch [:request-permissions + [:read-external-storage] + #(move-to-internal-storage config) + #(dispatch [:move-to-internal-failure-message])]) + (status/start-node config)))))) (reg-fx ::status-module-initialized-fx @@ -162,8 +177,8 @@ (fn [] (when config/testfairy-enabled? (utils/show-popup - (i18n/label :testfairy-title) - (i18n/label :testfairy-message))))) + (i18n/label :testfairy-title) + (i18n/label :testfairy-message))))) (reg-fx ::get-fcm-token-fx @@ -226,45 +241,52 @@ (register-handler-db :initialize-account-db - (fn [{:keys [accounts/accounts networks/networks network view-id navigation-stack + (fn [{:keys [accounts/accounts contacts/contacts networks/networks + network view-id navigation-stack chats + access-scope->commands-responses layout-height status-module-initialized? status-node-started?] :or [network (get app-db :network)] :as db} [_ address]] - (-> app-db - (assoc :current-chat-id console-chat-id - :accounts/current-account-id address - ;; TODO (yenda) bad, this is derived data and shouldn't be stored in the db - ;; the cost of retrieving public key from db with a function taking using - ;; current-account-id is negligeable - :current-public-key (:public-key (accounts address)) - :view-id view-id - :navigation-stack navigation-stack - :status-module-initialized? (or platform/ios? js/goog.DEBUG status-module-initialized?) - :status-node-started? status-node-started? - :accounts/accounts accounts - :accounts/creating-account? false - :networks/networks networks - :network network)))) + (let [console-contact (get contacts console-chat-id)] + (cond-> (assoc app-db + :access-scope->commands-responses access-scope->commands-responses + :accounts/current-account-id address + :layout-height layout-height + ;; TODO (yenda) bad, this is derived data and shouldn't be stored in the db + ;; the cost of retrieving public key from db with a function taking using + ;; current-account-id is negligeable + :current-public-key (:public-key (accounts address)) + :view-id view-id + :navigation-stack navigation-stack + :status-module-initialized? (or platform/ios? js/goog.DEBUG status-module-initialized?) + :status-node-started? status-node-started? + :accounts/accounts accounts + :accounts/creating-account? false + :networks/networks networks + :network network) + console-contact + (assoc :contacts/contacts {console-chat-id console-contact}))))) (register-handler-fx :initialize-account - (fn [_ [_ address]] - {:dispatch-n [[:initialize-account-db address] - [:load-processed-messages] - [:initialize-protocol address] - [:initialize-sync-listener] - [:initialize-chats] - [:load-contacts] - [:load-contact-groups] - [:init-chat] - [:init-discoveries] - [:initialize-debugging {:address address}] - [:send-account-update-if-needed] - [:start-requesting-discoveries] - [:remove-old-discoveries!] - [:update-wallet] - [:update-transactions] - [:get-fcm-token]]})) + (fn [_ [_ address events-after]] + {:dispatch-n (cond-> [[:initialize-account-db address] + [:load-processed-messages] + [:initialize-protocol address] + [:initialize-sync-listener] + [:initialize-chats] + [:load-contacts] + [:load-contact-groups] + [:init-discoveries] + [:initialize-debugging {:address address}] + [:send-account-update-if-needed] + [:start-requesting-discoveries] + [:remove-old-discoveries!] + [:update-wallet] + [:update-transactions] + [:get-fcm-token]] + (seq events-after) + (into events-after))})) (register-handler-fx :check-console-chat @@ -277,10 +299,9 @@ :view-id view :navigation-stack (list view))} (when (or (empty? accounts) open-console?) - {:dispatch-n (concat [[:init-console-chat] - [:load-commands!]] + {:dispatch-n (concat [[:init-console-chat]] (when open-console? - [[:navigate-to :chat console-chat-id]]))}))))) + [[:navigate-to-chat console-chat-id]]))}))))) (register-handler-fx :initialize-crypt @@ -308,6 +329,15 @@ (fn [_ _] {::get-fcm-token-fx nil})) +;; Because we send command to jail in params and command `:ref` is a lookup vector with +;; keyword in it (for example `["transactor" :command 51 "send"]`), we lose that keyword +;; information in the process of converting to/from JSON, and we need to restore it +(defn- restore-command-ref-keyword + [orig-params] + (if [(get-in orig-params [:command :command :ref])] + (update-in orig-params [:command :command :ref 1] keyword) + orig-params)) + (defn handle-jail-signal [{:keys [chat_id data]}] (let [{:keys [event data]} (types/json->clj data)] (case event @@ -319,7 +349,8 @@ :message data}]) "handler-result" (let [orig-params (:origParams data)] ;; TODO(janherich): figure out and fix chat_id from event - (dispatch [:command-handler! (:chat-id orig-params) orig-params + (dispatch [:command-handler! (:chat-id orig-params) + (restore-command-ref-keyword orig-params) {:result {:returned (dissoc data :origParams)}}])) (log/debug "Unknown jail signal " event)))) @@ -376,19 +407,19 @@ (fn [] (let [watch-id (atom nil)] (.getCurrentPosition - navigator.geolocation - #(dispatch [:update-geolocation (js->clj % :keywordize-keys true)]) - #(dispatch [:update-geolocation (js->clj % :keywordize-keys true)]) - (clj->js {:enableHighAccuracy true :timeout 20000 :maximumAge 1000})) + navigator.geolocation + #(dispatch [:update-geolocation (js->clj % :keywordize-keys true)]) + #(dispatch [:update-geolocation (js->clj % :keywordize-keys true)]) + (clj->js {:enableHighAccuracy true :timeout 20000 :maximumAge 1000})) (when platform/android? (reset! watch-id (.watchPosition - navigator.geolocation - #(do - (.clearWatch - navigator.geolocation - @watch-id) - (dispatch [:update-geolocation (js->clj % :keywordize-keys true)])))))))]})) + navigator.geolocation + #(do + (.clearWatch + navigator.geolocation + @watch-id) + (dispatch [:update-geolocation (js->clj % :keywordize-keys true)])))))))]})) (register-handler-db :update-geolocation diff --git a/src/status_im/ui/screens/profile/events.cljs b/src/status_im/ui/screens/profile/events.cljs index 953058c8f3..f56d2e7d61 100644 --- a/src/status_im/ui/screens/profile/events.cljs +++ b/src/status_im/ui/screens/profile/events.cljs @@ -1,19 +1,22 @@ (ns status-im.ui.screens.profile.events (:require [clojure.spec.alpha :as spec] [clojure.string :as string] - [re-frame.core :as re-frame :refer [reg-fx trim-v]] + [re-frame.core :as re-frame] [status-im.ui.components.react :refer [show-image-picker]] - [status-im.constants :refer [console-chat-id]] + [status-im.constants :as const] + [status-im.chat.constants :as chat-const] [status-im.ui.screens.profile.db :as db] [status-im.ui.screens.profile.navigation] [status-im.ui.screens.accounts.events :as accounts-events] + [status-im.chat.events :as chat-events] + [status-im.chat.events.input :as input-events] [status-im.utils.gfycat.core :as gfycat] [status-im.utils.handlers :as handlers] [status-im.utils.image-processing :refer [img->base64]] [status-im.utils.utils :as utils] [taoensso.timbre :as log])) -(reg-fx +(re-frame/reg-fx :open-image-picker ;; the image picker is only used here for now, this effect can be use in other scenarios as well (fn [callback-event] @@ -29,12 +32,12 @@ (handlers/register-handler-fx :profile/send-transaction - [trim-v] - (fn [{:keys [db]} [chat-id]] - (let [send-command (first (get-in db [:contacts/contacts "transactor-personal" :commands :send]))] - {:dispatch [:navigate-to :chat chat-id] - ;;TODO get rid of timeout - :dispatch-later [{:ms 100 :dispatch [:select-chat-input-command send-command]}]}))) + [re-frame/trim-v (re-frame/inject-cofx :get-stored-messages)] + (fn [{{:contacts/keys [contacts] :as db} :db :as cofx} [chat-id]] + (let [send-command (get-in contacts chat-const/send-command-ref)] + (-> (chat-events/navigate-to-chat cofx chat-id) + (as-> fx + (merge fx (input-events/select-chat-input-command (:db fx) send-command nil true))))))) (handlers/register-handler-fx :profile/send-message @@ -46,10 +49,11 @@ :my-profile/update-phone-number ;; Switch user to the console issuing the !phone command automatically to let him change his phone number. ;; We allow to change phone number only from console because this requires entering SMS verification code. - (fn [{:keys [db]} _] - (let [phone-command (first (get-in db [:contacts/contacts "console" :responses :phone]))] - {:dispatch-n [[:navigate-to-chat console-chat-id] - [:select-chat-input-command phone-command]]}))) + (fn [{{:contacts/keys [contacts] :as db} :db :as cofx} _] + (let [phone-command (get-in contacts chat-const/phone-command-ref)] + (-> (chat-events/navigate-to-chat cofx const/console-chat-id) + (as-> fx + (merge fx (input-events/select-chat-input-command (:db fx) phone-command nil true))))))) (defn get-current-account [{:keys [:accounts/current-account-id] :as db}] (get-in db [:accounts/accounts current-account-id])) diff --git a/src/status_im/ui/screens/subs.cljs b/src/status_im/ui/screens/subs.cljs index 1ec0f048af..c5d7fd7d90 100644 --- a/src/status_im/ui/screens/subs.cljs +++ b/src/status_im/ui/screens/subs.cljs @@ -1,6 +1,7 @@ (ns status-im.ui.screens.subs (:require [re-frame.core :refer [reg-sub subscribe]] status-im.chat.subs + status-im.commands.subs status-im.ui.screens.accounts.subs status-im.ui.screens.chats-list.subs status-im.ui.screens.group.chat-settings.subs @@ -20,21 +21,14 @@ (fn [db [_ k]] (get db k))) -(reg-sub :get-current-account - (fn [db] - (let [current-account-id (:accounts/current-account-id db)] - (get-in db [:accounts/accounts current-account-id])))) - (reg-sub :get-in (fn [db [_ path]] (get-in db path))) -(reg-sub :signed-up? - :<- [:get :accounts/current-account-id] - :<- [:get-accounts] - (fn [[account-id accounts]] - (when (and accounts account-id) - (get-in accounts [account-id :signed-up?])))) +(reg-sub :signed-up? + :<- [:get-current-account] + (fn [current-account] + (:signed-up? current-account))) (reg-sub :tabs-hidden? :<- [:get-in [:toolbar-search :show]] diff --git a/src/status_im/ui/screens/wallet/request/events.cljs b/src/status_im/ui/screens/wallet/request/events.cljs index 34817ea3ef..a08544666b 100644 --- a/src/status_im/ui/screens/wallet/request/events.cljs +++ b/src/status_im/ui/screens/wallet/request/events.cljs @@ -1,28 +1,34 @@ (ns status-im.ui.screens.wallet.request.events (:require [re-frame.core :as re-frame] - [status-im.ui.screens.wallet.db :as wallet.db] + [status-im.ui.screens.wallet.db :as wallet-db] [status-im.utils.handlers :as handlers] + [status-im.chat.constants :as chat-const] + [status-im.chat.events.input :as input-events] [status-im.utils.money :as money])) (handlers/register-handler-fx ::wallet-send-chat-request - (fn [_ [_ amount]] - {:dispatch [:select-chat-input-command {:name "request" :prefill [amount]}] - ;; TODO get rid of the timeout - :dispatch-later [{:ms 100 :dispatch [:send-current-message]}]})) + [re-frame/trim-v] + (fn [{{:contacts/keys [contacts] :as db} :db} [amount]] + (-> db + (input-events/select-chat-input-command + (assoc (get-in contacts chat-const/request-command-ref) :prefill [amount]) nil true) + (assoc :dispatch [:send-current-message])))) (handlers/register-handler-fx :wallet-send-request - (fn [{{:wallet/keys [request-transaction]} :db} [_ {:keys [whisper-identity]}]] + [re-frame/trim-v] + (fn [{{:wallet/keys [request-transaction]} :db} [{:keys [whisper-identity]}]] {:dispatch-n [[:navigate-back] [:navigate-to-clean :chat-list] - [:add-chat-loaded-event whisper-identity [::wallet-send-chat-request (str (:amount request-transaction))]] + [:add-chat-loaded-event whisper-identity + [::wallet-send-chat-request (some-> request-transaction :amount money/wei->ether str)]] [:start-chat whisper-identity]]})) (handlers/register-handler-fx :wallet.request/set-and-validate-amount (fn [{:keys [db]} [_ amount]] - (let [{:keys [value error]} (wallet.db/parse-amount amount)] + (let [{:keys [value error]} (wallet-db/parse-amount amount)] {:db (-> db (assoc-in [:wallet/request-transaction :amount] (money/ether->wei value)) (assoc-in [:wallet/request-transaction :amount-error] error))}))) diff --git a/src/status_im/utils/js_resources.cljs b/src/status_im/utils/js_resources.cljs index e5eb209860..9e60b6a153 100644 --- a/src/status_im/utils/js_resources.cljs +++ b/src/status_im/utils/js_resources.cljs @@ -11,9 +11,7 @@ (def default-contacts (json->clj (slurp "resources/default_contacts.json"))) (def default-contact-groups (json->clj (slurp "resources/default_contact_groups.json"))) -(def transactor-group-js (slurp-bot :transactor_group)) - -(def transactor-personal-js (slurp-bot :transactor_personal)) +(def transactor-js (slurp-bot :transactor)) (def console-js (slurp-bot :console "web3_metadata.js")) @@ -24,12 +22,11 @@ (def demo-bot-js (slurp-bot :demo_bot)) (def resources - {:transactor-group-bot transactor-group-js - :transactor-personal-bot transactor-personal-js - :console-bot console-js - :browse-bot browse-js - :mailman-bot mailman-js - :demo-bot demo-bot-js}) + {:transactor-bot transactor-js + :console-bot console-js + :browse-bot browse-js + :mailman-bot mailman-js + :demo-bot demo-bot-js}) (defn get-resource [url] (let [resource-name (keyword (subs url (count local-protocol)))] diff --git a/test/cljs/status_im/test/bots/events.cljs b/test/cljs/status_im/test/bots/events.cljs index 9505bd4746..5b1534c396 100644 --- a/test/cljs/status_im/test/bots/events.cljs +++ b/test/cljs/status_im/test/bots/events.cljs @@ -3,8 +3,7 @@ [status-im.bots.events :as bots-events])) (def ^:private initial-db - {:bot-db {} - :bot-subscriptions {} + {:bot-db {} :contacts/contacts {"bot1" {:subscriptions {:feeExplanation @@ -19,13 +18,13 @@ (deftest add-active-bot-subscriptions-test (testing "That active bot subscriptions are correctly transformed and added to db" - (let [db (bots-events/add-active-bot-subscriptions initial-db #{"bot1" "bot3"})] - (is (= #{"bot1" "bot3"} (-> db :bot-subscriptions keys set))) + (let [db (update-in initial-db [:contacts/contacts "bot1" :subscriptions] + bots-events/transform-bot-subscriptions)] (is (= {[:sliderValue] {:feeExplanation {:fee [:sliderValue] :tx [:transaction]}} [:transaction] {:feeExplanation {:fee [:sliderValue] :tx [:transaction]}}} - (-> db :bot-subscriptions (get "bot1"))))))) + (get-in db [:contacts/contacts "bot1" :subscriptions])))))) (defn- fake-subscription-call [db {:keys [chat-id parameters]}] @@ -37,7 +36,8 @@ (deftest set-in-bot-db-test (let [{:keys [db call-jail-function-n]} (-> initial-db - (bots-events/add-active-bot-subscriptions #{"bot1" "bot2"}) + (update-in [:contacts/contacts "bot1" :subscriptions] + bots-events/transform-bot-subscriptions) (bots-events/set-in-bot-db {:bot "bot1" :path [:sliderValue] :value 2})) diff --git a/test/cljs/status_im/test/chat/events.cljs b/test/cljs/status_im/test/chat/events.cljs index 077db3610f..4b4be0c0a8 100644 --- a/test/cljs/status_im/test/chat/events.cljs +++ b/test/cljs/status_im/test/chat/events.cljs @@ -17,14 +17,14 @@ (deftest init-console-chat (testing "initialising console if console is already added to chats, should not modify anything" (let [db {:chats {const/console-chat-id sign-up/console-chat}} - fx (chat-events/init-console-chat db false)] + fx (chat-events/init-console-chat db)] (is (= db (:db fx))) (is (= #{:db} (-> fx keys set))))) (testing "initialising console without existing account and console chat not initialisated" (let [fresh-db {:chats {} :accounts/current-account-id nil} - {:keys [db dispatch-n]} (chat-events/init-console-chat fresh-db false)] + {:keys [db dispatch-n]} (chat-events/init-console-chat fresh-db)] (is (= (:current-chat-id db) (:chat-id sign-up/console-chat))) (is (= (:current-chat-id db) @@ -36,7 +36,7 @@ (testing "initialising console with existing account and console chat not initialisated" (let [fresh-db {:chats {} :accounts/current-account-id (:whisper-identity contact)} - {:keys [db dispatch-n]} (chat-events/init-console-chat fresh-db false)] + {:keys [db dispatch-n]} (chat-events/init-console-chat fresh-db)] (is (= (:current-chat-id db) (:chat-id sign-up/console-chat))) (is (= (:current-chat-id db) diff --git a/test/cljs/status_im/test/chat/models/input.cljs b/test/cljs/status_im/test/chat/models/input.cljs index 564c57447a..132938a222 100644 --- a/test/cljs/status_im/test/chat/models/input.cljs +++ b/test/cljs/status_im/test/chat/models/input.cljs @@ -3,33 +3,41 @@ [status-im.chat.models.input :as input])) (def fake-db - {:global-commands {:command1 (list {:name "global-command1"})} - :chats {"test1" {:contacts [{:identity "0x1"}] - :requests nil - :seq-arguments ["arg1" "arg2"] - :possible-commands (list {:name "global-command1"} - {:name "command2"})} - "test2" {:contacts [{:identity "0x1"} - {:identity "0x2"}] - :requests [{:message-id "id1" :type :request1}] - :possible-commands (list {:name "global-command1"} - {:name "command2"} - {:name "command3"}) - :possible-requests (list {:name "request1"})} - "test3" {:contacts [{:identity "0x1"}] - :requests [{:message-id "id1" :type :request1}]} - "test4" {:contacts [{:identity "0x1"} - {:identity "0x2"}] - :requests [{:message-id "id2" :type :request2}] - :input-metadata {:meta-k "meta-v"} - :possible-commands (list {:name "global-command1"} - {:name "command2"} - {:name "command3"}) - :possible-requests (list {:name "request1"})}} - :contacts/contacts {"0x1" {:commands {:command2 (list {:name "command2"})} - :responses nil} - "0x2" {:commands {:command3 (list {:name "command3"})} - :responses {:request1 (list {:name "request1"})}}}}) + {:access-scope->commands-responses {#{:global :personal-chats :anonymous :dapps} {:command {"global-command1" ["0x1" :command 0 "global-command1"]}} + #{"0x1" :personal-chats :anonymous :dapps} {:command {"command2" ["0x1" :command 2 "command2"]}} + #{"0x1" :group-chats :anonymous :dapps} {:command {"command2" ["0x1" :command 4 "command2"]}} + #{"0x2" :personal-chats :anonymous :dapps} {:command {"command3" ["0x2" :command 2 "command3"]}} + #{"0x2" :group-chats :anonymous :dapps} {:response {"response1" ["0x2" :response 4 "response1"]}}} + :chats {"test1" {:contacts [{:identity "0x1"}] + :requests nil + :seq-arguments ["arg1" "arg2"]} + "test2" {:contacts [{:identity "0x1"} + {:identity "0x2"}] + :group-chat true + :requests {"id1" {:message-id "id1" + :response "response1"}}} + "test3" {:contacts [{:identity "0x1"}] + :requests {"id1" {:message-id "id1" + :response "request1"}}} + "test4" {:contacts [{:identity "0x1"} + {:identity "0x2"}] + :group-chat true + :requests {"id2" {:message-id "id2" + :response "response1"}} + :input-metadata {:meta-k "meta-v"}}} + :contacts/contacts {"0x1" {:dapp? true + :command {0 {"global-command1" {:name "global-command1" + :ref ["0x1" :command 0 "global-command1"]}} + 2 {"command2" {:name "command2" + :ref ["0x1" :command 2 "command2"]}} + 4 {"command2" {:name "command2" + :ref ["0x1" :command 4 "command2"]}}} + :response {}} + "0x2" {:dapp? true + :command {2 {"command3" {:name "command3" + :ref ["0x2" :command 2 "command3"]}}} + :response {4 {"response1" {:name "response1" + :ref ["0x2" :response 4 "response1"]}}}}}}) (deftest text->emoji (is (nil? (input/text->emoji nil))) @@ -58,32 +66,47 @@ (deftest selected-chat-command (is (= (input/selected-chat-command fake-db "test1" "/global-command1") - {:command {:name "global-command1"} :metadata nil :args ["arg1" "arg2"]})) - (is (= (input/selected-chat-command fake-db "test2" "/global-command1") - {:command {:name "global-command1"} :metadata nil :args []})) + {:command {:name "global-command1" + :ref ["0x1" :command 0 "global-command1"]} + :metadata nil + :args ["arg1" "arg2"]})) + (is (= (input/selected-chat-command fake-db "test2" "/command2") + {:command {:name "command2" + :ref ["0x1" :command 4 "command2"]} + :metadata nil + :args []})) (is (nil? (input/selected-chat-command fake-db "test1" "/command3"))) (is (= (input/selected-chat-command fake-db "test1" "/command2") - {:command {:name "command2"} :metadata nil :args ["arg1" "arg2"]})) - (is (= (input/selected-chat-command fake-db "test2" "/request1 arg1") - {:command {:name "request1"} :metadata nil :args ["arg1"]})) + {:command {:name "command2" + :ref ["0x1" :command 2 "command2"]} + :metadata nil + :args ["arg1" "arg2"]})) + (is (= (input/selected-chat-command fake-db "test2" "/response1 arg1") + {:command {:name "response1" + :ref ["0x2" :response 4 "response1"]} + :metadata nil + :args ["arg1"]})) (is (= (input/selected-chat-command fake-db "test4" "/command2 arg1") - {:command {:name "command2"} :metadata {:meta-k "meta-v"} :args ["arg1"]}))) + {:command {:name "command2" + :ref ["0x1" :command 4 "command2"]} + :metadata {:meta-k "meta-v"} + :args ["arg1"]}))) (deftest current-chat-argument-position (is (= (input/current-chat-argument-position - {:name "command1"} "/command1 arg1 arg2 " 0 nil) -1)) + {:name "command1"} "/command1 arg1 arg2 " 0 nil) -1)) (is (= (input/current-chat-argument-position - {:name "command1"} "/command1 argument1 arg2 " 9 nil) -1)) + {:name "command1"} "/command1 argument1 arg2 " 9 nil) -1)) (is (= (input/current-chat-argument-position - {:name "command1"} "/command1 argument1 arg2 " 10 nil) 0)) + {:name "command1"} "/command1 argument1 arg2 " 10 nil) 0)) (is (= (input/current-chat-argument-position - {:name "command1"} "/command1 argument1 arg2 " 19 nil) 0)) + {:name "command1"} "/command1 argument1 arg2 " 19 nil) 0)) (is (= (input/current-chat-argument-position - {:name "command1"} "/command1 argument1 arg2 " 20 nil) 1)) + {:name "command1"} "/command1 argument1 arg2 " 20 nil) 1)) (is (= (input/current-chat-argument-position - {:name "command2"} "/command2 \"a r g u m e n t 1\" argument2" 30 nil) 1)) + {:name "command2"} "/command2 \"a r g u m e n t 1\" argument2" 30 nil) 1)) (is (= (input/current-chat-argument-position - {:name "command3" :command {:sequential-params true}} "/command3" 0 ["test1" "test2"]) 2))) + {:name "command3" :command {:sequential-params true}} "/command3" 0 ["test1" "test2"]) 2))) (deftest argument-position "Doesn't require a separate test because it simply calls `current-chat-argument-position") @@ -136,4 +159,4 @@ (is (= {} (input/command-dependent-context-params "console" {:name "any"})))) (deftest modified-db-after-change - "Just a combination of db modifications. Can be skipped now") \ No newline at end of file + "Just a combination of db modifications. Can be skipped now")