From 98e2c07a7a6e99def868c985b96cc2d75b1e85a9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Garci=CC=81a?=
 <dani-garcia@users.noreply.github.com>
Date: Mon, 4 Dec 2023 20:12:51 +0100
Subject: [PATCH] Improve TS bindings

---
 .../js_webassembly/bitwarden_client/index.ts  | 190 +++++++++++-------
 .../bitwarden_client/logging_level.ts         |   7 -
 languages/js_webassembly/index.html           |   2 +-
 languages/js_webassembly/index.ts             |  35 ++--
 4 files changed, 135 insertions(+), 99 deletions(-)
 delete mode 100644 languages/js_webassembly/bitwarden_client/logging_level.ts

diff --git a/languages/js_webassembly/bitwarden_client/index.ts b/languages/js_webassembly/bitwarden_client/index.ts
index 3f858fd390..4a7c732745 100644
--- a/languages/js_webassembly/bitwarden_client/index.ts
+++ b/languages/js_webassembly/bitwarden_client/index.ts
@@ -1,71 +1,54 @@
 import * as rust from "../pkg/bitwarden_wasm";
-import { LoggingLevel } from "./logging_level";
 import {
+  AccessTokenLoginResponse,
   ClientSettings,
   Convert,
-  ResponseForPasswordLoginResponse,
-  ResponseForSecretIdentifiersResponse,
-  ResponseForSecretResponse,
-  ResponseForSecretsDeleteResponse,
-  ResponseForSyncResponse,
-  ResponseForUserAPIKeyResponse,
+  ProjectResponse,
+  ProjectsDeleteResponse,
+  ProjectsResponse,
+  SecretIdentifiersResponse,
+  SecretResponse,
+  SecretsDeleteResponse,
 } from "./schemas";
 
