Skip to content

Commit

Permalink
Use single shared HttpAgent instance rather than 1 per canister (#6221
Browse files Browse the repository at this point in the history
)
  • Loading branch information
hpeebles authored Aug 9, 2024
1 parent 4de6478 commit 3c9ce60
Show file tree
Hide file tree
Showing 34 changed files with 259 additions and 417 deletions.
21 changes: 7 additions & 14 deletions frontend/openchat-agent/src/services/candidService.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Actor, HttpAgent, type Identity } from "@dfinity/agent";
import type { IDL } from "@dfinity/candid";
import type { Principal } from "@dfinity/principal";
import { AuthError, DestinationInvalidError, SessionExpiryError, offline } from "openchat-shared";
import { AuthError, DestinationInvalidError, SessionExpiryError } from "openchat-shared";
import { ReplicaNotUpToDateError, toCanisterResponseError } from "./error";
import { ResponseTooLargeError } from "openchat-shared";
import { isMainnet } from "../utils/network";

const MAX_RETRIES = process.env.NODE_ENV === "production" ? 7 : 3;
const RETRY_DELAY = 100;
Expand All @@ -14,18 +13,9 @@ function debug(msg: string): void {
}

export abstract class CandidService {
protected createServiceClient<T>(
factory: IDL.InterfaceFactory,
canisterId: string,
config: { icUrl: string },
): T {
const host = config.icUrl;
const agent = HttpAgent.createSync({ identity: this.identity, host, retryTimes: 5 });
if (!isMainnet(config.icUrl) && !offline()) {
agent.fetchRootKey();
}
protected createServiceClient<T>(factory: IDL.InterfaceFactory, canisterId: string): T {
return Actor.createActor<T>(factory, {
agent,
agent: this.agent,
canisterId,
});
}
Expand Down Expand Up @@ -94,5 +84,8 @@ export abstract class CandidService {
});
}

constructor(protected identity: Identity) {}
constructor(
protected identity: Identity,
protected agent: HttpAgent,
) {}
}
12 changes: 3 additions & 9 deletions frontend/openchat-agent/src/services/ckbtcMinter/ckbtcMinter.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,25 @@
import type { Identity } from "@dfinity/agent";
import type { HttpAgent, Identity } from "@dfinity/agent";
import { Principal } from "@dfinity/principal";
import type { UpdateBtcBalanceResponse } from "openchat-shared";
import { idlFactory, type CkbtcMinterService } from "./candid/idl";
import { CandidService } from "../candidService";
import { updateBtcBalanceResponse } from "./mappers";
import type { AgentConfig } from "../../config";
import { apiOptional } from "../common/chatMappers";

const CKBTC_MINTER_CANISTER_ID = "mqygn-kiaaa-aaaar-qaadq-cai";

