WIP: LNS displaying owned records

This commit is contained in:
Kyle Zsembery 2020-10-07 14:02:44 +11:00
parent aefacaa7e7
commit c4eb822b0d
7 changed files with 366 additions and 259 deletions

View File

@ -5,6 +5,7 @@ on:
branches:
- master
- development
- lokinet-name-reg
jobs:
build:

View File

@ -488,8 +488,15 @@ export class Daemon {
if (!Array.isArray(owners) || owners.length === 0) {
return [];
}
console.log("Getting records for owners with owners:");
console.log(owners[0]);
const data = await this.sendRPC("lns_owners_to_names", { entries: owners });
// try just the main address as owner
const data = await this.sendRPC("lns_owners_to_names", {
entries: [owners[0]]
});
console.log("data returned from lns_owners_to_names call");
console.log(data);
if (!data.hasOwnProperty("result")) return [];
// We need to map request_index to owner
@ -514,7 +521,11 @@ export class Daemon {
entries: [
{
name_hash: nameHash,
types: [0] // Update this when we have other types. Type 0 = session
// Update this when we have other types.
// 0 = session
// 2+ = lokinet for different # years
// TODO: Ensure these are actually the correct types
types: [0, 2]
}
]
};
@ -525,15 +536,22 @@ export class Daemon {
const entries = this._sanitizeLNSRecords(data.result.entries);
if (entries.length === 0) return null;
console.log("Returning entries 0 of entries:");
console.log(entries);
return entries[0];
}
_sanitizeLNSRecords(records) {
console.log("sanitizing lns records: ");
return (records || []).map(record => {
// Record type is in uint16 format
// Session = 0
// For now since wallet and loki names haven't been implemented, we always assume it's session
const type = "session";
console.log("record is ");
console.log(record);
let type = "lokinet";
if (record.type === 0) {
type = "session";
}
return {
...record,
type

View File

@ -963,6 +963,7 @@ export class WalletRPC {
}
async updateLocalLNSRecords() {
console.log("calling updateLocalLNSRecords");
try {
const addressData = await this.sendRPC(
"get_address",
@ -985,6 +986,9 @@ export class WalletRPC {
addresses
);
console.log("records from owners");
console.log(records);
// We need to ensure that we decrypt any incoming records that we already have
const currentRecords = this.wallet_state.lnsRecords;
const recordsToUpdate = { ...this.purchasedNames };
@ -1016,6 +1020,14 @@ export class WalletRPC {
};
});
this.wallet_state.lnsRecords = newRecords;
// console.log("New LNS records found in update:");
// console.log(newRecords);
const isSession = record => record.type === "session";
let nonSessionRecords = newRecords.filter(record => !isSession(record));
console.log("non session records");
console.log(nonSessionRecords);
this.sendGateway("set_wallet_data", { lnsRecords: newRecords });
// Decrypt the records serially

View File

@ -4,18 +4,18 @@
<div class="q-mb-lg description">
{{ $t("strings.myLnsDescription") }}
</div>
<LNSRecordList />
<LNSRecords />
</div>
</div>
</template>
<script>
import LNSRecordList from "./lns_record_list";
import LNSRecords from "./lns_records";
export default {
name: "MyLNS",
components: {
LNSRecordList
LNSRecords
}
};
</script>

View File

@ -1,281 +1,88 @@
<template>
<div v-if="records.length > 0" class="lns-record-list">
<div
v-if="needsDecryption"
class="decrypt q-pa-md row justify-between items-end"
<q-list link no-border :dark="theme == 'dark'" class="loki-list">
<q-item
v-for="record in recordList"
:key="record.name_hash"
class="loki-list-item"
>
<LokiField
:label="$t('fieldLabels.decryptRecord')"
:disable="decrypting"
:error="$v.name.$error"
>
<q-input
v-model.trim="name"
:dark="theme == 'dark'"
borderless
dense
:placeholder="$t('placeholders.lnsDecryptName')"
:disable="decrypting"
@blur="$v.name.$touch"
/>
</LokiField>
<div class="btn-wrapper q-ml-md row items-center">
<q-btn
color="primary"
:label="$t('buttons.decrypt')"
:loading="decrypting"
@click="decrypt()"
/>
</div>
</div>
<q-list link no-border :dark="theme == 'dark'" class="loki-list">
<q-item
v-for="record in records"
:key="record.name_hash"
class="loki-list-item"
>
<q-item-section class="type" avatar>
<q-icon :name="isLocked(record) ? 'lock' : 'lock_open'" size="24px" />
</q-item-section>
<q-item-section>
<q-item-label :class="bindClass(record)">
{{ isLocked(record) ? record.name_hash : record.name }}
</q-item-label>
<q-item-label v-if="!isLocked(record)">
{{ record.value }}
</q-item-label>
</q-item-section>
<q-item-section side class="height">
<template v-if="isLocked(record)">
{{ record.register_height | blockHeight }}
</template>
<template v-else>
<q-item-section>
<q-btn
color="secondary"
:label="$t('buttons.update')"
@click="onUpdate(record)"
/>
</q-item-section>
</template>
</q-item-section>
<q-item-section v-if="!isLocked(record)" side>
<q-item-section class="type" avatar>
<q-icon :name="isLocked(record) ? 'lock' : 'lock_open'" size="24px" />
</q-item-section>
<q-item-section>
<q-item-label :class="bindClass(record)">
{{ isLocked(record) ? record.name_hash : record.name }}
</q-item-label>
<q-item-label v-if="!isLocked(record)">
{{ record.value }}
</q-item-label>
</q-item-section>
<q-item-section side class="height">
<template v-if="isLocked(record)">
{{ record.register_height | blockHeight }}
</q-item-section>
<ContextMenu
:menu-items="validMenuItems(record)"
@ownerCopy="
copy(record.owner, $t('notification.positive.ownerCopied'))
"
@nameCopy="copy(record.name, $t('notification.positive.nameCopied'))"
@copyValue="copyValue(record)"
@backupOwnerCopy="
copy(
record.backup_owner,
$t('notification.positive.backupOwnerCopied')
)
"
/>
</q-item>
</q-list>
</div>
<div v-else>
There are no LNS names registered to this wallet. If you have registered
names to this wallet, try refreshing.
</div>
</template>
<template v-else>
<q-item-section>
<q-btn
color="secondary"
:label="$t('buttons.update')"
@click="onUpdate(record)"
/>
</q-item-section>
</template>
</q-item-section>
<q-item-section v-if="!isLocked(record)" side>
{{ record.register_height | blockHeight }}
</q-item-section>
<!-- <ContextMenu
:menu-items="validMenuItems(record)"
@ownerCopy="copy(record.owner, $t('notification.positive.ownerCopied'))"
@nameCopy="copy(record.name, $t('notification.positive.nameCopied'))"
@copyValue="copyValue(record)"
@backupOwnerCopy="
copy(
record.backup_owner,
$t('notification.positive.backupOwnerCopied')
)
"
/> -->
</q-item>
</q-list>
</template>
<script>
const { clipboard } = require("electron");
import { mapState } from "vuex";
import { i18n } from "boot/i18n";
import LokiField from "components/loki_field";
import { session_id } from "src/validators/common";
import ContextMenu from "components/menus/contextmenu";
// import ContextMenu from "components/menus/contextmenu";
export default {
name: "LNSRecordList",
components: {
LokiField,
ContextMenu
props: {
recordList: {
type: Array,
required: true
}
},
// components: {
// ContextMenu
// },
computed: mapState({
theme: state => state.gateway.app.config.appearance.theme
}),
filters: {
blockHeight(value) {
const heightString = i18n.t("strings.blockHeight");
return `${heightString}: ${value}`;
}
},
data() {
return {
name: "",
decrypting: false
};
},
mounted() {
// fetch the lns names from the wallet
console.log("Fetching LNS names");
this.$gateway.send("wallet", "lns_known_records");
},
computed: mapState({
theme: state => state.gateway.app.config.appearance.theme,
ourAddresses(state) {
const { address_list } = state.gateway.wallet;
const { used, unused, primary } = address_list;
const all = [...used, ...unused, ...primary];
return all.map(a => a.address).filter(a => !!a);
},
records(state) {
const ourAddresses = this.ourAddresses;
const records = state.gateway.wallet.lnsRecords;
const ourRecords = records.filter(record => {
return (
ourAddresses.includes(record.owner) ||
ourAddresses.includes(record.backup_owner)
);
});
// Sort the records by decrypted ones first, followed by non-decrypted
return ourRecords.sort((a, b) => {
if (a.name && !b.name) {
return -1;
} else if (b.name && !a.name) {
return 1;
} else if (a.name && b.name) {
return a.name.localeCompare(b.name);
}
return b.register_height - a.register_height;
});
},
needsDecryption() {
return !!this.records.find(r => this.isLocked(r));
}
}),
methods: {
validMenuItems(record) {
const lockedItems = [
{ action: "nameCopy", i18n: "menuItems.copyName" },
{ action: "copyValue", i18n: this.copyValueI18nLabel(record) }
];
let menuItems = [{ action: "ownerCopy", i18n: "menuItems.copyOwner" }];
const backupOwnerItem = [
{ action: "backupOwnerCopy", i18n: "menuItems.copyBackupOwner" }
];
if (!this.isLocked(record)) {
menuItems = [...lockedItems, ...menuItems];
}
if (record.backup_owner !== "") {
menuItems = [...menuItems, ...backupOwnerItem];
}
return menuItems;
},
isLocked(record) {
return !record.name || !record.value;
},
copyValueI18nLabel(record) {
if (record.type === "session") {
return "menuItems.copySessionId";
}
return "menuItems.copyAddress";
},
decrypt() {
this.$v.name.$touch();
if (!this.name || this.name.trim().length === 0) {
this.$q.notify({
type: "negative",
timeout: 3000,
message: this.$t("notification.errors.enterName")
});
return;
}
if (this.$v.name.$error) {
this.$q.notify({
type: "negative",
timeout: 3000,
message: this.$t("notification.errors.invalidNameFormat")
});
return;
}
const name = this.name.trim();
this.$gateway.once("decrypt_record_result", data => {
if (data.decrypted) {
this.$q.notify({
type: "positive",
timeout: 2000,
message: this.$t("notification.positive.decryptedLNSRecord", {
name
})
});
this.name = "";
} else {
this.$q.notify({
type: "negative",
timeout: 3000,
message: this.$t("notification.errors.decryptLNSRecord", { name })
});
}
this.decrypting = false;
});
this.$gateway.send("wallet", "decrypt_record", {
name,
type: "session"
});
this.decrypting = true;
},
bindClass(record) {
return [this.isLocked(record) ? "locked" : "unlocked"];
},
onUpdate(record) {
this.$emit("onUpdate", record);
},
// TODO: Update this
copyValue(record) {
let message = this.$t("notification.positive.addressCopied");
if (record.type === "session") {
message = this.$t("notification.positive.sessionIdCopied");
}
this.copy(record.value, message);
},
copy(value, message) {
if (!value) return;
clipboard.writeText(value.trim());
this.$q.notify({
type: "positive",
timeout: 2000,
message
});
}
},
validations: {
name: {
// TODO: validate on both session id and lokinet addresses
session_id
}
}
};
</script>
<style lang="scss">
.lns-record-list {
.height {
font-size: 0.9em;
}
.q-item {
cursor: default;
}
.loki-field {
flex: 1;
}
.decrypt {
.btn-wrapper {
height: 46px;
}
}
}
</style>
<style></style>

View File

@ -0,0 +1,267 @@
<template>
<div class="lns-record-list">
<div
v-if="needsDecryption"
class="decrypt q-pa-md row justify-between items-end"
>
<LokiField
:label="$t('fieldLabels.decryptRecord')"
:disable="decrypting"
:error="$v.name.$error"
>
<q-input
v-model.trim="name"
:dark="theme == 'dark'"
borderless
dense
:placeholder="$t('placeholders.lnsDecryptName')"
:disable="decrypting"
@blur="$v.name.$touch"
/>
</LokiField>
<div class="btn-wrapper q-ml-md row items-center">
<q-btn
color="primary"
:label="$t('buttons.decrypt')"
:loading="decrypting"
@click="decrypt()"
/>
</div>
</div>
<h3>Session records</h3>
<LNSRecordList :record-list="session_records" />
<h3>Lokinet records</h3>
<LNSRecordList :record-list="lokinet_records" />
</div>
</template>
<script>
const { clipboard } = require("electron");
import { mapState } from "vuex";
import { i18n } from "boot/i18n";
import LokiField from "components/loki_field";
import { session_id } from "src/validators/common";
// import ContextMenu from "components/menus/contextmenu";
import LNSRecordList from "./lns_record_list";
export default {
name: "LNSRecords",
components: {
LokiField,
LNSRecordList
// ContextMenu
},
filters: {
blockHeight(value) {
const heightString = i18n.t("strings.blockHeight");
return `${heightString}: ${value}`;
}
},
data() {
return {
name: "",
decrypting: false
};
},
// mounted() {
// // fetch the lns names from the wallet
// console.log("Records are here: ");
// console.log(this.records);
// // this.$gateway.send("wallet", "lns_known_records");
// this.$gateway.send("update");
// },
computed: mapState({
theme: state => state.gateway.app.config.appearance.theme,
ourAddresses(state) {
const { address_list } = state.gateway.wallet;
const { used, unused, primary } = address_list;
const all = [...used, ...unused, ...primary];
return all.map(a => a.address).filter(a => !!a);
},
session_records(state) {
const ourAddresses = this.ourAddresses;
const records = state.gateway.wallet.lnsRecords;
const ourRecords = records.filter(record => {
return (
record.type === "session" &&
(ourAddresses.includes(record.owner) ||
ourAddresses.includes(record.backup_owner))
);
});
console.log("records in lokinet records:");
console.log(ourRecords);
// Sort the records by decrypted ones first, followed by non-decrypted
return ourRecords.sort((a, b) => {
if (a.name && !b.name) {
return -1;
} else if (b.name && !a.name) {
return 1;
} else if (a.name && b.name) {
return a.name.localeCompare(b.name);
}
return b.register_height - a.register_height;
});
},
lokinet_records(state) {
// can clean this up for less reptition
const ourAddresses = this.ourAddresses;
const records = state.gateway.wallet.lnsRecords;
console.log("records in lokinet records:");
console.log(ourRecords);
const ourRecords = records.filter(record => {
return (
record.type === "lokinet" &&
(ourAddresses.includes(record.owner) ||
ourAddresses.includes(record.backup_owner))
);
});
// Sort the records by decrypted ones first, followed by non-decrypted
return ourRecords.sort((a, b) => {
if (a.name && !b.name) {
return -1;
} else if (b.name && !a.name) {
return 1;
} else if (a.name && b.name) {
return a.name.localeCompare(b.name);
}
return b.register_height - a.register_height;
});
},
needsDecryption() {
const records = [...this.lokinet_records, ...this.session_records];
console.log("needs decryption??");
console.log(records);
return records.find(r => this.isLocked(r));
}
}),
methods: {
validMenuItems(record) {
const lockedItems = [
{ action: "nameCopy", i18n: "menuItems.copyName" },
{ action: "copyValue", i18n: this.copyValueI18nLabel(record) }
];
let menuItems = [{ action: "ownerCopy", i18n: "menuItems.copyOwner" }];
const backupOwnerItem = [
{ action: "backupOwnerCopy", i18n: "menuItems.copyBackupOwner" }
];
if (!this.isLocked(record)) {
menuItems = [...lockedItems, ...menuItems];
}
if (record.backup_owner !== "") {
menuItems = [...menuItems, ...backupOwnerItem];
}
return menuItems;
},
isLocked(record) {
return !record.name || !record.value;
},
copyValueI18nLabel(record) {
if (record.type === "session") {
return "menuItems.copySessionId";
}
return "menuItems.copyAddress";
},
decrypt() {
this.$v.name.$touch();
if (!this.name || this.name.trim().length === 0) {
this.$q.notify({
type: "negative",
timeout: 3000,
message: this.$t("notification.errors.enterName")
});
return;
}
if (this.$v.name.$error) {
this.$q.notify({
type: "negative",
timeout: 3000,
message: this.$t("notification.errors.invalidNameFormat")
});
return;
}
const name = this.name.trim();
this.$gateway.once("decrypt_record_result", data => {
if (data.decrypted) {
this.$q.notify({
type: "positive",
timeout: 2000,
message: this.$t("notification.positive.decryptedLNSRecord", {
name
})
});
this.name = "";
} else {
this.$q.notify({
type: "negative",
timeout: 3000,
message: this.$t("notification.errors.decryptLNSRecord", { name })
});
}
this.decrypting = false;
});
this.$gateway.send("wallet", "decrypt_record", {
name,
type: "session"
});
this.decrypting = true;
},
onUpdate(record) {
this.$emit("onUpdate", record);
},
// TODO: Update this
copyValue(record) {
let message = this.$t("notification.positive.addressCopied");
if (record.type === "session") {
message = this.$t("notification.positive.sessionIdCopied");
}
this.copy(record.value, message);
},
copy(value, message) {
if (!value) return;
clipboard.writeText(value.trim());
this.$q.notify({
type: "positive",
timeout: 2000,
message
});
}
},
validations: {
// name: function(value) {
// // TODO: validate on both session id and lokinet addresses
// session_id(value) || lokinet_name(value);
// }
name: {
session_id
}
}
};
</script>
<style lang="scss">
.lns-record-list {
.height {
font-size: 0.9em;
}
.q-item {
cursor: default;
}
.loki-field {
flex: 1;
}
.decrypt {
.btn-wrapper {
height: 46px;
}
}
}
</style>

View File

@ -131,6 +131,8 @@ export default {
},
methods: {
save() {
console.log("pending config to save");
console.log(this.pending_config);
this.$gateway.send("core", "save_config", this.pending_config);
this.isVisible = false;
},