-export class BitwardenClient {
-  client: rust.BitwardenClient;
+export type LogLevel = rust.LogLevel;
+export const LogLevel = rust.LogLevel;
 
-  constructor(settings?: ClientSettings, logging_level?: LoggingLevel) {
-    const settings_json = settings == null ? null : Convert.clientSettingsToJson(settings);
-    this.client = new rust.BitwardenClient(settings_json, logging_level ?? LoggingLevel.Info);
+function handleResponse<T>(response: { success: boolean; errorMessage?: string; data?: T }): T {
+  if (!response.success) {
+    throw new Error(response.errorMessage);
   }
 
-  async login(email: string, password: string): Promise<ResponseForPasswordLoginResponse> {
-    const response = await this.client.run_command(
-      Convert.commandToJson({
-        passwordLogin: {
-          email: email,
-          password: password,
-        },
-      })
-    );
-
-    return Convert.toResponseForPasswordLoginResponse(response);
-  }
+  return response.data as T;
+}
 
-  async getUserApiKey(
-    secret: string,
-    isOtp: boolean = false
-  ): Promise<ResponseForUserAPIKeyResponse> {
-    const response = await this.client.run_command(
-      Convert.commandToJson({
-        getUserApiKey: {
-          masterPassword: isOtp ? null : secret,
-          otp: isOtp ? secret : null,
-        },
-      })
-    );
+export class BitwardenClient {
+  client: rust.BitwardenClient;
 
-    return Convert.toResponseForUserAPIKeyResponse(response);
+  constructor(settings?: ClientSettings, log_level?: rust.LogLevel) {
+    const settings_json = settings == null ? null : Convert.clientSettingsToJson(settings);
+    this.client = new rust.BitwardenClient(settings_json, log_level ?? rust.LogLevel.Info);
   }
 
-
-  async sync(
-    excludeSubdomains: boolean = false
-  ): Promise<ResponseForSyncResponse> {
+  async accessTokenLogin(accessToken: string): Promise<AccessTokenLoginResponse> {
     const response = await this.client.run_command(
       Convert.commandToJson({
-        sync: {
-          excludeSubdomains
+        accessTokenLogin: {
+          accessToken,
         },
-      })
+      }),
     );
 
-    return Convert.toResponseForSyncResponse(response);
+    return handleResponse(Convert.toResponseForAccessTokenLoginResponse(response));
   }
 
   secrets(): SecretsClient {
     return new SecretsClient(this.client);
   }
+
+  projects(): ProjectsClient {
+    return new ProjectsClient(this.client);
+  }
 }
 
 export class SecretsClient {
@@ -75,81 +58,142 @@ export class SecretsClient {
     this.client = client;
   }
 
-  async get(
-    id: string
-  ): Promise<ResponseForSecretResponse> {
+  async get(id: string): Promise<SecretResponse> {
     const response = await this.client.run_command(
       Convert.commandToJson({
         secrets: {
-          get: { id }
+          get: { id },
         },
-      })
+      }),
     );
 
-    return Convert.toResponseForSecretResponse(response);
+    return handleResponse(Convert.toResponseForSecretResponse(response));
   }
 
   async create(
     key: string,
+    value: string,
     note: string,
     organizationId: string,
-    value: string,
-  ): Promise<ResponseForSecretResponse> {
+  ): Promise<SecretResponse> {
     const response = await this.client.run_command(
       Convert.commandToJson({
         secrets: {
-          create: { key, note, organizationId, value }
+          create: { key, value, note, organizationId },
         },
-      })
+      }),
     );
 
-    return Convert.toResponseForSecretResponse(response);
+    return handleResponse(Convert.toResponseForSecretResponse(response));
   }
 
-  async list(
-    organizationId: string
-  ): Promise<ResponseForSecretIdentifiersResponse> {
+  async list(organizationId: string): Promise<SecretIdentifiersResponse> {
     const response = await this.client.run_command(
       Convert.commandToJson({
         secrets: {
-          list: { organizationId }
+          list: { organizationId },
         },
-      })
+      }),
     );
 
-    return Convert.toResponseForSecretIdentifiersResponse(response);
+    return handleResponse(Convert.toResponseForSecretIdentifiersResponse(response));
   }
 
   async update(
     id: string,
     key: string,
+    value: string,
     note: string,
     organizationId: string,
-    value: string,
-  ): Promise<ResponseForSecretResponse> {
+  ): Promise<SecretResponse> {
     const response = await this.client.run_command(
       Convert.commandToJson({
         secrets: {
-          update: { id, key, note, organizationId, value }
+          update: { id, key, value, note, organizationId },
         },
-      })
+      }),
     );
 
-    return Convert.toResponseForSecretResponse(response);
+    return handleResponse(Convert.toResponseForSecretResponse(response));
   }
 
-  async delete(
-    ids: string[]
-  ): Promise<ResponseForSecretsDeleteResponse> {
+  async delete(ids: string[]): Promise<SecretsDeleteResponse> {
     const response = await this.client.run_command(
       Convert.commandToJson({
         secrets: {
-          delete: { ids }
+          delete: { ids },
         },
-      })
+      }),
     );
 
