Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,165 changes: 896 additions & 269 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
"eslint": "^7.6.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.0",
"husky": "^8.0.1",
"jasmine-core": "~3.5.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "^6.3.2",
Expand All @@ -109,13 +110,12 @@
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"ng-packagr": "^11.1.2",
"prettier": "^2.7.1",
"pretty-quick": "^3.1.3",
"protractor": "~7.0.0",
"ts-node": "~7.0.0",
"typescript": "^4.0.7",
"webpack-cli": "^3.3.10",
"husky": "^8.0.1",
"prettier": "^2.7.1",
"pretty-quick": "^3.1.3"
"webpack-cli": "^3.3.10"
},
"main": "main.js"
}
4 changes: 4 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,9 @@ import { BurnDaoCoinModalComponent } from './dao-coins/burn-dao-coin-modal/burn-
import { ThemeModule } from './theme/theme.module';
import { Theme } from './theme/symbols';
import { PostMultiplierComponent } from './feed/feed-post-dropdown/post-multiplier/post-multiplier.component';
import {
CreateMessagingGroupModalComponent
} from "./messages-page/create-messaging-group-modal/create-messaging-group-modal.component";
const lightTheme: Theme = { key: 'light', name: 'Light Theme' };
const darkTheme: Theme = { key: 'dark', name: 'Dark Theme' };
const icydarkTheme: Theme = { key: 'icydark', name: 'Icy Dark Theme' };
Expand Down Expand Up @@ -325,6 +328,7 @@ const greenishTheme: Theme = { key: 'greenish', name: 'Green Theme' };
DaoCoinsPageComponent,
TransferDAOCoinModalComponent,
BurnDaoCoinModalComponent,
CreateMessagingGroupModalComponent,
],
imports: [
BrowserModule,
Expand Down
270 changes: 265 additions & 5 deletions src/app/backend-api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
// get the browser to save the cookie in the response.
// https://github.com/github/fetch#sending-cookies
import { Injectable } from '@angular/core';
import { interval, Observable, of, throwError, zip } from 'rxjs';
import { from, interval, Observable, of, throwError, zip } from 'rxjs';
import {
map,
switchMap,
catchError,
concatMap,
filter,
map,
switchMap,
take,
concatMap,
} from 'rxjs/operators';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { IdentityService } from './identity.service';
Expand Down Expand Up @@ -40,8 +40,11 @@ export class BackendRoutes {
static RoutePathIsHodlingPublicKey = '/api/v0/is-hodling-public-key';
static RoutePathSendMessageStateless = '/api/v0/send-message-stateless';
static RoutePathGetMessagesStateless = '/api/v0/get-messages-stateless';
static RoutePathGetMessagesStatelessV1 = '/api/v1/get-messages-stateless';
static GetAllMessagingGroupKeys = '/api/v0/get-all-messaging-group-keys';
static RoutePathCheckPartyMessagingKeys =
'/api/v0/check-party-messaging-keys';
static RegisterGroupMessagingKey = '/api/v0/register-messaging-group-key';
static RoutePathMarkContactMessagesRead =
'/api/v0/mark-contact-messages-read';
static RoutePathMarkAllMessagesRead = '/api/v0/mark-all-messages-read';
Expand Down Expand Up @@ -538,6 +541,72 @@ export enum TransferRestrictionStatusString {
PERMANENTLY_UNRESTRICTED = 'permanently_unrestricted',
}

export type MessagingGroupMember = {
GroupMemberPublicKeyBase58Check: string;
GroupMemberKeyName: string;
EncryptedKey: string;
};

export type MessagingGroupEntryResponse = {
GroupOwnerPublicKeyBase58Check: string;
MessagingPublicKeyBase58Check: string;
MessagingGroupKeyName: string;
MessagingGroupMembers: MessagingGroupMember[];
EncryptedKey: string;
ExtraData: { [k: string]: string };
};

export type GetAllMessagingGroupKeysResponse = {
MessagingGroupEntries: MessagingGroupEntryResponse[];
};

export type MessagingGroupMemberResponse = {
// GroupMemberPublicKeyBase58Check is the main public key of the group member.
GroupMemberPublicKeyBase58Check: string;

// GroupMemberKeyName is the key name of the member that we encrypt the group messaging public key to. The group
// messaging public key should not be confused with the GroupMemberPublicKeyBase58Check, the former is the public
// key of the whole group, while the latter is the public key of the group member.
GroupMemberKeyName: string;

// EncryptedKey is the encrypted private key corresponding to the group messaging public key that's encrypted
// to the member's registered messaging key labeled with GroupMemberKeyName.
EncryptedKey: string;
};

export type MessageEntryResponse = {
SenderPublicKeyBase58Check: string;
RecipientPublicKeyBase58Check: string;
EncryptedText: string;
TstampNanos: number;
IsSender: boolean;
V2: boolean; // deprecated
Version: number;
SenderMessagingPublicKey: string;
SenderMessagingGroupKeyName: string;
RecipientMessagingPublicKey: string;
RecipientMessagingGroupKeyName: string;
ExtraData: { [k: string]: string };

// After Identity is done doing it's decryption, we have the below fields
DecryptedText: string | undefined;
};

export type MessageContactResponse = {
PublicKeyBase58Check: string; // NOTE: this is no good for v3
Messages: MessageEntryResponse[];
ProfileEntryResponse: ProfileEntryResponse; // NOTE: this is no good for v3
NumMessagesRead: number; // NOTE: this isn't supported for v3 yet.

MessagingGroup: MessagingGroupEntryResponse | undefined;
};

export type GetMessagesStatelessResponseV1 = {
PublicKeyToProfileEntry: { [k: string]: ProfileEntryResponse };
OrderedContactsWithMessages: MessageContactResponse[];
MessagingGroups: MessagingGroupEntryResponse[];
};

@Injectable({
providedIn: 'root',
})
Expand Down Expand Up @@ -802,8 +871,14 @@ export class BackendApiService {
FeeRateSatoshisPerKB: number,
Broadcast: boolean
): Observable<any> {
// Check if the user is logged in with a derived key and operating as the owner key.
const DerivedPublicKeyBase58Check =
this.identityService.identityServiceUsers[PublicKeyBase58Check]
?.derivedPublicKeyBase58Check;

let req = this.post(endpoint, BackendRoutes.ExchangeBitcoinRoute, {
PublicKeyBase58Check,
DerivedPublicKeyBase58Check,
BurnAmountSatoshis,
LatestBitcionAPIResponse,
BTCDepositAddress,
Expand All @@ -829,6 +904,7 @@ export class BackendApiService {
switchMap((res) =>
this.post(endpoint, BackendRoutes.ExchangeBitcoinRoute, {
PublicKeyBase58Check,
DerivedPublicKeyBase58Check,
BurnAmountSatoshis,
LatestBitcionAPIResponse,
BTCDepositAddress,
Expand Down Expand Up @@ -961,6 +1037,87 @@ export class BackendApiService {
);
}

SendGroupMessage(
endpoint: string,
SenderPublicKeyBase58Check: string,
MessagingPublicKeyBase58Check: string,
SenderMessagingGroupKeyName: string,
MessagingGroupKeyName: string,
MessageText: string,
GroupOwnerPublicKeyBase58Check: string,
MinFeeRateNanosPerKB: number
): Observable<any> {
// Once we determine the messaging keys of the parties, we will then encrypt a message based on the keys.
const request = this.identityService
.encrypt({
...this.identityService.identityServiceParamsForKey(
SenderPublicKeyBase58Check
),
recipientPublicKey: MessagingPublicKeyBase58Check,
senderGroupKeyName: SenderMessagingGroupKeyName,
message: MessageText,
})
.pipe(
switchMap((encrypted) => {
// Now we will use the ciphertext encrypted to user's messaging keys as part of the metadata of the
// sendMessage transaction.
const EncryptedMessageText = encrypted.encryptedMessage;
// Determine whether to use V3 messaging group key names for sender or recipient.
return this.post(
endpoint,
BackendRoutes.RoutePathSendMessageStateless,
{
SenderPublicKeyBase58Check,
RecipientMessagingGroupKeyName: MessagingGroupKeyName,
EncryptedMessageText,
SenderMessagingGroupKeyName,
RecipientPublicKeyBase58Check: GroupOwnerPublicKeyBase58Check,
MinFeeRateNanosPerKB,
}
).pipe(
map((request) => {
return { ...request };
})
);
})
);
return this.signAndSubmitTransaction(
endpoint,
request,
SenderPublicKeyBase58Check
);
}

RegisterGroupMessagingKey(
endpoint: string,
OwnerPublicKeyBase58Check: string,
MessagingPublicKeyBase58Check: string,
MessagingGroupKeyName: string,
MessagingKeySignatureHex: string,
MessagingGroupMembers: MessagingGroupMemberResponse[],
ExtraData: { [k: string]: string },
MinFeeRateNanosPerKB: number
): Observable<any> {
const request = this.post(
endpoint,
BackendRoutes.RegisterGroupMessagingKey,
{
OwnerPublicKeyBase58Check,
MessagingPublicKeyBase58Check,
MessagingGroupKeyName,
MessagingKeySignatureHex,
MessagingGroupMembers,
ExtraData,
MinFeeRateNanosPerKB,
}
);
return this.signAndSubmitTransaction(
endpoint,
request,
OwnerPublicKeyBase58Check
);
}

// User-related functions.
GetUsersStateless(
endpoint: string,
Expand Down Expand Up @@ -1782,6 +1939,81 @@ export class BackendApiService {
);
}

// TODO: add types
GetMessagesV1(
endpoint: string,
PublicKeyBase58Check: string
): Observable<GetMessagesStatelessResponseV1> {
let req = this.post(
endpoint,
BackendRoutes.RoutePathGetMessagesStatelessV1,
{ PublicKeyBase58Check }
);

// create an array of messages to decrypt
req = req.pipe(
map((res: any) => {
// This array contains encrypted messages with public keys
// Public keys of the other party involved in the correspondence
const encryptedMessages = res.OrderedContactsWithMessages.flatMap(
(thread) =>
thread.Messages.flatMap((message) => ({
EncryptedHex: message.EncryptedText,
PublicKey: message.IsSender
? message.RecipientPublicKeyBase58Check
: message.SenderPublicKeyBase58Check,
IsSender: message.IsSender,
Legacy: !message.V2 && (!message.Version || message.Version < 2),
Version: message.Version,
SenderMessagingPublicKey: message.SenderMessagingPublicKey,
SenderMessagingGroupKeyName: message.SenderMessagingGroupKeyName,
RecipientMessagingPublicKey: message.RecipientMessagingPublicKey,
RecipientMessagingGroupKeyName:
message.RecipientMessagingGroupKeyName,
}))
);
return { ...res, encryptedMessages };
})
);

// decrypt all the messages
req = req.pipe(
switchMap((res) => {
// console.log(res.MessagingGroups);
return this.identityService
.decrypt({
...this.identityService.identityServiceParamsForKey(
PublicKeyBase58Check
),
encryptedMessages: res.encryptedMessages,
messagingGroups: res.MessagingGroups,
})
.pipe(
map((decrypted) => {
res.OrderedContactsWithMessages.forEach((threads) => {
threads.Messages.forEach(
(message) =>
(message.DecryptedText =
decrypted.decryptedHexes[message.EncryptedText])
);
threads.MessagingGroup = res.MessagingGroups.find((mg) => {
return (
mg.MessagingGroupKeyName ===
threads.MessagingGroupKey.GroupKeyName &&
mg.GroupOwnerPublicKeyBase58Check ===
threads.MessagingGroupKey.GroupOwnerPublicKeyBase58Check
);
});
});
return { ...res, ...decrypted };
})
);
})
);

return req.pipe(catchError(this._handleError));
}

GetMessages(
endpoint: string,
PublicKeyBase58Check: string,
Expand All @@ -1792,7 +2024,8 @@ export class BackendApiService {
FollowersOnly: boolean = false,
FollowingOnly: boolean = false,
SortAlgorithm: string = 'time'
): Observable<any> {
): Observable<GetMessagesStatelessResponseV1> {
// return this.GetMessagesV1(endpoint, PublicKeyBase58Check);
let req = this.httpClient.post<any>(
this._makeRequestURL(
endpoint,
Expand Down Expand Up @@ -1839,12 +2072,14 @@ export class BackendApiService {
// decrypt all the messages
req = req.pipe(
switchMap((res) => {
// console.log(res.MessagingGroups);
return this.identityService
.decrypt({
...this.identityService.identityServiceParamsForKey(
PublicKeyBase58Check
),
encryptedMessages: res.encryptedMessages,
messagingGroups: res.MessagingGroups,
})
.pipe(
map((decrypted) => {
Expand All @@ -1864,6 +2099,31 @@ export class BackendApiService {
return req.pipe(catchError(this._handleError));
}

GetAllMessagingGroupKeys(
endpoint: string,
OwnerPublicKeyBase58Check: string
): Observable<GetAllMessagingGroupKeysResponse> {
return this.post(endpoint, BackendRoutes.GetAllMessagingGroupKeys, {
OwnerPublicKeyBase58Check,
});
}

GetDefaultKey(
endpoint: string,
publicKeyBase58Check: string
): Observable<MessagingGroupEntryResponse | null> {
return this.GetAllMessagingGroupKeys(endpoint, publicKeyBase58Check).pipe(
map((res) => {
const defaultKeys = res.MessagingGroupEntries.filter(
(messagingGroup: MessagingGroupEntryResponse) => {
return messagingGroup.MessagingGroupKeyName === 'default-key';
}
);
return defaultKeys.length ? defaultKeys[0] : null;
})
);
}

CreateLike(
endpoint: string,
ReaderPublicKeyBase58Check: string,
Expand Down
Loading