Skip to content

Commit 99a3ae4

Browse files
authored
[Chat] Fixed Image Placeholder Being Shown When Fetching Failed (#4172)
1 parent 61897b0 commit 99a3ae4

4 files changed

Lines changed: 64 additions & 15 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "patch",
3+
"area": "fix",
4+
"workstream": "Inline Image",
5+
"comment": "Fixed the issue where image placeholder being shown when fetching failed",
6+
"packageName": "@azure/communication-react",
7+
"email": "109105353+jpeng-ms@users.noreply.github.com",
8+
"dependentChangeType": "patch"
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "patch",
3+
"area": "fix",
4+
"workstream": "Inline Image",
5+
"comment": "Fixed the issue where image placeholder being shown when fetching failed",
6+
"packageName": "@azure/communication-react",
7+
"email": "109105353+jpeng-ms@users.noreply.github.com",
8+
"dependentChangeType": "patch"
9+
}

packages/chat-stateful-client/src/ResourceDownloadQueue.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ describe('ResourceDownloadQueue api functions', () => {
3535

3636
/* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
3737
describe('ResourceDownloadQueue api functions', () => {
38+
// URL.createObjectURL is not available in jest-dom
39+
// so we need to mock it in tests
40+
if (typeof URL.createObjectURL === 'undefined') {
41+
Object.defineProperty(window.URL, 'createObjectURL', {
42+
value: () => {
43+
return 'http://mocked-url';
44+
}
45+
});
46+
}
3847
test('should add a message to the queue and contains message', () => {
3948
const context = new ChatContext();
4049
const tokenCredential = stubCommunicationTokenCredential();

packages/chat-stateful-client/src/ResourceDownloadQueue.ts

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -58,19 +58,14 @@ export class ResourceDownloadQueue {
5858
continue;
5959
}
6060

61-
try {
62-
if (options) {
63-
const singleUrl = options.singleUrl;
64-
message = await this.downloadSingleUrl(message, singleUrl, operation);
65-
} else {
66-
message = await this.downloadAllPreviewUrls(message, operation);
67-
}
68-
this._context.setChatMessage(threadId, message);
69-
} catch (error) {
70-
console.log('Downloading Resource error: ', error);
71-
} finally {
72-
this.isActive = false;
61+
if (options) {
62+
const singleUrl = options.singleUrl;
63+
message = await this.downloadSingleUrl(message, singleUrl, operation);
64+
} else {
65+
message = await this.downloadAllPreviewUrls(message, operation);
7366
}
67+
this._context.setChatMessage(threadId, message);
68+
this.isActive = false;
7469
}
7570
}
7671

@@ -79,7 +74,7 @@ export class ResourceDownloadQueue {
7974
resourceUrl: string,
8075
operation: ImageRequest
8176
): Promise<ChatMessageWithStatus> {
82-
const blobUrl = await operation(resourceUrl, this._credential);
77+
const blobUrl = await this.downloadResource(operation, resourceUrl);
8378
message = { ...message, resourceCache: { ...message.resourceCache, [resourceUrl]: blobUrl } };
8479
return message;
8580
}
@@ -95,14 +90,24 @@ export class ResourceDownloadQueue {
9590
}
9691
for (const attachment of attachments) {
9792
if (attachment.previewUrl && attachment.attachmentType === 'image') {
98-
const blobUrl = await operation(attachment.previewUrl, this._credential);
93+
const blobUrl = await this.downloadResource(operation, attachment.previewUrl);
9994
message.resourceCache[attachment.previewUrl] = blobUrl;
10095
}
10196
}
10297
}
10398

10499
return message;
105100
}
101+
102+
private async downloadResource(operation: ImageRequest, url: string): Promise<string> {
103+
let blobUrl = URL.createObjectURL(new Blob());
104+
try {
105+
blobUrl = await operation(url, this._credential);
106+
} catch (error) {
107+
console.log('Downloading Resource error: ', error);
108+
}
109+
return blobUrl;
110+
}
106111
}
107112
/* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
108113
/**
@@ -141,11 +146,28 @@ export const fetchImageSource = async (src: string, credential: CommunicationTok
141146
const headers = new Headers();
142147
headers.append('Authorization', `Bearer ${token}`);
143148
try {
144-
return await fetch(url, { headers });
149+
return await fetchWithTimeout(url, { headers });
145150
} catch (err) {
146151
throw new ChatError('ChatThreadClient.getMessage', err as Error);
147152
}
148153
}
154+
async function fetchWithTimeout(
155+
resource: string | URL | Request,
156+
options: { timeout?: number; headers?: Headers }
157+
): Promise<Response> {
158+
// default timeout is 30 seconds
159+
const { timeout = 30000 } = options;
160+
161+
const controller = new AbortController();
162+
const id = setTimeout(() => controller.abort(), timeout);
163+
164+
const response = await fetch(resource, {
165+
...options,
166+
signal: controller.signal
167+
});
168+
clearTimeout(id);
169+
return response;
170+
}
149171
const accessToken = await credential.getToken();
150172
const response = await fetchWithAuthentication(src, accessToken.token);
151173
const blob = await response.blob();

0 commit comments

Comments
 (0)