-    return Convert.toResponseForSecretsDeleteResponse(response);
+    return handleResponse(Convert.toResponseForSecretsDeleteResponse(response));
+  }
+}
+
+export class ProjectsClient {
+  client: rust.BitwardenClient;
+
+  constructor(client: rust.BitwardenClient) {
+    this.client = client;
   }
 
+  async get(id: string): Promise<ProjectResponse> {
+    const response = await this.client.run_command(
+      Convert.commandToJson({
+        projects: {
+          get: { id },
+        },
+      }),
+    );
+
+    return handleResponse(Convert.toResponseForProjectResponse(response));
+  }
+
+  async create(name: string, organizationId: string): Promise<ProjectResponse> {
+    const response = await this.client.run_command(
+      Convert.commandToJson({
+        projects: {
+          create: { name, organizationId },
+        },
+      }),
+    );
+
+    return handleResponse(Convert.toResponseForProjectResponse(response));
+  }
+
+  async list(organizationId: string): Promise<ProjectsResponse> {
+    const response = await this.client.run_command(
+      Convert.commandToJson({
+        projects: {
+          list: { organizationId },
+        },
+      }),
+    );
+
+    return handleResponse(Convert.toResponseForProjectsResponse(response));
+  }
+
+  async update(id: string, name: string, organizationId: string): Promise<ProjectResponse> {
+    const response = await this.client.run_command(
+      Convert.commandToJson({
+        projects: {
+          update: { id, name, organizationId },
+        },
+      }),
+    );
+
+    return handleResponse(Convert.toResponseForProjectResponse(response));
+  }
+
+  async delete(ids: string[]): Promise<ProjectsDeleteResponse> {
+    const response = await this.client.run_command(
+      Convert.commandToJson({
+        projects: {
+          delete: { ids },
+        },
+      }),
+    );
+
+    return handleResponse(Convert.toResponseForProjectsDeleteResponse(response));
+  }
 }
diff --git a/languages/js_webassembly/bitwarden_client/logging_level.ts b/languages/js_webassembly/bitwarden_client/logging_level.ts
deleted file mode 100644
index 2453b1eb64..0000000000
--- a/languages/js_webassembly/bitwarden_client/logging_level.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export enum LoggingLevel {
-  Trace,
-  Debug,
-  Info,
-  Warn,
-  Error,
-}
diff --git a/languages/js_webassembly/index.html b/languages/js_webassembly/index.html
index 661517934b..229c69bae5 100644
--- a/languages/js_webassembly/index.html
+++ b/languages/js_webassembly/index.html
@@ -1,4 +1,4 @@
-<!DOCTYPE html>
+<!doctype html>
 
 <html>
   <head>
diff --git a/languages/js_webassembly/index.ts b/languages/js_webassembly/index.ts
index 047a815bbb..e4802ca5e8 100644
--- a/languages/js_webassembly/index.ts
+++ b/languages/js_webassembly/index.ts
@@ -1,26 +1,25 @@
-import { LoggingLevel } from "./bitwarden_client/logging_level";
 import { DeviceType } from "./bitwarden_client/schemas";
 
 import("./bitwarden_client").then(async (module) => {
-  const client = new module.BitwardenClient({
-    apiUrl: "http://localhost:8081/api",
-    identityUrl: "http://localhost:8081/identity",
-    deviceType: DeviceType.SDK,
-    userAgent: "Bitwarden JS SDK",
-  }, LoggingLevel.Debug);
-  const result = await client.login("test@bitwarden.com", "asdfasdf");
-  console.log(`auth result success: ${result.success}`);
+  const client = new module.BitwardenClient(
+    {
+      apiUrl: "http://localhost:8081/api",
+      identityUrl: "http://localhost:8081/identity",
+      deviceType: DeviceType.SDK,
+      userAgent: "Bitwarden JS SDK",
+    },
+    module.LogLevel.Debug,
+  );
 
-  const apikeyResponse = await client.getUserApiKey("asdfasdf");
-  console.log(`user API key: ${apikeyResponse.data.apiKey}`);
+  const accessToken = "insert your access token here";
+  const organizationId = "insert your organization id here";
 
-  const sync = await client.sync();
-  console.log("Sync result", sync);
+  await client.accessTokenLogin(accessToken);
 
-  const org_id = sync.data.profile.organizations[0].id;
+  const secret = await client
+    .secrets()
+    .create("TEST_KEY", "Secret1234!", "This is a test secret", organizationId);
+  console.log("New secret: ", secret);
 
-  const secret = await client.secrets().create("TEST_KEY", "This is a test secret", org_id, "Secret1234!");
-  console.log("New secret: ", secret.data);
-
-  await client.secrets().delete([secret.data.id]);
+  await client.secrets().delete([secret.id]);
 });