{"version":3,"file":"Websocket2023NotificationSubscription.mjs","names":[],"sources":["../../src/notifications/Websocket2023NotificationSubscription.ts"],"sourcesContent":["import { SubscriptionClient } from \"@solid-notifications/subscription\";\nimport { WebSocket } from \"ws\";\nimport {\n  DisconnectedAttemptingReconnectError,\n  DisconnectedNotAttemptingReconnectError,\n  UnsupportedNotificationError,\n} from \"./results/NotificationErrors\";\nimport type { SolidNotificationMessage } from \"./SolidNotificationMessage\";\nimport { UnexpectedResourceError, type ConnectedContext } from \"@ldo/connected\";\nimport type {\n  ChannelType,\n  NotificationChannel,\n} from \"@solid-notifications/types\";\nimport { NotificationSubscription } from \"@ldo/connected\";\nimport type { SolidConnectedPlugin } from \"../SolidConnectedPlugin\";\nimport type { SolidLeaf } from \"../resources/SolidLeaf\";\nimport type { SolidContainer } from \"../resources/SolidContainer\";\nimport { guaranteeFetch } from \"../util/guaranteeFetch\";\n\nconst CHANNEL_TYPE =\n  \"http://www.w3.org/ns/solid/notifications#WebSocketChannel2023\";\n\n/**\n * @internal\n * An implementation of the Websocket2023 notifiction spec\n */\nexport class Websocket2023NotificationSubscription extends NotificationSubscription<\n  SolidConnectedPlugin,\n  SolidNotificationMessage\n> {\n  private socket: WebSocket | undefined;\n  private createWebsocket: (address: string) => WebSocket;\n\n  // Reconnection data\n  // How often we should attempt a reconnection\n  private reconnectInterval = 5000;\n  // How many attempts have already been tried for a reconnection\n  private reconnectAttempts = 0;\n  // Whether or not the socket was manually closes\n  private isManualClose = false;\n  // Maximum number of attempts to reconnect\n  private maxReconnectAttempts = 6;\n\n  constructor(\n    resource: SolidLeaf | SolidContainer,\n    parentSubscription: (message: SolidNotificationMessage) => void,\n    context: ConnectedContext<SolidConnectedPlugin[]>,\n    createWebsocket?: (address: string) => WebSocket,\n  ) {\n    super(resource, parentSubscription, context);\n    this.createWebsocket = createWebsocket ?? createWebsocketDefault;\n  }\n\n  async open(): Promise<void> {\n    try {\n      const notificationChannel = await this.discoverNotificationChannel();\n      await this.subscribeToWebsocket(notificationChannel);\n    } catch (err) {\n      if (\n        err instanceof Error &&\n        err.message.startsWith(\"Discovery did not succeed\")\n      ) {\n        this.onNotificationError(\n          new UnsupportedNotificationError(this.resource, err.message),\n        );\n      } else {\n        this.onNotificationError(\n          UnexpectedResourceError.fromThrown(this.resource, err),\n        );\n      }\n      this.onClose();\n    }\n  }\n\n  public async discoverNotificationChannel(): Promise<NotificationChannel> {\n    const client = new SubscriptionClient(\n      guaranteeFetch(this.context.solid.fetch),\n    );\n    return await client.subscribe(\n      this.resource.uri,\n      CHANNEL_TYPE as ChannelType,\n    );\n  }\n\n  public async subscribeToWebsocket(\n    notificationChannel: NotificationChannel,\n  ): Promise<void> {\n    this.socket = this.createWebsocket(\n      notificationChannel.receiveFrom as string,\n    );\n\n    this.socket.onopen = () => {\n      this.reconnectAttempts = 0; // Reset attempts on successful connection\n      this.isManualClose = false; // Reset manual close flag\n    };\n\n    this.socket.onmessage = (message) => {\n      const messageData = message.data.toString();\n      // TODO uncompliant Pod error on misformatted message\n      this.onNotification(JSON.parse(messageData) as SolidNotificationMessage);\n    };\n\n    this.socket.onclose = () => {\n      this.onClose();\n    };\n\n    this.socket.onerror = (err) => {\n      this.onNotificationError(\n        new UnexpectedResourceError(this.resource, err.error),\n      );\n    };\n    return;\n  }\n\n  private onClose() {\n    if (!this.isManualClose) {\n      // Attempt to reconnect only if the disconnection was unintentional\n      if (this.reconnectAttempts < this.maxReconnectAttempts) {\n        this.reconnectAttempts++;\n        setTimeout(() => {\n          this.open();\n        }, this.reconnectInterval);\n        this.onNotificationError(\n          new DisconnectedAttemptingReconnectError(\n            this.resource,\n            `Attempting to reconnect to Websocket for ${this.resource.uri}.`,\n          ),\n        );\n      } else {\n        this.onNotificationError(\n          new DisconnectedNotAttemptingReconnectError(\n            this.resource,\n            `Lost connection to websocket for ${this.resource.uri}.`,\n          ),\n        );\n      }\n    }\n  }\n\n  protected async close(): Promise<void> {\n    this.socket?.close();\n  }\n}\n\nfunction createWebsocketDefault(address: string): WebSocket {\n  const WebSocketImpl =\n    typeof window !== \"undefined\" ? window.WebSocket : WebSocket;\n  return new WebSocketImpl(address) as WebSocket;\n}\n"],"mappings":";;;;;;AAmBA,MAAM,eACJ;;;;;AAMF,IAAa,wCAAb,cAA2D,yBAGzD;CAcA,YACE,UACA,oBACA,SACA,iBACA;AACA,QAAM,UAAU,oBAAoB,QAAQ;AAd9C,OAAQ,oBAAoB;AAE5B,OAAQ,oBAAoB;AAE5B,OAAQ,gBAAgB;AAExB,OAAQ,uBAAuB;AAS7B,OAAK,kBAAkB,mBAAmB;;CAG5C,MAAM,OAAsB;AAC1B,MAAI;GACF,MAAM,sBAAsB,MAAM,KAAK,6BAA6B;AACpE,SAAM,KAAK,qBAAqB,oBAAoB;WAC7C,KAAK;AACZ,OACE,eAAe,SACf,IAAI,QAAQ,WAAW,4BAA4B,CAEnD,MAAK,oBACH,IAAI,6BAA6B,KAAK,UAAU,IAAI,QAAQ,CAC7D;OAED,MAAK,oBACH,wBAAwB,WAAW,KAAK,UAAU,IAAI,CACvD;AAEH,QAAK,SAAS;;;CAIlB,MAAa,8BAA4D;AAIvE,SAAO,MAAM,IAHM,mBACjB,eAAe,KAAK,QAAQ,MAAM,MAAM,CAEvB,CAAC,UAClB,KAAK,SAAS,KACd,aACD;;CAGH,MAAa,qBACX,qBACe;AACf,OAAK,SAAS,KAAK,gBACjB,oBAAoB,YACrB;AAED,OAAK,OAAO,eAAe;AACzB,QAAK,oBAAoB;AACzB,QAAK,gBAAgB;;AAGvB,OAAK,OAAO,aAAa,YAAY;GACnC,MAAM,cAAc,QAAQ,KAAK,UAAU;AAE3C,QAAK,eAAe,KAAK,MAAM,YAAY,CAA6B;;AAG1E,OAAK,OAAO,gBAAgB;AAC1B,QAAK,SAAS;;AAGhB,OAAK,OAAO,WAAW,QAAQ;AAC7B,QAAK,oBACH,IAAI,wBAAwB,KAAK,UAAU,IAAI,MAAM,CACtD;;;CAKL,UAAkB;AAChB,MAAI,CAAC,KAAK,cAER,KAAI,KAAK,oBAAoB,KAAK,sBAAsB;AACtD,QAAK;AACL,oBAAiB;AACf,SAAK,MAAM;MACV,KAAK,kBAAkB;AAC1B,QAAK,oBACH,IAAI,qCACF,KAAK,UACL,4CAA4C,KAAK,SAAS,IAAI,GAC/D,CACF;QAED,MAAK,oBACH,IAAI,wCACF,KAAK,UACL,oCAAoC,KAAK,SAAS,IAAI,GACvD,CACF;;CAKP,MAAgB,QAAuB;AACrC,OAAK,QAAQ,OAAO;;;AAIxB,SAAS,uBAAuB,SAA4B;AAG1D,QAAO,KADL,OAAO,WAAW,cAAc,OAAO,YAAY,WAC5B,QAAQ"}