UNPKG

nuxt-socket-io

Version:

Socket.io client and server module for Nuxt. Just plug it in and GO

1,054 lines (963 loc) 28.3 kB
/* eslint-disable no-console */ /* * Copyright 2022 Richard Schloss (https://github.com/richardeschloss/nuxt-socket-io) */ import io from 'socket.io-client' import Debug from 'debug' import emitter from 'tiny-emitter/instance.js' // @ts-ignore import { watch } from 'vue' import { defineNuxtPlugin, useState } from '#app' /* TODO: 1) will enable when '@nuxtjs/composition-api' reaches stable version: 2) will bump from devDep to dep when stable */ const debug = Debug('nuxt-socket-io') const isRefImpl = any => any && any.constructor.name === 'RefImpl' const delay = (ms, timerObj) => new Promise((resolve, reject) => { timerObj.timer = setTimeout(() => resolve(true), ms) timerObj.abort = () => { clearTimeout(timerObj.timer) reject(new Error('AbortError')) } }) const _sockets = {} export const mutations = { SET_API (state, { label, api }) { state.ioApis[label] = api }, SET_CLIENT_API (state, { label = 'clientAPI', ...api }) { state.clientApis[label] = api }, SET_EMIT_ERRORS (state, { label, emitEvt, err }) { if (state.emitErrors[label] === undefined) { state.emitErrors[label] = {} } if (state.emitErrors[label][emitEvt] === undefined) { state.emitErrors[label][emitEvt] = [] } state.emitErrors[label][emitEvt].push(err) }, SET_EMIT_TIMEOUT (state, { label, emitTimeout }) { if (!state.emitTimeouts[label]) { state.emitTimeouts[label] = {} } state.emitTimeouts[label] = emitTimeout } } export const ioState = () => useState('$io', () => ({})) export const useNuxtSocket = () => useState('$nuxtSocket', () => ({ clientApis: {}, ioApis: {}, emitErrors: {}, emitTimeouts: {} })) export async function emit ({ // TBD: test... label = '', socket = _sockets[label], evt, msg, emitTimeout = useNuxtSocket().value.emitTimeouts[label], noAck = false }) { const state = useNuxtSocket().value debug('$nuxtSocket.emit', label, evt) if (socket === undefined) { throw new Error( 'socket instance required. Please provide a valid socket label or socket instance' ) } register.emitP(socket) debug(`Emitting ${evt} with msg`, msg) const timerObj = {} const p = [socket.emitP(evt, msg)] if (noAck) { return } if (emitTimeout) { p.push( register.emitTimeout({ emitTimeout, timerObj }).catch((err) => { if (label !== undefined && label !== '') { mutations.SET_EMIT_ERRORS(state, { label, emitEvt: evt, err }) debug( `[nuxt-socket-io]: ${label} Emit error occurred and logged to vuex `, err ) } else { throw new Error(err.message) } }) ) } const resp = await Promise.race(p) debug('Emitter response rxd', { evt, resp }) if (timerObj.abort) { timerObj.abort() } const { emitError, ...errorDetails } = resp || {} if (emitError !== undefined) { const err = { message: emitError, emitEvt: evt, errorDetails, timestamp: Date.now() } debug('Emit error occurred', err) if (label !== undefined && label !== '') { debug( `[nuxt-socket-io]: ${label} Emit error ${err.message} occurred and logged to vuex `, err ) mutations.SET_EMIT_ERRORS(state, { label, emitEvt: evt, err }) } else { throw new Error(err.message) } } else { return resp } } let warn, infoMsgs function camelCase (str) { return str .replace(/[_\-\s](.)/g, function ($1) { return $1.toUpperCase() }) .replace(/[-_\s]/g, '') .replace(/^(.)/, function ($1) { return $1.toLowerCase() }) .replace(/[^\w\s]/gi, '') } function propExists (obj, path) { // eslint-disable-next-line array-callback-return const exists = path.split('.').reduce((out, prop) => { if (out !== undefined && out[prop] !== undefined) { return out[prop] } }, obj) return exists !== undefined } function parseEntry (entry, entryType) { let evt, mapTo, pre, emitEvt, msgLabel, post if (typeof entry === 'string') { let subItems = [] let body const items = entry.trim().split(/\s*\]\s*/) if (items.length > 1) { pre = items[0] subItems = items[1].split(/\s*\[\s*/) } else { subItems = items[0].split(/\s*\[\s*/) } ;[body, post] = subItems if (body.includes('-->')) { ;[evt, mapTo] = body.split(/\s*-->\s*/) } else if (body.includes('<--')) { ;[evt, mapTo] = body.split(/\s*<--\s*/) } else { evt = body } if (entryType === 'emitter') { ;[emitEvt, msgLabel] = evt.split(/\s*\+\s*/) } else if (mapTo === undefined) { mapTo = evt } } return { pre, post, evt, mapTo, emitEvt, msgLabel } } function assignMsg (ctx, prop) { let msg if (prop !== undefined) { if (ctx[prop] !== undefined) { if (typeof ctx[prop] === 'object') { msg = Array.isArray(ctx[prop]) ? [] : {} Object.assign(msg, ctx[prop]) } else { msg = ctx[prop] } } else { warn(`prop or data item "${prop}" not defined`) } debug(`assigned ${prop} to ${msg}`) } return msg } function assignResp (ctx, prop, resp) { if (prop !== undefined) { if (ctx[prop] !== undefined) { if (typeof ctx[prop] !== 'function') { // In vue3, it's possible to create // reactive refs on the fly with ref() // so check for that here. // (this would elimnate the need for v2's // this.$set because we just set the value prop // to trigger the UI changes) if (isRefImpl(ctx[prop])) { ctx[prop].value = resp } else { ctx[prop] = resp } debug(`assigned ${resp} to ${prop}`) } } else { warn(`${prop} not defined on instance`) } } } async function runHook (ctx, prop, data) { if (prop !== undefined) { if (ctx[prop]) { return await ctx[prop](data) } else { warn(`method ${prop} not defined`) } } } /** * Validate the provided sockets are an array * of at least 1 item * @param {Array<*>} sockets */ function validateSockets (sockets) { return (sockets && Array.isArray(sockets) && sockets.length > 0) } export const register = { clientApiEvents ({ ctx, socket, api }) { const { evts } = api Object.entries(evts).forEach(([emitEvt, schema]) => { const { data: dataT } = schema const fn = emitEvt + 'Emit' if (ctx[emitEvt] !== undefined) { if (dataT !== undefined) { Object.entries(dataT).forEach(([key, val]) => { ctx.$set(ctx[emitEvt], key, val) }) debug('Initialized data for', emitEvt, dataT) } } if (ctx[fn] !== undefined) { return } ctx[fn] = async (fnArgs) => { const { label: apiLabel, ack, ...args } = fnArgs || {} const label = apiLabel || api.label const msg = Object.keys(args).length > 0 ? args : { ...ctx[emitEvt] } msg.method = fn if (ack) { const ackd = await emit({ label, socket, evt: emitEvt, msg }) return ackd } else { emit({ label, socket, evt: emitEvt, msg, noAck: true }) } } debug('Registered clientAPI method', fn) }) }, clientApiMethods ({ ctx, socket, api }) { const { methods } = api const evts = Object.assign({}, methods, { getAPI: {} }) Object.entries(evts).forEach(([evt, schema]) => { if (socket.hasListeners(evt)) { warn(`evt ${evt} already has a listener registered`) } socket.on(evt, async (msg, cb) => { if (evt === 'getAPI') { if (cb) { cb(api) } } else if (ctx[evt] !== undefined) { msg.method = evt const resp = await ctx[evt](msg) if (cb) { cb(resp) } } else if (cb) { // eslint-disable-next-line node/no-callback-literal cb({ emitErr: 'notImplemented', msg: `Client has not yet implemented method (${evt})` }) } }) debug(`registered client api method ${evt}`) if (evt !== 'getAPI' && ctx[evt] === undefined) { warn( `client api method ${evt} has not been defined. ` + 'Either update the client api or define the method so it can be used by callers' ) } }) }, clientAPI ({ ctx, socket, clientAPI }) { const state = useNuxtSocket().value if (clientAPI.methods) { register.clientApiMethods({ ctx, socket, api: clientAPI }) } if (clientAPI.evts) { register.clientApiEvents({ ctx, socket, api: clientAPI }) } mutations.SET_CLIENT_API(state, clientAPI) debug('clientAPI registered', clientAPI) }, serverApiEvents ({ ctx, socket, api, label, ioDataProp, apiIgnoreEvts }) { const { evts } = api Object.entries(evts).forEach(([evt, entry]) => { const { methods = [], data: dataT } = entry if (apiIgnoreEvts.includes(evt)) { debug( `Event ${evt} is in ignore list ("apiIgnoreEvts"), not registering.` ) return } if (socket.hasListeners(evt)) { warn(`evt ${evt} already has a listener registered`) } if (methods.length === 0) { let initVal = dataT if (typeof initVal === 'object') { initVal = Array.isArray(dataT) ? [] : {} } ctx.$set(ctx[ioDataProp], evt, initVal) } else { methods.forEach((method) => { if (ctx[ioDataProp][method] === undefined) { ctx.$set(ctx[ioDataProp], method, {}) } ctx.$set( ctx[ioDataProp][method], evt, Array.isArray(dataT) ? [] : {} ) }) } socket.on(evt, (msg, cb) => { debug(`serverAPI event ${evt} rxd with msg`, msg) const { method, data } = msg if (method !== undefined) { if (ctx[ioDataProp][method] === undefined) { ctx.$set(ctx[ioDataProp], method, {}) } ctx.$set(ctx[ioDataProp][method], evt, data) } else { ctx.$set(ctx[ioDataProp], evt, data) } if (cb) { // eslint-disable-next-line node/no-callback-literal cb({ ack: 'ok' }) } }) debug(`Registered listener for ${evt} on ${label}`) }) }, serverApiMethods ({ ctx, socket, api, label, ioApiProp, ioDataProp }) { Object.entries(api.methods).forEach(([fn, schema]) => { const { msg: msgT, resp: respT } = schema if (ctx[ioDataProp][fn] === undefined) { ctx.$set(ctx[ioDataProp], fn, {}) if (msgT !== undefined) { ctx.$set(ctx[ioDataProp][fn], 'msg', { ...msgT }) } if (respT !== undefined) { ctx.$set( ctx[ioDataProp][fn], 'resp', Array.isArray(respT) ? [] : {} ) } } ctx[ioApiProp][fn] = async (args) => { const emitEvt = fn const msg = args !== undefined ? args : { ...ctx[ioDataProp][fn].msg } debug(`${ioApiProp}:${label}: Emitting ${emitEvt} with ${msg}`) const resp = await emit({ label, socket, evt: emitEvt, msg }) ctx[ioDataProp][fn].resp = resp return resp } }) }, async serverAPI ({ ctx, socket, label, apiIgnoreEvts, ioApiProp, ioDataProp, serverAPI, clientAPI = {} }) { const state = useNuxtSocket().value if (ctx[ioApiProp] === undefined) { console.error( `[nuxt-socket-io]: ${ioApiProp} needs to be defined in the current context for ` + 'serverAPI registration (vue requirement)' ) return } const apiLabel = serverAPI.label || label debug('register api for', apiLabel) const api = state.ioApis[apiLabel] || {} const fetchedApi = await emit({ label: apiLabel, socket, evt: serverAPI.evt || 'getAPI', msg: serverAPI.data || {} }) const isPeer = clientAPI.label === fetchedApi.label && parseFloat(clientAPI.version) === parseFloat(fetchedApi.version) if (isPeer) { Object.assign(api, clientAPI) mutations.SET_API(state, { label: apiLabel, api }) debug(`api for ${apiLabel} registered`, api) } else if (parseFloat(api.version) !== parseFloat(fetchedApi.version)) { Object.assign(api, fetchedApi) mutations.SET_API(state, { label: apiLabel, api }) debug(`api for ${apiLabel} registered`, api) } ctx.$set(ctx, ioApiProp, api) if (api.methods !== undefined) { register.serverApiMethods({ ctx, socket, api, label, ioApiProp, ioDataProp }) debug( `Attached methods for ${label} to ${ioApiProp}`, Object.keys(api.methods) ) } if (api.evts !== undefined) { register.serverApiEvents({ ctx, socket, api, label, ioDataProp, apiIgnoreEvts }) debug(`registered evts for ${label} to ${ioApiProp}`) } ctx.$set(ctx[ioApiProp], 'ready', true) debug('ioApi', ctx[ioApiProp]) }, emitErrors ({ ctx, err, emitEvt, emitErrorsProp }) { if (ctx[emitErrorsProp][emitEvt] === undefined) { ctx[emitErrorsProp][emitEvt] = [] } ctx[emitErrorsProp][emitEvt].push(err) }, /** * @param {*} info * @param {number} info.emitTimeout * @param {*} info.timerObj * @param {*} [info.ctx] * @param {string} [info.emitEvt] * @param {string} [info.emitErrorsProp] */ async emitTimeout ({ ctx, emitEvt, emitErrorsProp, emitTimeout, timerObj }) { const timedOut = await delay(emitTimeout, timerObj).catch(() => {}) if (!timedOut) { return } const err = { message: 'emitTimeout', emitEvt, emitTimeout, hint: [ `1) Is ${emitEvt} supported on the backend?`, `2) Is emitTimeout ${emitTimeout} ms too small?` ].join('\r\n'), timestamp: Date.now() } debug('emitEvt timed out', err) if (ctx !== undefined && typeof ctx[emitErrorsProp] === 'object') { register.emitErrors({ ctx, err, emitEvt, emitErrorsProp }) } else { throw err } }, emitBacks ({ ctx, socket, entries }) { entries.forEach((entry) => { const { pre, post, evt, mapTo } = parseEntry(entry, 'emitBack') if (propExists(ctx, mapTo)) { debug('registered local emitBack', { mapTo }) ctx.$watch(mapTo, async function (data, oldData) { debug('local data changed', evt, data) const preResult = await runHook(ctx, pre, { data, oldData }) if (preResult === false) { return } debug('Emitting back:', { evt, mapTo, data }) const p = socket.emitP(evt, { data }) if (post === undefined) { return } const resp = await p runHook(ctx, post, resp) return resp }) } else { warn(`Specified emitback ${mapTo} is not defined in component`) } }) }, emitters ({ ctx, socket, entries, emitTimeout, emitErrorsProp }) { entries.forEach((entry) => { const { pre, post, mapTo, emitEvt, msgLabel } = parseEntry( entry, 'emitter' ) ctx[emitEvt] = async function (msg = assignMsg(ctx, msgLabel)) { debug('Emit evt', { emitEvt, msg }) const preResult = await runHook(ctx, pre, msg) if (preResult === false) { return } const p = [socket.emitP(emitEvt, msg)] const timerObj = {} if (emitTimeout) { p.push(register .emitTimeout({ ctx, emitEvt, emitErrorsProp, emitTimeout, timerObj })) } const resp = await Promise.race(p) debug('Emitter response rxd', { emitEvt, resp }) if (timerObj.abort) { timerObj.abort() } const { emitError, ...errorDetails } = resp || {} if (emitError !== undefined) { const err = { message: emitError, emitEvt, errorDetails, timestamp: Date.now() } debug('Emit error occurred', err) if (typeof ctx[emitErrorsProp] === 'object') { register.emitErrors({ ctx, err, emitEvt, emitErrorsProp }) } else { throw err } } else { assignResp(ctx.$data || ctx, mapTo, resp) runHook(ctx, post, resp) return resp } } debug('Emitter created', { emitter: emitEvt }) }) }, listeners ({ ctx, socket, entries }) { entries.forEach((entry) => { const { pre, post, evt, mapTo } = parseEntry(entry) debug('Registered local listener', evt) socket.on(evt, async (resp) => { debug('Local listener received data', { evt, resp }) await runHook(ctx, pre) assignResp(ctx.$data || ctx, mapTo, resp) runHook(ctx, post, resp) }) }) }, namespace ({ ctx, namespaceCfg, socket, emitTimeout, emitErrorsProp }) { const { emitters = [], listeners = [], emitBacks = [] } = namespaceCfg const sets = { emitters, listeners, emitBacks } Object.entries(sets).forEach(([setName, entries]) => { if (Array.isArray(entries)) { register[setName]({ ctx, socket, entries, emitTimeout, emitErrorsProp }) } else { warn( `[nuxt-socket-io]: ${setName} needs to be an array in namespace config` ) } }) }, iox ({ stateOpts, socket, useSocket }) { debug('register.iox', stateOpts) const iox = ioState().value const entryRegex = /\s*<*-->*\s*/ const toRegex = /\s*[^<]-->\s*/ const fromRegex = /\s*<--[^>]\s*/ stateOpts.forEach((entry) => { let [evt, dest] = entry.split(entryRegex) let nsp if (dest === undefined) { dest = evt } const destParts = dest.split('/') if (destParts.length > 1) { nsp = destParts[0] if (iox[nsp] === undefined) { iox[nsp] = {} } dest = destParts[1] } function receiveEvt () { socket.on(evt, (msg) => { debug('iox evt received', evt, msg) if (nsp) { iox[nsp][dest] = msg debug('iox evt saved', evt, `${nsp}/${dest}`) } else { iox[dest] = msg debug('iox evt saved', evt, dest) } }) } function emitBack () { const watchPath = nsp ? `${nsp}/${dest}` : dest if (useSocket.registeredWatchers.includes(watchPath)) { return } watch(() => nsp ? iox[nsp][dest] : iox[dest], (n, o) => { socket.emit(evt, n) }) useSocket.registeredWatchers.push(watchPath) } if (toRegex.test(entry)) { receiveEvt() } else if (fromRegex.test(entry)) { emitBack() } else { receiveEvt() emitBack() } }) }, /** * @param {import('@nuxt/types').Context } ctx * @param {import('socket.io-client').Socket} socket * @param {string} connectUrl * @param {string} statusProp */ socketStatus (ctx, socket, connectUrl, statusProp) { const socketStatus = { connectUrl } const clientEvts = [ 'connect_error', 'connect_timeout', 'reconnect', 'reconnect_attempt', 'reconnect_error', 'reconnect_failed', 'ping', 'pong' ] clientEvts.forEach((evt) => { const prop = camelCase(evt) socketStatus[prop] = '' // @ts-ignore socket.io.on(evt, /** @param {*} resp */ (resp) => { Object.assign(ctx[statusProp], { [prop]: resp }) }) }) Object.assign(ctx, { [statusProp]: socketStatus }) }, teardown ({ ctx, socket, useSocket }) { // Setup listener for "closeSockets" in case // multiple instances of nuxtSocket exist in the same // component (only one destroy/unmount event takes place). // When we teardown, we want to remove the listeners of all // the socket.io-client instances ctx.$once('closeSockets', function () { debug('closing socket id=' + socket.id) socket.removeAllListeners() socket.close() }) if (!ctx.registeredTeardown) { // ctx.$destroy is defined in vue2 // but will go away in vue3 (in favor of onUnmounted) // save user's destroy method and honor it after // we run nuxt-socket-io's teardown ctx.onComponentDestroy = ctx.$destroy || ctx.onUnmounted debug('teardown enabled for socket', { name: useSocket.name }) // Our $destroy method // Gets called automatically on the destroy lifecycle // in v2. In v3, we have call it with the // onUnmounted hook ctx.$destroy = function () { debug('component destroyed, closing socket(s)', { name: useSocket.name, url: useSocket.url }) useSocket.registeredVuexListeners = [] ctx.$emit('closeSockets') // Only run the user's destroy method // if it exists if (ctx.onComponentDestroy) { ctx.onComponentDestroy() } } // onUnmounted will only exist in v3 if (ctx.onUnmounted) { ctx.onUnmounted = ctx.$destroy } ctx.registeredTeardown = true } socket.on('disconnect', () => { debug('server disconnected', { name: useSocket.name, url: useSocket.url }) socket.close() }) }, /** * @param {import('vue/types/vue').Vue } ctx */ stubs (ctx) { // Use a tiny event bus now. Can probably // be replaced by watch eventually. For now this works. if (!('$on' in ctx)) { // || !ctx.$emit || !ctx.$once) { ctx.$once = (...args) => emitter.once(...args) ctx.$on = (...args) => emitter.on(...args) ctx.$off = (...args) => emitter.off(...args) ctx.$$emit = (...args) => emitter.emit(...args) // TBD: replace ctx.$emit with ctx.$$emit calls (Vue already defines ctx.$emit). ctx.$$emit for internal use } if (!('$set' in ctx)) { ctx.$set = (obj, key, val) => { if (isRefImpl(obj[key])) { obj[key].value = val } else { obj[key] = val } } } if (!('$watch' in ctx)) { // TBD: seems to be defined in Nuxt3 ? ctx.$watch = (label, cb) => { // will enable when '@nuxtjs/composition-api' reaches stable version: // vueWatch(ctx.$data[label], cb) } } }, /** * Promisified emit */ emitP (socket) { socket.emitP = (evt, msg) => new Promise(resolve => socket.emit(evt, msg, resolve) ) }, /** * Promisified once. * Where's the promisified on? Answer: doesn't * really fit in to the websocket design. */ onceP (socket) { socket.onceP = evt => new Promise(resolve => socket.once(evt, resolve) ) } } /** * @param {import('./types.d').NuxtSocketOpts} ioOpts */ function nuxtSocket (ioOpts) { const { name, channel = '', statusProp = 'socketStatus', persist, teardown = !persist, emitTimeout, emitErrorsProp = 'emitErrors', ioApiProp = 'ioApi', ioDataProp = 'ioData', apiIgnoreEvts = [], serverAPI, clientAPI, vuex, namespaceCfg, ...connectOpts } = ioOpts const { $config } = this const { nuxtSocketIO: pluginOptions } = $config.public const state = useNuxtSocket().value const runtimeOptions = { ...pluginOptions } // If runtime config is also defined, // gracefully merge those options in here. // If naming conflicts extist between sockets, give // module options the priority if ($config.io) { Object.assign(runtimeOptions, $config.io) runtimeOptions.sockets = validateSockets(pluginOptions.sockets) ? pluginOptions.sockets : [] if (validateSockets($config.io.sockets)) { $config.io.sockets.forEach((socket) => { const fnd = runtimeOptions.sockets.find(({ name }) => name === socket.name) if (fnd === undefined) { runtimeOptions.sockets.push(socket) } }) } } const mergedOpts = { ...runtimeOptions, ...ioOpts } const { sockets, warnings = true, info = true } = mergedOpts warn = warnings && process.env.NODE_ENV !== 'production' ? console.warn : () => {} infoMsgs = info && process.env.NODE_ENV !== 'production' ? console.info : () => {} if (!validateSockets(sockets)) { throw new Error( "Please configure sockets if planning to use nuxt-socket-io: \r\n [{name: '', url: ''}]" ) } register.stubs(this) let useSocket = null if (!name) { useSocket = sockets.find(s => s.default === true) } else { useSocket = sockets.find(s => s.name === name) } if (!useSocket) { useSocket = sockets[0] } if (!useSocket.name) { useSocket.name = 'dflt' } if (!useSocket.url) { warn( `URL not defined for socket "${useSocket.name}". Defaulting to "window.location"` ) } if (!useSocket.registeredWatchers) { useSocket.registeredWatchers = [] } if (!useSocket.registeredVuexListeners) { useSocket.registeredVuexListeners = [] } let { url: connectUrl } = useSocket if (connectUrl) { connectUrl += channel } const { namespaces = {} } = useSocket let socket const label = persist && typeof persist === 'string' ? persist : `${useSocket.name}${channel}` function connectSocket () { if (connectUrl) { socket = io(connectUrl, connectOpts) infoMsgs('[nuxt-socket-io]: connect', useSocket.name, connectUrl, connectOpts) } else { socket = io(channel, connectOpts) infoMsgs( '[nuxt-socket-io]: connect', useSocket.name, window.location, channel, connectOpts ) } } if (persist) { if (_sockets[label]) { debug(`resuing persisted socket ${label}`) socket = _sockets[label] if (socket.disconnected) { debug('persisted socket disconnected, reconnecting...') connectSocket() } } else { debug(`socket ${label} does not exist, creating and connecting to it..`) connectSocket() _sockets[label] = socket } } else { connectSocket() } register.emitP(socket) register.onceP(socket) if (emitTimeout) { mutations.SET_EMIT_TIMEOUT(state, { label, emitTimeout }) } const mergedNspCfg = Object.assign({ ...namespaces[channel] }, namespaceCfg) if (mergedNspCfg.emitters || mergedNspCfg.listeners || mergedNspCfg.emitBacks) { register.namespace({ ctx: this, namespaceCfg: mergedNspCfg, socket, emitTimeout, emitErrorsProp }) debug('namespaces configured for socket', { name: useSocket.name, channel, namespaceCfg }) } if (serverAPI) { register.serverAPI({ label, apiIgnoreEvts, ioApiProp, ioDataProp, ctx: this, socket, serverAPI, clientAPI }) } if (clientAPI) { register.clientAPI({ ctx: this, socket, clientAPI }) } const stateOpts = [...(useSocket.iox || []), ...(ioOpts.iox || [])] if (stateOpts) { register.iox({ stateOpts, socket, useSocket }) } if ('socketStatus' in this && typeof this.socketStatus === 'object' ) { register.socketStatus(this, socket, connectUrl || window.location.origin, statusProp) debug('socketStatus registered for socket', { name: useSocket.name, url: connectUrl }) } if (teardown) { register.teardown({ ctx: this, socket, useSocket }) } return socket } export default defineNuxtPlugin((nuxtApp) => { nuxtApp.provide('nuxtSocket', nuxtSocket) nuxtApp.provide('ioState', ioState) })