Refactored jail loading + commands/responses

This commit is contained in:
janherich 2017-11-04 13:59:19 +01:00 committed by Jan Herich
parent 91a0d40ceb
commit e50d56a4d0
65 changed files with 1417 additions and 2356 deletions

View File

@ -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 {} %)))})
(rr/enable-re-frisk-remote! {:host "localhost:4567" :on-init core/init :pre-send (fn [db] (update db :chats #(into {} %)))})

View File

@ -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":

View File

@ -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,

View File

@ -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",

View File

@ -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({
)
};
}
});
});

View File

@ -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)
});

View File

@ -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')
)
};
}
}
});

View File

@ -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ư ('
}
};

View File

@ -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;

View File

@ -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}]

View File

@ -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)])))

View File

@ -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"])

View File

@ -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])))))

View File

@ -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)))

View File

@ -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]}]

View File

@ -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}))))

View File

@ -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]}]})))

View File

@ -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))))

View File

@ -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

View File

@ -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/<! (a/timeout 100))
(dispatch [:send-current-message])))))))
(register-handler :update-group-message
(u/side-effect!
(fn [{:keys [current-public-key web3 chats]}

View File

@ -1,46 +0,0 @@
(ns status-im.chat.handlers.requests
(:require [re-frame.core :refer [after dispatch enrich]]
[status-im.utils.handlers :refer [register-handler]]
[status-im.data-store.requests :as requests]
[status-im.utils.handlers :refer [register-handler] :as u]
[taoensso.timbre :as log]))
(defn store-request!
[{:keys [new-request]}]
(requests/save new-request))
(defn add-request
[db [_ chat-id {:keys [message-id content] :as r}]]
(let [request {:chat-id chat-id
:message-id message-id
:bot (:bot content)
:type (:command content)
:added (js/Date.)}
request' (update request :type keyword)]
(log/debug "Adding request: " request')
(-> 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!))

View File

@ -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!

View File

@ -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 [])))
(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)))

View File

@ -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)

View File

@ -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]

View File

@ -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"})

View File

@ -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?))

View File

@ -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]]

View File

@ -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)

View File

@ -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])]]])

View File

@ -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)))])])]]]))

View File

@ -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

View File

@ -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

View File

@ -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)))))))

View File

@ -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)))

View File

@ -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)))

View File

@ -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?))

View File

@ -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))))

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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}
])

View File

@ -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}}})

View File

@ -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"))

View File

@ -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"}}})

View File

@ -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))

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -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)))))

View File

@ -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

View File

@ -4,4 +4,11 @@
(reg-sub
:get-accounts
(fn [db _]
(get db :accounts/accounts)))
(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))))

View File

@ -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

View File

@ -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]]}))

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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]))

View File

@ -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]]

View File

@ -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))})))

View File

@ -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)))]

View File

@ -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}))

View File

@ -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)

View File

@ -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")
"Just a combination of db modifications. Can be skipped now")