export class CkbtcMinterClient extends CandidService {
private service: CkbtcMinterService;

private constructor(identity: Identity, config: AgentConfig) {
super(identity);
constructor(identity: Identity, agent: HttpAgent) {
super(identity, agent);

this.service = this.createServiceClient<CkbtcMinterService>(
idlFactory,
CKBTC_MINTER_CANISTER_ID,
config,
);
}

static create(identity: Identity, config: AgentConfig): CkbtcMinterClient {
return new CkbtcMinterClient(identity, config);
}

updateBalance(userId: string): Promise<UpdateBtcBalanceResponse> {
return this.handleResponse(
this.service.update_balance({
Expand Down
25 changes: 8 additions & 17 deletions frontend/openchat-agent/src/services/community/community.client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import type { Identity } from "@dfinity/agent";
import type { HttpAgent, Identity } from "@dfinity/agent";
import { idlFactory, type CommunityService } from "./candid/idl";
import { CandidService } from "../candidService";
import { apiOptionUpdate, identity } from "../../utils/mapping";
Expand Down Expand Up @@ -182,26 +182,17 @@ import {
export class CommunityClient extends CandidService {
private service: CommunityService;

private constructor(
private communityId: string,
constructor(
identity: Identity,
agent: HttpAgent,
private config: AgentConfig,
private communityId: string,
private db: Database,
private inviteCode: string | undefined,
) {
super(identity);
super(identity, agent);

this.service = this.createServiceClient<CommunityService>(idlFactory, communityId, config);
}

static create(
communityId: string,
identity: Identity,
config: AgentConfig,
db: Database,
inviteCode: string | undefined,
): CommunityClient {
return new CommunityClient(communityId, identity, config, db, inviteCode);
this.service = this.createServiceClient<CommunityService>(idlFactory, communityId);
}

claimPrize(channelId: string, messageId: bigint): Promise<ClaimPrizeResponse> {
Expand Down Expand Up @@ -382,7 +373,7 @@ export class CommunityClient extends CandidService {
blockLevelMarkdown: boolean | undefined,
newAchievement: boolean,
): Promise<EditMessageResponse> {
return DataClient.create(this.identity, this.config)
return new DataClient(this.identity, this.agent, this.config)
.uploadData(message.content, [chatId.communityId])
.then((content) => {
return this.handleResponse(
Expand Down Expand Up @@ -982,7 +973,7 @@ export class CommunityClient extends CandidService {
// pre-emtively remove the failed message from indexeddb - it will get re-added if anything goes wrong
removeFailedMessage(this.db, chatId, event.event.messageId, threadRootMessageIndex);

const dataClient = DataClient.create(this.identity, this.config);
const dataClient = new DataClient(this.identity, this.agent, this.config);
const uploadContentPromise = event.event.forwarded
? dataClient.forwardData(event.event.content, [chatId.communityId])
: dataClient.uploadData(event.event.content, [chatId.communityId]);
Expand Down
66 changes: 28 additions & 38 deletions frontend/openchat-agent/src/services/data/data.client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Identity } from "@dfinity/agent";
import type { HttpAgent, Identity } from "@dfinity/agent";
import { Principal } from "@dfinity/principal";
import { v1 as uuidv1 } from "uuid";
import { sha3_256 } from "js-sha3";
Expand All @@ -19,17 +19,15 @@ import { StorageIndexClient } from "../storageIndex/storageIndex.client";
import { StorageBucketClient } from "../storageBucket/storageBucket.client";

export class DataClient extends EventTarget {
static create(identity: Identity, config: AgentConfig): DataClient {
const storageIndexClient = StorageIndexClient.create(identity, config);
return new DataClient(identity, config, storageIndexClient);
}
private storageIndexClient: StorageIndexClient;

private constructor(
constructor(
private identity: Identity,
private agent: HttpAgent,
private config: AgentConfig,
private storageIndexClient: StorageIndexClient
) {
super();
this.storageIndexClient = new StorageIndexClient(identity, agent, config);
}

static newBlobId(): bigint {
Expand All @@ -54,7 +52,7 @@ export class DataClient extends EventTarget {

async uploadData(
content: MessageContent,
accessorCanisterIds: string[]
accessorCanisterIds: string[],
): Promise<StoredMediaContent | undefined> {
let byteLimit: number | undefined = undefined;
let bytesUsed: number | undefined = undefined;
Expand All @@ -71,7 +69,7 @@ export class DataClient extends EventTarget {
const response = await this.uploadFile(
content.mimeType,
accessorIds,
content.blobData
content.blobData,
);

const ref = this.extractBlobReference(response);
Expand All @@ -83,7 +81,7 @@ export class DataClient extends EventTarget {
this.config.blobUrlPattern,
ref.canisterId,
ref.blobId,
"blobs"
"blobs",
),
};
byteLimit = Number(response.projectedAllowance.byteLimit);
Expand Down Expand Up @@ -113,7 +111,7 @@ export class DataClient extends EventTarget {
this.config.blobUrlPattern,
videoRef.canisterId,
videoRef.blobId,
"blobs"
"blobs",
),
},
imageData: {
Expand All @@ -123,15 +121,15 @@ export class DataClient extends EventTarget {
this.config.blobUrlPattern,
imageRef.canisterId,
imageRef.blobId,
"blobs"
"blobs",
),
},
};
byteLimit = Number(video.projectedAllowance.byteLimit);
bytesUsed = Number(
video.projectedAllowance.bytesUsedAfterOperation +
image.projectedAllowance.bytesUsedAfterOperation -
image.projectedAllowance.bytesUsed
image.projectedAllowance.bytesUsed,
);
});
}
Expand All @@ -142,7 +140,7 @@ export class DataClient extends EventTarget {
new StorageUpdated({
byteLimit,
bytesUsed,
})
}),
);
}

Expand All @@ -151,7 +149,7 @@ export class DataClient extends EventTarget {

async forwardData(
content: MessageContent,
accessorCanisterIds: string[]
accessorCanisterIds: string[],
): Promise<StoredMediaContent | undefined> {
let byteLimit: number | undefined = undefined;
let bytesUsed: number | undefined = undefined;
Expand All @@ -169,7 +167,7 @@ export class DataClient extends EventTarget {
const response = await this.forwardFile(
content.blobReference.canisterId,
content.blobReference.blobId,
accessorIds
accessorIds,
);
if (response.kind === "success") {
byteLimit = Number(response.projectedAllowance.byteLimit);
Expand All @@ -184,7 +182,7 @@ export class DataClient extends EventTarget {
this.config.blobUrlPattern,
content.blobReference.canisterId,
content.blobReference.blobId,
"blobs"
"blobs",
),
};
} else {
Expand All @@ -208,20 +206,20 @@ export class DataClient extends EventTarget {
this.forwardFile(
videoCanisterId,
content.videoData.blobReference.blobId,
accessorIds
accessorIds,
),
this.forwardFile(
imageCanisterId,
content.imageData.blobReference.blobId,
accessorIds
accessorIds,
),
]).then(([video, image]) => {
if (video.kind === "success" && image.kind === "success") {
byteLimit = Number(video.projectedAllowance.byteLimit);
bytesUsed = Number(
video.projectedAllowance.bytesUsedAfterOperation +
image.projectedAllowance.bytesUsedAfterOperation -
image.projectedAllowance.bytesUsed
image.projectedAllowance.bytesUsed,
);
updatedContent = {
...content,
Expand All @@ -235,7 +233,7 @@ export class DataClient extends EventTarget {
this.config.blobUrlPattern,
videoCanisterId,
video.newFileId,
"blobs"
"blobs",
),
},
imageData: {
Expand All @@ -248,7 +246,7 @@ export class DataClient extends EventTarget {
this.config.blobUrlPattern,
imageCanisterId,
image.newFileId,
"blobs"
"blobs",
),
},
};
Expand All @@ -272,7 +270,7 @@ export class DataClient extends EventTarget {
new StorageUpdated({
byteLimit,
bytesUsed,
})
}),
);
}

Expand All @@ -295,15 +293,15 @@ export class DataClient extends EventTarget {
accessors: Array<Principal>,
bytes: ArrayBuffer,
expiryTimestampMillis?: bigint,
onProgress?: (percentComplete: number) => void
onProgress?: (percentComplete: number) => void,
): Promise<UploadFileResponse> {
const hash = new Uint8Array(hashBytes(bytes));
const fileSize = bytes.byteLength;

const allocatedBucketResponse = await this.storageIndexClient.allocatedBucket(
hash,
BigInt(fileSize),
random128()
random128(),
);

if (allocatedBucketResponse.kind !== "success") {
Expand All @@ -316,11 +314,7 @@ export class DataClient extends EventTarget {
const chunkSize = allocatedBucketResponse.chunkSize;
const chunkCount = Math.ceil(fileSize / chunkSize);
const chunkIndexes = [...Array(chunkCount).keys()];
const bucketClient = StorageBucketClient.create(
this.identity,
this.config,
bucketCanisterId
);
const bucketClient = new StorageBucketClient(this.identity, this.agent, bucketCanisterId);

let chunksCompleted = 0;

Expand All @@ -342,7 +336,7 @@ export class DataClient extends EventTarget {
chunkSize,
chunkIndex,
chunkBytes,
expiryTimestampMillis
expiryTimestampMillis,
);

if (chunkResponse === "success") {
Expand Down Expand Up @@ -370,13 +364,9 @@ export class DataClient extends EventTarget {
private async forwardFile(
bucketCanisterId: string,
fileId: bigint,
accessors: Array<Principal>
accessors: Array<Principal>,
): Promise<ForwardFileResponse> {
const bucketClient = StorageBucketClient.create(
this.identity,
this.config,
bucketCanisterId
);
const bucketClient = new StorageBucketClient(this.identity, this.agent, bucketCanisterId);

const fileInfoResponse = await bucketClient.fileInfo(fileId);
if (fileInfoResponse.kind === "file_not_found") {
Expand All @@ -385,7 +375,7 @@ export class DataClient extends EventTarget {

const canForwardResponse = await this.storageIndexClient.canForward(
fileInfoResponse.fileHash,
fileInfoResponse.fileSize
fileInfoResponse.fileSize,
);
switch (canForwardResponse.kind) {
case "user_not_found":
Expand Down
Loading

0 comments on commit 3c9ce60

Please sign in to comment.