@inditextech/weave-store-azure-web-pubsub 3.3.1 → 3.4.0-SNAPSHOT.85.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.d.ts CHANGED
@@ -134,6 +134,7 @@ type FetchClient = (input: string | URL | globalThis.Request, init?: RequestInit
134
134
  type FetchInitialState = (doc: Doc) => void;
135
135
  type PersistRoom = (roomId: string, actualState: Uint8Array<ArrayBufferLike>) => Promise<void>;
136
136
  type FetchRoom = (roomId: string) => Promise<Uint8Array | null>;
137
+ type WeaveRoomData = Uint8Array | FetchInitialState;
137
138
  type WeaveStoreAzureWebPubsubEvents = {
138
139
  onConnect: WeaveStoreAzureWebPubsubOnConnectEvent;
139
140
  onConnected: WeaveStoreAzureWebPubsubOnConnectedEvent;
@@ -229,6 +230,7 @@ type WeaveStoreAzureWebPubsubSyncHostOptions = {
229
230
  attemptsLimit: number;
230
231
  };
231
232
  };
233
+
232
234
  //#endregion
233
235
  //#region src/client.d.ts
234
236
  declare class WeaveStoreAzureWebPubSubSyncClient extends Emittery {
@@ -285,16 +287,18 @@ declare class WeaveStoreAzureWebPubsub extends WeaveStore {
285
287
  private roomId;
286
288
  private started;
287
289
  private initialRoomData;
290
+ private actualStatus;
288
291
  protected provider: WeaveStoreAzureWebPubSubSyncClient;
289
292
  protected name: string;
290
293
  protected supportsUndoManager: boolean;
291
294
  protected awarenessCallback: (changes: any) => void;
292
- constructor(initialRoomData: Uint8Array | FetchInitialState | undefined, storeOptions: WeaveStoreOptions, azureWebPubsubOptions: Pick<WeaveStoreAzureWebPubsubOptions, "roomId" | "url"> & Partial<Omit<WeaveStoreAzureWebPubsubOptions, "roomId" | "url">>);
295
+ constructor(initialRoomData: WeaveRoomData | undefined, storeOptions: WeaveStoreOptions, azureWebPubsubOptions: Pick<WeaveStoreAzureWebPubsubOptions, "roomId" | "url"> & Partial<Omit<WeaveStoreAzureWebPubsubOptions, "roomId" | "url">>);
293
296
  setup(): void;
294
297
  private loadRoomInitialData;
295
298
  private init;
296
299
  emitEvent<T>(name: string, payload?: T): void;
297
300
  getClientId(): string | null;
301
+ switchToRoom(roomId: string, roomData: Uint8Array | FetchInitialState | undefined): Promise<void>;
298
302
  connect(extraParams?: Record<string, string>): Promise<void>;
299
303
  disconnect(): void;
300
304
  simulateWebsocketError(): void;
@@ -304,4 +308,4 @@ declare class WeaveStoreAzureWebPubsub extends WeaveStore {
304
308
  }
305
309
 
306
310
  //#endregion
307
- export { FetchClient, FetchInitialState, FetchRoom, Message, MessageData, MessageDataType, MessageHandler, MessageType, PersistRoom, WEAVE_STORE_AZURE_WEB_PUBSUB, WEAVE_STORE_AZURE_WEB_PUBSUB_CONNECTION_STATUS, WEAVE_STORE_AZURE_WEB_PUBSUB_DESTROY_ROOM_STATUS, WEAVE_STORE_AZURE_WEB_PUBSUB_SYNC_CLIENT_DEFAULT_OPTIONS, WEAVE_STORE_AZURE_WEB_PUBSUB_SYNC_HOST_DEFAULT_OPTIONS, WEAVE_STORE_HORIZONTAL_SYNC_HANDLER_CLIENT_TYPE, WeaveAzureWebPubsubSyncHandlerOptions, WeaveStoreAzureWebPubSubSyncClientConnectionStatus, WeaveStoreAzureWebPubSubSyncClientConnectionStatusKeys, WeaveStoreAzureWebPubSubSyncClientOptions, WeaveStoreAzureWebPubSubSyncHostClientConnectOptions, WeaveStoreAzureWebPubsub, WeaveStoreAzureWebPubsubConfig, WeaveStoreAzureWebPubsubEvents, WeaveStoreAzureWebPubsubOnConnectEvent, WeaveStoreAzureWebPubsubOnConnectedEvent, WeaveStoreAzureWebPubsubOnDisconnectedEvent, WeaveStoreAzureWebPubsubOnStoreFetchConnectionUrlEvent, WeaveStoreAzureWebPubsubOnWebsocketCloseEvent, WeaveStoreAzureWebPubsubOnWebsocketErrorEvent, WeaveStoreAzureWebPubsubOnWebsocketJoinGroupEvent, WeaveStoreAzureWebPubsubOnWebsocketMessageEvent, WeaveStoreAzureWebPubsubOnWebsocketOpenEvent, WeaveStoreAzureWebPubsubOnWebsocketReconnectEvent, WeaveStoreAzureWebPubsubOptions, WeaveStoreAzureWebPubsubSyncHandlerDestroyRoomStatus, WeaveStoreAzureWebPubsubSyncHandlerDestroyRoomStatusKeys, WeaveStoreAzureWebPubsubSyncHostOptions };
311
+ export { FetchClient, FetchInitialState, FetchRoom, Message, MessageData, MessageDataType, MessageHandler, MessageType, PersistRoom, WEAVE_STORE_AZURE_WEB_PUBSUB, WEAVE_STORE_AZURE_WEB_PUBSUB_CONNECTION_STATUS, WEAVE_STORE_AZURE_WEB_PUBSUB_DESTROY_ROOM_STATUS, WEAVE_STORE_AZURE_WEB_PUBSUB_SYNC_CLIENT_DEFAULT_OPTIONS, WEAVE_STORE_AZURE_WEB_PUBSUB_SYNC_HOST_DEFAULT_OPTIONS, WEAVE_STORE_HORIZONTAL_SYNC_HANDLER_CLIENT_TYPE, WeaveAzureWebPubsubSyncHandlerOptions, WeaveRoomData, WeaveStoreAzureWebPubSubSyncClientConnectionStatus, WeaveStoreAzureWebPubSubSyncClientConnectionStatusKeys, WeaveStoreAzureWebPubSubSyncClientOptions, WeaveStoreAzureWebPubSubSyncHostClientConnectOptions, WeaveStoreAzureWebPubsub, WeaveStoreAzureWebPubsubConfig, WeaveStoreAzureWebPubsubEvents, WeaveStoreAzureWebPubsubOnConnectEvent, WeaveStoreAzureWebPubsubOnConnectedEvent, WeaveStoreAzureWebPubsubOnDisconnectedEvent, WeaveStoreAzureWebPubsubOnStoreFetchConnectionUrlEvent, WeaveStoreAzureWebPubsubOnWebsocketCloseEvent, WeaveStoreAzureWebPubsubOnWebsocketErrorEvent, WeaveStoreAzureWebPubsubOnWebsocketJoinGroupEvent, WeaveStoreAzureWebPubsubOnWebsocketMessageEvent, WeaveStoreAzureWebPubsubOnWebsocketOpenEvent, WeaveStoreAzureWebPubsubOnWebsocketReconnectEvent, WeaveStoreAzureWebPubsubOptions, WeaveStoreAzureWebPubsubSyncHandlerDestroyRoomStatus, WeaveStoreAzureWebPubsubSyncHandlerDestroyRoomStatusKeys, WeaveStoreAzureWebPubsubSyncHostOptions };
package/dist/client.js CHANGED
@@ -3971,7 +3971,6 @@ var WeaveStoreAzureWebPubSubSyncClient = class extends Emittery {
3971
3971
  if (message.type === MessageType.System) return;
3972
3972
  const messageData = message.data;
3973
3973
  if (messageData.type === "heartbeat") {
3974
- console.log("[Azure Web PubSub] received heartbeat, updating last heartbeat time.");
3975
3974
  this._lastHeartbeatTime = Date.now();
3976
3975
  return;
3977
3976
  }
@@ -4042,7 +4041,6 @@ var WeaveStoreAzureWebPubSubSyncClient = class extends Emittery {
4042
4041
  this._checkHeartbeatId = setInterval(() => {
4043
4042
  const now = Date.now();
4044
4043
  if (now - this._lastHeartbeatTime > this._synClientOptions.heartbeat.checkWindowTimeMs) {
4045
- console.log(`[Azure Web PubSub] heartbeat not received in ${this._synClientOptions.heartbeat.checkWindowTimeMs}ms window, handling it...`);
4046
4044
  this.disconnect();
4047
4045
  this.createWebSocket();
4048
4046
  }
@@ -4144,6 +4142,7 @@ var WeaveStoreAzureWebPubsub = class extends WeaveStore {
4144
4142
  this.roomId = roomId;
4145
4143
  this.initialRoomData = initialRoomData;
4146
4144
  this.started = false;
4145
+ this.actualStatus = WEAVE_STORE_CONNECTION_STATUS.DISCONNECTED;
4147
4146
  this.init();
4148
4147
  }
4149
4148
  setup() {
@@ -4157,7 +4156,8 @@ var WeaveStoreAzureWebPubsub = class extends WeaveStore {
4157
4156
  }
4158
4157
  init() {
4159
4158
  const { url } = this.azureWebPubsubOptions;
4160
- this.provider = new WeaveStoreAzureWebPubSubSyncClient(this, url, this.roomId, this.getDocument(), this.azureWebPubsubOptions.syncClientOptions);
4159
+ const patchedUrl = url.replace("[roomId]", this.roomId);
4160
+ this.provider = new WeaveStoreAzureWebPubSubSyncClient(this, patchedUrl, this.roomId, this.getDocument(), this.azureWebPubsubOptions.syncClientOptions);
4161
4161
  const awareness = this.provider.awareness;
4162
4162
  awareness.on("update", this.handleAwarenessChange.bind(this));
4163
4163
  awareness.on("change", this.handleAwarenessChange.bind(this));
@@ -4169,7 +4169,11 @@ var WeaveStoreAzureWebPubsub = class extends WeaveStore {
4169
4169
  this.handleConnectionStatusChange(WEAVE_STORE_CONNECTION_STATUS.DISCONNECTED);
4170
4170
  });
4171
4171
  this.provider.on("status", (status) => {
4172
- this.handleConnectionStatusChange(status);
4172
+ if (this.actualStatus !== WEAVE_STORE_CONNECTION_STATUS.SWITCHING_ROOM || this.actualStatus === WEAVE_STORE_CONNECTION_STATUS.SWITCHING_ROOM && status === WEAVE_STORE_CONNECTION_STATUS.CONNECTED) {
4173
+ this.handleConnectionStatusChange(status);
4174
+ if (this.actualStatus === WEAVE_STORE_CONNECTION_STATUS.SWITCHING_ROOM && status === WEAVE_STORE_CONNECTION_STATUS.CONNECTED) this.instance.emitEvent("onRoomSwitchingEnd", { room: this.roomId });
4175
+ this.actualStatus = status;
4176
+ }
4173
4177
  if (status === WEAVE_STORE_CONNECTION_STATUS.CONNECTED && !this.started) {
4174
4178
  this.loadRoomInitialData();
4175
4179
  this.started = true;
@@ -4183,9 +4187,23 @@ var WeaveStoreAzureWebPubsub = class extends WeaveStore {
4183
4187
  if (this.provider) return this.provider.getClientId();
4184
4188
  return null;
4185
4189
  }
4190
+ async switchToRoom(roomId, roomData) {
4191
+ this.instance.emitEvent("onRoomSwitchingStart", { room: roomId });
4192
+ this.actualStatus = WEAVE_STORE_CONNECTION_STATUS.SWITCHING_ROOM;
4193
+ this.disconnect();
4194
+ this.restartDocument();
4195
+ await this.instance.switchRoom();
4196
+ this.roomId = roomId;
4197
+ this.initialRoomData = roomData;
4198
+ this.started = false;
4199
+ this.init();
4200
+ this.setup();
4201
+ this.connect();
4202
+ }
4186
4203
  async connect(extraParams) {
4187
4204
  const { fetchClient } = this.azureWebPubsubOptions;
4188
4205
  this.provider.setFetchClient(fetchClient ?? window.fetch);
4206
+ this.instance.emitEvent("onStoreRoomChanged", { room: this.roomId });
4189
4207
  await this.provider.connect(extraParams);
4190
4208
  }
4191
4209
  disconnect() {
package/dist/server.d.ts CHANGED
@@ -42,16 +42,18 @@ declare class WeaveStoreAzureWebPubsub extends WeaveStore {
42
42
  private roomId;
43
43
  private started;
44
44
  private initialRoomData;
45
+ private actualStatus;
45
46
  protected provider: WeaveStoreAzureWebPubSubSyncClient;
46
47
  protected name: string;
47
48
  protected supportsUndoManager: boolean;
48
49
  protected awarenessCallback: (changes: any) => void;
49
- constructor(initialRoomData: Uint8Array | FetchInitialState | undefined, storeOptions: WeaveStoreOptions, azureWebPubsubOptions: Pick<WeaveStoreAzureWebPubsubOptions, "roomId" | "url"> & Partial<Omit<WeaveStoreAzureWebPubsubOptions, "roomId" | "url">>);
50
+ constructor(initialRoomData: WeaveRoomData | undefined, storeOptions: WeaveStoreOptions, azureWebPubsubOptions: Pick<WeaveStoreAzureWebPubsubOptions, "roomId" | "url"> & Partial<Omit<WeaveStoreAzureWebPubsubOptions, "roomId" | "url">>);
50
51
  setup(): void;
51
52
  private loadRoomInitialData;
52
53
  private init;
53
54
  emitEvent<T>(name: string, payload?: T): void;
54
55
  getClientId(): string | null;
56
+ switchToRoom(roomId: string, roomData: Uint8Array | FetchInitialState | undefined): Promise<void>;
55
57
  connect(extraParams?: Record<string, string>): Promise<void>;
56
58
  disconnect(): void;
57
59
  simulateWebsocketError(): void;
@@ -143,6 +145,7 @@ type FetchClient = (input: string | URL | globalThis.Request, init?: RequestInit
143
145
  type FetchInitialState = (doc: Doc) => void;
144
146
  type PersistRoom = (roomId: string, actualState: Uint8Array<ArrayBufferLike>) => Promise<void>;
145
147
  type FetchRoom = (roomId: string) => Promise<Uint8Array | null>;
148
+ type WeaveRoomData = Uint8Array | FetchInitialState;
146
149
  type WeaveStoreAzureWebPubsubEvents = {
147
150
  onConnect: WeaveStoreAzureWebPubsubOnConnectEvent;
148
151
  onConnected: WeaveStoreAzureWebPubsubOnConnectedEvent;
@@ -419,7 +422,8 @@ declare enum MqttDisconnectReasonCode {
419
422
  * Description: The Server does not support Wildcard Subscriptions; the subscription is not accepted.
420
423
  */
421
424
  WildcardSubscriptionsNotSupported = 162,
422
- } //#endregion
425
+ }
426
+ //#endregion
423
427
  //#region src/server/event-handler/enum/mqtt-error-codes/mqtt-v311-connect-return-code.d.ts
424
428
  /**
425
429
  * MQTT 3.1.1 Connect Return Codes.
@@ -1134,4 +1138,4 @@ declare class WeaveAzureWebPubsubServer extends Emittery {
1134
1138
  }
1135
1139
 
1136
1140
  //#endregion
1137
- export { Certificate, ConnectErrorResponse, ConnectRequest, ConnectResponse, ConnectResponseHandler, ConnectedRequest, ConnectionContext, DisconnectedRequest, FetchClient, FetchInitialState, FetchRoom, Message, MessageData, MessageDataType, MessageHandler, MessageType, MqttConnectErrorResponse, MqttConnectErrorResponseProperties, MqttConnectProperties, MqttConnectRequest, MqttConnectResponse, MqttConnectResponseProperties, MqttConnectionContextProperties, MqttDisconnectPacket, MqttDisconnectReasonCode, MqttDisconnectedProperties, MqttDisconnectedRequest, MqttUserProperty, MqttV311ConnectReturnCode, MqttV500ConnectReasonCode, PersistRoom, UserEventRequest, UserEventResponseHandler, WEAVE_STORE_AZURE_WEB_PUBSUB, WEAVE_STORE_AZURE_WEB_PUBSUB_CONNECTION_STATUS, WEAVE_STORE_AZURE_WEB_PUBSUB_DESTROY_ROOM_STATUS, WEAVE_STORE_AZURE_WEB_PUBSUB_SYNC_CLIENT_DEFAULT_OPTIONS, WEAVE_STORE_AZURE_WEB_PUBSUB_SYNC_HOST_DEFAULT_OPTIONS, WEAVE_STORE_HORIZONTAL_SYNC_HANDLER_CLIENT_TYPE, WeaveAzureWebPubsubServer, WeaveAzureWebPubsubSyncHandlerOptions, WeaveStoreAzureWebPubSubSyncClientConnectionStatus, WeaveStoreAzureWebPubSubSyncClientConnectionStatusKeys, WeaveStoreAzureWebPubSubSyncClientOptions, WeaveStoreAzureWebPubSubSyncHost, WeaveStoreAzureWebPubSubSyncHostClientConnectOptions, WeaveStoreAzureWebPubsubConfig, WeaveStoreAzureWebPubsubEvents, WeaveStoreAzureWebPubsubOnConnectEvent, WeaveStoreAzureWebPubsubOnConnectedEvent, WeaveStoreAzureWebPubsubOnDisconnectedEvent, WeaveStoreAzureWebPubsubOnStoreFetchConnectionUrlEvent, WeaveStoreAzureWebPubsubOnWebsocketCloseEvent, WeaveStoreAzureWebPubsubOnWebsocketErrorEvent, WeaveStoreAzureWebPubsubOnWebsocketJoinGroupEvent, WeaveStoreAzureWebPubsubOnWebsocketMessageEvent, WeaveStoreAzureWebPubsubOnWebsocketOpenEvent, WeaveStoreAzureWebPubsubOnWebsocketReconnectEvent, WeaveStoreAzureWebPubsubOptions, WeaveStoreAzureWebPubsubSyncHandlerDestroyRoomStatus, WeaveStoreAzureWebPubsubSyncHandlerDestroyRoomStatusKeys, WeaveStoreAzureWebPubsubSyncHostOptions, WebPubSubClientProtocol, WebPubSubEventHandler, WebPubSubEventHandlerOptions };
1141
+ export { Certificate, ConnectErrorResponse, ConnectRequest, ConnectResponse, ConnectResponseHandler, ConnectedRequest, ConnectionContext, DisconnectedRequest, FetchClient, FetchInitialState, FetchRoom, Message, MessageData, MessageDataType, MessageHandler, MessageType, MqttConnectErrorResponse, MqttConnectErrorResponseProperties, MqttConnectProperties, MqttConnectRequest, MqttConnectResponse, MqttConnectResponseProperties, MqttConnectionContextProperties, MqttDisconnectPacket, MqttDisconnectReasonCode, MqttDisconnectedProperties, MqttDisconnectedRequest, MqttUserProperty, MqttV311ConnectReturnCode, MqttV500ConnectReasonCode, PersistRoom, UserEventRequest, UserEventResponseHandler, WEAVE_STORE_AZURE_WEB_PUBSUB, WEAVE_STORE_AZURE_WEB_PUBSUB_CONNECTION_STATUS, WEAVE_STORE_AZURE_WEB_PUBSUB_DESTROY_ROOM_STATUS, WEAVE_STORE_AZURE_WEB_PUBSUB_SYNC_CLIENT_DEFAULT_OPTIONS, WEAVE_STORE_AZURE_WEB_PUBSUB_SYNC_HOST_DEFAULT_OPTIONS, WEAVE_STORE_HORIZONTAL_SYNC_HANDLER_CLIENT_TYPE, WeaveAzureWebPubsubServer, WeaveAzureWebPubsubSyncHandlerOptions, WeaveRoomData, WeaveStoreAzureWebPubSubSyncClientConnectionStatus, WeaveStoreAzureWebPubSubSyncClientConnectionStatusKeys, WeaveStoreAzureWebPubSubSyncClientOptions, WeaveStoreAzureWebPubSubSyncHost, WeaveStoreAzureWebPubSubSyncHostClientConnectOptions, WeaveStoreAzureWebPubsubConfig, WeaveStoreAzureWebPubsubEvents, WeaveStoreAzureWebPubsubOnConnectEvent, WeaveStoreAzureWebPubsubOnConnectedEvent, WeaveStoreAzureWebPubsubOnDisconnectedEvent, WeaveStoreAzureWebPubsubOnStoreFetchConnectionUrlEvent, WeaveStoreAzureWebPubsubOnWebsocketCloseEvent, WeaveStoreAzureWebPubsubOnWebsocketErrorEvent, WeaveStoreAzureWebPubsubOnWebsocketJoinGroupEvent, WeaveStoreAzureWebPubsubOnWebsocketMessageEvent, WeaveStoreAzureWebPubsubOnWebsocketOpenEvent, WeaveStoreAzureWebPubsubOnWebsocketReconnectEvent, WeaveStoreAzureWebPubsubOptions, WeaveStoreAzureWebPubsubSyncHandlerDestroyRoomStatus, WeaveStoreAzureWebPubsubSyncHandlerDestroyRoomStatusKeys, WeaveStoreAzureWebPubsubSyncHostOptions, WebPubSubClientProtocol, WebPubSubEventHandler, WebPubSubEventHandlerOptions };
package/dist/server.js CHANGED
@@ -19970,6 +19970,7 @@ var require_application = __commonJS({ "../../node_modules/koa/lib/application.j
19970
19970
  * Module dependencies.
19971
19971
  */
19972
19972
  const util$1 = __require("node:util");
19973
+ const v8 = __require("node:v8");
19973
19974
  const debug = util$1.debuglog("koa:application");
19974
19975
  const Emitter = __require("node:events");
19975
19976
  const Stream = __require("node:stream");
@@ -19999,6 +20000,10 @@ var require_application = __commonJS({ "../../node_modules/koa/lib/application.j
19999
20000
  * Expose `Application` class.
20000
20001
  * Inherits from `Emitter.prototype`.
20001
20002
  */
20003
+ function getAsyncLocalStorage(options) {
20004
+ if (options.asyncLocalStorage instanceof AsyncLocalStorage) return options.asyncLocalStorage;
20005
+ return new AsyncLocalStorage();
20006
+ }
20002
20007
  module.exports = class Application extends Emitter {
20003
20008
  /**
20004
20009
  * Initialize a new `Application`.
@@ -20034,8 +20039,15 @@ var require_application = __commonJS({ "../../node_modules/koa/lib/application.j
20034
20039
  this.response = Object.create(response);
20035
20040
  /* istanbul ignore else */
20036
20041
  if (util$1.inspect.custom) this[util$1.inspect.custom] = this.inspect;
20037
- if (options.asyncLocalStorage) if (options.asyncLocalStorage instanceof AsyncLocalStorage) this.ctxStorage = options.asyncLocalStorage;
20038
- else this.ctxStorage = new AsyncLocalStorage();
20042
+ if (options.asyncLocalStorage) if (v8.startupSnapshot?.isBuildingSnapshot?.()) {
20043
+ this.ctxStorage = null;
20044
+ v8.startupSnapshot.addDeserializeCallback(({ app, options: options$1 }) => {
20045
+ app.ctxStorage = getAsyncLocalStorage(options$1);
20046
+ }, {
20047
+ app: this,
20048
+ options
20049
+ });
20050
+ } else this.ctxStorage = getAsyncLocalStorage(options);
20039
20051
  }
20040
20052
  /**
20041
20053
  * Shorthand for:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inditextech/weave-store-azure-web-pubsub",
3
- "version": "3.3.1",
3
+ "version": "3.4.0-SNAPSHOT.85.1",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Jesus Manuel Piñeiro Cid <jesusmpc@inditex.com>",
@@ -57,8 +57,8 @@
57
57
  "dependencies": {
58
58
  "@azure/identity": "4.10.2",
59
59
  "@azure/web-pubsub": "1.2.0",
60
- "@inditextech/weave-types": "3.3.1",
61
- "@inditextech/weave-sdk": "3.3.1",
60
+ "@inditextech/weave-types": "3.4.0-SNAPSHOT.85.1",
61
+ "@inditextech/weave-sdk": "3.4.0-SNAPSHOT.85.1",
62
62
  "@syncedstore/core": "0.6.0",
63
63
  "buffer": "6.0.3",
64
64
  "reconnecting-websocket": "4.4.0",