diff --git a/gui/.eslintrc.js b/gui/.eslintrc.js index 1a31391b..4de64cd7 100644 --- a/gui/.eslintrc.js +++ b/gui/.eslintrc.js @@ -1,7 +1,7 @@ // inspired by 6.031 .eslintrc.js module.exports = { root: true, // don't look any higher in filesystem for eslint config - noInlineConfig: true, // don't allow comments in source files to suppress eslint comments + noInlineConfig: false, // allow comments in source files to suppress eslint comments env: { es2021: true, node: true, diff --git a/gui/package-lock.json b/gui/package-lock.json index f3be6258..98159d41 100644 --- a/gui/package-lock.json +++ b/gui/package-lock.json @@ -35,7 +35,7 @@ "@storybook/testing-library": "^0.0.9", "@tailwindcss/forms": "^0.5.2", "@types/google-protobuf": "^3.15.6", - "@types/node": "17.0.5", + "@types/node": "^17.0.5", "@types/react": "^17.0.38", "@types/react-dom": "^17.0.11", "@types/react-test-renderer": "^17.0.1", diff --git a/gui/package.json b/gui/package.json index 0ca733ba..c1215fe0 100644 --- a/gui/package.json +++ b/gui/package.json @@ -37,7 +37,7 @@ "@storybook/testing-library": "^0.0.9", "@tailwindcss/forms": "^0.5.2", "@types/google-protobuf": "^3.15.6", - "@types/node": "17.0.5", + "@types/node": "^17.0.5", "@types/react": "^17.0.38", "@types/react-dom": "^17.0.11", "@types/react-test-renderer": "^17.0.1", diff --git a/gui/src/main/daemon.ts b/gui/src/main/daemon.ts index 066d9424..52ef0c30 100644 --- a/gui/src/main/daemon.ts +++ b/gui/src/main/daemon.ts @@ -3,26 +3,37 @@ // SPDX-License-Identifier: GPL-3.0-only // -const grpc = require("@grpc/grpc-js"); +import * as grpc from "@grpc/grpc-js"; +import * as daemon_pb from "../daemon/schema/daemon_pb"; +import { Friend, IncomingMessage, dateToProtobufDate } from "../types"; import daemonS from "../daemon/schema/daemon_grpc_pb"; import path from "path"; +import { promisify } from "util"; +import { RELEASE_COMMIT_HASH } from "./constants"; -function get_socket_path() { - if (process.env.XDG_RUNTIME_DIR) { +const FAKE_DATA = process.env["ASPHR_FAKE_DATA"] === "true"; + +function getSocketPath(): string { + if (process.env["XDG_RUNTIME_DIR"] != null) { return path.join( - process.env.XDG_RUNTIME_DIR, + process.env["XDG_RUNTIME_DIR"], "anysphere", "anysphere.sock" ); - } else if (process.env.XDG_CONFIG_HOME) { + } else if (process.env["XDG_CONFIG_HOME"] != null) { return path.join( - process.env.XDG_CONFIG_HOME, + process.env["XDG_CONFIG_HOME"], "anysphere", "run", "anysphere.sock" ); - } else if (process.env.HOME) { - return path.join(process.env.HOME, ".anysphere", "run", "anysphere.sock"); + } else if (process.env["HOME"] != null) { + return path.join( + process.env["HOME"], + ".anysphere", + "run", + "anysphere.sock" + ); } else { process.stderr.write( "$HOME or $XDG_CONFIG_HOME or $XDG_RUNTIME_DIR not set! Cannot find socket, aborting. :(" @@ -31,20 +42,890 @@ function get_socket_path() { } } -function get_socket_address() { - return "unix://" + get_socket_path(); +function getSocketAddress(): string { + return "unix://" + getSocketPath(); } export function getDaemonClient(): daemonS.DaemonClient { return new daemonS.DaemonClient( - get_socket_address(), + getSocketAddress(), grpc.credentials.createInsecure() ); } -export function truncate(str: string, maxLength: number) { +export function truncate(str: string, maxLength: number): string { if (str.length <= maxLength) { return str; } return str.substring(0, maxLength - 3) + "..."; } + +export interface Daemon { + registerUser( + registerUserRequest: daemon_pb.RegisterUserRequest.AsObject + ): Promise; + + getMyPublicID(): Promise; + + getFriendList(): Promise; + + removeFriend( + removeFriendRequest: daemon_pb.RemoveFriendRequest.AsObject + ): Promise; + + addSyncFriend( + addSyncFriendRequest: daemon_pb.AddSyncFriendRequest.AsObject + ): Promise; + + addAsyncFriend( + addAsyncFriendRequest: daemon_pb.AddAsyncFriendRequest.AsObject + ): Promise; + + getOutgoingSyncInvitations( + getOutgoingSyncInvitationsRequest: daemon_pb.GetOutgoingSyncInvitationsRequest.AsObject + ): Promise; + + getOutgoingAsyncInvitations( + getOutgoingAsyncInvitationsRequest: daemon_pb.GetOutgoingAsyncInvitationsRequest.AsObject + ): Promise; + + getIncomingAsyncInvitations( + getIncomingAsyncInvitationsRequest: daemon_pb.GetIncomingAsyncInvitationsRequest.AsObject + ): Promise; + + acceptAsyncInvitation( + acceptAsyncInvitationRequest: daemon_pb.AcceptAsyncInvitationRequest.AsObject + ): Promise; + + rejectAsyncInvitation( + rejectAsyncInvitationRequest: daemon_pb.RejectAsyncInvitationRequest.AsObject + ): Promise; + + sendMessage( + sendMessageRequest: daemon_pb.SendMessageRequest.AsObject + ): Promise; + + getMessages( + getMessagesRequest: daemon_pb.GetMessagesRequest.AsObject + ): Promise; + + getOutboxMessages( + getOutboxMessagesRequest: daemon_pb.GetOutboxMessagesRequest.AsObject + ): Promise; + + getSentMessages( + getSentMessagesRequest: daemon_pb.GetSentMessagesRequest.AsObject + ): Promise; + + messageSeen( + messageSeenRequest: daemon_pb.MessageSeenRequest.AsObject + ): Promise; + + // + // not super necessary in the UI + // + + getStatus( + getStatusRequest: daemon_pb.GetStatusRequest.AsObject + ): Promise; + + // getLatency( + // getLatencyRequest: daemon_pb.GetLatencyRequest.AsObject + // ): Promise; + + // changeLatency( + // changeLatencyRequest: daemon_pb.ChangeLatencyRequest.AsObject + // ): Promise; + + // kill( + // killRequest: daemon_pb.KillRequest.AsObject + // ): Promise; + + getMessagesStreamed( + getMessagesRequest: daemon_pb.GetMessagesRequest.AsObject, + messageHandler: (_: IncomingMessage[]) => void + ): () => void; + + hasRegistered(): Promise; +} + +export class DaemonImpl implements Daemon { + private readonly client: daemonS.DaemonClient; + + public constructor() { + this.client = getDaemonClient(); + } + + public async sendMessage( + sendMessageRequest: daemon_pb.SendMessageRequest.AsObject + ): Promise { + if (FAKE_DATA) { + console.log("Sending message:", sendMessageRequest); + return {}; + } + const request = new daemon_pb.SendMessageRequest(); + request.setUniqueNameList(sendMessageRequest.uniqueNameList); + request.setMessage(sendMessageRequest.message); + + const boundSendMessage: ( + argument: daemon_pb.SendMessageRequest, + callback: grpc.requestCallback + ) => grpc.ClientUnaryCall = this.client.sendMessage.bind(this.client); + const promisifiedSendMessage = promisify(boundSendMessage); + const response = await promisifiedSendMessage(request); + + if (response === undefined) { + throw new Error("sendMessage returned undefined"); + } + return response.toObject(); + } + + public async getStatus( + getStatusRequest: daemon_pb.GetStatusRequest.AsObject + ): Promise { + if (FAKE_DATA) { + console.log("Getting status:", getStatusRequest); + return { + registered: true, + releaseHash: RELEASE_COMMIT_HASH, + latencySeconds: 60, + serverAddress: "server1.anysphere.co:443", + }; + } + const request = new daemon_pb.GetStatusRequest(); + + const boundGetStatus: ( + argument: daemon_pb.GetStatusRequest, + callback: grpc.requestCallback + ) => grpc.ClientUnaryCall = this.client.getStatus.bind(this.client); + const promisifiedGetStatus = promisify(boundGetStatus); + const response = await promisifiedGetStatus(request); + + if (response === undefined) { + throw new Error("getStatus returned undefined"); + } + + return response.toObject(); + } + + public async getMessages( + getMessagesRequest: daemon_pb.GetMessagesRequest.AsObject + ): Promise { + if (FAKE_DATA) { + if ( + getMessagesRequest.filter === daemon_pb.GetMessagesRequest.Filter.NEW + ) { + return { + messagesList: [ + { + uid: 1, + message: "Hello! This is a test message.", + fromUniqueName: "sualeh-asif", + fromDisplayName: "Sualeh Asif", + otherRecipientsList: [], + deliveredAt: dateToProtobufDate(new Date()), + seen: false, + delivered: true, + }, + { + uid: 2, + message: + "This is a new message. It has been delivered, but not yet seen.", + fromUniqueName: "stzh1555", + fromDisplayName: "Shengtong Zhang", + otherRecipientsList: [], + deliveredAt: dateToProtobufDate(new Date()), + seen: false, + delivered: true, + }, + ], + }; + } else { + return { + messagesList: [ + { + uid: 1, + message: "Hello! This is a test message.", + fromUniqueName: "sualeh-asif", + fromDisplayName: "Sualeh Asif", + otherRecipientsList: [], + deliveredAt: dateToProtobufDate(new Date()), + seen: false, + delivered: true, + }, + { + uid: 2, + message: + "This is a new message. It has been delivered, but not yet seen.", + fromUniqueName: "stzh1555", + fromDisplayName: "Shengtong Zhang", + otherRecipientsList: [], + deliveredAt: dateToProtobufDate(new Date()), + seen: false, + delivered: true, + }, + { + uid: 3, + message: + "This is a new message. This message has been marked as seen!", + fromUniqueName: "stzh1555", + fromDisplayName: "Shengtong Zhang", + otherRecipientsList: [], + deliveredAt: dateToProtobufDate(new Date()), + seen: true, + delivered: true, + }, + ], + }; + } + } + const request = new daemon_pb.GetMessagesRequest(); + request.setFilter(getMessagesRequest.filter); + + const boundGetMessages: ( + argument: daemon_pb.GetMessagesRequest, + callback: grpc.requestCallback + ) => grpc.ClientUnaryCall = this.client.getMessages.bind(this.client); + const promisifiedGetMessages = promisify(boundGetMessages); + const response = await promisifiedGetMessages(request); + + if (response === undefined) { + throw new Error("getMessages returned undefined"); + } + return response.toObject(); + } + + public getMessagesStreamed( + getMessagesRequest: daemon_pb.GetMessagesRequest.AsObject, + messageHandler: (_: IncomingMessage[]) => void + ): () => void { + if (FAKE_DATA) { + if ( + getMessagesRequest.filter === daemon_pb.GetMessagesRequest.Filter.NEW + ) { + const l: IncomingMessage[] = [ + { + uid: 1, + message: + "Can you schedule a meeting with Srini for next week, please?", + fromUniqueName: "sualeh-asif", + fromDisplayName: "Sualeh Asif", + otherRecipientsList: [], + deliveredAt: dateToProtobufDate(new Date()), + seen: false, + delivered: true, + }, + { + uid: 2, + message: + "I pushed some of my comments on the white paper draft to the repo, but I figured I should explain some of it here, too.", + fromUniqueName: "sualeh-asif", + fromDisplayName: "Sualeh Asif", + otherRecipientsList: [], + deliveredAt: dateToProtobufDate(new Date()), + seen: false, + delivered: true, + }, + ]; + messageHandler(l); + return () => { + console.log("Stopped streaming messages."); + }; + } else { + const l: IncomingMessage[] = [ + { + uid: 1, + message: + "Can you schedule a meeting with Srini for next week, please?", + fromUniqueName: "sualeh-asif", + fromDisplayName: "Sualeh Asif", + otherRecipientsList: [], + deliveredAt: dateToProtobufDate(new Date()), + seen: false, + delivered: true, + }, + { + uid: 2, + message: + "I pushed some of my comments on the white paper draft to the repo, but I figured I should explain some of it here, too.", + fromUniqueName: "sualeh-asif", + fromDisplayName: "Sualeh Asif", + otherRecipientsList: [], + deliveredAt: dateToProtobufDate(new Date()), + seen: false, + delivered: true, + }, + { + uid: 3, + message: + "I was thinking about the thing you told me about the other day, and I think you're right.", + fromUniqueName: "sualeh-asif", + fromDisplayName: "Sualeh Asif", + otherRecipientsList: [], + deliveredAt: dateToProtobufDate(new Date()), + seen: true, + delivered: true, + }, + { + uid: 4, + message: + "Can you take a look at the server branch and see if it works? I made some changes.", + fromUniqueName: "sualeh-asif", + fromDisplayName: "Sualeh Asif", + otherRecipientsList: [], + deliveredAt: dateToProtobufDate(new Date()), + seen: true, + delivered: true, + }, + { + uid: 5, + message: + "Hi Arvid,\n\nThank you so much for onboarding me to Anysphere! I am very excited to work with you.\n\nBest,\nShengtong", + fromUniqueName: "stzh1555", + fromDisplayName: "Shengtong Zhang", + otherRecipientsList: [], + deliveredAt: dateToProtobufDate(new Date()), + seen: true, + delivered: true, + }, + { + uid: 6, + message: + "Dear Arvid,\n\nThis is my first ever completely private message to you. No one will be able to read this message, find out when it was sent, or even suspect that I sent anything to you at all.\n\nHere's to a thoughtful, private and free future.\n\nYours truly,\nSualeh", + fromUniqueName: "stzh1555", + fromDisplayName: "Shengtong Zhang", + otherRecipientsList: [], + deliveredAt: dateToProtobufDate(new Date()), + seen: true, + delivered: true, + }, + ]; + messageHandler(l); + return () => { + console.log("Stopped streaming messages."); + }; + } + } + const request = new daemon_pb.GetMessagesRequest(); + request.setFilter(getMessagesRequest.filter); + + const call = this.client.getMessagesStreamed(request); + + call.on("data", function (r: daemon_pb.GetMessagesResponse) { + try { + const lm = r.getMessagesList(); + messageHandler(lm.map((m) => m.toObject())); + } catch (e) { + console.log(`error in getAllMessagesStreamed: ${e}`); + } + }); + call.on("end", function () { + // The server has finished sending + console.log("getAllMessagesStreamed end"); + }); + call.on("error", function (e: Error) { + // An error has occurred and the stream has been closed. + console.log("getAllMessagesStreamed error", e); + }); + call.on("status", function (status: grpc.StatusObject) { + // process status + console.log("getAllMessagesStreamed status", status); + }); + return () => { + console.log("cancelling grpc!"); + call.cancel(); + }; + } + + public async getOutboxMessages( + getOutboxMessagesRequest: daemon_pb.GetOutboxMessagesRequest.AsObject + ): Promise { + if (FAKE_DATA) { + return { + messagesList: [ + { + uid: 1, + message: + "This is an outbox message that doesn't seem to want to be delivered...", + toFriendsList: [ + { + uniqueName: "sualeh-asif", + displayName: "Sualeh Asif", + delivered: false, + }, + ], + sentAt: dateToProtobufDate(new Date()), + }, + { + uid: 2, + message: "I'm confused...", + toFriendsList: [ + { + uniqueName: "stzh1555", + displayName: "Shengtong Zhang", + delivered: false, + }, + ], + sentAt: dateToProtobufDate(new Date()), + }, + { + uid: 3, + message: "I'm very confused...", + toFriendsList: [ + { + uniqueName: "stzh1555", + displayName: "Shengtong Zhang", + delivered: false, + }, + ], + sentAt: dateToProtobufDate(new Date()), + }, + ], + }; + } + const request = new daemon_pb.GetOutboxMessagesRequest(); + + const boundGetOutboxMessages: ( + argument: daemon_pb.GetOutboxMessagesRequest, + callback: grpc.requestCallback + ) => grpc.ClientUnaryCall = this.client.getOutboxMessages.bind(this.client); + const promisifiedGetOutboxMessages = promisify(boundGetOutboxMessages); + const response = await promisifiedGetOutboxMessages(request); + + if (response === undefined) { + throw new Error("getOutboxMessages returned undefined"); + } + return response.toObject(); + } + + public async getSentMessages( + getSentMessagesRequest: daemon_pb.GetSentMessagesRequest.AsObject + ): Promise { + if (FAKE_DATA) { + return { + messagesList: [ + { + uid: 1, + message: "The draft looks good. Let me know if you need any help!", + toFriendsList: [ + { + uniqueName: "sualeh-asif", + displayName: "Sualeh Asif", + delivered: true, + deliveredAt: dateToProtobufDate(new Date()), + }, + ], + sentAt: dateToProtobufDate(new Date()), + }, + { + uid: 2, + message: + "Welcome! We are extremely excited to have you as a part of our team.", + toFriendsList: [ + { + uniqueName: "shengtong-zhang", + displayName: "Shengtong Zhang", + delivered: true, + deliveredAt: dateToProtobufDate(new Date()), + }, + ], + sentAt: dateToProtobufDate(new Date()), + }, + ], + }; + } + + const request = new daemon_pb.GetSentMessagesRequest(); + + const boundGetSentMessages: ( + argument: daemon_pb.GetSentMessagesRequest, + callback: grpc.requestCallback + ) => grpc.ClientUnaryCall = this.client.getSentMessages.bind(this.client); + const promisifiedGetSentMessages = promisify(boundGetSentMessages); + const response = await promisifiedGetSentMessages(request); + + if (response === undefined) { + throw new Error("getSentMessages returned undefined"); + } + return response.toObject(); + } + + public async messageSeen( + messageSeenRequest: daemon_pb.MessageSeenRequest.AsObject + ): Promise { + const request = new daemon_pb.MessageSeenRequest(); + request.setId(messageSeenRequest.id); + + const boundMessageSeen: ( + argument: daemon_pb.MessageSeenRequest, + callback: grpc.requestCallback + ) => grpc.ClientUnaryCall = this.client.messageSeen.bind(this.client); + const promisifiedMessageSeen = promisify(boundMessageSeen); + const response = await promisifiedMessageSeen(request); + + if (response === undefined) { + throw new Error("messageSeen returned undefined"); + } + return response.toObject(); + } + + public async hasRegistered(): Promise { + if (FAKE_DATA) { + return true; + } + const request = new daemon_pb.GetStatusRequest(); + + const boundGetStatus: ( + argument: daemon_pb.GetStatusRequest, + callback: grpc.requestCallback + ) => grpc.ClientUnaryCall = this.client.getStatus.bind(this.client); + const promisifiedGetStatus = promisify(boundGetStatus); + const response = await promisifiedGetStatus(request); + + if (response === undefined) { + throw new Error("hasRegistered returned undefined"); + } + + return response.getRegistered(); + } + + public async registerUser( + registerUserRequest: daemon_pb.RegisterUserRequest.AsObject + ): Promise { + const request = new daemon_pb.RegisterUserRequest(); + request.setName(registerUserRequest.name); + request.setBetaKey(registerUserRequest.betaKey); + + const boundRegisterUser: ( + argument: daemon_pb.RegisterUserRequest, + callback: grpc.requestCallback + ) => grpc.ClientUnaryCall = this.client.registerUser.bind(this.client); + const promisifiedRegisterUser = promisify(boundRegisterUser); + const response = await promisifiedRegisterUser(request); + + if (response === undefined) { + throw new Error("registerUser returned undefined"); + } + return response.toObject(); + } + + public async getFriendList(): Promise { + if (FAKE_DATA) { + return [ + { + uniqueName: "Sualeh", + displayName: "Sualeh Asif", + publicId: "asdfasdf", + invitationProgress: daemon_pb.InvitationProgress.COMPLETE, + }, + { + uniqueName: "Shengtong", + displayName: "Shengtong", + publicId: "asdfasdf", + invitationProgress: daemon_pb.InvitationProgress.COMPLETE, + }, + { + uniqueName: "Bob", + displayName: "Bob", + publicId: "asdfasdf", + invitationProgress: daemon_pb.InvitationProgress.COMPLETE, + }, + ]; + } + + const request = new daemon_pb.GetFriendListRequest(); + + const boundGetFriendList: ( + argument: daemon_pb.GetFriendListRequest, + callback: grpc.requestCallback + ) => grpc.ClientUnaryCall = this.client.getFriendList.bind(this.client); + const promisifiedGetFriendList = promisify(boundGetFriendList); + const response = await promisifiedGetFriendList(request); + + if (response === undefined) { + throw new Error("getFriendList returned undefined"); + } + + return response + .getFriendInfosList() + .map((friendInfo) => friendInfo.toObject()); + } + + public async removeFriend( + removeFriendRequest: daemon_pb.RemoveFriendRequest.AsObject + ): Promise { + if (FAKE_DATA) { + return {}; + } + + const request = new daemon_pb.RemoveFriendRequest(); + request.setUniqueName(removeFriendRequest.uniqueName); + + const boundRemoveFriend: ( + argument: daemon_pb.RemoveFriendRequest, + callback: grpc.requestCallback + ) => grpc.ClientUnaryCall = this.client.removeFriend.bind(this.client); + const promisifiedRemoveFriend = promisify(boundRemoveFriend); + const response = await promisifiedRemoveFriend(request); + + if (response === undefined) { + throw new Error("removeFriend returned undefined"); + } + + return response.toObject(); + } + + public async addAsyncFriend( + addAsyncFriendRequest: daemon_pb.AddAsyncFriendRequest.AsObject + ): Promise { + if (FAKE_DATA) { + return {}; + } + + const request = new daemon_pb.AddAsyncFriendRequest(); + request.setUniqueName(addAsyncFriendRequest.uniqueName); + request.setDisplayName(addAsyncFriendRequest.displayName); + request.setPublicId(addAsyncFriendRequest.publicId); + request.setMessage(addAsyncFriendRequest.message); + + const boundAddAsyncFriend: ( + argument: daemon_pb.AddAsyncFriendRequest, + callback: grpc.requestCallback + ) => grpc.ClientUnaryCall = this.client.addAsyncFriend.bind(this.client); + const promisifiedAddAsyncFriend = promisify(boundAddAsyncFriend); + const response = await promisifiedAddAsyncFriend(request); + + if (response === undefined) { + throw new Error("addAsyncFriend returned undefined"); + } + + return response.toObject(); + } + + public async addSyncFriend( + addSyncFriendRequest: daemon_pb.AddSyncFriendRequest.AsObject + ): Promise { + if (FAKE_DATA) { + return {}; + } + + const request = new daemon_pb.AddSyncFriendRequest(); + request.setUniqueName(addSyncFriendRequest.uniqueName); + request.setDisplayName(addSyncFriendRequest.displayName); + request.setStory(addSyncFriendRequest.story); + + const boundAddSyncFriend: ( + argument: daemon_pb.AddSyncFriendRequest, + callback: grpc.requestCallback + ) => grpc.ClientUnaryCall = this.client.addSyncFriend.bind(this.client); + const promisifiedAddSyncFriend = promisify(boundAddSyncFriend); + const response = await promisifiedAddSyncFriend(request); + + if (response === undefined) { + throw new Error("addSyncFriend returned undefined"); + } + + return response.toObject(); + } + + public async getOutgoingSyncInvitations( + getOutgoingSyncInvitationsRequest: daemon_pb.GetOutgoingSyncInvitationsRequest.AsObject + ): Promise { + if (FAKE_DATA) { + return { + invitationsList: [ + { + uniqueName: "arvid", + displayName: "Arvid Lunnemark", + story: + "Haphazard ambassador hides gradient. Cool mark resolves ambush. Eerie kiss revises distinction. Fashionable film fosters age. New customer views proprietor. Boiled plant kicks truck. Spectacular commission complies triumph. Faint.", + sentAt: dateToProtobufDate(new Date()), + }, + ], + }; + } + + const request = new daemon_pb.GetOutgoingSyncInvitationsRequest(); + + const boundGetOutgoingSyncInvitations: ( + argument: daemon_pb.GetOutgoingSyncInvitationsRequest, + callback: grpc.requestCallback + ) => grpc.ClientUnaryCall = this.client.getOutgoingSyncInvitations.bind( + this.client + ); + const promisifiedGetOutgoingSyncInvitations = promisify( + boundGetOutgoingSyncInvitations + ); + const response = await promisifiedGetOutgoingSyncInvitations(request); + + if (response === undefined) { + throw new Error("getOutgoingSyncInvitations returned undefined"); + } + + return response.toObject(); + } + + public async getOutgoingAsyncInvitations(): Promise { + if (FAKE_DATA) { + return { + invitationsList: [ + { + uniqueName: "OutgoingSualeh", + displayName: "Sualeh Asif ooooo", + publicId: "asdfasdf", + message: "hihihihihihihihih", + sentAt: dateToProtobufDate(new Date()), + }, + { + uniqueName: "OutgoingShengtong", + displayName: "Shengtong aaaaaa", + publicId: "asdfasdf", + message: "hihihihihihihihih", + sentAt: dateToProtobufDate(new Date()), + }, + ], + }; + } + + const request = new daemon_pb.GetOutgoingAsyncInvitationsRequest(); + + const boundGetOutgoingAsyncInvitations: ( + argument: daemon_pb.GetOutgoingAsyncInvitationsRequest, + callback: grpc.requestCallback + ) => grpc.ClientUnaryCall = this.client.getOutgoingAsyncInvitations.bind( + this.client + ); + const promisifiedGetOutgoingAsyncInvitations = promisify( + boundGetOutgoingAsyncInvitations + ); + const response = await promisifiedGetOutgoingAsyncInvitations(request); + + if (response === undefined) { + throw new Error("getOutgoingAsyncInvitations returned undefined"); + } + + return response.toObject(); + } + + public async getIncomingAsyncInvitations(): Promise { + if (FAKE_DATA) { + return { + invitationsList: [ + { + message: "Im First", + publicId: "asdfasdf", + receivedAt: dateToProtobufDate(new Date()), + }, + { + message: "Im Second", + publicId: "asdfasdf", + receivedAt: dateToProtobufDate(new Date()), + }, + ], + }; + } + + const request = new daemon_pb.GetIncomingAsyncInvitationsRequest(); + + const boundGetIncomingAsyncInvitations: ( + argument: daemon_pb.GetIncomingAsyncInvitationsRequest, + callback: grpc.requestCallback + ) => grpc.ClientUnaryCall = this.client.getIncomingAsyncInvitations.bind( + this.client + ); + const promisifiedGetIncomingAsyncInvitations = promisify( + boundGetIncomingAsyncInvitations + ); + const response = await promisifiedGetIncomingAsyncInvitations(request); + + if (response === undefined) { + throw new Error("getIncomingAsyncInvitations returned undefined"); + } + + return response.toObject(); + } + + public async acceptAsyncInvitation( + acceptAsyncInvitationRequest: daemon_pb.AcceptAsyncInvitationRequest.AsObject + ): Promise { + if (FAKE_DATA) { + return {}; + } + + const request = new daemon_pb.AcceptAsyncInvitationRequest(); + request.setPublicId(acceptAsyncInvitationRequest.publicId); + request.setUniqueName(acceptAsyncInvitationRequest.uniqueName); + request.setDisplayName(acceptAsyncInvitationRequest.displayName); + + const boundAcceptAsyncInvitation: ( + argument: daemon_pb.AcceptAsyncInvitationRequest, + callback: grpc.requestCallback + ) => grpc.ClientUnaryCall = this.client.acceptAsyncInvitation.bind( + this.client + ); + const promisifiedAcceptAsyncInvitation = promisify( + boundAcceptAsyncInvitation + ); + const response = await promisifiedAcceptAsyncInvitation(request); + + if (response === undefined) { + throw new Error("acceptAsyncInvitation returned undefined"); + } + + return response.toObject(); + } + + public async rejectAsyncInvitation( + rejectAsyncInvitationRequest: daemon_pb.RejectAsyncInvitationRequest.AsObject + ): Promise { + if (FAKE_DATA) { + return {}; + } + + const request = new daemon_pb.RejectAsyncInvitationRequest(); + request.setPublicId(rejectAsyncInvitationRequest.publicId); + + const boundRejectAsyncInvitation: ( + argument: daemon_pb.RejectAsyncInvitationRequest, + callback: grpc.requestCallback + ) => grpc.ClientUnaryCall = this.client.rejectAsyncInvitation.bind( + this.client + ); + const promisifiedRejectAsyncInvitation = promisify( + boundRejectAsyncInvitation + ); + const response = await promisifiedRejectAsyncInvitation(request); + + if (response === undefined) { + throw new Error("rejectAsyncInvitation returned undefined"); + } + + return response.toObject(); + } + + public async getMyPublicID(): Promise { + if (FAKE_DATA) { + return { + publicId: + "3X2riNETQqUuFTqVrWqkGF8EsepzoarMYrNASmRJAzKwr9XB4Z7amnFNrgGm9wuq3wXAvvhPpu47eHWv29ikv5fbbzeeyfRvcgYqT", + story: + "Haphazard ambassador hides gradient. Cool mark resolves ambush. Eerie kiss revises distinction. Fashionable film fosters age. New customer views proprietor. Boiled plant kicks truck. Spectacular commission complies triumph. Faint.", + }; + } + + const request = new daemon_pb.GetMyPublicIDRequest(); + + const boundGetMyPublicID: ( + argument: daemon_pb.GetMyPublicIDRequest, + callback: grpc.requestCallback + ) => grpc.ClientUnaryCall = this.client.getMyPublicID.bind(this.client); + const promisifiedGetMyPublicID = promisify(boundGetMyPublicID); + const response = await promisifiedGetMyPublicID(request); + + if (response === undefined) { + throw new Error("getMyPublicID returned undefined"); + } + + return response.toObject(); + } +} diff --git a/gui/src/main/main.ts b/gui/src/main/main.ts index 4437ae5b..8168b3ff 100644 --- a/gui/src/main/main.ts +++ b/gui/src/main/main.ts @@ -3,8 +3,6 @@ // SPDX-License-Identifier: GPL-3.0-only // -/* eslint global-require: off, no-console: off, promise/always-return: off */ - import path from "path"; import { app, BrowserWindow, session, shell, Notification } from "electron"; import MenuBuilder from "./menu"; @@ -14,32 +12,29 @@ import { promisify } from "util"; import { exec as execNonPromisified } from "child_process"; const exec = promisify(execNonPromisified); -import { - getDaemonClient, - convertProtobufIncomingMessageToTypedMessage, - truncate, -} from "./daemon"; -import daemonM from "../daemon/schema/daemon_pb"; +import { truncate, DaemonImpl } from "./daemon"; +import { IncomingMessage } from "../types"; +import * as daemon_pb from "../daemon/schema/daemon_pb"; import { PLIST_CONTENTS, PLIST_PATH, RELEASE_COMMIT_HASH } from "./constants"; import { exit } from "process"; import fs from "fs"; -const daemonClient = getDaemonClient(); + +const daemon = new DaemonImpl(); const isDevelopment = - process.env.NODE_ENV === "development" || process.env.DEBUG_PROD === "true"; + process.env["NODE_ENV"] === "development" || + process.env["DEBUG_PROD"] === "true"; -if (process.env.NODE_ENV === "production") { +if (process.env["NODE_ENV"] === "production") { const sourceMapSupport = require("source-map-support"); - sourceMapSupport.install(); + sourcemapsupport.install(); } -const startDaemonIfNeeded = async (pkgPath: string) => { - const request = new daemonM.GetStatusRequest(); - const getStatus = promisify(daemonClient.getStatus).bind(daemonClient); +async function startDaemonIfNeeded(pkgPath: string): Promise { try { - const response = (await getStatus(request)) as daemonM.GetStatusResponse; + const daemonStatus = await daemon.getStatus({}); // if release hash is wrong, we need to restart! - if (response.getReleaseHash() !== RELEASE_COMMIT_HASH) { + if (daemonStatus.releaseHash !== RELEASE_COMMIT_HASH) { throw new Error("incorrect release hash"); } // daemon is running, correct version, nothing to do @@ -69,19 +64,19 @@ const startDaemonIfNeeded = async (pkgPath: string) => { // TODO(arvid): handle windows and linux too // possible problem, so let's start the daemon! // unload the plist if it exists. - const plist_path = PLIST_PATH(); + const plistPath = PLIST_PATH(); // 0: create the directory - const mkdir_plist = await exec(`mkdir -p ${path.dirname(plist_path)}`); - if (mkdir_plist.stderr) { - process.stderr.write(mkdir_plist.stderr); + const mkdirPlist = await exec(`mkdir -p ${path.dirname(plistPath)}`); + if (mkdirPlist.stderr) { + process.stderr.write(mkdirPlist.stderr); } // 1: unload plist - await exec("launchctl unload " + plist_path); // we don't care if it fails or not! + await exec("launchctl unload " + plistPath); // we don't care if it fails or not! let logPath = ""; - if (process.env.XDG_CACHE_HOME) { - logPath = path.join(process.env.XDG_CACHE_HOME, "anysphere", "logs"); - } else if (process.env.HOME) { - logPath = path.join(process.env.HOME, ".anysphere", "cache", "logs"); + if (process.env["XDG_CACHE_HOME"] !== undefined) { + logPath = path.join(process.env["XDG_CACHE_HOME"], "anysphere", "logs"); + } else if (process.env["HOME"] !== undefined) { + logPath = path.join(process.env["HOME"], ".anysphere", "cache", "logs"); } else { process.stderr.write( "$HOME or $XDG_CACHE_HOME not set! Cannot create daemon, aborting :(" @@ -90,15 +85,15 @@ const startDaemonIfNeeded = async (pkgPath: string) => { } const contents = PLIST_CONTENTS(pkgPath, logPath); // 2: write plist - await fs.promises.writeFile(plist_path, contents); + await fs.promises.writeFile(plistPath, contents); // 3: load plist - const response = await exec("launchctl load " + plist_path); + const response = await exec("launchctl load " + plistPath); if (response.stderr) { process.stderr.write(response.stderr); exit(1); } } -}; +} const installExtensions = async () => { const installer = require("electron-devtools-installer"); @@ -148,7 +143,7 @@ const createWindow = async () => { if (!mainWindow) { throw new Error('"mainWindow" is not defined'); } - if (process.env.START_MINIMIZED) { + if (process.env["START_MINIMIZED"] === "true") { mainWindow.minimize(); } else { mainWindow.show(); @@ -184,50 +179,28 @@ app.on("window-all-closed", () => { } }); -function registerForNotifications() { - const request = new daemonM.GetMessagesRequest(); - request.setFilter(daemonM.GetMessagesRequest.Filter.NEW); - var call = daemonClient.getMessagesStreamed(request); - +function registerForNotifications(): () => void { let firstTime = true; - - call.on("data", function (r) { - if (firstTime) { - firstTime = false; - return; - } - try { - const lm = r.getMessagesList(); - const l = lm.map(convertProtobufIncomingMessageToTypedMessage); - // notify!!! - for (const m of l) { + const cancel = daemon.getMessagesStreamed( + { filter: daemon_pb.GetMessagesRequest.Filter.NEW }, + (messages: IncomingMessage[]) => { + if (firstTime) { + firstTime = false; + return; + } + for (const m of messages) { const notification = new Notification({ - title: m.from, + title: m.fromDisplayName, body: truncate(m.message, 50), }); notification.show(); } - } catch (e) { - console.log(`error in getNewMessagesStreamed: ${e}`); } - }); - call.on("end", function () { - // The server has finished sending - // TODO(arvid): resubscribe? - console.log("getNewMessagesStreamed end"); - }); - call.on("error", function (e) { - // An error has occurred and the stream has been closed. - // TODO(arvid): resubscribe? - console.log("getNewMessagesStreamed error", e); - }); - call.on("status", function (status) { - // process status - console.log("getNewMessagesStreamed status", status); - }); + ); + return () => { console.log("cancelling notifications!"); - call.cancel(); + cancel(); }; } diff --git a/gui/src/main/preload.ts b/gui/src/main/preload.ts index b4545571..c8e3aa53 100644 --- a/gui/src/main/preload.ts +++ b/gui/src/main/preload.ts @@ -3,25 +3,8 @@ // SPDX-License-Identifier: GPL-3.0-only // -import { contextBridge, clipboard, contentTracing } from "electron"; -import { promisify } from "util"; -import grpc from "@grpc/grpc-js"; -import daemonM from "../daemon/schema/daemon_pb"; -import * as daemon_pb from "../daemon/schema/daemon_pb"; -import { - Friend, - IncomingMessage, - OutgoingMessage, - protobufDateToDate, - dateToProtobufDate, -} from "../types"; - -import { getDaemonClient } from "./daemon"; -import { Timestamp } from "google-protobuf/google/protobuf/timestamp_pb"; - -const daemonClient = getDaemonClient(); - -const FAKE_DATA = process.env["ASPHR_FAKE_DATA"] === "true"; +import { contextBridge, clipboard } from "electron"; +import { DaemonImpl } from "./daemon"; contextBridge.exposeInMainWorld("copyToClipboard", (s: string) => { clipboard.writeText(s, "selection"); @@ -31,784 +14,13 @@ contextBridge.exposeInMainWorld("isPlatformMac", () => { return process.platform === "darwin"; }); -contextBridge.exposeInMainWorld( - "sendMessage", - async ( - sendMessageRequest: daemon_pb.SendMessageRequest.AsObject - ): Promise => { - if (FAKE_DATA) { - console.log("Sending message:", sendMessageRequest); - return {}; - } - const request = new daemonM.SendMessageRequest(); - request.setUniqueNameList(sendMessageRequest.uniqueNameList); - request.setMessage(sendMessageRequest.message); - const sendMessage = promisify(daemonClient.sendMessage).bind(daemonClient); - return ( - (await sendMessage(request)) as daemon_pb.SendMessageResponse - ).toObject(); - } -); - -contextBridge.exposeInMainWorld( - "getMessages", - async ( - getMessagesRequest: daemon_pb.GetMessagesRequest.AsObject - ): Promise => { - if (FAKE_DATA) { - if ( - getMessagesRequest.filter === daemon_pb.GetMessagesRequest.Filter.NEW - ) { - return { - messagesList: [ - { - uid: 1, - message: "Hello! This is a test message.", - fromUniqueName: "sualeh-asif", - fromDisplayName: "Sualeh Asif", - otherRecipientsList: [], - deliveredAt: dateToProtobufDate(new Date()), - seen: false, - delivered: true, - }, - { - uid: 2, - message: - "This is a new message. It has been delivered, but not yet seen.", - fromUniqueName: "stzh1555", - fromDisplayName: "Shengtong Zhang", - otherRecipientsList: [], - deliveredAt: dateToProtobufDate(new Date()), - seen: false, - delivered: true, - }, - ], - }; - } else { - return { - messagesList: [ - { - uid: 1, - message: "Hello! This is a test message.", - fromUniqueName: "sualeh-asif", - fromDisplayName: "Sualeh Asif", - otherRecipientsList: [], - deliveredAt: dateToProtobufDate(new Date()), - seen: false, - delivered: true, - }, - { - uid: 2, - message: - "This is a new message. It has been delivered, but not yet seen.", - fromUniqueName: "stzh1555", - fromDisplayName: "Shengtong Zhang", - otherRecipientsList: [], - deliveredAt: dateToProtobufDate(new Date()), - seen: false, - delivered: true, - }, - { - uid: 3, - message: - "This is a new message. This message has been marked as seen!", - fromUniqueName: "stzh1555", - fromDisplayName: "Shengtong Zhang", - otherRecipientsList: [], - deliveredAt: dateToProtobufDate(new Date()), - seen: true, - delivered: true, - }, - ], - }; - } - } - const request = new daemonM.GetMessagesRequest(); - request.setFilter(getMessagesRequest.filter); - const getMessages = promisify(daemonClient.getMessages).bind(daemonClient); - return ( - (await getNewMessages(request)) as daemonM.GetMessagesResponse - ).toObject(); - } -); - -contextBridge.exposeInMainWorld( - "getMessagesStreamed", - ( - getMessagesRequest: daemon_pb.GetMessagesRequest.AsObject, - messageHandler: (_: IncomingMessage[]) => void - ): (() => void) => { - if (FAKE_DATA) { - if ( - getMessagesRequest.filter === daemon_pb.GetMessagesRequest.Filter.NEW - ) { - const l: IncomingMessage[] = [ - { - uid: 1, - message: - "Can you schedule a meeting with Srini for next week, please?", - fromUniqueName: "sualeh-asif", - fromDisplayName: "Sualeh Asif", - otherRecipientsList: [], - deliveredAt: dateToProtobufDate(new Date()), - seen: false, - delivered: true, - }, - { - uid: 2, - message: - "I pushed some of my comments on the white paper draft to the repo, but I figured I should explain some of it here, too.", - fromUniqueName: "sualeh-asif", - fromDisplayName: "Sualeh Asif", - otherRecipientsList: [], - deliveredAt: dateToProtobufDate(new Date()), - seen: false, - delivered: true, - }, - ]; - messageHandler(l); - return () => { - console.log("Stopped streaming messages."); - }; - } else { - const l: IncomingMessage[] = [ - { - uid: 1, - message: - "Can you schedule a meeting with Srini for next week, please?", - fromUniqueName: "sualeh-asif", - fromDisplayName: "Sualeh Asif", - otherRecipientsList: [], - deliveredAt: dateToProtobufDate(new Date()), - seen: false, - delivered: true, - }, - { - uid: 2, - message: - "I pushed some of my comments on the white paper draft to the repo, but I figured I should explain some of it here, too.", - fromUniqueName: "sualeh-asif", - fromDisplayName: "Sualeh Asif", - otherRecipientsList: [], - deliveredAt: dateToProtobufDate(new Date()), - seen: false, - delivered: true, - }, - { - uid: 3, - message: - "I was thinking about the thing you told me about the other day, and I think you're right.", - fromUniqueName: "sualeh-asif", - fromDisplayName: "Sualeh Asif", - otherRecipientsList: [], - deliveredAt: dateToProtobufDate(new Date()), - seen: true, - delivered: true, - }, - { - uid: 4, - message: - "Can you take a look at the server branch and see if it works? I made some changes.", - fromUniqueName: "sualeh-asif", - fromDisplayName: "Sualeh Asif", - otherRecipientsList: [], - deliveredAt: dateToProtobufDate(new Date()), - seen: true, - delivered: true, - }, - { - uid: 5, - message: - "Hi Arvid,\n\nThank you so much for onboarding me to Anysphere! I am very excited to work with you.\n\nBest,\nShengtong", - fromUniqueName: "stzh1555", - fromDisplayName: "Shengtong Zhang", - otherRecipientsList: [], - deliveredAt: dateToProtobufDate(new Date()), - seen: true, - delivered: true, - }, - { - uid: 6, - message: - "Dear Arvid,\n\nThis is my first ever completely private message to you. No one will be able to read this message, find out when it was sent, or even suspect that I sent anything to you at all.\n\nHere's to a thoughtful, private and free future.\n\nYours truly,\nSualeh", - fromUniqueName: "stzh1555", - fromDisplayName: "Shengtong Zhang", - otherRecipientsList: [], - deliveredAt: dateToProtobufDate(new Date()), - seen: true, - delivered: true, - }, - ]; - messageHandler(l); - return () => { - console.log("Stopped streaming messages."); - }; - } - } - const request = new daemonM.GetMessagesRequest(); - request.setFilter(getMessagesRequest.filter); - const call = daemonClient.getMessagesStreamed(request); - call.on("data", function (r: daemonM.GetMessagesResponse) { - try { - const lm = r.getMessagesList(); - messageHandler(lm.map((m) => m.toObject())); - } catch (e) { - console.log(`error in getAllMessagesStreamed: ${e}`); - } - }); - call.on("end", function () { - // The server has finished sending - console.log("getAllMessagesStreamed end"); - }); - call.on("error", function (e: Error) { - // An error has occurred and the stream has been closed. - console.log("getAllMessagesStreamed error", e); - }); - call.on("status", function (status: grpc.StatusObject) { - // process status - console.log("getAllMessagesStreamed status", status); - }); - return () => { - console.log("cancelling grpc!"); - call.cancel(); - }; - } -); - -contextBridge.exposeInMainWorld( - "getOutboxMessages", - async ( - getOutboxMessagesRequest: daemon_pb.GetOutboxMessagesRequest.AsObject - ): Promise => { - if (FAKE_DATA) { - return { - messagesList: [ - { - uid: 1, - message: - "This is an outbox message that doesn't seem to want to be delivered...", - toFriendsList: [ - { - uniqueName: "sualeh-asif", - displayName: "Sualeh Asif", - delivered: false, - }, - ], - sentAt: dateToProtobufDate(new Date()), - }, - { - uid: 2, - message: "I'm confused...", - toFriendsList: [ - { - uniqueName: "stzh1555", - displayName: "Shengtong Zhang", - delivered: false, - }, - ], - sentAt: dateToProtobufDate(new Date()), - }, - { - uid: 3, - message: "I'm very confused...", - toFriendsList: [ - { - uniqueName: "stzh1555", - displayName: "Shengtong Zhang", - delivered: false, - }, - ], - sentAt: dateToProtobufDate(new Date()), - }, - ], - }; - } - const request = new daemonM.GetOutboxMessagesRequest(); - const getOutboxMessages = promisify(daemonClient.getOutboxMessages).bind( - daemonClient - ); - return ( - (await getOutboxMessages(request)) as daemonM.GetOutboxMessagesResponse - ).toObject(); - } -); - -contextBridge.exposeInMainWorld( - "getSentMessages", - async ( - getSentMessagesRequest: daemon_pb.GetSentMessagesRequest.AsObject - ): Promise => { - if (FAKE_DATA) { - return { - messagesList: [ - { - uid: 1, - message: "The draft looks good. Let me know if you need any help!", - toFriendsList: [ - { - uniqueName: "sualeh-asif", - displayName: "Sualeh Asif", - delivered: true, - deliveredAt: dateToProtobufDate(new Date()), - }, - ], - sentAt: dateToProtobufDate(new Date()), - }, - { - uid: 2, - message: - "Welcome! We are extremely excited to have you as a part of our team.", - toFriendsList: [ - { - uniqueName: "shengtong-zhang", - displayName: "Shengtong Zhang", - delivered: true, - deliveredAt: dateToProtobufDate(new Date()), - }, - ], - sentAt: dateToProtobufDate(new Date()), - }, - ], - }; - } - - const request = new daemonM.GetSentMessagesRequest(); - const getSentMessages = promisify(daemonClient.getSentMessages).bind( - daemonClient - ); - return ( - (await getSentMessages(request)) as daemonM.GetSentMessagesResponse - ).toObject(); - } -); - -contextBridge.exposeInMainWorld( - "messageSeen", - async (messageSeenRequest: daemon_pb.MessageSeenRequest.AsObject) => { - const request = new daemonM.MessageSeenRequest(); - request.setId(messageSeenRequest.id); - const messageSeen = promisify(daemonClient.messageSeen).bind(daemonClient); - try { - const response = await messageSeen(request); - console.log("messageSeen response", response); - return true; - } catch (e) { - console.log(`error in send: ${e}`); - return false; - } - } -); - -contextBridge.exposeInMainWorld("hasRegistered", async () => { - if (FAKE_DATA) { - return true; - } - const request = new daemonM.GetStatusRequest(); - const getStatus = promisify(daemonClient.getStatus).bind(daemonClient); - try { - const response = (await getStatus(request)) as daemonM.GetStatusResponse; - return response.getRegistered(); - } catch (e) { - console.log(`error in hasRegistered: ${e}`); - return false; - } -}); - -contextBridge.exposeInMainWorld( - "registerUser", - async (registerUserRequest: daemon_pb.RegisterUserRequest.AsObject) => { - const request = new daemonM.RegisterUserRequest(); - request.setName(registerUserRequest.name); - request.setBetaKey(registerUserRequest.betaKey); - const register = promisify(daemonClient.registerUser).bind(daemonClient); - await register(request); - } -); - -contextBridge.exposeInMainWorld( - "getFriendList", - async (): Promise => { - if (FAKE_DATA) { - return [ - { - uniqueName: "Sualeh", - displayName: "Sualeh Asif", - publicId: "asdfasdf", - invitationProgress: daemon_pb.InvitationProgress.COMPLETE, - }, - { - uniqueName: "Shengtong", - displayName: "Shengtong", - publicId: "asdfasdf", - invitationProgress: daemon_pb.InvitationProgress.COMPLETE, - }, - { - uniqueName: "Bob", - displayName: "Bob", - publicId: "asdfasdf", - invitationProgress: daemon_pb.InvitationProgress.COMPLETE, - }, - ]; - } - - const request = new daemonM.GetFriendListRequest(); - const getFriendList = promisify(daemonClient.getFriendList).bind( - daemonClient - ); - - try { - const response = (await getFriendList( - request - )) as daemonM.GetFriendListResponse; - const lm = response.getFriendInfosList(); - const l = lm.map((m) => { - // TODO: fix this when the schema is cleaned up. - return { - uniqueName: m.getUniqueName(), - displayName: m.getDisplayName(), - publicId: m.getPublicId(), - invitationProgress: m.getInvitationProgress(), - }; - }); - return l; - } catch (e) { - console.log(`error in getFriendList: ${e}`); - return []; - } - } -); - -// removeFriend -contextBridge.exposeInMainWorld( - "removeFriend", - async (uniqueName: string): Promise => { - if (FAKE_DATA) { - return true; - } - - const request = new daemonM.RemoveFriendRequest(); - request.setUniqueName(uniqueName); - const removeFriend = promisify(daemonClient.removeFriend).bind( - daemonClient - ); - - try { - await removeFriend(request); - return true; - } catch (e) { - console.log(`error in removeFriend: ${e}`); - return false; - } - } -); - -// addAsyncFriend -contextBridge.exposeInMainWorld( - "addAsyncFriend", - async ( - addAsyncFriendRequest: daemon_pb.AddAsyncFriendRequest.AsObject - ): Promise => { - if (FAKE_DATA) { - return true; - } - - const request = new daemonM.AddAsyncFriendRequest(); - request.setUniqueName(addAsyncFriendRequest.uniqueName); - request.setDisplayName(addAsyncFriendRequest.displayName); - request.setPublicId(addAsyncFriendRequest.publicId); - request.setMessage(addAsyncFriendRequest.message); - - const addAsyncFriend = promisify(daemonClient.addAsyncFriend).bind( - daemonClient - ); - - try { - await addAsyncFriend(request); - return true; - } catch (e) { - console.log(`error in addAsyncFriend: ${e}`); - return false; - } - } -); - -// addSyncFriend -contextBridge.exposeInMainWorld( - "addSyncFriend", - async ( - addSyncFriendRequest: daemon_pb.AddSyncFriendRequest.AsObject - ): Promise => { - if (FAKE_DATA) { - return; - } - - const request = new daemonM.AddSyncFriendRequest(); - request.setUniqueName(addSyncFriendRequest.uniqueName); - request.setDisplayName(addSyncFriendRequest.displayName); - request.setStory(addSyncFriendRequest.story); - - const addSyncFriend = promisify(daemonClient.addSyncFriend).bind( - daemonClient - ); - - try { - await addSyncFriend(request); - } catch (e) { - console.log(`error in addSyncFriend: ${e}`); - throw e; - } - } -); - -// getOutgoingSyncInvitations -contextBridge.exposeInMainWorld( - "getOutgoingSyncInvitations", - async (): Promise => { - if (FAKE_DATA) { - return { - invitationsList: [ - { - uniqueName: "arvid", - displayName: "Arvid Lunnemark", - story: - "Haphazard ambassador hides gradient. Cool mark resolves ambush. Eerie kiss revises distinction. Fashionable film fosters age. New customer views proprietor. Boiled plant kicks truck. Spectacular commission complies triumph. Faint.", - sentAt: Timestamp.fromDate(new Date()).toObject(), - }, - ], - }; - } - - const request = new daemonM.GetOutgoingSyncInvitationsRequest(); - - const getOutgoingSyncInvitations = promisify( - daemonClient.getOutgoingSyncInvitations - ).bind(daemonClient); - - try { - const response = (await getOutgoingSyncInvitations( - request - )) as daemonM.GetOutgoingSyncInvitationsResponse; - - return response.toObject(); - } catch (e) { - console.log(`error in getOutgoingSyncInvitations: ${e}`); - return { - invitationsList: [], - }; - } - } -); - -// getOutgoingAsyncInvitations -contextBridge.exposeInMainWorld( - "getOutgoingAsyncInvitations", - async (): Promise => { - if (FAKE_DATA) { - return { - invitationsList: [ - { - uniqueName: "OutgoingSualeh", - displayName: "Sualeh Asif ooooo", - publicId: "asdfasdf", - message: "hihihihihihihihih", - sentAt: Timestamp.fromDate(new Date()).toObject(), - }, - { - uniqueName: "OutgoingShengtong", - displayName: "Shengtong aaaaaa", - publicId: "asdfasdf", - message: "hihihihihihihihih", - sentAt: Timestamp.fromDate(new Date()).toObject(), - }, - ], - }; - } - - const request = new daemonM.GetOutgoingAsyncInvitationsRequest(); - - const getOutgoingAsyncInvitations = promisify( - daemonClient.getOutgoingAsyncInvitations - ).bind(daemonClient); - - try { - const response = (await getOutgoingAsyncInvitations( - request - )) as daemonM.GetOutgoingAsyncInvitationsResponse; - - return response.toObject(); - } catch (e) { - console.log(`error in getOutgoingAsyncInvitations: ${e}`); - return { - invitationsList: [], - }; - } - } -); - -// getIncomingAsyncInvitations -contextBridge.exposeInMainWorld( - "getIncomingAsyncInvitations", - async (): Promise => { - if (FAKE_DATA) { - return { - invitationsList: [ - { - message: "Im First", - publicId: "asdfasdf", - receivedAt: Timestamp.fromDate(new Date()).toObject(), - }, - { - message: "Im Second", - publicId: "asdfasdf", - receivedAt: Timestamp.fromDate(new Date()).toObject(), - }, - ], - }; - } - - const request = new daemonM.GetIncomingAsyncInvitationsRequest(); - - const getIncomingAsyncInvitations = promisify( - daemonClient.getIncomingAsyncInvitations - ).bind(daemonClient); - - try { - const response = (await getIncomingAsyncInvitations( - request - )) as daemonM.GetIncomingAsyncInvitationsResponse; - - return response.toObject(); - } catch (e) { - console.log(`error in getIncomingAsyncInvitations: ${e}`); - return { - invitationsList: [], - }; - } - } -); - -// acceptAsyncInvitation -contextBridge.exposeInMainWorld( - "acceptAsyncInvitation", - async ( - acceptAsyncInvitationRequest: daemon_pb.AcceptAsyncInvitationRequest.AsObject - ): Promise => { - if (FAKE_DATA) { - return true; - } - - const request = new daemonM.AcceptAsyncInvitationRequest(); - request.setPublicId(acceptAsyncInvitationRequest.publicId); - request.setUniqueName(acceptAsyncInvitationRequest.uniqueName); - request.setDisplayName(acceptAsyncInvitationRequest.displayName); - - const acceptAsyncInvitation = promisify( - daemonClient.acceptAsyncInvitation - ).bind(daemonClient); - - try { - await acceptAsyncInvitation(request); - return true; - } catch (e) { - console.log(`error in acceptAsyncInvitation: ${e}`); - return false; - } - } -); - -// rejectAsyncInvitation -contextBridge.exposeInMainWorld( - "rejectAsyncInvitation", - async ( - rejectAsyncInvitationRequest: daemon_pb.RejectAsyncInvitationRequest.AsObject - ): Promise => { - if (FAKE_DATA) { - return true; - } - - const request = new daemonM.RejectAsyncInvitationRequest(); - request.setPublicId(rejectAsyncInvitationRequest.publicId); - - const rejectAsyncInvitation = promisify( - daemonClient.rejectAsyncInvitation - ).bind(daemonClient); - - try { - await rejectAsyncInvitation(request); - return true; - } catch (e) { - console.log(`error in rejectAsyncInvitation: ${e}`); - return false; - } - } -); - -// getMyPublicID -contextBridge.exposeInMainWorld( - "getMyPublicID", - async (): Promise => { - if (FAKE_DATA) { - return { - publicId: - "3X2riNETQqUuFTqVrWqkGF8EsepzoarMYrNASmRJAzKwr9XB4Z7amnFNrgGm9wuq3wXAvvhPpu47eHWv29ikv5fbbzeeyfRvcgYqT", - story: - "Haphazard ambassador hides gradient. Cool mark resolves ambush. Eerie kiss revises distinction. Fashionable film fosters age. New customer views proprietor. Boiled plant kicks truck. Spectacular commission complies triumph. Faint.", - }; - } - - const request = new daemonM.GetMyPublicIDRequest(); - - const getMyPublicID = promisify(daemonClient.getMyPublicID).bind( - daemonClient - ); - - try { - const response = (await getMyPublicID( - request - )) as daemonM.GetMyPublicIDResponse; - - return response.toObject(); - } catch (e) { - // TODO: maybe this error is unrecoverable? maybe we want to try to restart the daemon? - console.log(`error in getMyPublicID: ${e}`); - throw e; - } - } -); - -/** - * @deprecated - * @param requestedFriend the friend to get messages for - */ -contextBridge.exposeInMainWorld( - "generateFriendKey", - async (requestedFriend: string) => { - try { - return { - friend: requestedFriend, - key: "6aFLPa03ldA9OyY-XlCRibbo3SG8Wsprw1iylnjvZIiFc", - }; - } catch (e) { - console.log(`error in generateFriendKey: ${e}`); - return null; - } - } -); - -/** - * @deprecated - * @param requestedFriend the friend to add. - */ -contextBridge.exposeInMainWorld( - "addFriend", - async (_requestedFriend: string, _requestedFriendKey: string) => { - try { - return true; - } catch (e) { - console.log(`error in addFriend: ${e}`); - return false; - } - } -); - -contextBridge.exposeInMainWorld("sendAsyncFriendRequest", async () => {}); +const daemonI = new DaemonImpl(); +const classToObject = (theClass) => { + const originalClass = theClass || {}; + const keys = Object.getOwnPropertyNames(Object.getPrototypeOf(originalClass)); + return keys.reduce((classAsObj, key) => { + classAsObj[key] = originalClass[key]; + return classAsObj; + }, {}); +}; +contextBridge.exposeInMainWorld("daemon", classToObject(daemonI)); diff --git a/gui/src/preload.d.ts b/gui/src/preload.d.ts index dad11024..1a18b3da 100644 --- a/gui/src/preload.d.ts +++ b/gui/src/preload.d.ts @@ -3,94 +3,14 @@ // SPDX-License-Identifier: GPL-3.0-only // -import * as daemon_pb from "./daemon/schema/daemon_pb"; -import { IncomingMessage, OutgoingMessage, Friend } from "./types"; +import { Daemon } from "./main/daemon"; declare global { interface Window { copyToClipboard(s: string): void; isPlatformMac(): boolean; - registerUser( - registerUserRequest: daemon_pb.RegisterUserRequest.AsObject - ): Promise; - - getMyPublicID(): Promise; - - getFriendList(): Promise; - - removeFriend( - removeFriendRequest: daemon_pb.RemoveFriendRequest.AsObject - ): Promise; - - addSyncFriend( - addSyncFriendRequest: daemon_pb.AddSyncFriendRequest.AsObject - ): Promise; - - addAsyncFriend( - addAsyncFriendRequest: daemon_pb.AddAsyncFriendRequest.AsObject - ): Promise; - - getOutgoingSyncInvitations(): Promise; - - getOutgoingAsyncInvitations( - getOutgoingAsyncInvitationsRequest: daemon_pb.GetOutgoingAsyncInvitationsRequest.AsObject - ): Promise; - - getIncomingAsyncInvitations( - getIncomingAsyncInvitationsRequest: daemon_pb.GetIncomingAsyncInvitationsRequest.AsObject - ): Promise; - - acceptAsyncInvitation( - acceptAsyncInvitationRequest: daemon_pb.AcceptAsyncInvitationRequest.AsObject - ): Promise; - - rejectAsyncInvitation( - rejectAsyncInvitationRequest: daemon_pb.RejectAsyncInvitationRequest.AsObject - ): Promise; - - sendMessage( - sendMessageRequest: daemon_pb.SendMessageRequest.AsObject - ): Promise; - - getMessages( - getMessagesRequest: daemon_pb.GetMessagesRequest.AsObject - ): Promise; - - getOutboxMessages( - getOutboxMessagesRequest: daemon_pb.GetOutboxMessagesRequest.AsObject - ): Promise; - - getSentMessages( - getSentMessagesRequest: daemon_pb.GetSentMessagesRequest.AsObject - ): Promise; - - messageSeen( - messageSeenRequest: daemon_pb.MessageSeenRequest.AsObject - ): Promise; - - getStatus( - getStatusRequest: daemon_pb.GetStatusRequest.AsObject - ): Promise; - - getLatency( - getLatencyRequest: daemon_pb.GetLatencyRequest.AsObject - ): Promise; - - changeLatency( - changeLatencyRequest: daemon_pb.ChangeLatencyRequest.AsObject - ): Promise; - - kill( - killRequest: daemon_pb.KillRequest.AsObject - ): Promise; - - getMessagesStreamed( - getMessagesRequest: daemon_pb.GetMessagesRequest.AsObject, - messageHandler: (_: IncomingMessage[]) => void - ): () => void; - - hasRegistered(): Promise; + daemon: Daemon; } } diff --git a/gui/src/renderer/Main.tsx b/gui/src/renderer/Main.tsx index 97f29dcd..a20e63d8 100644 --- a/gui/src/renderer/Main.tsx +++ b/gui/src/renderer/Main.tsx @@ -62,7 +62,7 @@ function Main(): JSX.Element { return; } } - window.messageSeen({ id: message.uid }).catch(console.error); + window.daemon.messageSeen({ id: message.uid }).catch(console.error); let title = ""; if ("fromDisplayName" in message) { title = `${truncate(message.message, 10)} - ${message.fromDisplayName}`; @@ -194,7 +194,7 @@ function Main(): JSX.Element { } React.useEffect(() => { - window + window.daemon .hasRegistered() .then((registered: boolean) => { if (!registered) { @@ -202,7 +202,7 @@ function Main(): JSX.Element { {}} // should not be able to close modal by clicking outside onRegister={(username: string, key: string) => { - window + window.daemon .registerUser({ name: username, betaKey: key }) .then(() => { closeModal(); @@ -278,13 +278,13 @@ function Main(): JSX.Element { case SideBarButton.ADD_FRIEND: return openFriendModal(); default: - return React.useCallback(() => {}, []); + return () => {}; break; } }; // CmdK options and shortcuts - const CmdKActions = [ + const cmdKActions = [ { id: "friend", name: "Add and Manage Friends", @@ -344,7 +344,7 @@ function Main(): JSX.Element { // }, ]; - const CmdKOptions: KBarOptions = { + const cmdKOptions: KBarOptions = { callbacks: { onClose: () => { closeModal(); @@ -388,7 +388,7 @@ function Main(): JSX.Element { - + {modal} diff --git a/gui/src/renderer/components/AddFriend/AddFriend.tsx b/gui/src/renderer/components/AddFriend/AddFriend.tsx index 8cf8242e..24fcd33c 100644 --- a/gui/src/renderer/components/AddFriend/AddFriend.tsx +++ b/gui/src/renderer/components/AddFriend/AddFriend.tsx @@ -33,7 +33,7 @@ export default function AddFriend({ onClose={onClose} setStatus={setStatus} chooseInperson={() => { - window + window.daemon .getMyPublicID() .then((publicID) => { setStory(publicID.story); @@ -48,7 +48,7 @@ export default function AddFriend({ }); }} chooseRemote={() => { - window + window.daemon .getMyPublicID() .then((publicID) => { setPublicID(publicID.publicId); diff --git a/gui/src/renderer/components/AddFriend/AddFriendInPerson.tsx b/gui/src/renderer/components/AddFriend/AddFriendInPerson.tsx index 663f9944..2fb4d2c0 100644 --- a/gui/src/renderer/components/AddFriend/AddFriendInPerson.tsx +++ b/gui/src/renderer/components/AddFriend/AddFriendInPerson.tsx @@ -425,7 +425,7 @@ export default function AddFriendInPerson({ }); return; } - window + window.daemon .addSyncFriend({ uniqueName, displayName, diff --git a/gui/src/renderer/components/Compose/Write.tsx b/gui/src/renderer/components/Compose/Write.tsx index b5faf6f7..bb47c009 100644 --- a/gui/src/renderer/components/Compose/Write.tsx +++ b/gui/src/renderer/components/Compose/Write.tsx @@ -201,7 +201,7 @@ function Write({ // get both the complete friends and the sync invitations // the sync invitations have verified each other so it is safe to treat as a real friend // in the daemon.proto we keep them separate because we still want to display progress information - window + window.daemon .getFriendList() .then((friends: Friend[]) => { setFriends((f) => [ @@ -223,7 +223,7 @@ function Write({ // get both the complete friends and the sync invitations // the sync invitations have verified each other so it is safe to treat as a real friend // in the daemon.proto we keep them separate because we still want to display progress information - window + window.daemon .getOutgoingSyncInvitations() .then( ( @@ -285,7 +285,7 @@ function Write({ const displayNames = data.multiSelectState.friends.map( (friend) => friend.displayName ); - window + window.daemon .sendMessage({ uniqueNameList: uniqueNames, message: content, diff --git a/gui/src/renderer/components/MessageList.tsx b/gui/src/renderer/components/MessageList.tsx index b8e80943..aab5495d 100644 --- a/gui/src/renderer/components/MessageList.tsx +++ b/gui/src/renderer/components/MessageList.tsx @@ -95,7 +95,7 @@ export function IncomingMessageList({ React.useEffect(() => { if (type === "new") { setMessages([]); - const cancel = window.getMessagesStreamed( + const cancel = window.daemon.getMessagesStreamed( { filter: daemon_pb.GetMessagesRequest.Filter.NEW }, (messages: IncomingMessage[]) => { setMessages((prev: IncomingMessage[]) => { @@ -121,7 +121,7 @@ export function IncomingMessageList({ return cancel; } else if (type === "all") { setMessages([]); - const cancel = window.getMessagesStreamed( + const cancel = window.daemon.getMessagesStreamed( { filter: daemon_pb.GetMessagesRequest.Filter.ALL }, (messages: IncomingMessage[]) => { setMessages((prev: IncomingMessage[]) => { @@ -195,7 +195,7 @@ export function OutgoingMessageList({ React.useEffect(() => { if (type === "outbox") { - window + window.daemon .getOutboxMessages({}) .then(({ messagesList }: { messagesList: OutgoingMessage[] }) => { setMessages(messagesList); @@ -204,7 +204,7 @@ export function OutgoingMessageList({ console.error(err); }); } else if (type === "sent") { - window + window.daemon .getSentMessages({}) .then(({ messagesList }: { messagesList: OutgoingMessage[] }) => { setMessages(messagesList); diff --git a/gui/src/renderer/components/Outbox/Outbox.tsx b/gui/src/renderer/components/Outbox/Outbox.tsx deleted file mode 100644 index 345a0627..00000000 --- a/gui/src/renderer/components/Outbox/Outbox.tsx +++ /dev/null @@ -1,106 +0,0 @@ -// -// Copyright 2022 Anysphere, Inc. -// SPDX-License-Identifier: GPL-3.0-only -// - -import * as React from "react"; -import { Message } from "../../../types"; -import { truncate, formatTime } from "../../utils"; -import { SelectableList } from "../SelectableList"; - -function MessageBlurb({ - message, - active, -}: { - message: Message; - active: boolean; -}) { - let timestamp_string = ""; - try { - console.log(message.timestamp); - timestamp_string = formatTime(message.timestamp); - } catch {} - - return ( -
-
-
{message.from}
-
- {truncate(message.message, 70)} -
-
-
{timestamp_string}
-
-
- ); -} - -function NoMessages({ explanation }: { explanation: string }) { - return ( -
-
- {explanation} -
-
- ); -} - -export default function OutboxMessageList(props: { - messages: string; - readCallback: (message: Message) => void; -}) { - const [messages, setMessages] = React.useState([]); - - React.useEffect(() => { - if (props.messages === "new") { - window.getNewMessages().then((messages: Message[]) => { - setMessages(messages); - }); - } else { - window.getAllMessages().then((messages: Message[]) => { - setMessages(messages); - }); - } - }, [props.messages]); - - const noMessageExplanation = - props.messages === "new" ? "Nothing in Outbox." : "No messages."; - - return ( -
-
-
- { - return { - id: message.id, - data: message, - action: () => props.readCallback(message), - }; - })} - searchable={false} - globalAction={() => {}} - onRender={({ item, active }) => - typeof item === "string" ? ( -
{item}
- ) : ( - - ) - } - /> -
-
- {messages.length === 0 && ( - - )} -
- ); -}