Source: dock-wallet.js

  1. import {
  2. contentsFromEncryptedWalletCredential,
  3. exportContentsAsCredential,
  4. lockWalletContents,
  5. unlockWalletContents,
  6. } from './methods/contents-crypto';
  7. import { passwordToKeypair } from './methods/password';
  8. import {
  9. WALLET_DEFAULT_CONTEXT,
  10. WALLET_DEFAULT_TYPE,
  11. WALLET_DEFAULT_ID,
  12. } from './constants';
  13. function ensureValidContent(content) {
  14. if (!content['@context']) {
  15. throw new Error('Content object requires valid JSON-LD with @context property');
  16. }
  17. if (!content.id) {
  18. throw new Error('Content object requires an id property');
  19. }
  20. if (!content.type) {
  21. throw new Error('Content object requires an type property');
  22. }
  23. }
  24. function ensureWalletUnlocked(wallet) {
  25. if (wallet.status === 'LOCKED') {
  26. throw new Error('Wallet is locked!');
  27. }
  28. }
  29. /** The Dock Wallet */
  30. class DockWallet {
  31. /**
  32. * Creates a new unlocked wallet instance with empty contents
  33. * @constructor
  34. */
  35. constructor(id = WALLET_DEFAULT_ID) {
  36. this.id = id;
  37. this.status = DockWallet.Unlocked;
  38. this.contents = [];
  39. }
  40. /**
  41. * Adds a content item to the wallet
  42. * The wallet must be unlocked to make this call
  43. * @param {any} content - Content item
  44. * @return {DockWallet} Returns itself
  45. */
  46. add(content) {
  47. ensureWalletUnlocked(this);
  48. ensureValidContent(content);
  49. if (this.has(content.id)) {
  50. throw new Error(`Duplication error: ID: ${content.id} already exists`);
  51. }
  52. this.contents.push(content);
  53. return this;
  54. }
  55. /**
  56. * Removes a content item from the wallet
  57. * The wallet must be unlocked to make this call
  58. * @param {string} contentId - Content item ID
  59. * @return {DockWallet} Returns itself
  60. */
  61. remove(contentId) {
  62. ensureWalletUnlocked(this);
  63. this.contents = this.contents.filter((i) => i.id !== contentId);
  64. return this;
  65. }
  66. update(content) {
  67. ensureWalletUnlocked(this);
  68. const contentItems = this.contents.filter((c) => c.id === content.id);
  69. if (contentItems.length) {
  70. const contentIndex = this.contents.indexOf(contentItems[0]);
  71. this.contents[contentIndex] = content;
  72. } else {
  73. throw new Error(`Cannot find content with ID ${content.id} to update`);
  74. }
  75. }
  76. /**
  77. * Checks if a wallet has content with specific ID
  78. * The wallet must be unlocked to make this call
  79. * @param {string} contentId - Content item ID
  80. * @return {Boolean} Whether the wallet has this content
  81. */
  82. has(contentId) {
  83. ensureWalletUnlocked(this);
  84. return this.contents.some((i) => i.id === contentId);
  85. }
  86. /**
  87. * Locks the wallet with a given password
  88. * @param {string} password - Wallet password
  89. * @return {Promise<DockWallet>} Returns itself
  90. */
  91. async lock(password) {
  92. if (this.status === DockWallet.Locked) {
  93. throw new Error('Wallet is already locked');
  94. }
  95. const keyPair = await passwordToKeypair(password);
  96. this.contents = await lockWalletContents(
  97. this.contents,
  98. keyPair,
  99. );
  100. this.status = DockWallet.Locked;
  101. return this;
  102. }
  103. /**
  104. * Unlocks the wallet with a given password
  105. * @param {string} password - Wallet password
  106. * @return {Promise<DockWallet>} Returns itself
  107. */
  108. async unlock(password) {
  109. if (this.status === DockWallet.Unlocked) {
  110. throw new Error('Wallet is already unlocked');
  111. }
  112. const keyPair = await passwordToKeypair(password);
  113. this.contents = await unlockWalletContents(
  114. this.contents,
  115. keyPair,
  116. );
  117. this.status = DockWallet.Unlocked;
  118. return this;
  119. }
  120. /**
  121. * Imports an encrypted wallet with a given password
  122. * @param {object} encryptedWalletCredential - A encrypted wallet credential JSON-LD object
  123. * @param {string} password - Wallet password
  124. * @return {Promise<DockWallet>} Returns itself
  125. */
  126. async import(encryptedWalletCredential, password) {
  127. if (this.contents.length) {
  128. throw new Error('Cannot import over existing wallet content.');
  129. }
  130. this.id = encryptedWalletCredential.id || WALLET_DEFAULT_ID;
  131. const keyPair = await passwordToKeypair(password);
  132. this.contents = await contentsFromEncryptedWalletCredential(
  133. encryptedWalletCredential,
  134. keyPair,
  135. );
  136. this.status = DockWallet.Unlocked;
  137. return this;
  138. }
  139. /**
  140. * Exports the wallet to an encrypted wallet credential JSON-LD object
  141. * @param {string} password - Wallet password
  142. * @param {Date} [issuanceDate] - Optional credential issuance date
  143. * @return {Promise<DockWallet>} Returns itself
  144. */
  145. async export(password, issuanceDate) {
  146. ensureWalletUnlocked(this);
  147. const keyPair = await passwordToKeypair(password);
  148. return exportContentsAsCredential(this.contents, keyPair, issuanceDate);
  149. }
  150. /**
  151. * Takes a Query and Type as input, and returns a collection of results based on current wallet contents.
  152. * A custom wallet implementation could override this method to support more querying types
  153. * @param {object} search - Search query object
  154. * @return {array<any>} List of contents results
  155. */
  156. async query(search) {
  157. // Really basic "search" of contents
  158. // typically a wallet class would extend this method
  159. const { equals = {} } = search; // TODO: support "has" query (A string with an attribute name to match or an array of such strings.)
  160. return this.contents.filter((content) => {
  161. const terms = Object.keys(equals);
  162. for (let i = 0; i < terms.length; i++) {
  163. const term = terms[i];
  164. const termSplit = term.split('.');
  165. const termProperty = termSplit[1];
  166. if (termSplit[0] === 'content') {
  167. const contentValue = content[termProperty];
  168. const equalsValue = equals[term];
  169. if ((Array.isArray(contentValue) && contentValue.indexOf(equalsValue) > -1) || content[termProperty] === equals[term]) {
  170. return true;
  171. }
  172. } else {
  173. throw new Error('Equals terms must be for content');
  174. }
  175. }
  176. return false;
  177. });
  178. }
  179. prove() {
  180. // TODO: Implement and define params
  181. }
  182. transfer() {
  183. // TODO: Implement and define params
  184. }
  185. signRaw() {
  186. // TODO: Implement and define params, this method may not be needed
  187. }
  188. verifyRaw() {
  189. // TODO: Implement and define params, this method may not be needed
  190. }
  191. /**
  192. * Returns this wallet instance formatted as an unlocked universal wallet
  193. * The wallet must be unlocked to make this call
  194. * @return {object} An unlocked wallet JSON-LD representation
  195. */
  196. toJSON() {
  197. ensureWalletUnlocked(this);
  198. return {
  199. '@context': WALLET_DEFAULT_CONTEXT,
  200. id: this.id,
  201. type: WALLET_DEFAULT_TYPE,
  202. status: this.status,
  203. contents: this.contents,
  204. };
  205. }
  206. /**
  207. * Locked wallet status constant
  208. * @return {string} LOCKED
  209. */
  210. static get Locked() {
  211. return 'LOCKED';
  212. }
  213. /**
  214. * Unlocked wallet status constant
  215. * @return {string} UNLOCKED
  216. */
  217. static get Unlocked() {
  218. return 'UNLOCKED';
  219. }
  220. }
  221. export default DockWallet;