Skip to content

Commit b56a9fa

Browse files
authored
Temporarily revert "Clean up browser communication protocol (#51226)" (#51344)
1 parent 61a25c9 commit b56a9fa

File tree

4 files changed

+115
-84
lines changed

4 files changed

+115
-84
lines changed

src/BuiltInTools/HotReloadClient/Web/AbstractBrowserRefreshServer.cs

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ internal abstract class AbstractBrowserRefreshServer(string middlewareAssemblyPa
2929
{
3030
public const string ServerLogComponentName = "BrowserRefreshServer";
3131

32+
private static readonly ReadOnlyMemory<byte> s_reloadMessage = Encoding.UTF8.GetBytes("Reload");
33+
private static readonly ReadOnlyMemory<byte> s_waitMessage = Encoding.UTF8.GetBytes("Wait");
34+
private static readonly ReadOnlyMemory<byte> s_pingMessage = Encoding.UTF8.GetBytes("""{ "type" : "Ping" }""");
3235
private static readonly JsonSerializerOptions s_jsonSerializerOptions = new(JsonSerializerDefaults.Web);
3336

3437
private readonly List<BrowserConnection> _activeConnections = [];
@@ -230,15 +233,19 @@ public ValueTask SendJsonMessageAsync<TValue>(TValue value, CancellationToken ca
230233
public ValueTask SendReloadMessageAsync(CancellationToken cancellationToken)
231234
{
232235
logger.Log(LogEvents.ReloadingBrowser);
233-
return SendAsync(JsonReloadRequest.Message, cancellationToken);
236+
return SendAsync(s_reloadMessage, cancellationToken);
234237
}
235238

236239
public ValueTask SendWaitMessageAsync(CancellationToken cancellationToken)
237240
{
238241
logger.Log(LogEvents.SendingWaitMessage);
239-
return SendAsync(JsonWaitRequest.Message, cancellationToken);
242+
return SendAsync(s_waitMessage, cancellationToken);
240243
}
241244

245+
// obsolete: to be removed
246+
public ValueTask SendPingMessageAsync(CancellationToken cancellationToken)
247+
=> SendAsync(s_pingMessage, cancellationToken);
248+
242249
private ValueTask SendAsync(ReadOnlyMemory<byte> messageBytes, CancellationToken cancellationToken)
243250
=> SendAndReceiveAsync(request: _ => messageBytes, response: null, cancellationToken);
244251

@@ -286,13 +293,13 @@ public async ValueTask SendAndReceiveAsync<TRequest>(
286293
public ValueTask RefreshBrowserAsync(CancellationToken cancellationToken)
287294
{
288295
logger.Log(LogEvents.RefreshingBrowser);
289-
return SendAsync(JsonRefreshBrowserRequest.Message, cancellationToken);
296+
return SendJsonMessageAsync(new AspNetCoreHotReloadApplied(), cancellationToken);
290297
}
291298

292299
public ValueTask ReportCompilationErrorsInBrowserAsync(ImmutableArray<string> compilationErrors, CancellationToken cancellationToken)
293300
{
294301
logger.Log(LogEvents.UpdatingDiagnostics);
295-
return SendJsonMessageAsync(new JsonReportDiagnosticsRequest { Diagnostics = compilationErrors }, cancellationToken);
302+
return SendJsonMessageAsync(new HotReloadDiagnostics { Diagnostics = compilationErrors }, cancellationToken);
296303
}
297304

298305
public async ValueTask UpdateStaticAssetsAsync(IEnumerable<string> relativeUrls, CancellationToken cancellationToken)
@@ -301,37 +308,24 @@ public async ValueTask UpdateStaticAssetsAsync(IEnumerable<string> relativeUrls,
301308
foreach (var relativeUrl in relativeUrls)
302309
{
303310
logger.Log(LogEvents.SendingStaticAssetUpdateRequest, relativeUrl);
304-
var message = JsonSerializer.SerializeToUtf8Bytes(new JasonUpdateStaticFileRequest { Path = relativeUrl }, s_jsonSerializerOptions);
311+
var message = JsonSerializer.SerializeToUtf8Bytes(new UpdateStaticFileMessage { Path = relativeUrl }, s_jsonSerializerOptions);
305312
await SendAsync(message, cancellationToken);
306313
}
307314
}
308315

309-
private readonly struct JsonWaitRequest
310-
{
311-
public string Type => "Wait";
312-
public static readonly ReadOnlyMemory<byte> Message = JsonSerializer.SerializeToUtf8Bytes(new JsonWaitRequest(), s_jsonSerializerOptions);
313-
}
314-
315-
private readonly struct JsonReloadRequest
316-
{
317-
public string Type => "Reload";
318-
public static readonly ReadOnlyMemory<byte> Message = JsonSerializer.SerializeToUtf8Bytes(new JsonReloadRequest(), s_jsonSerializerOptions);
319-
}
320-
321-
private readonly struct JsonRefreshBrowserRequest
316+
private readonly struct AspNetCoreHotReloadApplied
322317
{
323-
public string Type => "RefreshBrowser";
324-
public static readonly ReadOnlyMemory<byte> Message = JsonSerializer.SerializeToUtf8Bytes(new JsonRefreshBrowserRequest(), s_jsonSerializerOptions);
318+
public string Type => "AspNetCoreHotReloadApplied";
325319
}
326320

327-
private readonly struct JsonReportDiagnosticsRequest
321+
private readonly struct HotReloadDiagnostics
328322
{
329-
public string Type => "ReportDiagnostics";
323+
public string Type => "HotReloadDiagnosticsv1";
330324

331325
public IEnumerable<string> Diagnostics { get; init; }
332326
}
333327

334-
private readonly struct JasonUpdateStaticFileRequest
328+
private readonly struct UpdateStaticFileMessage
335329
{
336330
public string Type => "UpdateStaticFile";
337331
public string Path { get; init; }

src/BuiltInTools/HotReloadClient/Web/WebAssemblyHotReloadClient.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ public override async Task<ApplyStatus> ApplyManagedCodeUpdatesAsync(ImmutableAr
133133
var anyFailure = false;
134134

135135
await browserRefreshServer.SendAndReceiveAsync(
136-
request: sharedSecret => new JsonApplyManagedCodeUpdatesRequest
136+
request: sharedSecret => new JsonApplyHotReloadDeltasRequest
137137
{
138138
SharedSecret = sharedSecret,
139139
UpdateId = batchId,
@@ -178,9 +178,9 @@ private static bool ReceiveUpdateResponseAsync(ReadOnlySpan<byte> value, ILogger
178178
public override Task InitialUpdatesAppliedAsync(CancellationToken cancellationToken)
179179
=> Task.CompletedTask;
180180

181-
private readonly struct JsonApplyManagedCodeUpdatesRequest
181+
private readonly struct JsonApplyHotReloadDeltasRequest
182182
{
183-
public string Type => "ApplyManagedCodeUpdates";
183+
public string Type => "BlazorHotReloadDeltav3";
184184
public string? SharedSecret { get; init; }
185185

186186
public int UpdateId { get; init; }
@@ -211,7 +211,7 @@ private readonly struct JsonLogEntry
211211

212212
private readonly struct JsonGetApplyUpdateCapabilitiesRequest
213213
{
214-
public string Type => "GetApplyUpdateCapabilities";
214+
public string Type => "BlazorRequestApplyUpdateCapabilities2";
215215
}
216216
}
217217
}

src/BuiltInTools/Web.Middleware/WebSocketScriptInjection.js

Lines changed: 88 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,38 @@ setTimeout(async function () {
2626

2727
let waiting = false;
2828

29-
connection.onmessage = function (message) {
30-
const payload = JSON.parse(message.data);
31-
const action = {
32-
'Reload': () => reload(),
33-
'Wait': () => wait(),
34-
'UpdateStaticFile': () => updateStaticFile(payload.path),
35-
'ApplyManagedCodeUpdates': () => applyManagedCodeUpdates(payload.sharedSecret, payload.updateId, payload.deltas, payload.responseLoggingLevel),
36-
'ReportDiagnostics': () => reportDiagnostics(payload.diagnostics),
37-
'GetApplyUpdateCapabilities': () => getApplyUpdateCapabilities(),
38-
'RefreshBrowser': () => refreshBrowser()
39-
};
40-
41-
if (payload.type && action.hasOwnProperty(payload.type)) {
42-
action[payload.type]();
29+
connection.onmessage = function (message) {
30+
if (message.data === 'Reload') {
31+
console.debug('Server is ready. Reloading...');
32+
location.reload();
33+
} else if (message.data === 'Wait') {
34+
if (waiting) {
35+
return;
36+
}
37+
waiting = true;
38+
console.debug('File changes detected. Waiting for application to rebuild.');
39+
const glyphs = ['☱', '☲', '☴'];
40+
const title = document.title;
41+
let i = 0;
42+
setInterval(function () { document.title = glyphs[i++ % glyphs.length] + ' ' + title; }, 240);
4343
} else {
44-
console.error('Unknown payload:', message.data);
44+
const payload = JSON.parse(message.data);
45+
const action = {
46+
'UpdateStaticFile': () => updateStaticFile(payload.path),
47+
'BlazorHotReloadDeltav1': () => applyBlazorDeltas_legacy(payload.sharedSecret, payload.deltas, false),
48+
'BlazorHotReloadDeltav2': () => applyBlazorDeltas_legacy(payload.sharedSecret, payload.deltas, true),
49+
'BlazorHotReloadDeltav3': () => applyBlazorDeltas(payload.sharedSecret, payload.updateId, payload.deltas, payload.responseLoggingLevel),
50+
'HotReloadDiagnosticsv1': () => displayDiagnostics(payload.diagnostics),
51+
'BlazorRequestApplyUpdateCapabilities': () => getBlazorWasmApplyUpdateCapabilities(false),
52+
'BlazorRequestApplyUpdateCapabilities2': () => getBlazorWasmApplyUpdateCapabilities(true),
53+
'AspNetCoreHotReloadApplied': () => aspnetCoreHotReloadApplied()
54+
};
55+
56+
if (payload.type && action.hasOwnProperty(payload.type)) {
57+
action[payload.type]();
58+
} else {
59+
console.error('Unknown payload:', message.data);
60+
}
4561
}
4662
}
4763

@@ -90,12 +106,12 @@ setTimeout(async function () {
90106
return messageAndStack
91107
}
92108

93-
function getApplyUpdateCapabilities() {
109+
function getBlazorWasmApplyUpdateCapabilities(sendErrorToClient) {
94110
let applyUpdateCapabilities;
95111
try {
96112
applyUpdateCapabilities = window.Blazor._internal.getApplyUpdateCapabilities();
97113
} catch (error) {
98-
applyUpdateCapabilities = "!" + getMessageAndStack(error);
114+
applyUpdateCapabilities = sendErrorToClient ? "!" + getMessageAndStack(error) : '';
99115
}
100116
connection.send(applyUpdateCapabilities);
101117
}
@@ -121,6 +137,41 @@ setTimeout(async function () {
121137
styleElement.parentNode.insertBefore(newElement, styleElement.nextSibling);
122138
}
123139

140+
async function applyBlazorDeltas_legacy(serverSecret, deltas, sendErrorToClient) {
141+
if (sharedSecret && (serverSecret != sharedSecret.encodedSharedSecret)) {
142+
// Validate the shared secret if it was specified. It might be unspecified in older versions of VS
143+
// that do not support this feature as yet.
144+
throw 'Unable to validate the server. Rejecting apply-update payload.';
145+
}
146+
147+
let applyError = undefined;
148+
149+
try {
150+
applyDeltas_legacy(deltas)
151+
} catch (error) {
152+
console.warn(error);
153+
applyError = error;
154+
}
155+
156+
const body = JSON.stringify({
157+
id: deltas[0].sequenceId,
158+
deltas: deltas
159+
});
160+
try {
161+
await fetch('/_framework/blazor-hotreload', { method: 'post', headers: { 'content-type': 'application/json' }, body: body });
162+
} catch (error) {
163+
console.warn(error);
164+
applyError = error;
165+
}
166+
167+
if (applyError) {
168+
sendDeltaNotApplied(sendErrorToClient ? applyError : undefined);
169+
} else {
170+
sendDeltaApplied();
171+
notifyHotReloadApplied();
172+
}
173+
}
174+
124175
function applyDeltas_legacy(deltas) {
125176
let apply = window.Blazor?._internal?.applyHotReload
126177

@@ -139,16 +190,26 @@ setTimeout(async function () {
139190
});
140191
}
141192
}
193+
function sendDeltaApplied() {
194+
connection.send(new Uint8Array([1]).buffer);
195+
}
196+
197+
function sendDeltaNotApplied(error) {
198+
if (error) {
199+
let encoder = new TextEncoder()
200+
connection.send(encoder.encode("\0" + error.message + "\0" + error.stack));
201+
} else {
202+
connection.send(new Uint8Array([0]).buffer);
203+
}
204+
}
142205

143-
async function applyManagedCodeUpdates(serverSecret, updateId, deltas, responseLoggingLevel) {
206+
async function applyBlazorDeltas(serverSecret, updateId, deltas, responseLoggingLevel) {
144207
if (sharedSecret && (serverSecret != sharedSecret.encodedSharedSecret)) {
145208
// Validate the shared secret if it was specified. It might be unspecified in older versions of VS
146209
// that do not support this feature as yet.
147210
throw 'Unable to validate the server. Rejecting apply-update payload.';
148211
}
149212

150-
console.debug('Applying managed code updates.');
151-
152213
const AgentMessageSeverity_Error = 2
153214

154215
let applyError = undefined;
@@ -200,13 +261,11 @@ setTimeout(async function () {
200261
}));
201262

202263
if (!applyError) {
203-
displayChangesAppliedToast();
264+
notifyHotReloadApplied();
204265
}
205266
}
206267

207-
function reportDiagnostics(diagnostics) {
208-
console.debug('Reporting Hot Reload diagnostics.');
209-
268+
function displayDiagnostics(diagnostics) {
210269
document.querySelectorAll('#dotnet-compile-error').forEach(el => el.remove());
211270
const el = document.body.appendChild(document.createElement('div'));
212271
el.id = 'dotnet-compile-error';
@@ -221,7 +280,7 @@ setTimeout(async function () {
221280
});
222281
}
223282

224-
function displayChangesAppliedToast() {
283+
function notifyHotReloadApplied() {
225284
document.querySelectorAll('#dotnet-compile-error').forEach(el => el.remove());
226285
if (document.querySelector('#dotnet-hotreload-toast')) {
227286
return;
@@ -239,47 +298,25 @@ setTimeout(async function () {
239298
setTimeout(() => el.remove(), 2000);
240299
}
241300

242-
function refreshBrowser() {
301+
function aspnetCoreHotReloadApplied() {
243302
if (window.Blazor) {
244303
window[hotReloadActiveKey] = true;
245304
// hotReloadApplied triggers an enhanced navigation to
246305
// refresh pages that have been statically rendered with
247306
// Blazor SSR.
248307
if (window.Blazor?._internal?.hotReloadApplied)
249308
{
250-
console.debug('Refreshing browser: WASM.');
251309
Blazor._internal.hotReloadApplied();
252310
}
253311
else
254312
{
255-
console.debug('Refreshing browser.');
256-
displayChangesAppliedToast();
313+
notifyHotReloadApplied();
257314
}
258315
} else {
259-
console.debug('Refreshing browser: Reloading.');
260316
location.reload();
261317
}
262318
}
263319

264-
function reload() {
265-
console.debug('Reloading.');
266-
location.reload();
267-
}
268-
269-
function wait() {
270-
console.debug('Waiting for application to rebuild.');
271-
272-
if (waiting) {
273-
return;
274-
}
275-
276-
waiting = true;
277-
const glyphs = ['☱', '☲', '☴'];
278-
const title = document.title;
279-
let i = 0;
280-
setInterval(function () { document.title = glyphs[i++ % glyphs.length] + ' ' + title; }, 240);
281-
}
282-
283320
async function getSecret(serverKeyString) {
284321
if (!serverKeyString || !window.crypto || !window.crypto.subtle) {
285322
return null;
@@ -345,8 +382,8 @@ setTimeout(async function () {
345382
webSocket.addEventListener('close', onClose);
346383
if (window.Blazor?.removeEventListener && window.Blazor?.addEventListener)
347384
{
348-
webSocket.addEventListener('close', () => window.Blazor?.removeEventListener('enhancedload', displayChangesAppliedToast));
349-
window.Blazor?.addEventListener('enhancedload', displayChangesAppliedToast);
385+
webSocket.addEventListener('close', () => window.Blazor?.removeEventListener('enhancedload', notifyHotReloadApplied));
386+
window.Blazor?.addEventListener('enhancedload', notifyHotReloadApplied);
350387
}
351388
});
352389
}

test/dotnet-watch.Tests/Browser/BrowserTests.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public async Task BrowserDiagnostics()
6565
await App.WaitForOutputLineContaining("Do you want to restart your app?");
6666

6767
await App.WaitUntilOutputContains($$"""
68-
🧪 Received: {"type":"ReportDiagnostics","diagnostics":[{{jsonErrorMessage}}]}
68+
🧪 Received: {"type":"HotReloadDiagnosticsv1","diagnostics":[{{jsonErrorMessage}}]}
6969
""");
7070

7171
// auto restart next time:
@@ -76,7 +76,7 @@ await App.WaitUntilOutputContains($$"""
7676

7777
// browser page was reloaded after the app restarted:
7878
await App.WaitUntilOutputContains("""
79-
🧪 Received: {"type":"Reload"}
79+
🧪 Received: Reload
8080
""");
8181

8282
// no other browser message sent:
@@ -93,14 +93,14 @@ await App.WaitUntilOutputContains("""
9393
await App.WaitForOutputLineContaining("[auto-restart] " + errorMessage);
9494

9595
await App.WaitUntilOutputContains($$"""
96-
🧪 Received: {"type":"ReportDiagnostics","diagnostics":["Restarting application to apply changes ..."]}
96+
🧪 Received: {"type":"HotReloadDiagnosticsv1","diagnostics":["Restarting application to apply changes ..."]}
9797
""");
9898

9999
await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges);
100100

101101
// browser page was reloaded after the app restarted:
102102
await App.WaitUntilOutputContains("""
103-
🧪 Received: {"type":"Reload"}
103+
🧪 Received: Reload
104104
""");
105105

106106
// no other browser message sent:
@@ -114,11 +114,11 @@ await App.WaitUntilOutputContains("""
114114
await App.WaitForOutputLineContaining(MessageDescriptor.HotReloadSucceeded);
115115

116116
await App.WaitUntilOutputContains($$"""
117-
🧪 Received: {"type":"ReportDiagnostics","diagnostics":[]}
117+
🧪 Received: {"type":"HotReloadDiagnosticsv1","diagnostics":[]}
118118
""");
119119

120120
await App.WaitUntilOutputContains($$"""
121-
🧪 Received: {"type":"RefreshBrowser"}
121+
🧪 Received: {"type":"AspNetCoreHotReloadApplied"}
122122
""");
123123

124124
// no other browser message sent:

0 commit comments

Comments
 (0)