From 446c5fa29a163eb0fa4e4d7c32c0b5d57ab760a7 Mon Sep 17 00:00:00 2001 From: xingsy97 <87063252+xingsy97@users.noreply.github.com> Date: Tue, 10 Feb 2026 17:02:22 +0800 Subject: [PATCH] unify commits --- sdk/webpubsub-chat-client/.gitignore | 7 + sdk/webpubsub-chat-client/.yarnrc.yml | 7 + sdk/webpubsub-chat-client/README.md | 121 ++ .../examples/quickstart/.yarnrc.yml | 5 + .../examples/quickstart/README.md | 44 + .../examples/quickstart/client.js | 100 ++ .../examples/quickstart/package.json | 17 + .../examples/quickstart/server.js | 30 + sdk/webpubsub-chat-client/package.json | 69 + .../scripts/esbuild.config.mjs | 43 + .../scripts/pack-for-publish.mjs | 43 + sdk/webpubsub-chat-client/src/chatClient.ts | 307 ++++ sdk/webpubsub-chat-client/src/constant.ts | 19 + .../src/generatedTypes.ts | 408 +++++ sdk/webpubsub-chat-client/src/index.ts | 15 + sdk/webpubsub-chat-client/src/logger.ts | 6 + sdk/webpubsub-chat-client/src/utils.ts | 31 + .../swagger/openapi.yaml | 732 ++++++++ .../tests/integration.test.ts | 189 ++ sdk/webpubsub-chat-client/tests/testUtils.ts | 56 + sdk/webpubsub-chat-client/tsconfig.json | 18 + sdk/webpubsub-chat-client/yarn.lock | 1581 +++++++++++++++++ 22 files changed, 3848 insertions(+) create mode 100644 sdk/webpubsub-chat-client/.gitignore create mode 100644 sdk/webpubsub-chat-client/.yarnrc.yml create mode 100644 sdk/webpubsub-chat-client/README.md create mode 100644 sdk/webpubsub-chat-client/examples/quickstart/.yarnrc.yml create mode 100644 sdk/webpubsub-chat-client/examples/quickstart/README.md create mode 100644 sdk/webpubsub-chat-client/examples/quickstart/client.js create mode 100644 sdk/webpubsub-chat-client/examples/quickstart/package.json create mode 100644 sdk/webpubsub-chat-client/examples/quickstart/server.js create mode 100644 sdk/webpubsub-chat-client/package.json create mode 100644 sdk/webpubsub-chat-client/scripts/esbuild.config.mjs create mode 100644 sdk/webpubsub-chat-client/scripts/pack-for-publish.mjs create mode 100644 sdk/webpubsub-chat-client/src/chatClient.ts create mode 100644 sdk/webpubsub-chat-client/src/constant.ts create mode 100644 sdk/webpubsub-chat-client/src/generatedTypes.ts create mode 100644 sdk/webpubsub-chat-client/src/index.ts create mode 100644 sdk/webpubsub-chat-client/src/logger.ts create mode 100644 sdk/webpubsub-chat-client/src/utils.ts create mode 100644 sdk/webpubsub-chat-client/swagger/openapi.yaml create mode 100644 sdk/webpubsub-chat-client/tests/integration.test.ts create mode 100644 sdk/webpubsub-chat-client/tests/testUtils.ts create mode 100644 sdk/webpubsub-chat-client/tsconfig.json create mode 100644 sdk/webpubsub-chat-client/yarn.lock diff --git a/sdk/webpubsub-chat-client/.gitignore b/sdk/webpubsub-chat-client/.gitignore new file mode 100644 index 000000000..89827b2e1 --- /dev/null +++ b/sdk/webpubsub-chat-client/.gitignore @@ -0,0 +1,7 @@ +node_modules +dist +types +tsconfig.tsbuildinfo +.env +.yarn +*.tgz diff --git a/sdk/webpubsub-chat-client/.yarnrc.yml b/sdk/webpubsub-chat-client/.yarnrc.yml new file mode 100644 index 000000000..58ee78802 --- /dev/null +++ b/sdk/webpubsub-chat-client/.yarnrc.yml @@ -0,0 +1,7 @@ +nodeLinker: node-modules + +# Temporary: Use MyGet feed for @azure scoped packages during preview. +# This will be removed once the package is published to npm. +npmScopes: + azure: + npmRegistryServer: "https://www.myget.org/F/azure-signalr-dev/npm/" diff --git a/sdk/webpubsub-chat-client/README.md b/sdk/webpubsub-chat-client/README.md new file mode 100644 index 000000000..b566f08cd --- /dev/null +++ b/sdk/webpubsub-chat-client/README.md @@ -0,0 +1,121 @@ +# Azure Web PubSub Chat Client SDK + +A client SDK for building chat applications with Azure Web PubSub. + +> ⚠️ **Internal Preview**: This package is currently for internal use only and is not ready for production. + +## Installation + +```bash +npm install @azure/web-pubsub-chat-client +``` + +## Quick Start + +For a complete example, see [examples/quickstart](./examples/quickstart). + +```javascript +import { ChatClient } from '@azure/web-pubsub-chat-client'; + +// Get client access URL from your server +const url = await fetch('/negotiate?userId=alice').then(r => r.json()).then(d => d.url); + +// Option 1: Login with an existing WebPubSubClient +const wpsClient = new WebPubSubClient(url); +const client = await ChatClient.login(wpsClient); + +// Option 2: Login directly with URL +// const client = await new ChatClient(url).login(); + +console.log(`Logged in as: ${client.userId}`); + +// Listen for events +client.addListenerForNewMessage((notification) => { + const msg = notification.message; + console.log(`${msg.createdBy}: ${msg.content.text}`); +}); + +client.addListenerForNewRoom((room) => { + console.log(`Joined room: ${room.title}`); +}); + +// Create a room and send messages +const room = await client.createRoom('My Room', ['bob']); +await client.sendToRoom(room.roomId, 'Hello!'); + +// Get message history +const history = await client.listRoomMessage(room.roomId, null, null); + +// Manage room members +await client.addUserToRoom(room.roomId, 'charlie'); +await client.removeUserFromRoom(room.roomId, 'charlie'); + +// Cleanup +client.stop(); +``` + +## API + +### ChatClient + +#### Constructor + +```typescript +// With existing WebPubSubClient +new ChatClient(wpsClient: WebPubSubClient) + +// With client access URL +new ChatClient(clientAccessUrl: string, options?: WebPubSubClientOptions) + +// With credential +new ChatClient(credential: WebPubSubClientCredential, options?: WebPubSubClientOptions) +``` + +#### Static Methods + +| Method | Description | +|--------|-------------| +| `ChatClient.login(wpsClient)` | Create and login using an existing WebPubSubClient | + +#### Properties + +| Property | Type | Description | +|----------|------|-------------| +| `userId` | `string` | Current user's ID (throws if not logged in) | +| `rooms` | `RoomInfo[]` | List of joined rooms | +| `connection` | `WebPubSubClient` | Underlying WebPubSub connection | + +#### Methods + +| Method | Description | +|--------|-------------| +| `login()` | Connect and authenticate, returns `ChatClient` | +| `stop()` | Disconnect | +| `createRoom(title, members, roomId?)` | Create a new room with initial members | +| `getRoom(roomId, withMembers)` | Get room info | +| `addUserToRoom(roomId, userId)` | Add user to room (admin operation) | +| `removeUserFromRoom(roomId, userId)` | Remove user from room (admin operation) | +| `sendToRoom(roomId, message)` | Send text message to room, returns message ID | +| `listRoomMessage(roomId, startId, endId, maxCount?)` | Get room message history | +| `getUserInfo(userId)` | Get user profile | + +#### Event Listeners + +| Method | Callback Parameter | Description | +|--------|-------------------|-------------| +| `addListenerForNewMessage(callback)` | `NewMessageNotificationBody` | New message received | +| `addListenerForNewRoom(callback)` | `RoomInfo` | Joined a new room | +| `addListenerForMemberJoined(callback)` | `MemberJoinedNotificationBody` | Member joined a room | +| `addListenerForMemberLeft(callback)` | `MemberLeftNotificationBody` | Member left a room | +| `addListenerForRoomLeft(callback)` | `RoomLeftNotificationBody` | Self left a room | +| `onConnected(callback)` | `OnConnectedArgs` | Connection established | +| `onDisconnected(callback)` | `OnDisconnectedArgs` | Connection lost | +| `onStopped(callback)` | `OnStoppedArgs` | Connection stopped | + +## Examples + +See the [examples](./examples) directory for complete working examples. + +## License + +MIT diff --git a/sdk/webpubsub-chat-client/examples/quickstart/.yarnrc.yml b/sdk/webpubsub-chat-client/examples/quickstart/.yarnrc.yml new file mode 100644 index 000000000..3b452cd5a --- /dev/null +++ b/sdk/webpubsub-chat-client/examples/quickstart/.yarnrc.yml @@ -0,0 +1,5 @@ +nodeLinker: node-modules + +npmScopes: + azure: + npmRegistryServer: "https://www.myget.org/F/azure-signalr-dev/npm/" diff --git a/sdk/webpubsub-chat-client/examples/quickstart/README.md b/sdk/webpubsub-chat-client/examples/quickstart/README.md new file mode 100644 index 000000000..ca8064c62 --- /dev/null +++ b/sdk/webpubsub-chat-client/examples/quickstart/README.md @@ -0,0 +1,44 @@ +# Minimal Example + +A minimal example demonstrating the basic usage of Web PubSub Chat SDK. + +## Prerequisites + +1. An Azure Web PubSub resource with: + - A Persistent Storage configured (Storage Account with Table enabled) + - A Chat hub created (with Chat feature enabled, using the Persistent Storage above) + +## Quick Start + +```bash +yarn install +``` + +### 1. Start the server + +```bash +yarn server -- "" +``` + +Or set the environment variable: + +```bash +export WebPubSubConnectionString="" +yarn server +``` + +### 2. Run the client + +In a new terminal: + +```bash +yarn client +``` + +## What this example does + +1. Creates two chat clients (Alice and Bob) +2. Alice creates a room and invites Bob +3. Alice sends messages to the room +4. Bob receives notifications for new room and messages +5. Lists message history from the room diff --git a/sdk/webpubsub-chat-client/examples/quickstart/client.js b/sdk/webpubsub-chat-client/examples/quickstart/client.js new file mode 100644 index 000000000..24c7aa827 --- /dev/null +++ b/sdk/webpubsub-chat-client/examples/quickstart/client.js @@ -0,0 +1,100 @@ +import { ChatClient } from '@azure/web-pubsub-chat-client'; +import { WebPubSubClient } from '@azure/web-pubsub-client'; + +const SERVER_URL = process.env.SERVER_URL || 'http://localhost:3000'; + +const getClientAccessUrl = (userId) => + fetch(`${SERVER_URL}/negotiate?userId=${userId}`).then(r => r.json()).then(d => d.url); + +function setupListeners(client) { + // chat event listeners + client.addListenerForNewRoom((room) => { + console.log(`[${client.userId}] joined room "${room.title}" (${room.roomId})`); + }); + client.addListenerForNewMessage((notification) => { + const msg = notification.message; + console.log(`[${client.userId}] received message from ${msg.createdBy}: ${msg.content.text}`); + }); + client.addListenerForMemberJoined((info) => { + console.log(`[${client.userId}] saw ${info.userId} joined room ${info.roomId}`); + }); + client.addListenerForMemberLeft((info) => { + console.log(`[${client.userId}] saw ${info.userId} left room ${info.roomId}`); + }); + client.addListenerForRoomLeft((info) => { + console.log(`[${client.userId}] left room ${info.roomId}`); + }); + // chat connection listeners + client.onStopped((e) => { + console.log(`connection used by ${client.userId} stopped`); + }); + client.onDisconnected((e) => { + console.log(`connection used by ${client.userId} disconnected`); + }); +} + +async function main() { + // Create chat clients for Alice, Bob, and Mike + + // Option 1: create a chat client with a existing WebPubSubClient + const url1 = await getClientAccessUrl('alice'); + const webPubSubClient = new WebPubSubClient(url1); + const alice = await ChatClient.login(webPubSubClient); + console.log(`Alice logged in as: ${alice.userId}`); + + // Option 2: create a chat client directly with client access URL + const url2 = await getClientAccessUrl('bob'), url3 = await getClientAccessUrl('mike'); + const bob = await new ChatClient(url2).login(); + const mike = await new ChatClient(url3).login(); + + console.log(`Bob logged in as: ${bob.userId}`); + console.log(`Mike logged in as: ${mike.userId}`); + + // Setup event listeners + + setupListeners(alice); + setupListeners(bob); + setupListeners(mike); + + // Alice creates a room and invites Bob + console.log('\n--- Alice creates a room ---'); + const room = await alice.createRoom('Hello World Room', [bob.userId]); + + // Alice sends messages to the room + console.log('\n--- Alice sends messages ---'); + for (let i = 1; i <= 3; i++) { + console.log(`[Alice] will send message #${i}`); + const msgId = await alice.sendToRoom(room.roomId, `Hello from Alice #${i}`); + } + + // Bob replies to the room + console.log('\n--- Bob replies ---'); + for (let i = 1; i <= 2; i++) { + console.log(`[Bob] will send message #${i}`); + const msgId = await bob.sendToRoom(room.roomId, `Hi Alice, this is Bob #${i}`); + } + + // List message history + console.log('\n--- Message History ---'); + const history = await alice.listRoomMessage(room.roomId, null, null); + for (const msg of history.messages) { + console.log(` [${msg.createdBy}] [${msg.createdAt}] ${msg.content.text}`); + } + + // Alice manages room members + console.log('\n--- Alice manages room members ---'); + + + // Alice adds mike to the room + await alice.addUserToRoom(room.roomId, mike.userId); + + // Alice removes bob and mike from the room + await alice.removeUserFromRoom(room.roomId, bob.userId); + await alice.removeUserFromRoom(room.roomId, mike.userId); + + // Cleanup + console.log('\n--- Cleanup ---'); + [alice, bob, mike].forEach(client => client.stop()); +} + +main().catch(console.error); diff --git a/sdk/webpubsub-chat-client/examples/quickstart/package.json b/sdk/webpubsub-chat-client/examples/quickstart/package.json new file mode 100644 index 000000000..1e534bacd --- /dev/null +++ b/sdk/webpubsub-chat-client/examples/quickstart/package.json @@ -0,0 +1,17 @@ +{ + "name": "quickstart-example", + "version": "1.0.0", + "description": "Quickstart example for Web PubSub Chat SDK", + "type": "module", + "scripts": { + "server": "node server.js", + "client": "node client.js" + }, + "author": "Microsoft", + "license": "MIT", + "dependencies": { + "@azure/web-pubsub": "^1.2.0", + "@azure/web-pubsub-chat-client": "1.0.0-beta.1", + "express": "^5.2.1" + } +} diff --git a/sdk/webpubsub-chat-client/examples/quickstart/server.js b/sdk/webpubsub-chat-client/examples/quickstart/server.js new file mode 100644 index 000000000..899ec0a49 --- /dev/null +++ b/sdk/webpubsub-chat-client/examples/quickstart/server.js @@ -0,0 +1,30 @@ +import express from 'express'; +import { WebPubSubServiceClient } from '@azure/web-pubsub'; + +const hubName = 'chat'; +const port = 3000 || process.env.PORT + +// Get connection string from environment variable or command line argument +const connectionString = process.env.WebPubSubConnectionString || process.argv[2]; +if (!connectionString) { + console.error('Please provide WebPubSubConnectionString via environment variable or command line argument'); + process.exit(1); +} + +const app = express(); +const serviceClient = new WebPubSubServiceClient(connectionString, hubName, { allowInsecureConnection: true }); + +// Negotiate endpoint for client to get access token +app.get('/negotiate', async (req, res) => { + console.log(`received negotiate request: ${JSON.stringify(req.query)}`); + const userId = req.query.userId; + if (!userId) { + return res.status(500).json({ error: 'userId is required' }); + } + const token = await serviceClient.getClientAccessToken({ userId }); + res.json({ url: token.url }); +}); + +app.listen(port, () => { + console.log(`Server listening at http://localhost:${port}`); +}); diff --git a/sdk/webpubsub-chat-client/package.json b/sdk/webpubsub-chat-client/package.json new file mode 100644 index 000000000..a31a56a97 --- /dev/null +++ b/sdk/webpubsub-chat-client/package.json @@ -0,0 +1,69 @@ +{ + "name": "@azure/web-pubsub-chat-client", + "version": "1.0.0-beta.1", + "description": "Client SDK for building chat applications with Azure Web PubSub", + "author": "Microsoft", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/Azure/azure-webpubsub/" + }, + "homepage": "https://github.com/Azure/azure-webpubsub/", + "bugs": { + "url": "https://github.com/Azure/azure-webpubsub/issues" + }, + "keywords": [ + "azure", + "webpubsub", + "chat", + "realtime", + "messaging" + ], + "type": "module", + "main": "dist/index.js", + "browser": "dist/browser/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "browser": { + "types": "./dist/index.d.ts", + "default": "./dist/browser/index.js" + }, + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "./package.json": "./package.json" + }, + "files": [ + "dist", + "README.md" + ], + "scripts": { + "build": "tsc -p tsconfig.json", + "build:bundle": "node scripts/esbuild.config.mjs && tsc -p tsconfig.json --emitDeclarationOnly", + "build:watch": "tsc -p tsconfig.json --watch", + "pack:publish": "node scripts/pack-for-publish.mjs", + "generate:types": "openapi-typescript swagger/openapi.yaml -o src/generatedTypes.ts && node -e \"const fs=require('fs');const c=fs.readFileSync('src/generatedTypes.ts','utf8');const schemas=[...c.matchAll(/^\\s{8}(\\w+):/gm)].map(m=>m[1]);const exports='\\n// Flattened schema exports\\nexport type Schemas = components[\\\"schemas\\\"];\\n'+schemas.map(s=>'export type '+s+' = Schemas[\\\"'+s+'\\\"];').join('\\n')+'\\n';fs.appendFileSync('src/generatedTypes.ts',exports)\"", + "test:start-server": "node .\\examples\\quickstart\\server.js", + "test": "npx tsx --test tests/**/*.ts", + "test:integration": "tsx --test tests/integration.test.ts", + "test:one": "tsx --test --test-name-pattern" + }, + "dependencies": { + "@azure/logger": "^1.3.0", + "@azure/web-pubsub-client": "1.0.5-beta.1", + "events": "^3.3.0", + "ws": "^8.0.0" + }, + "devDependencies": { + "@types/events": "^3.0.3", + "@types/node": "^25.0.3", + "esbuild": "^0.27.3", + "esbuild-plugin-polyfill-node": "^0.3.0", + "openapi-typescript": "^7.10.1", + "tsx": "^4.21.0", + "typescript": "^5.9.3" + } +} diff --git a/sdk/webpubsub-chat-client/scripts/esbuild.config.mjs b/sdk/webpubsub-chat-client/scripts/esbuild.config.mjs new file mode 100644 index 000000000..b9af61099 --- /dev/null +++ b/sdk/webpubsub-chat-client/scripts/esbuild.config.mjs @@ -0,0 +1,43 @@ +import * as esbuild from 'esbuild'; +import { polyfillNode } from 'esbuild-plugin-polyfill-node'; + +// Node.js build +await esbuild.build({ + entryPoints: ['src/index.ts'], + bundle: true, + outfile: 'dist/index.js', + format: 'esm', + platform: 'node', + target: 'es2020', + sourcemap: true, + external: ['ws'], +}); + +// Browser build — Node.js built-ins are automatically polyfilled +await esbuild.build({ + entryPoints: ['src/index.ts'], + bundle: true, + outfile: 'dist/browser/index.js', + format: 'esm', + platform: 'browser', + target: 'es2020', + sourcemap: true, + plugins: [ + polyfillNode({ + globals: { process: true, Buffer: true, global: true }, + }), + // 'ws' is not needed in browser — stub it to use native WebSocket + { + name: 'browser-ws-stub', + setup(build) { + build.onResolve({ filter: /^ws$/ }, () => ({ path: 'ws', namespace: 'browser-ws' })); + build.onLoad({ filter: /.*/, namespace: 'browser-ws' }, () => ({ + contents: 'export default globalThis.WebSocket;', + })); + }, + }, + ], + alias: undefined, +}); + +console.log('Build complete: dist/index.js (node) + dist/browser/index.js (browser)'); diff --git a/sdk/webpubsub-chat-client/scripts/pack-for-publish.mjs b/sdk/webpubsub-chat-client/scripts/pack-for-publish.mjs new file mode 100644 index 000000000..d097b7419 --- /dev/null +++ b/sdk/webpubsub-chat-client/scripts/pack-for-publish.mjs @@ -0,0 +1,43 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { execSync } from 'child_process'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const rootDir = path.resolve(__dirname, '..'); +const packageJsonPath = path.join(rootDir, 'package.json'); + +// 1. Build bundle +console.log('📦 Building bundle with esbuild...'); +execSync('node scripts/esbuild.config.mjs', { cwd: rootDir, stdio: 'inherit' }); + +// 2. Generate type declarations +console.log('📝 Generating type declarations...'); +execSync('yarn tsc -p tsconfig.json --emitDeclarationOnly', { cwd: rootDir, stdio: 'inherit' }); + +// 3. Backup and modify package.json +console.log('🔧 Preparing package.json for publish...'); +const originalPackageJson = fs.readFileSync(packageJsonPath, 'utf8'); +const pkg = JSON.parse(originalPackageJson); + +// Remove local file dependencies and dev-only dependencies +delete pkg.dependencies['@azure/web-pubsub-client']; +delete pkg.dependencies['@azure/logger']; +delete pkg.dependencies['events']; + +// Keep ws as it's external in esbuild config +// pkg.dependencies should now only have: { "ws": "^8.0.0" } + +fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2) + '\n'); + +try { + // 4. Pack + console.log('📦 Creating package tarball...'); + execSync('yarn pack -o azure-web-pubsub-chat-client-%v.tgz', { cwd: rootDir, stdio: 'inherit' }); + + console.log('✅ Done! Package ready for upload to MyGet.'); +} finally { + // 5. Restore original package.json + console.log('🔄 Restoring package.json...'); + fs.writeFileSync(packageJsonPath, originalPackageJson); +} diff --git a/sdk/webpubsub-chat-client/src/chatClient.ts b/sdk/webpubsub-chat-client/src/chatClient.ts new file mode 100644 index 000000000..344b48627 --- /dev/null +++ b/sdk/webpubsub-chat-client/src/chatClient.ts @@ -0,0 +1,307 @@ +import { OnConnectedArgs, OnDisconnectedArgs, OnStoppedArgs, WebPubSubClient, WebPubSubClientCredential, WebPubSubClientOptions, WebPubSubDataType } from "@azure/web-pubsub-client"; +import { EventEmitter } from "events"; +import { + MessageInfo, + MessageRangeQuery, + RoomInfo, + UserProfile, + RoomInfoWithMembers, + Notification, + NewMessageNotificationBody, + NewRoomNotificationBody, + SendMessageResponse, + ManageRoomMemberRequest, + MemberJoinedNotificationBody, + NotificationType, + MemberLeftNotificationBody, + RoomLeftNotification, + RoomLeftNotificationBody, +} from "./generatedTypes.js"; +import { ERRORS, INVOCATION_NAME } from "./constant.js"; +import { logger } from "./logger.js"; +import { isWebPubSubClient } from "./utils.js"; + +class ChatClient { + public readonly connection: WebPubSubClient; + + private readonly _emitter = new EventEmitter(); + private readonly _rooms = new Map(); + protected _conversationIds = new Set(); + private _userId: string | undefined; + private _isLoggedIn = false; + + constructor(clientAccessUrl: string, options?: WebPubSubClientOptions); + constructor(credential: WebPubSubClientCredential, options?: WebPubSubClientOptions); + constructor(credential: string | WebPubSubClientCredential, options?: WebPubSubClientOptions); + constructor(wpsClient: WebPubSubClient); + + constructor(arg1: string | WebPubSubClientCredential | WebPubSubClient, options?: WebPubSubClientOptions) { + if (isWebPubSubClient(arg1)) { + this.connection = arg1; + } else { + this.connection = new WebPubSubClient(arg1 as any, options); + } + this.connection.on("group-message", (e) => { + this._handleNotification(e.message.data as Notification); + }); + this.connection.on("server-message", (e) => { + this._handleNotification(e.message.data as Notification); + }); + } + + private async _handleNotification(data: Notification): Promise { + logger.info("Received notification:", data); + try { + const type = data.notificationType; + switch (type) { + case "MessageCreated": + const notificationBody = data.body as NewMessageNotificationBody; + this._emitter.emit(type, notificationBody); + break; + case "RoomJoined": + const roomInfo = data.body as NewRoomNotificationBody as RoomInfo; + this._rooms.set(roomInfo.roomId, roomInfo); // Add to _rooms first so listeners can use listRoomMessage + this._emitter.emit(type, roomInfo); + break; + case "RoomMemberJoined": + const memberJoinedInfo = data.body as MemberJoinedNotificationBody; + this._emitter.emit(type, memberJoinedInfo); + break; + // someone (not self) left a specific room + case "RoomMemberLeft": + const memberLeftInfo = data.body as MemberLeftNotificationBody; + this._emitter.emit(type, memberLeftInfo); + break; + // self left a specific room + case "RoomLeft": + const roomLeftInfo = data.body as RoomLeftNotificationBody; + this._emitter.emit(type, roomLeftInfo); + this._rooms.delete(roomLeftInfo.roomId); + break; + case "MessageUpdated": + case "MessageDeleted": + case "RoomClosed": + case "AddContact": + logger.warning(`Known notification type ${type} received but not implemented yet.`); + break; + default: + logger.warning(`Unknown notification type received: ${type}`); + } + } + catch (err) { + logger.error(`Error processing notification, error = ${err}, data: `, data); + } + } + + /** Invoke server event and return typed data */ + private async invokeWithReturnType(eventName: string, payload: any, dataType: WebPubSubDataType): Promise { + logger.verbose(`invoke event: '${eventName}', dataType: ${dataType}, payload:`, payload); + + const rawResponse = await this.connection.invokeEvent(eventName, payload, dataType); + + logger.verbose(`invoke response for '${eventName}':`, rawResponse); + + const dataString = JSON.stringify(rawResponse); + if (dataString?.indexOf("InvalidRequest") !== -1) { + throw new Error(`Invocation of event "${eventName}" failed: ${dataString || "Unknown error"}`); + } + // todo: handle rawResponse.success + return rawResponse.data as T; + } + + /** create a chat client based on an existing WebPubSubClient. */ + public static async login(wpsClient: WebPubSubClient): Promise { + const chatClient = new ChatClient(wpsClient); + return await chatClient.login(); + } + + /** create a chat client based on an existing WebPubSubClient. */ + public async login(): Promise { + await this.connection.start(); + const loginResponse = await this.invokeWithReturnType(INVOCATION_NAME.LOGIN, "", "text"); + logger.info("loginResponse", loginResponse); + this._userId = loginResponse.userId; + this._isLoggedIn = true; + this._conversationIds = new Set(loginResponse.conversationIds || []); + // Use Promise.all to wait for all room info to be fetched + const roomInfos = await Promise.all( + (loginResponse.roomIds || []).map(async (roomId) => { + const roomInfo = await this.getRoom(roomId, false); + return { roomId, roomInfo }; + }) + ); + roomInfos.forEach(({ roomId, roomInfo }) => { + this._rooms.set(roomId, roomInfo); + }); + return this; + } + + private ensureLoggedIn(): void { + if (!this._isLoggedIn) { + throw new Error("Not logged in. Please call login() first."); + } + } + + public async getUserInfo(userId: string): Promise { + this.ensureLoggedIn(); + return this.invokeWithReturnType(INVOCATION_NAME.GET_USER_PROPERTIES, { userId: userId }, "json"); + } + + public async sendToConversation(conversationId: string, message: string): Promise { + this.ensureLoggedIn(); + const payload = { + conversation: { conversationId: conversationId }, + content: message, + }; + const resp = await this.invokeWithReturnType(INVOCATION_NAME.SEND_TEXT_MESSAGE, payload, "json"); + if (!resp || !resp.id) { + throw new Error(`Failed to send message to conversation ${conversationId}, got invalid invoke response: ${JSON.stringify(resp)}`); + } + const msgId = resp.id; + // sender won't receive conversation message via notification mechanism, so emit event here + const roomId = Array.from(this._rooms.values()).find((r) => r.defaultConversationId === conversationId)?.roomId; + if (!roomId) { + logger.warning(`Failed to find roomId for conversationId ${conversationId} when sending message.`); + } + this._emitter.emit("MessageCreated" as NotificationType, { + conversation: { conversationId: conversationId, roomId: roomId || "" }, + message: { + messageId: msgId, + createdBy: this.userId, + content: { + text: message, + binary: null, + }, + } as MessageInfo, + } as NewMessageNotificationBody); + return msgId; + } + + public async sendToRoom(roomId: string, message: string): Promise { + this.ensureLoggedIn(); + const conversationId = this._rooms.get(roomId)?.defaultConversationId; + if (!conversationId) { + throw Error(`Failed to sendToRoom, not found roomId ${roomId}`); + } + return await this.sendToConversation(conversationId, message); + } + + public async getRoom(roomId: string, withMembers: boolean): Promise { + this.ensureLoggedIn(); + return this.invokeWithReturnType(INVOCATION_NAME.GET_ROOM, { id: roomId, withMembers: withMembers }, "json"); + } + + /** Create a room and its initial members. If `roomId` is not set, the service will create a random one. */ + public async createRoom(title: string, members: string[], roomId?: string): Promise { + this.ensureLoggedIn(); + let roomDetails = { + title: title, + members: [...new Set([...members, this.userId])], // deduplicate and add self + } as any; + if (roomId) { + roomDetails = { ...roomDetails, roomId: roomId }; + } + const roomInfo = await this.invokeWithReturnType(INVOCATION_NAME.CREATE_ROOM, roomDetails, "json"); + if ((roomInfo as any).code === ERRORS.ROOM_ALREADY_EXISTS) { + throw new Error(ERRORS.ROOM_ALREADY_EXISTS); + } + this._rooms.set(roomInfo.roomId, roomInfo); + this._emitter.emit("RoomJoined" as NotificationType, roomInfo); + return roomInfo; + } + + private async manageRoomMember(request: ManageRoomMemberRequest): Promise { + const ret = await this.invokeWithReturnType(INVOCATION_NAME.MANAGE_ROOM_MEMBER, request, "json"); + if ((ret as any).code === ERRORS.NO_PERMISSION_IN_ROOM) { + throw new Error(ERRORS.NO_PERMISSION_IN_ROOM); + } + } + + /** Add a user to a room. This is an admin operation where one user adds another user to a room. */ + public async addUserToRoom(roomId: string, userId: string): Promise { + this.ensureLoggedIn(); + const payload: ManageRoomMemberRequest = { roomId: roomId, operation: "Add", userId: userId }; + await this.manageRoomMember(payload); + } + + /** Remove a user from a room. This is an admin operation where one user removes another user from a room. */ + public async removeUserFromRoom(roomId: string, userId: string): Promise { + this.ensureLoggedIn(); + const payload: ManageRoomMemberRequest = { roomId: roomId, operation: "Delete", userId: userId }; + await this.manageRoomMember(payload); + } + + /** List messages in a conversation. It returns messages and a query for the next query parameter. */ + public async listMessage(conversationId: string, startId: string | null, endId: string | null, maxCount: number = 100): Promise<{ messages: MessageInfo[]; nextQuery: MessageRangeQuery }> { + this.ensureLoggedIn(); + const query: MessageRangeQuery = { + conversation: { conversationId: conversationId }, + start: startId, + end: endId, + maxCount: maxCount, + }; + const result = await this.invokeWithReturnType<{ messages: MessageInfo[]; nextQuery: MessageRangeQuery }>(INVOCATION_NAME.LIST_MESSAGES, query, "json"); + return result; + } + + /** List messages in a room. It returns messages and a query for the next query parameter. */ + public async listRoomMessage(roomId: string, startId: string | null, endId: string | null, maxCount: number = 100): Promise<{ messages: MessageInfo[]; nextQuery: MessageRangeQuery }> { + this.ensureLoggedIn(); + const conversationId = this._rooms.get(roomId)?.defaultConversationId; + if (!conversationId) { + throw Error(`Failed to listRoomMessage, not found roomId ${roomId}`); + } + const query: MessageRangeQuery = { + conversation: { conversationId: conversationId }, + start: startId, + end: endId, + maxCount: maxCount, + }; + const result = await this.invokeWithReturnType<{ messages: MessageInfo[]; nextQuery: MessageRangeQuery }>(INVOCATION_NAME.LIST_MESSAGES, query, "json"); + return result; + } + + /** Cached rooms known to the client. */ + public get rooms(): RoomInfo[] { + return Array.from(this._rooms.values()); + } + + public get userId(): string { + if (!this._userId) { + throw new Error("User ID is not set. Please login first."); + } + return this._userId; + } + /** add callback for new message events. */ + public addListenerForNewMessage = (callback: (message: NewMessageNotificationBody) => void) => this._emitter.on("MessageCreated" as NotificationType, callback); + + /** add callback for new room events. */ + public addListenerForNewRoom = (callback: (room: RoomInfo) => void) => this._emitter.on("RoomJoined" as NotificationType, callback); + + /** add callback for new member joined room events */ + public addListenerForMemberJoined = (callback: (info: MemberJoinedNotificationBody) => void) => this._emitter.on("RoomMemberJoined" as NotificationType, callback); + + /** add callback for member left room events */ + public addListenerForMemberLeft = (callback: (info: MemberLeftNotificationBody) => void) => this._emitter.on("RoomMemberLeft" as NotificationType, callback); + + /** add callback for user self left room events */ + public addListenerForRoomLeft = (callback: (info: RoomLeftNotificationBody) => void) => this._emitter.on("RoomLeft" as NotificationType, callback); + + public stop = (): void => { + this.connection.stop(); + }; + + public onConnected = (callback: (e: OnConnectedArgs) => void): void => { + return this.connection.on("connected", callback); + }; + + public onDisconnected = (callback: (e: OnDisconnectedArgs) => void): void => { + return this.connection.on("disconnected", callback); + }; + + public onStopped = (callback: (e: OnStoppedArgs) => void): void => { + return this.connection.on("stopped", callback); + }; +} + +export { ChatClient }; diff --git a/sdk/webpubsub-chat-client/src/constant.ts b/sdk/webpubsub-chat-client/src/constant.ts new file mode 100644 index 000000000..ad8273db3 --- /dev/null +++ b/sdk/webpubsub-chat-client/src/constant.ts @@ -0,0 +1,19 @@ +const INVOCATION_NAME = { + LOGIN: "chat.login", + LIST_USER_CONVERSATION: "chat.listUserConversation", + GET_USER_PROPERTIES: "chat.getUserProperties", + GET_ROOM: "chat.getRoom", + LIST_MESSAGES: "chat.queryMessageHistory", + SEND_TEXT_MESSAGE: "chat.sendTextMessage", + CREATE_ROOM: "chat.createRoom", + JOIN_ROOM: "chat.joinRoom", + MANAGE_ROOM_MEMBER: "chat.manageRoomMember", +} as const; + +const ERRORS = { + ROOM_ALREADY_EXISTS: "RoomAlreadyExists", + USER_ALREADY_IN_ROOM: "UserAlreadyInRoom", + NO_PERMISSION_IN_ROOM: "NoPermissionInRoom", +} as const; + +export { INVOCATION_NAME, ERRORS }; diff --git a/sdk/webpubsub-chat-client/src/generatedTypes.ts b/sdk/webpubsub-chat-client/src/generatedTypes.ts new file mode 100644 index 000000000..56a9dc233 --- /dev/null +++ b/sdk/webpubsub-chat-client/src/generatedTypes.ts @@ -0,0 +1,408 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export type paths = Record; +export type webhooks = Record; +export interface components { + schemas: { + /** @enum {string} */ + NotificationType: "MessageCreated" | "MessageUpdated" | "MessageDeleted" | "RoomJoined" | "RoomLeft" | "RoomClosed" | "RoomMemberJoined" | "RoomMemberLeft" | "AddContact"; + Notification: { + notificationType: components["schemas"]["NotificationType"]; + body: components["schemas"]["NewMessageNotificationBody"] | components["schemas"]["NewRoomNotificationBody"] | components["schemas"]["UpdateMessageNotificationBody"] | components["schemas"]["AddContactNotificationBody"] | components["schemas"]["MemberJoinedNotificationBody"] | components["schemas"]["MemberLeftNotificationBody"] | components["schemas"]["RoomLeftNotificationBody"]; + }; + NewMessageNotificationBody: { + conversation: components["schemas"]["ChatConversation"]; + message: components["schemas"]["MessageInfo"]; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + notificationType: "MessageCreated"; + }; + NewMessageNotification: components["schemas"]["Notification"] & { + /** @enum {string} */ + notificationType?: "NewMessage"; + }; + NewRoomNotificationBody: { + roomId: string; + title: string; + defaultConversationId?: string; + /** @description null for now */ + properties?: Record | null; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + notificationType: "RoomJoined"; + }; + NewRoomNotification: components["schemas"]["Notification"] & { + /** @enum {string} */ + notificationType?: "NewRoom"; + body?: components["schemas"]["NewRoomNotificationBody"]; + }; + UpdateMessageNotificationBody: { + conversation: components["schemas"]["ChatConversation"]; + message: components["schemas"]["MessageInfo"]; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + notificationType: "MessageUpdated"; + }; + UpdateMessageNotification: components["schemas"]["Notification"] & { + /** @enum {string} */ + notificationType?: "UpdateMessage"; + body?: components["schemas"]["UpdateMessageNotificationBody"]; + }; + AddContactNotificationBody: { + userId: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + notificationType: "AddContact"; + }; + AddContactNotification: components["schemas"]["Notification"] & { + /** @enum {string} */ + notificationType?: "AddContact"; + body?: components["schemas"]["AddContactNotificationBody"]; + }; + MemberJoinedNotificationBody: { + roomId: string; + title: string; + userId: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + notificationType: "RoomMemberJoined"; + }; + MemberJoinedNotification: components["schemas"]["Notification"] & { + /** @enum {string} */ + notificationType?: "MemberJoined"; + body?: components["schemas"]["MemberJoinedNotificationBody"]; + }; + MemberLeftNotificationBody: { + roomId: string; + title: string; + userId: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + notificationType: "RoomMemberLeft"; + }; + MemberLeftNotification: components["schemas"]["Notification"] & { + /** @enum {string} */ + notificationType?: "MemberLeft"; + body?: components["schemas"]["MemberLeftNotificationBody"]; + }; + RoomLeftNotificationBody: { + roomId: string; + title: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + notificationType: "RoomLeft"; + }; + RoomLeftNotification: components["schemas"]["Notification"] & { + /** @enum {string} */ + notificationType?: "RoomLeft"; + body?: components["schemas"]["RoomLeftNotificationBody"]; + }; + RoomClosedNotificationBody: { + roomId: string; + title: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + notificationType: "RoomClosed"; + }; + RoomClosedNotification: components["schemas"]["Notification"] & { + /** @enum {string} */ + notificationType?: "RoomClosed"; + body?: components["schemas"]["RoomClosedNotificationBody"]; + }; + MessageDeletedNotificationBody: { + conversation: components["schemas"]["ChatConversation"]; + messageId: string; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + notificationType: "MessageDeleted"; + }; + MessageDeletedNotification: components["schemas"]["Notification"] & { + /** @enum {string} */ + notificationType?: "MessageDeleted"; + body?: components["schemas"]["MessageDeletedNotificationBody"]; + }; + UserProfile: { + /** + * @description Unique identifier for the user + * @example user123 + */ + userId: string; + /** + * @description Array of room IDs the user is in + * @example [ + * "room1", + * "room2", + * "room3" + * ] + */ + roomIds?: string[]; + /** + * @description Array of conversation IDs the user is in + * @example [ + * "id1", + * "id2", + * "id3" + * ] + */ + conversationIds?: string[]; + }; + ListUserConversationRequest: { + continuationToken?: string | null; + /** @default 1000 */ + maxCount: number | null; + }; + ListUserConversationResponse: { + conversations: components["schemas"]["ChatConversation"][]; + continuationToken?: string | null; + }; + ChatConversation: { + roomId?: string | null; + /** @description null for now */ + topicId?: string | null; + conversationId?: string | null; + }; + /** @enum {string} */ + ApprovalEnum: "AutoApprove" | "ManualApprove" | "AutoDeny"; + UserPolicy: { + addContact: components["schemas"]["ApprovalEnum"]; + /** + * @example [ + * "Nickname" + * ] + */ + publicProperties?: string[]; + friendProperties?: string[]; + privateProperties?: string[]; + }; + /** @enum {string} */ + ContactResultState: "OK" | "Pending" | "Failed"; + AddContactResult: { + userId: string; + state: components["schemas"]["ContactResultState"]; + message: string; + }; + ContactRequest: { + userId: string; + message: string; + }; + /** @enum {string} */ + ContactOperation: "Approve" | "Deny" | "Block"; + ContactRequestOperation: { + operation: components["schemas"]["ContactOperation"]; + userId: string; + }; + RoomInfo: { + roomId: string; + title: string; + defaultConversationId: string; + /** @description null for now */ + properties?: Record | null; + }; + RoomInfoWithMembers: components["schemas"]["RoomInfo"] & { + /** @description List of user id */ + members: string[]; + }; + /** @enum {string} */ + RoomMemberJoinEnum: "AutoApprove" | "ManualApprove" | "InviteOnly"; + /** @enum {string} */ + RoomMessagePermissionEnum: "Allow" | "AdminOnly" | "Deny"; + /** @enum {string} */ + RoomReactPermissionEnum: "Allow" | "Deny"; + RoomPolicy: { + memberJoin: components["schemas"]["RoomMemberJoinEnum"]; + messageTypeText?: components["schemas"]["RoomMessagePermissionEnum"]; + messageTypeImage?: components["schemas"]["RoomMessagePermissionEnum"]; + react?: components["schemas"]["RoomReactPermissionEnum"]; + }; + MessageRangeQuery: { + conversation: components["schemas"]["ChatConversation"]; + /** @description MessageId */ + start?: string | null; + /** @description MessageId */ + end?: string | null; + /** @default 100 */ + maxCount: number | null; + }; + MessageInfo: { + messageId: string; + /** @description UserId */ + createdBy?: string; + /** Format: date-time */ + createdAt?: string; + /** @example Join/Leave/Text/Emoji/File/Image/Voice/... */ + bodyType?: string; + /** @example Inline/Reference/External/... */ + messageBodyType: string; + content: { + text?: string | null; + /** Format: binary */ + binary?: string | null; + }; + refMessageId?: string | null; + }; + CreateTextMessage: { + conversation: components["schemas"]["ChatConversation"]; + message: string; + refMessageId?: string | null; + /** @description Array of UserId */ + extMentions?: string[] | null; + extDeleteAfterRead?: boolean | null; + /** Format: date-time */ + extScheduled?: string | null; + }; + CreateMessage: { + conversation: components["schemas"]["ChatConversation"]; + /** @example Text/Emoji/File/Image/Voice/... */ + messageType: string; + content: { + text?: string | null; + /** Format: binary */ + binary?: string | null; + }; + refMessageId?: string | null; + /** @description Array of UserId */ + extMentions?: string[] | null; + extDeleteAfterRead?: boolean | null; + /** Format: date-time */ + extScheduled?: string | null; + }; + MessageBody: { + conversation: components["schemas"]["ChatConversation"]; + messageId: string; + /** @example Join/Leave/Text/Emoji/File/Image/Voice/... */ + messageType: string; + /** @example Inline/Reference/External/... */ + messageBodyType: string; + content: { + text?: string | null; + /** Format: binary */ + binary?: string | null; + }; + refMessageId?: string | null; + }; + /** @enum {string} */ + JoinRoomState: "OK" | "Pending" | "Failed"; + JoinRoomResult: { + /** @description RoomId */ + room: string; + state: components["schemas"]["JoinRoomState"]; + message: string; + }; + JoinRoomRequest: { + userId: string; + message: string; + }; + /** @enum {string} */ + JoinRoomOperationEnum: "Approve" | "Deny" | "Block"; + JoinRoomOperation: { + /** @description RoomId */ + room: string; + userId: string; + operation: components["schemas"]["JoinRoomOperationEnum"]; + }; + RoomMember: { + userId: string; + role: string; + }; + /** @enum {string} */ + RoomMemberOperationEnum: "Add" | "Delete" | "Update"; + RoomMemberOperation: { + operation: components["schemas"]["RoomMemberOperationEnum"]; + member: components["schemas"]["RoomMember"]; + }; + /** @enum {string} */ + RoomMemberOperationType: "Add" | "Delete"; + ManageRoomMemberRequest: { + roomId: string; + operation: components["schemas"]["RoomMemberOperationType"]; + userId: string; + }; + SendMessageResponse: { + id: string; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export type operations = Record; + +// Flattened schema exports +export type Schemas = components["schemas"]; +export type NotificationType = Schemas["NotificationType"]; +export type Notification = Schemas["Notification"]; +export type NewMessageNotificationBody = Schemas["NewMessageNotificationBody"]; +export type NewMessageNotification = Schemas["NewMessageNotification"]; +export type NewRoomNotificationBody = Schemas["NewRoomNotificationBody"]; +export type NewRoomNotification = Schemas["NewRoomNotification"]; +export type UpdateMessageNotificationBody = Schemas["UpdateMessageNotificationBody"]; +export type UpdateMessageNotification = Schemas["UpdateMessageNotification"]; +export type AddContactNotificationBody = Schemas["AddContactNotificationBody"]; +export type AddContactNotification = Schemas["AddContactNotification"]; +export type MemberJoinedNotificationBody = Schemas["MemberJoinedNotificationBody"]; +export type MemberJoinedNotification = Schemas["MemberJoinedNotification"]; +export type MemberLeftNotificationBody = Schemas["MemberLeftNotificationBody"]; +export type MemberLeftNotification = Schemas["MemberLeftNotification"]; +export type RoomLeftNotificationBody = Schemas["RoomLeftNotificationBody"]; +export type RoomLeftNotification = Schemas["RoomLeftNotification"]; +export type RoomClosedNotificationBody = Schemas["RoomClosedNotificationBody"]; +export type RoomClosedNotification = Schemas["RoomClosedNotification"]; +export type MessageDeletedNotificationBody = Schemas["MessageDeletedNotificationBody"]; +export type MessageDeletedNotification = Schemas["MessageDeletedNotification"]; +export type UserProfile = Schemas["UserProfile"]; +export type ListUserConversationRequest = Schemas["ListUserConversationRequest"]; +export type ListUserConversationResponse = Schemas["ListUserConversationResponse"]; +export type ChatConversation = Schemas["ChatConversation"]; +export type ApprovalEnum = Schemas["ApprovalEnum"]; +export type UserPolicy = Schemas["UserPolicy"]; +export type ContactResultState = Schemas["ContactResultState"]; +export type AddContactResult = Schemas["AddContactResult"]; +export type ContactRequest = Schemas["ContactRequest"]; +export type ContactOperation = Schemas["ContactOperation"]; +export type ContactRequestOperation = Schemas["ContactRequestOperation"]; +export type RoomInfo = Schemas["RoomInfo"]; +export type RoomInfoWithMembers = Schemas["RoomInfoWithMembers"]; +export type RoomMemberJoinEnum = Schemas["RoomMemberJoinEnum"]; +export type RoomMessagePermissionEnum = Schemas["RoomMessagePermissionEnum"]; +export type RoomReactPermissionEnum = Schemas["RoomReactPermissionEnum"]; +export type RoomPolicy = Schemas["RoomPolicy"]; +export type MessageRangeQuery = Schemas["MessageRangeQuery"]; +export type MessageInfo = Schemas["MessageInfo"]; +export type CreateTextMessage = Schemas["CreateTextMessage"]; +export type CreateMessage = Schemas["CreateMessage"]; +export type MessageBody = Schemas["MessageBody"]; +export type JoinRoomState = Schemas["JoinRoomState"]; +export type JoinRoomResult = Schemas["JoinRoomResult"]; +export type JoinRoomRequest = Schemas["JoinRoomRequest"]; +export type JoinRoomOperationEnum = Schemas["JoinRoomOperationEnum"]; +export type JoinRoomOperation = Schemas["JoinRoomOperation"]; +export type RoomMember = Schemas["RoomMember"]; +export type RoomMemberOperationEnum = Schemas["RoomMemberOperationEnum"]; +export type RoomMemberOperation = Schemas["RoomMemberOperation"]; +export type RoomMemberOperationType = Schemas["RoomMemberOperationType"]; +export type ManageRoomMemberRequest = Schemas["ManageRoomMemberRequest"]; +export type SendMessageResponse = Schemas["SendMessageResponse"]; diff --git a/sdk/webpubsub-chat-client/src/index.ts b/sdk/webpubsub-chat-client/src/index.ts new file mode 100644 index 000000000..8a89ebbe1 --- /dev/null +++ b/sdk/webpubsub-chat-client/src/index.ts @@ -0,0 +1,15 @@ +import { ChatClient } from './chatClient.js'; + +export type { + MessageInfo, + MessageRangeQuery, + RoomInfo, + RoomInfoWithMembers, + UserProfile, + Notification, + NewMessageNotificationBody, + NewRoomNotificationBody, + SendMessageResponse, +} from './generatedTypes.js'; + +export { ChatClient }; diff --git a/sdk/webpubsub-chat-client/src/logger.ts b/sdk/webpubsub-chat-client/src/logger.ts new file mode 100644 index 000000000..6f3b5c1a9 --- /dev/null +++ b/sdk/webpubsub-chat-client/src/logger.ts @@ -0,0 +1,6 @@ +import { createClientLogger } from "@azure/logger"; + +/** + * The \@azure\/logger configuration for this package. + */ +export const logger = createClientLogger("web-pubsub-chat-client:*"); diff --git a/sdk/webpubsub-chat-client/src/utils.ts b/sdk/webpubsub-chat-client/src/utils.ts new file mode 100644 index 000000000..d0d04be02 --- /dev/null +++ b/sdk/webpubsub-chat-client/src/utils.ts @@ -0,0 +1,31 @@ +import { WebPubSubClient } from "@azure/web-pubsub-client"; + +export function decodeMessageBody(base64: string | null | undefined): string { + if (!base64) return ""; + if (typeof Buffer !== 'undefined') { + return Buffer.from(base64, 'base64').toString('utf-8'); + } + // compatibility for browser environment + return decodeURIComponent(escape(atob(base64))); +} + + +/** + * Type guard for WebPubSubClient. + * We avoid using `instanceof` because it can fail in scenarios with multiple + * dependency copies (e.g., monorepo with yarn/pnpm link) or across different + * execution contexts (iframe, worker). Instead, we check for stable public + * methods to ensure reliable detection. + */ +export function isWebPubSubClient(obj: unknown): obj is WebPubSubClient { + if (typeof obj !== "object" || obj === null) return false; + const anyObj = obj as any; + return ( + typeof anyObj === "object" && + ( + typeof anyObj.start === "function" || + typeof anyObj.stop === "function" || + typeof anyObj.on === "function" + ) + ); +} \ No newline at end of file diff --git a/sdk/webpubsub-chat-client/swagger/openapi.yaml b/sdk/webpubsub-chat-client/swagger/openapi.yaml new file mode 100644 index 000000000..36b69a867 --- /dev/null +++ b/sdk/webpubsub-chat-client/swagger/openapi.yaml @@ -0,0 +1,732 @@ +openapi: 3.0.0 +info: + title: Web PubSub Chat API + version: 1.0.0 + description: API for Web PubSub Chat SDK + +components: + schemas: + NotificationType: + type: string + enum: + - MessageCreated + - MessageUpdated + - MessageDeleted + - RoomJoined + - RoomLeft + - RoomClosed + - RoomMemberJoined + - RoomMemberLeft + - AddContact + + Notification: + type: object + properties: + notificationType: + $ref: '#/components/schemas/NotificationType' + body: + oneOf: + - $ref: '#/components/schemas/NewMessageNotificationBody' + - $ref: '#/components/schemas/NewRoomNotificationBody' + - $ref: '#/components/schemas/UpdateMessageNotificationBody' + - $ref: '#/components/schemas/AddContactNotificationBody' + - $ref: '#/components/schemas/MemberJoinedNotificationBody' + - $ref: '#/components/schemas/MemberLeftNotificationBody' + - $ref: '#/components/schemas/RoomLeftNotificationBody' + discriminator: + propertyName: notificationType + mapping: + MessageCreated: '#/components/schemas/NewMessageNotificationBody' + MessageUpdated: '#/components/schemas/UpdateMessageNotificationBody' + MessageDeleted: '#/components/schemas/MessageDeletedNotificationBody' + RoomJoined: '#/components/schemas/NewRoomNotificationBody' + RoomLeft: '#/components/schemas/RoomLeftNotificationBody' + RoomClosed: '#/components/schemas/RoomClosedNotificationBody' + RoomMemberJoined: '#/components/schemas/MemberJoinedNotificationBody' + RoomMemberLeft: '#/components/schemas/MemberLeftNotificationBody' + AddContact: '#/components/schemas/AddContactNotificationBody' + required: + - notificationType + - body + + NewMessageNotificationBody: + type: object + properties: + conversation: + $ref: '#/components/schemas/ChatConversation' + message: + $ref: '#/components/schemas/MessageInfo' + required: + - conversation + - message + + NewMessageNotification: + allOf: + - $ref: '#/components/schemas/Notification' + - type: object + properties: + notificationType: + type: string + enum: [NewMessage] + NewRoomNotificationBody: + type: object + properties: + roomId: + type: string + title: + type: string + defaultConversationId: + type: string + properties: + type: object + nullable: true + description: null for now + required: + - roomId + - title + + NewRoomNotification: + allOf: + - $ref: '#/components/schemas/Notification' + - type: object + properties: + notificationType: + type: string + enum: [NewRoom] + body: + $ref: '#/components/schemas/NewRoomNotificationBody' + required: + - roomId + - title + + UpdateMessageNotificationBody: + type: object + properties: + conversation: + $ref: '#/components/schemas/ChatConversation' + message: + $ref: '#/components/schemas/MessageInfo' + required: + - conversation + - message + + UpdateMessageNotification: + allOf: + - $ref: '#/components/schemas/Notification' + - type: object + properties: + notificationType: + type: string + enum: [UpdateMessage] + body: + $ref: '#/components/schemas/UpdateMessageNotificationBody' + + AddContactNotificationBody: + type: object + properties: + userId: + type: string + required: + - userId + + AddContactNotification: + allOf: + - $ref: '#/components/schemas/Notification' + - type: object + properties: + notificationType: + type: string + enum: [AddContact] + body: + $ref: '#/components/schemas/AddContactNotificationBody' + + MemberJoinedNotificationBody: + type: object + properties: + roomId: + type: string + title: + type: string + userId: + type: string + required: + - roomId + - title + - userId + + MemberJoinedNotification: + allOf: + - $ref: '#/components/schemas/Notification' + - type: object + properties: + notificationType: + type: string + enum: [MemberJoined] + body: + $ref: '#/components/schemas/MemberJoinedNotificationBody' + + MemberLeftNotificationBody: + type: object + properties: + roomId: + type: string + title: + type: string + userId: + type: string + required: + - roomId + - title + - userId + + MemberLeftNotification: + allOf: + - $ref: '#/components/schemas/Notification' + - type: object + properties: + notificationType: + type: string + enum: [MemberLeft] + body: + $ref: '#/components/schemas/MemberLeftNotificationBody' + + RoomLeftNotificationBody: + type: object + properties: + roomId: + type: string + title: + type: string + required: + - roomId + - title + + RoomLeftNotification: + allOf: + - $ref: '#/components/schemas/Notification' + - type: object + properties: + notificationType: + type: string + enum: [RoomLeft] + body: + $ref: '#/components/schemas/RoomLeftNotificationBody' + + RoomClosedNotificationBody: + type: object + properties: + roomId: + type: string + title: + type: string + required: + - roomId + - title + + RoomClosedNotification: + allOf: + - $ref: '#/components/schemas/Notification' + - type: object + properties: + notificationType: + type: string + enum: [RoomClosed] + body: + $ref: '#/components/schemas/RoomClosedNotificationBody' + + MessageDeletedNotificationBody: + type: object + properties: + conversation: + $ref: '#/components/schemas/ChatConversation' + messageId: + type: string + required: + - conversation + - messageId + + MessageDeletedNotification: + allOf: + - $ref: '#/components/schemas/Notification' + - type: object + properties: + notificationType: + type: string + enum: [MessageDeleted] + body: + $ref: '#/components/schemas/MessageDeletedNotificationBody' + + UserProfile: + type: object + properties: + userId: + type: string + description: Unique identifier for the user + example: "user123" + roomIds: + type: array + items: + type: string + description: Array of room IDs the user is in + example: ["room1", "room2", "room3"] + conversationIds: + type: array + items: + type: string + description: Array of conversation IDs the user is in + example: ["id1", "id2", "id3"] + required: + - userId + - rooms + + ListUserConversationRequest: + type: object + properties: + continuationToken: + type: string + nullable: true + maxCount: + type: integer + minimum: 1 + maximum: 1000 + default: 1000 + nullable: true + + ListUserConversationResponse: + type: object + properties: + conversations: + type: array + items: + $ref: '#/components/schemas/ChatConversation' + continuationToken: + type: string + nullable: true + required: + - conversations + + ChatConversation: + type: object + properties: + roomId: + type: string + nullable: true + topicId: + type: string + nullable: true + description: null for now + conversationId: + type: string + nullable: true + + ApprovalEnum: + type: string + enum: + - AutoApprove + - ManualApprove + - AutoDeny + + UserPolicy: + type: object + properties: + addContact: + $ref: '#/components/schemas/ApprovalEnum' + publicProperties: + type: array + items: + type: string + example: ["Nickname"] + friendProperties: + type: array + items: + type: string + privateProperties: + type: array + items: + type: string + required: + - addContact + + ContactResultState: + type: string + enum: + - OK + - Pending + - Failed + + AddContactResult: + type: object + properties: + userId: + type: string + state: + $ref: '#/components/schemas/ContactResultState' + message: + type: string + required: + - userId + - state + - message + + ContactRequest: + type: object + properties: + userId: + type: string + message: + type: string + required: + - userId + - message + + ContactOperation: + type: string + enum: + - Approve + - Deny + - Block + + ContactRequestOperation: + type: object + properties: + operation: + $ref: '#/components/schemas/ContactOperation' + userId: + type: string + required: + - operation + - userId + + RoomInfo: + type: object + properties: + roomId: + type: string + title: + type: string + defaultConversationId: + type: string + properties: + type: object + nullable: true + description: null for now + required: + - roomId + - title + - defaultConversationId + + RoomInfoWithMembers: + allOf: + - $ref: '#/components/schemas/RoomInfo' + - type: object + properties: + members: + type: array + items: + type: string + description: List of user id + required: + - members + + RoomMemberJoinEnum: + type: string + enum: + - AutoApprove + - ManualApprove + - InviteOnly + + RoomMessagePermissionEnum: + type: string + enum: + - Allow + - AdminOnly + - Deny + + RoomReactPermissionEnum: + type: string + enum: + - Allow + - Deny + + RoomPolicy: + type: object + properties: + memberJoin: + $ref: '#/components/schemas/RoomMemberJoinEnum' + messageTypeText: + $ref: '#/components/schemas/RoomMessagePermissionEnum' + messageTypeImage: + $ref: '#/components/schemas/RoomMessagePermissionEnum' + react: + $ref: '#/components/schemas/RoomReactPermissionEnum' + required: + - memberJoin + + MessageRangeQuery: + type: object + properties: + conversation: + $ref: '#/components/schemas/ChatConversation' + start: + type: string + nullable: true + description: MessageId + end: + type: string + nullable: true + description: MessageId + maxCount: + type: integer + default: 100 + nullable: true + required: + - conversation + + MessageInfo: + type: object + properties: + messageId: + type: string + createdBy: + type: string + description: UserId + createdAt: + type: string + format: date-time + bodyType: + type: string + example: Join/Leave/Text/Emoji/File/Image/Voice/... + messageBodyType: + type: string + example: Inline/Reference/External/... + content: + type: object + properties: + text: + type: string + nullable: true + binary: + type: string + format: binary + nullable: true + refMessageId: + type: string + nullable: true + required: + - messageId + - sender + - sentAt + - messageType + - messageBodyType + - content + + CreateTextMessage: + type: object + properties: + conversation: + $ref: '#/components/schemas/ChatConversation' + message: + type: string + refMessageId: + type: string + nullable: true + extMentions: + type: array + items: + type: string + nullable: true + description: Array of UserId + extDeleteAfterRead: + type: boolean + nullable: true + extScheduled: + type: string + format: date-time + nullable: true + required: + - conversation + - message + + CreateMessage: + type: object + properties: + conversation: + $ref: '#/components/schemas/ChatConversation' + messageType: + type: string + example: Text/Emoji/File/Image/Voice/... + content: + type: object + properties: + text: + type: string + nullable: true + binary: + type: string + format: binary + nullable: true + refMessageId: + type: string + nullable: true + extMentions: + type: array + items: + type: string + nullable: true + description: Array of UserId + extDeleteAfterRead: + type: boolean + nullable: true + extScheduled: + type: string + format: date-time + nullable: true + required: + - conversation + - messageType + - content + + MessageBody: + type: object + properties: + conversation: + $ref: '#/components/schemas/ChatConversation' + messageId: + type: string + messageType: + type: string + example: Join/Leave/Text/Emoji/File/Image/Voice/... + messageBodyType: + type: string + example: Inline/Reference/External/... + content: + type: object + properties: + text: + type: string + nullable: true + binary: + type: string + format: binary + nullable: true + refMessageId: + type: string + nullable: true + required: + - conversation + - messageId + - messageType + - messageBodyType + - content + + JoinRoomState: + type: string + enum: + - OK + - Pending + - Failed + + JoinRoomResult: + type: object + properties: + room: + type: string + description: RoomId + state: + $ref: '#/components/schemas/JoinRoomState' + message: + type: string + required: + - room + - state + - message + + JoinRoomRequest: + type: object + properties: + userId: + type: string + message: + type: string + required: + - userId + - message + + JoinRoomOperationEnum: + type: string + enum: + - Approve + - Deny + - Block + + JoinRoomOperation: + type: object + properties: + room: + type: string + description: RoomId + userId: + type: string + operation: + $ref: '#/components/schemas/JoinRoomOperationEnum' + required: + - room + - userId + - operation + + RoomMember: + type: object + properties: + userId: + type: string + role: + type: string + required: + - userId + - role + + RoomMemberOperationEnum: + type: string + enum: + - Add + - Delete + - Update + + RoomMemberOperation: + type: object + properties: + operation: + $ref: '#/components/schemas/RoomMemberOperationEnum' + member: + $ref: '#/components/schemas/RoomMember' + required: + - operation + - member + + RoomMemberOperationType: + type: string + enum: + - Add + - Delete + + ManageRoomMemberRequest: + type: object + properties: + roomId: + type: string + operation: + $ref: '#/components/schemas/RoomMemberOperationType' + userId: + type: string + required: + - roomId + - operation + - userId + + SendMessageResponse: + type: object + properties: + id: + type: string + required: + - id diff --git a/sdk/webpubsub-chat-client/tests/integration.test.ts b/sdk/webpubsub-chat-client/tests/integration.test.ts new file mode 100644 index 000000000..f1652e496 --- /dev/null +++ b/sdk/webpubsub-chat-client/tests/integration.test.ts @@ -0,0 +1,189 @@ +import { test, after } from "node:test"; +import assert from "node:assert/strict"; +import { randomUUID } from "node:crypto"; +import { ChatClient } from "../src/chatClient.js"; +import { + SHORT_TEST_TIMEOUT, + LONG_TEST_TIMEOUT, + createTestClient, + getMultipleClients, + stopClients, + forceExitAfterTests, +} from "./testUtils.js"; + +test("same user id login twice", { timeout: LONG_TEST_TIMEOUT }, async (t) => { + let chat0, chat1; + try { + chat0 = await createTestClient(); + + // first login + chat1 = await createTestClient(); + const chat1UserId = chat1.userId; + let messageReceived = 0; + chat1.addListenerForNewMessage((notification) => { + messageReceived++; + }); + assert.equal(chat1.userId, chat1UserId, `chat1 userId should be '${chat1UserId}'`); + + const roomName = `room-${Math.floor(Math.random() * 10000)}`; + const createdRoom = await chat0.createRoom(roomName, [chat1.userId], `uid_${roomName}`); + await chat0.sendToRoom(createdRoom.roomId, `Hello from chat0`); + // sleep 100ms + await new Promise((resolve) => setTimeout(resolve, 100)); + assert.equal(messageReceived, 1, `chat1 should receive 1 message at first login`); + + chat1.stop(); + + // second login with same userId + chat1 = await createTestClient(chat1UserId); // login again with same userId + messageReceived = 0; + chat1.addListenerForNewMessage((notification) => { + messageReceived++; + }); + assert.equal(chat1.userId, chat1UserId, `chat1 userId should still be '${chat1UserId}' after re-login`); + + const sentMsgId = await chat0.sendToRoom(createdRoom.roomId, `Hello from chat0`); + + // sleep 100ms + await new Promise((resolve) => setTimeout(resolve, 100)); + assert.equal(messageReceived, 1, `chat1 should receive 1 message at second login`); + } catch (e) { + t.diagnostic((e as any).toString()); + throw e; + } finally { + const clientsToStop = [chat0, chat1].filter(Boolean) as ChatClient[]; + stopClients(clientsToStop); + } +}); + +test("single client", { timeout: SHORT_TEST_TIMEOUT }, async (t) => { + let chat1; + try { + chat1 = await createTestClient(); + + assert.ok(chat1.userId && typeof chat1.userId === "string"); + + const roomId = `room-id-${randomUUID().substring(0, 3)}`; + const created = await chat1.createRoom("ut-single-room", [], roomId); + assert.equal(created.roomId, roomId, "roomId should match"); + assert.equal(created.title, "ut-single-room", "room title should match"); + assert.ok(Array.isArray(created.members), "members should be an array"); + assert.deepEqual(created.members, [chat1.userId], "members should contain only the creator"); + assert.ok(created.members.includes(chat1.userId), "members should include the creator"); + + const fetched = await chat1.getRoom(created.roomId, true); + assert.equal(fetched.roomId, created.roomId, "fetched roomId should match created"); + assert.equal(fetched.title, created.title, "fetched title should match created"); + assert.ok(Array.isArray(fetched.members), "fetched members should be an array"); + } catch (e) { + t.diagnostic((e as any).toString()); + throw e; + } finally { + if (chat1) stopClients([chat1]); + } +}); + +test("create room with multiple users", { timeout: LONG_TEST_TIMEOUT }, async (t) => { + let chats; + try { + chats = await getMultipleClients(3); + + var joinedRoomCounts = [0, 0, 0], + receivedMsgCounts = [0, 0, 0]; + + for (let i = 0; i < 3; i++) { + chats[i].addListenerForNewRoom((room) => { + joinedRoomCounts[i]++; + }); + chats[i].addListenerForNewMessage((message) => { + receivedMsgCounts[i]++; + }); + } + + const createdRoom = await chats[0].createRoom("test-room", [chats[1].userId, chats[2].userId]); + + for (let i = 1; i <= 5; i++) { + const msgId = await chats[0].sendToRoom(createdRoom.roomId, `HelloMessage,#${i}`); + assert.equal(msgId, i.toString(), `sent message id should be ${i} but got ${msgId}`); + } + + const listedMsgs = await chats[0].listMessage(createdRoom.defaultConversationId, "0", null, 100); + let listedMsgCount = 0; + for (const message of listedMsgs.messages) { + assert.equal(message.messageId, (5 - listedMsgCount).toString(), `message id should match expected order, expect ${5 - listedMsgCount} but got ${message.messageId}`); + assert.equal(message.content.text, `HelloMessage,#${5 - listedMsgCount}`, `message body should match expected content, expect 'HelloMessage,#${5 - listedMsgCount}' but got '${message.content.text}'`); + listedMsgCount++; + } + + await new Promise((resolve) => setTimeout(resolve, 100)); // wait for events + assert.equal(listedMsgCount, 5, "should list 5 messages"); + assert.deepEqual(joinedRoomCounts, [1, 1, 1], "chat2 should receive new room event"); + assert.deepEqual(receivedMsgCounts, [5, 5, 5], "chat2 should receive 5 new messages"); + } catch (e) { + t.diagnostic((e as any).toString()); + throw e; + } finally { + if (chats && Array.isArray(chats)) { + stopClients(chats); + } + } +}); + +test("admin adds multiple users to a group", { timeout: LONG_TEST_TIMEOUT }, async (t) => { + let chats; + try { + chats = await getMultipleClients(3); + const createdRoom = await chats[0].createRoom("ut-room", []); + // Admin (chats[0]) adds other users to the room + for (let i = 1; i < chats.length; i++) { + await chats[0].addUserToRoom(createdRoom.roomId, chats[i].userId); + } + + let messageReceivedCounts = new Array(chats.length).fill(0); + + chats.forEach((chat, index) => { + chat.addListenerForNewMessage((notification) => { + messageReceivedCounts[index]++; + }); + }); + + + // client 0..n-1 send message, should be received by all others + for (let i = 1; i < chats.length; i++) { + const sentMsgId = await chats[i].sendToRoom(createdRoom.roomId, `Hello from chat${i}`); + assert.equal(sentMsgId, i.toString(), `sent message id should be ${i} but got ${sentMsgId}`); + } + + // sleep 100ms + await new Promise((resolve) => setTimeout(resolve, 1000)); + + for (let i = 1; i < chats.length; i++) { + assert.equal(messageReceivedCounts[i], chats.length - 1, `chat${i} should receive ${chats.length - 1} messages`); + } + assert.equal(messageReceivedCounts[0], chats.length - 1, `creator should receive ${chats.length - 1} messages`); + + // client 0 send message + const finalMsgId = await chats[0].sendToRoom(createdRoom.roomId, "final message"); + assert.equal(finalMsgId, chats.length.toString(), `sent message id should be ${chats.length} but got ${finalMsgId}`); + + // sleep 100ms + await new Promise((resolve) => setTimeout(resolve, 100)); + + for (let i = 1; i < chats.length; i++) { + assert.equal(messageReceivedCounts[i], chats.length, `chat${i} should receive ${chats.length} messages`); + } + assert.equal(messageReceivedCounts[0], chats.length, `creator should receive ${chats.length} messages`); + } catch (e) { + t.diagnostic((e as any).toString()); + throw e; + } finally { + if (chats && Array.isArray(chats)) { + stopClients(chats); + } + } +}); + +// Force exit after all tests to avoid hanging on open connections +after(() => { + forceExitAfterTests(); +}); \ No newline at end of file diff --git a/sdk/webpubsub-chat-client/tests/testUtils.ts b/sdk/webpubsub-chat-client/tests/testUtils.ts new file mode 100644 index 000000000..472fad721 --- /dev/null +++ b/sdk/webpubsub-chat-client/tests/testUtils.ts @@ -0,0 +1,56 @@ +import { WebPubSubClient } from "@azure/web-pubsub-client"; +import { ChatClient } from "../src/chatClient.js"; +import { randomInt as secureRandomInt } from "crypto"; + +// Test configuration +export const negotiateUrl = "http://localhost:3000/negotiate"; +export const SHORT_TEST_TIMEOUT = 5 * 1000; +export const LONG_TEST_TIMEOUT = 10 * 1000; + +// Helper functions +export const randomInt = () => secureRandomInt(0, 10000000); + +export const getUserIds = (count: number): string[] => { + const userIds: string[] = []; + for (let i = 0; i < count; i++) { + userIds.push(`user-${i}-${randomInt()}`); + } + return userIds; +}; + +export async function createTestClient(userId?: string): Promise { + if (!userId) { + userId = `uid-${randomInt()}`; + } + const wpsClient = new WebPubSubClient({ + getClientAccessUrl: async () => { + const res = await fetch(negotiateUrl + (userId ? `?userId=${encodeURIComponent(userId)}` : "")); + const value = (await res.json()) as { url?: string }; + if (!value?.url) throw new Error("Failed to get negotiate url"); + return value.url; + }, + }); + return await ChatClient.login(wpsClient); +} + +export async function getMultipleClients(count: number): Promise { + const userIds = getUserIds(count); + const clients: ChatClient[] = []; + for (const userId of userIds) { + clients.push(await createTestClient(userId)); + } + return clients; +} + +// Helper to stop multiple clients immediately +export function stopClients(clients: ChatClient[]): void { + clients.forEach((c) => c.stop()); +} + +// Force exit after all tests complete (call this at the end of test file) +export function forceExitAfterTests(): void { + // Use setImmediate to allow the test runner to finish reporting + setImmediate(() => { + process.exit(0); + }); +} diff --git a/sdk/webpubsub-chat-client/tsconfig.json b/sdk/webpubsub-chat-client/tsconfig.json new file mode 100644 index 000000000..840efc83d --- /dev/null +++ b/sdk/webpubsub-chat-client/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "composite": true /* Enable constraints that allow a TypeScript project to be used with project references. */, + "declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */, + "declarationMap": true /* Create sourcemaps for d.ts files. */, + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + "strict": true /* Enable all strict type-checking options. */, + "skipLibCheck": true /* Skip type checking all .d.ts files. */, + "moduleResolution": "NodeNext", + "rootDir": "src", + "target": "ESNext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + "module": "NodeNext" /* Specify what module code is generated. */, + "outDir": "./dist" /* Specify an output folder for all emitted files. */ + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "dist-esm", "types", "types-esm"] +} \ No newline at end of file diff --git a/sdk/webpubsub-chat-client/yarn.lock b/sdk/webpubsub-chat-client/yarn.lock new file mode 100644 index 000000000..c0837b608 --- /dev/null +++ b/sdk/webpubsub-chat-client/yarn.lock @@ -0,0 +1,1581 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@azure/abort-controller@npm:^2.1.2": + version: 2.1.2 + resolution: "@azure/abort-controller@npm:2.1.2" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/3771b6820e33ebb56e79c7c68e2288296b8c2529556fbd29cf4cf2fbff7776e7ce1120072972d8df9f1bf50e2c3224d71a7565362b589595563f710b8c3d7b79 + languageName: node + linkType: hard + +"@azure/core-util@npm:^1.11.0": + version: 1.13.1 + resolution: "@azure/core-util@npm:1.13.1" + dependencies: + "@azure/abort-controller": "npm:^2.1.2" + "@typespec/ts-http-runtime": "npm:^0.3.0" + tslib: "npm:^2.6.2" + checksum: 10c0/37067621cdac933c51775c26648fdcea315f07b08bd875cff4610e403eabf9c12532525f0bf094e258dadc03a55d35f12c9242f662526847b32c85cdcc2d6603 + languageName: node + linkType: hard + +"@azure/logger@npm:^1.1.4, @azure/logger@npm:^1.3.0": + version: 1.3.0 + resolution: "@azure/logger@npm:1.3.0" + dependencies: + "@typespec/ts-http-runtime": "npm:^0.3.0" + tslib: "npm:^2.6.2" + checksum: 10c0/aaa6a88fd4f26d41100865ff2c53b400347f632d315d9ae8ffa28db03974d35461e743031bdca40cad617ace172d1ba598ffdd18c345ebc564f63a51c32c4a29 + languageName: node + linkType: hard + +"@azure/web-pubsub-chat-client@workspace:.": + version: 0.0.0-use.local + resolution: "@azure/web-pubsub-chat-client@workspace:." + dependencies: + "@azure/logger": "npm:^1.3.0" + "@azure/web-pubsub-client": "npm:1.0.5-beta.1" + "@types/events": "npm:^3.0.3" + "@types/node": "npm:^25.0.3" + esbuild: "npm:^0.27.3" + esbuild-plugin-polyfill-node: "npm:^0.3.0" + events: "npm:^3.3.0" + openapi-typescript: "npm:^7.10.1" + tsx: "npm:^4.21.0" + typescript: "npm:^5.9.3" + ws: "npm:^8.0.0" + languageName: unknown + linkType: soft + +"@azure/web-pubsub-client@npm:1.0.5-beta.1": + version: 1.0.5-beta.1 + resolution: "@azure/web-pubsub-client@npm:1.0.5-beta.1::__archiveUrl=https%3A%2F%2Fwww.myget.org%2FF%2Fazure-signalr-dev%2Fnpm%2F%40azure%2Fweb-pubsub-client%2F-%2F%40azure%2Fweb-pubsub-client-1.0.5-beta.1.tgz" + dependencies: + "@azure/abort-controller": "npm:^2.1.2" + "@azure/core-util": "npm:^1.11.0" + "@azure/logger": "npm:^1.1.4" + buffer: "npm:^6.0.0" + events: "npm:^3.3.0" + tslib: "npm:^2.8.1" + ws: "npm:^8.18.0" + checksum: 10c0/aa67daf0657101e51b59ce68716e021f120aae88c2906e16e4b814dc1fbdada890833f1d43128311f251a01bbeebf791f42b51390f9d30e0393ed588a627bcde + languageName: node + linkType: hard + +"@babel/code-frame@npm:^7.26.2": + version: 7.27.1 + resolution: "@babel/code-frame@npm:7.27.1" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.27.1" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.1.1" + checksum: 10c0/5dd9a18baa5fce4741ba729acc3a3272c49c25cb8736c4b18e113099520e7ef7b545a4096a26d600e4416157e63e87d66db46aa3fbf0a5f2286da2705c12da00 + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.27.1": + version: 7.28.5 + resolution: "@babel/helper-validator-identifier@npm:7.28.5" + checksum: 10c0/42aaebed91f739a41f3d80b72752d1f95fd7c72394e8e4bd7cdd88817e0774d80a432451bcba17c2c642c257c483bf1d409dd4548883429ea9493a3bc4ab0847 + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/aix-ppc64@npm:0.27.2" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/aix-ppc64@npm:0.27.3" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/android-arm64@npm:0.27.2" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/android-arm64@npm:0.27.3" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/android-arm@npm:0.27.2" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/android-arm@npm:0.27.3" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/android-x64@npm:0.27.2" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/android-x64@npm:0.27.3" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/darwin-arm64@npm:0.27.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/darwin-arm64@npm:0.27.3" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/darwin-x64@npm:0.27.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/darwin-x64@npm:0.27.3" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/freebsd-arm64@npm:0.27.2" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/freebsd-arm64@npm:0.27.3" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/freebsd-x64@npm:0.27.2" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/freebsd-x64@npm:0.27.3" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-arm64@npm:0.27.2" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-arm64@npm:0.27.3" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-arm@npm:0.27.2" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-arm@npm:0.27.3" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-ia32@npm:0.27.2" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-ia32@npm:0.27.3" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-loong64@npm:0.27.2" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-loong64@npm:0.27.3" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-mips64el@npm:0.27.2" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-mips64el@npm:0.27.3" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-ppc64@npm:0.27.2" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-ppc64@npm:0.27.3" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-riscv64@npm:0.27.2" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-riscv64@npm:0.27.3" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-s390x@npm:0.27.2" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-s390x@npm:0.27.3" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-x64@npm:0.27.2" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-x64@npm:0.27.3" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/netbsd-arm64@npm:0.27.2" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/netbsd-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/netbsd-arm64@npm:0.27.3" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/netbsd-x64@npm:0.27.2" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/netbsd-x64@npm:0.27.3" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/openbsd-arm64@npm:0.27.2" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/openbsd-arm64@npm:0.27.3" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/openbsd-x64@npm:0.27.2" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/openbsd-x64@npm:0.27.3" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openharmony-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/openharmony-arm64@npm:0.27.2" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openharmony-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/openharmony-arm64@npm:0.27.3" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/sunos-x64@npm:0.27.2" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/sunos-x64@npm:0.27.3" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/win32-arm64@npm:0.27.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/win32-arm64@npm:0.27.3" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/win32-ia32@npm:0.27.2" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/win32-ia32@npm:0.27.3" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/win32-x64@npm:0.27.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/win32-x64@npm:0.27.3" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@isaacs/balanced-match@npm:^4.0.1": + version: 4.0.1 + resolution: "@isaacs/balanced-match@npm:4.0.1" + checksum: 10c0/7da011805b259ec5c955f01cee903da72ad97c5e6f01ca96197267d3f33103d5b2f8a1af192140f3aa64526c593c8d098ae366c2b11f7f17645d12387c2fd420 + languageName: node + linkType: hard + +"@isaacs/brace-expansion@npm:^5.0.1": + version: 5.0.1 + resolution: "@isaacs/brace-expansion@npm:5.0.1" + dependencies: + "@isaacs/balanced-match": "npm:^4.0.1" + checksum: 10c0/e5d67c7bbf1f17b88132a35bc638af306d48acbb72810d48fa6e6edd8ab375854773108e8bf70f021f7ef6a8273455a6d1f0c3b5aa2aff06ce7894049ab77fb8 + languageName: node + linkType: hard + +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: "npm:^7.0.4" + checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 + languageName: node + linkType: hard + +"@jspm/core@npm:^2.0.1": + version: 2.1.0 + resolution: "@jspm/core@npm:2.1.0" + checksum: 10c0/4e10f912b60f33d216a68f46351dd430f10a2024ce5b149ac93e4d19f85d0dbf0b929cbb90397ea0e8cef28f1723ea1f94c88b7c5d16ecf1f62e391ea072bc33 + languageName: node + linkType: hard + +"@npmcli/agent@npm:^4.0.0": + version: 4.0.0 + resolution: "@npmcli/agent@npm:4.0.0" + dependencies: + agent-base: "npm:^7.1.0" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.1" + lru-cache: "npm:^11.2.1" + socks-proxy-agent: "npm:^8.0.3" + checksum: 10c0/f7b5ce0f3dd42c3f8c6546e8433573d8049f67ef11ec22aa4704bc41483122f68bf97752e06302c455ead667af5cb753e6a09bff06632bc465c1cfd4c4b75a53 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^5.0.0": + version: 5.0.0 + resolution: "@npmcli/fs@npm:5.0.0" + dependencies: + semver: "npm:^7.3.5" + checksum: 10c0/26e376d780f60ff16e874a0ac9bc3399186846baae0b6e1352286385ac134d900cc5dafaded77f38d77f86898fc923ae1cee9d7399f0275b1aa24878915d722b + languageName: node + linkType: hard + +"@redocly/ajv@npm:^8.11.2": + version: 8.17.1 + resolution: "@redocly/ajv@npm:8.17.1" + dependencies: + fast-deep-equal: "npm:^3.1.3" + fast-uri: "npm:^3.0.1" + json-schema-traverse: "npm:^1.0.0" + require-from-string: "npm:^2.0.2" + checksum: 10c0/f6fd7fc0455ab1f465fd1138b5e33732c177b357b992fa0a474902aff749aef3353d1c9785203870b460aad0752276751eaa6dfa5b57f357e6de7ac926684061 + languageName: node + linkType: hard + +"@redocly/config@npm:^0.22.0": + version: 0.22.2 + resolution: "@redocly/config@npm:0.22.2" + checksum: 10c0/625e947e7939e2d59bd83f516af5a581411167e3fc83adf7322bddf9bc69038fc601ed4ee8abae44d298ed367a16a1a09e7cdbe8b5dde172b4ce53c88d8717f4 + languageName: node + linkType: hard + +"@redocly/openapi-core@npm:^1.34.5": + version: 1.34.6 + resolution: "@redocly/openapi-core@npm:1.34.6" + dependencies: + "@redocly/ajv": "npm:^8.11.2" + "@redocly/config": "npm:^0.22.0" + colorette: "npm:^1.2.0" + https-proxy-agent: "npm:^7.0.5" + js-levenshtein: "npm:^1.1.6" + js-yaml: "npm:^4.1.0" + minimatch: "npm:^5.0.1" + pluralize: "npm:^8.0.0" + yaml-ast-parser: "npm:0.0.43" + checksum: 10c0/3033dc0810fd7d25f033f90f921ce625672851948c264c51426908fe17b363b12a6ddf0aebc231a16befed9396658fa122de9f25fee82fe087b7d4d01291d4e8 + languageName: node + linkType: hard + +"@types/events@npm:^3.0.3": + version: 3.0.3 + resolution: "@types/events@npm:3.0.3" + checksum: 10c0/3a56f8c51eb4ebc0d05dcadca0c6636c816acc10216ce36c976fad11e54a01f4bb979a07211355686015884753b37f17d74bfdc7aaf4ebb027c1e8a501c7b21d + languageName: node + linkType: hard + +"@types/node@npm:^25.0.3": + version: 25.0.3 + resolution: "@types/node@npm:25.0.3" + dependencies: + undici-types: "npm:~7.16.0" + checksum: 10c0/b7568f0d765d9469621615e2bb257c7fd1953d95e9acbdb58dffb6627a2c4150d405a4600aa1ad8a40182a94fe5f903cafd3c0a2f5132814debd0e3bfd61f835 + languageName: node + linkType: hard + +"@typespec/ts-http-runtime@npm:^0.3.0": + version: 0.3.2 + resolution: "@typespec/ts-http-runtime@npm:0.3.2" + dependencies: + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/33cb3d7a38851a4144b8370c39d6ad9e77b722213cb0eafef9a9e52e35f611d88ebdfd264bb3b51147a2fa4de8d78c8cff294af9e58ebb39ab6c439588771d2b + languageName: node + linkType: hard + +"abbrev@npm:^4.0.0": + version: 4.0.0 + resolution: "abbrev@npm:4.0.0" + checksum: 10c0/b4cc16935235e80702fc90192e349e32f8ef0ed151ef506aa78c81a7c455ec18375c4125414b99f84b2e055199d66383e787675f0bcd87da7a4dbd59f9eac1d5 + languageName: node + linkType: hard + +"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": + version: 7.1.4 + resolution: "agent-base@npm:7.1.4" + checksum: 10c0/c2c9ab7599692d594b6a161559ada307b7a624fa4c7b03e3afdb5a5e31cd0e53269115b620fcab024c5ac6a6f37fa5eb2e004f076ad30f5f7e6b8b671f7b35fe + languageName: node + linkType: hard + +"ansi-colors@npm:^4.1.3": + version: 4.1.3 + resolution: "ansi-colors@npm:4.1.3" + checksum: 10c0/ec87a2f59902f74e61eada7f6e6fe20094a628dab765cfdbd03c3477599368768cffccdb5d3bb19a1b6c99126783a143b1fee31aab729b31ffe5836c7e5e28b9 + languageName: node + linkType: hard + +"argparse@npm:^2.0.1": + version: 2.0.1 + resolution: "argparse@npm:2.0.1" + checksum: 10c0/c5640c2d89045371c7cedd6a70212a04e360fd34d6edeae32f6952c63949e3525ea77dbec0289d8213a99bbaeab5abfa860b5c12cf88a2e6cf8106e90dd27a7e + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee + languageName: node + linkType: hard + +"base64-js@npm:^1.3.1": + version: 1.5.1 + resolution: "base64-js@npm:1.5.1" + checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf + languageName: node + linkType: hard + +"brace-expansion@npm:^2.0.1": + version: 2.0.2 + resolution: "brace-expansion@npm:2.0.2" + dependencies: + balanced-match: "npm:^1.0.0" + checksum: 10c0/6d117a4c793488af86b83172deb6af143e94c17bc53b0b3cec259733923b4ca84679d506ac261f4ba3c7ed37c46018e2ff442f9ce453af8643ecd64f4a54e6cf + languageName: node + linkType: hard + +"buffer@npm:^6.0.0": + version: 6.0.3 + resolution: "buffer@npm:6.0.3" + dependencies: + base64-js: "npm:^1.3.1" + ieee754: "npm:^1.2.1" + checksum: 10c0/2a905fbbcde73cc5d8bd18d1caa23715d5f83a5935867c2329f0ac06104204ba7947be098fe1317fbd8830e26090ff8e764f08cd14fefc977bb248c3487bcbd0 + languageName: node + linkType: hard + +"cacache@npm:^20.0.1": + version: 20.0.3 + resolution: "cacache@npm:20.0.3" + dependencies: + "@npmcli/fs": "npm:^5.0.0" + fs-minipass: "npm:^3.0.0" + glob: "npm:^13.0.0" + lru-cache: "npm:^11.1.0" + minipass: "npm:^7.0.3" + minipass-collect: "npm:^2.0.1" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + p-map: "npm:^7.0.2" + ssri: "npm:^13.0.0" + unique-filename: "npm:^5.0.0" + checksum: 10c0/c7da1ca694d20e8f8aedabd21dc11518f809a7d2b59aa76a1fc655db5a9e62379e465c157ddd2afe34b19230808882288effa6911b2de26a088a6d5645123462 + languageName: node + linkType: hard + +"change-case@npm:^5.4.4": + version: 5.4.4 + resolution: "change-case@npm:5.4.4" + checksum: 10c0/2a9c2b9c9ad6ab2491105aaf506db1a9acaf543a18967798dcce20926c6a173aa63266cb6189f3086e3c14bf7ae1f8ea4f96ecc466fcd582310efa00372f3734 + languageName: node + linkType: hard + +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 + languageName: node + linkType: hard + +"colorette@npm:^1.2.0": + version: 1.4.0 + resolution: "colorette@npm:1.4.0" + checksum: 10c0/4955c8f7daafca8ae7081d672e4bd89d553bd5782b5846d5a7e05effe93c2f15f7e9c0cb46f341b59f579a39fcf436241ff79594899d75d5f3460c03d607fe9e + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.3.4": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/d79136ec6c83ecbefd0f6a5593da6a9c91ec4d7ddc4b54c883d6e71ec9accb5f67a1a5e96d00a328196b5b5c86d365e98d8a3a70856aaf16b4e7b1985e67f5a6 + languageName: node + linkType: hard + +"encoding@npm:^0.1.13": + version: 0.1.13 + resolution: "encoding@npm:0.1.13" + dependencies: + iconv-lite: "npm:^0.6.2" + checksum: 10c0/36d938712ff00fe1f4bac88b43bcffb5930c1efa57bbcdca9d67e1d9d6c57cfb1200fb01efe0f3109b2ce99b231f90779532814a81370a1bd3274a0f58585039 + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 + languageName: node + linkType: hard + +"err-code@npm:^2.0.2": + version: 2.0.3 + resolution: "err-code@npm:2.0.3" + checksum: 10c0/b642f7b4dd4a376e954947550a3065a9ece6733ab8e51ad80db727aaae0817c2e99b02a97a3d6cecc648a97848305e728289cf312d09af395403a90c9d4d8a66 + languageName: node + linkType: hard + +"esbuild-plugin-polyfill-node@npm:^0.3.0": + version: 0.3.0 + resolution: "esbuild-plugin-polyfill-node@npm:0.3.0" + dependencies: + "@jspm/core": "npm:^2.0.1" + import-meta-resolve: "npm:^3.0.0" + peerDependencies: + esbuild: "*" + checksum: 10c0/8e7e7ee7034a11995f99eefbb75b56b162f5b43b849f7d199cbc7b1089867a8595f48a640a1487f496d2af27986b467ccf3a34bf81cbf9961928c8b8d8cbd3ac + languageName: node + linkType: hard + +"esbuild@npm:^0.27.3": + version: 0.27.3 + resolution: "esbuild@npm:0.27.3" + dependencies: + "@esbuild/aix-ppc64": "npm:0.27.3" + "@esbuild/android-arm": "npm:0.27.3" + "@esbuild/android-arm64": "npm:0.27.3" + "@esbuild/android-x64": "npm:0.27.3" + "@esbuild/darwin-arm64": "npm:0.27.3" + "@esbuild/darwin-x64": "npm:0.27.3" + "@esbuild/freebsd-arm64": "npm:0.27.3" + "@esbuild/freebsd-x64": "npm:0.27.3" + "@esbuild/linux-arm": "npm:0.27.3" + "@esbuild/linux-arm64": "npm:0.27.3" + "@esbuild/linux-ia32": "npm:0.27.3" + "@esbuild/linux-loong64": "npm:0.27.3" + "@esbuild/linux-mips64el": "npm:0.27.3" + "@esbuild/linux-ppc64": "npm:0.27.3" + "@esbuild/linux-riscv64": "npm:0.27.3" + "@esbuild/linux-s390x": "npm:0.27.3" + "@esbuild/linux-x64": "npm:0.27.3" + "@esbuild/netbsd-arm64": "npm:0.27.3" + "@esbuild/netbsd-x64": "npm:0.27.3" + "@esbuild/openbsd-arm64": "npm:0.27.3" + "@esbuild/openbsd-x64": "npm:0.27.3" + "@esbuild/openharmony-arm64": "npm:0.27.3" + "@esbuild/sunos-x64": "npm:0.27.3" + "@esbuild/win32-arm64": "npm:0.27.3" + "@esbuild/win32-ia32": "npm:0.27.3" + "@esbuild/win32-x64": "npm:0.27.3" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/fdc3f87a3f08b3ef98362f37377136c389a0d180fda4b8d073b26ba930cf245521db0a368f119cc7624bc619248fff1439f5811f062d853576f8ffa3df8ee5f1 + languageName: node + linkType: hard + +"esbuild@npm:~0.27.0": + version: 0.27.2 + resolution: "esbuild@npm:0.27.2" + dependencies: + "@esbuild/aix-ppc64": "npm:0.27.2" + "@esbuild/android-arm": "npm:0.27.2" + "@esbuild/android-arm64": "npm:0.27.2" + "@esbuild/android-x64": "npm:0.27.2" + "@esbuild/darwin-arm64": "npm:0.27.2" + "@esbuild/darwin-x64": "npm:0.27.2" + "@esbuild/freebsd-arm64": "npm:0.27.2" + "@esbuild/freebsd-x64": "npm:0.27.2" + "@esbuild/linux-arm": "npm:0.27.2" + "@esbuild/linux-arm64": "npm:0.27.2" + "@esbuild/linux-ia32": "npm:0.27.2" + "@esbuild/linux-loong64": "npm:0.27.2" + "@esbuild/linux-mips64el": "npm:0.27.2" + "@esbuild/linux-ppc64": "npm:0.27.2" + "@esbuild/linux-riscv64": "npm:0.27.2" + "@esbuild/linux-s390x": "npm:0.27.2" + "@esbuild/linux-x64": "npm:0.27.2" + "@esbuild/netbsd-arm64": "npm:0.27.2" + "@esbuild/netbsd-x64": "npm:0.27.2" + "@esbuild/openbsd-arm64": "npm:0.27.2" + "@esbuild/openbsd-x64": "npm:0.27.2" + "@esbuild/openharmony-arm64": "npm:0.27.2" + "@esbuild/sunos-x64": "npm:0.27.2" + "@esbuild/win32-arm64": "npm:0.27.2" + "@esbuild/win32-ia32": "npm:0.27.2" + "@esbuild/win32-x64": "npm:0.27.2" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/cf83f626f55500f521d5fe7f4bc5871bec240d3deb2a01fbd379edc43b3664d1167428738a5aad8794b35d1cca985c44c375b1cd38a2ca613c77ced2c83aafcd + languageName: node + linkType: hard + +"events@npm:^3.3.0": + version: 3.3.0 + resolution: "events@npm:3.3.0" + checksum: 10c0/d6b6f2adbccbcda74ddbab52ed07db727ef52e31a61ed26db9feb7dc62af7fc8e060defa65e5f8af9449b86b52cc1a1f6a79f2eafcf4e62add2b7a1fa4a432f6 + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.3 + resolution: "exponential-backoff@npm:3.1.3" + checksum: 10c0/77e3ae682b7b1f4972f563c6dbcd2b0d54ac679e62d5d32f3e5085feba20483cf28bd505543f520e287a56d4d55a28d7874299941faf637e779a1aa5994d1267 + languageName: node + linkType: hard + +"fast-deep-equal@npm:^3.1.3": + version: 3.1.3 + resolution: "fast-deep-equal@npm:3.1.3" + checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0 + languageName: node + linkType: hard + +"fast-uri@npm:^3.0.1": + version: 3.1.0 + resolution: "fast-uri@npm:3.1.0" + checksum: 10c0/44364adca566f70f40d1e9b772c923138d47efeac2ae9732a872baafd77061f26b097ba2f68f0892885ad177becd065520412b8ffeec34b16c99433c5b9e2de7 + languageName: node + linkType: hard + +"fdir@npm:^6.5.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f + languageName: node + linkType: hard + +"fs-minipass@npm:^3.0.0": + version: 3.0.3 + resolution: "fs-minipass@npm:3.0.3" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94 + languageName: node + linkType: hard + +"fsevents@npm:~2.3.3": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + +"get-tsconfig@npm:^4.7.5": + version: 4.13.0 + resolution: "get-tsconfig@npm:4.13.0" + dependencies: + resolve-pkg-maps: "npm:^1.0.0" + checksum: 10c0/2c49ef8d3907047a107f229fd610386fe3b7fe9e42dfd6b42e7406499493cdda8c62e83e57e8d7a98125610774b9f604d3a0ff308d7f9de5c7ac6d1b07cb6036 + languageName: node + linkType: hard + +"glob@npm:^13.0.0": + version: 13.0.1 + resolution: "glob@npm:13.0.1" + dependencies: + minimatch: "npm:^10.1.2" + minipass: "npm:^7.1.2" + path-scurry: "npm:^2.0.0" + checksum: 10c0/af7b863dec8dff74f61d7d6e53104e1f6bbdd482157a196cade8ed857481e876ec35181b38a059b2a7b93ea3b08248f4ff0792fef6dc91814fd5097a716f48e4 + languageName: node + linkType: hard + +"graceful-fs@npm:^4.2.6": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 + languageName: node + linkType: hard + +"http-cache-semantics@npm:^4.1.1": + version: 4.2.0 + resolution: "http-cache-semantics@npm:4.2.0" + checksum: 10c0/45b66a945cf13ec2d1f29432277201313babf4a01d9e52f44b31ca923434083afeca03f18417f599c9ab3d0e7b618ceb21257542338b57c54b710463b4a53e37 + languageName: node + linkType: hard + +"http-proxy-agent@npm:^7.0.0": + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" + dependencies: + agent-base: "npm:^7.1.0" + debug: "npm:^4.3.4" + checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^7.0.0, https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.5": + version: 7.0.6 + resolution: "https-proxy-agent@npm:7.0.6" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:4" + checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac + languageName: node + linkType: hard + +"iconv-lite@npm:^0.6.2": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 + languageName: node + linkType: hard + +"ieee754@npm:^1.2.1": + version: 1.2.1 + resolution: "ieee754@npm:1.2.1" + checksum: 10c0/b0782ef5e0935b9f12883a2e2aa37baa75da6e66ce6515c168697b42160807d9330de9a32ec1ed73149aea02e0d822e572bca6f1e22bdcbd2149e13b050b17bb + languageName: node + linkType: hard + +"import-meta-resolve@npm:^3.0.0": + version: 3.1.1 + resolution: "import-meta-resolve@npm:3.1.1" + checksum: 10c0/75545f3f0f4f789f15b91a541b2d3e9d5b25fc9e8c60e8423cbdef4fff226f45520bd040219c63eee001878f075e82b52e436ca0d7d05e6c4fdc0348b7f251dd + languageName: node + linkType: hard + +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 + languageName: node + linkType: hard + +"index-to-position@npm:^1.1.0": + version: 1.2.0 + resolution: "index-to-position@npm:1.2.0" + checksum: 10c0/d7ac9fae9fad1d7fbeb7bd92e1553b26e8b10522c2d80af5c362828428a41360e21fc5915d7b8c8227eb0f0d37b12099846ac77381a04d6c0059eb81749e374d + languageName: node + linkType: hard + +"ip-address@npm:^10.0.1": + version: 10.1.0 + resolution: "ip-address@npm:10.1.0" + checksum: 10c0/0103516cfa93f6433b3bd7333fa876eb21263912329bfa47010af5e16934eeeff86f3d2ae700a3744a137839ddfad62b900c7a445607884a49b5d1e32a3d7566 + languageName: node + linkType: hard + +"isexe@npm:^3.1.1": + version: 3.1.2 + resolution: "isexe@npm:3.1.2" + checksum: 10c0/1081adb0e9d8dd6d313916e39c81b683ab0e304bcec388b7bb400da988180dc576be7b298e6cd55d89fc5e98f32c1d73c2e04d2454c6115f58b28a8040d421ed + languageName: node + linkType: hard + +"js-levenshtein@npm:^1.1.6": + version: 1.1.6 + resolution: "js-levenshtein@npm:1.1.6" + checksum: 10c0/14045735325ea1fd87f434a74b11d8a14380f090f154747e613529c7cff68b5ee607f5230fa40665d5fb6125a3791f4c223f73b9feca754f989b059f5c05864f + languageName: node + linkType: hard + +"js-tokens@npm:^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed + languageName: node + linkType: hard + +"js-yaml@npm:^4.1.0": + version: 4.1.1 + resolution: "js-yaml@npm:4.1.1" + dependencies: + argparse: "npm:^2.0.1" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/561c7d7088c40a9bb53cc75becbfb1df6ae49b34b5e6e5a81744b14ae8667ec564ad2527709d1a6e7d5e5fa6d483aa0f373a50ad98d42fde368ec4a190d4fae7 + languageName: node + linkType: hard + +"json-schema-traverse@npm:^1.0.0": + version: 1.0.0 + resolution: "json-schema-traverse@npm:1.0.0" + checksum: 10c0/71e30015d7f3d6dc1c316d6298047c8ef98a06d31ad064919976583eb61e1018a60a0067338f0f79cabc00d84af3fcc489bd48ce8a46ea165d9541ba17fb30c6 + languageName: node + linkType: hard + +"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1": + version: 11.2.5 + resolution: "lru-cache@npm:11.2.5" + checksum: 10c0/cc98958d25dddf1c8a8cbdc49588bd3b24450e8dfa78f32168fd188a20d4a0331c7406d0f3250c86a46619ee288056fd7a1195e8df56dc8a9592397f4fbd8e1d + languageName: node + linkType: hard + +"make-fetch-happen@npm:^15.0.0": + version: 15.0.3 + resolution: "make-fetch-happen@npm:15.0.3" + dependencies: + "@npmcli/agent": "npm:^4.0.0" + cacache: "npm:^20.0.1" + http-cache-semantics: "npm:^4.1.1" + minipass: "npm:^7.0.2" + minipass-fetch: "npm:^5.0.0" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + negotiator: "npm:^1.0.0" + proc-log: "npm:^6.0.0" + promise-retry: "npm:^2.0.1" + ssri: "npm:^13.0.0" + checksum: 10c0/525f74915660be60b616bcbd267c4a5b59481b073ba125e45c9c3a041bb1a47a2bd0ae79d028eb6f5f95bf9851a4158423f5068539c3093621abb64027e8e461 + languageName: node + linkType: hard + +"minimatch@npm:^10.1.2": + version: 10.1.2 + resolution: "minimatch@npm:10.1.2" + dependencies: + "@isaacs/brace-expansion": "npm:^5.0.1" + checksum: 10c0/0cccef3622201703de6ecf9d772c0be1d5513dcc038ed9feb866c20cf798243e678ac35605dac3f1a054650c28037486713fe9e9a34b184b9097959114daf086 + languageName: node + linkType: hard + +"minimatch@npm:^5.0.1": + version: 5.1.6 + resolution: "minimatch@npm:5.1.6" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/3defdfd230914f22a8da203747c42ee3c405c39d4d37ffda284dac5e45b7e1f6c49aa8be606509002898e73091ff2a3bbfc59c2c6c71d4660609f63aa92f98e3 + languageName: node + linkType: hard + +"minipass-collect@npm:^2.0.1": + version: 2.0.1 + resolution: "minipass-collect@npm:2.0.1" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e + languageName: node + linkType: hard + +"minipass-fetch@npm:^5.0.0": + version: 5.0.1 + resolution: "minipass-fetch@npm:5.0.1" + dependencies: + encoding: "npm:^0.1.13" + minipass: "npm:^7.0.3" + minipass-sized: "npm:^2.0.0" + minizlib: "npm:^3.0.1" + dependenciesMeta: + encoding: + optional: true + checksum: 10c0/50bcf48c9841ebb25e29a2817468595219c72cfffc7c175a1d7327843c8bef9b72cb01778f46df7eca695dfe47ab98e6167af4cb026ddd80f660842919a5193c + languageName: node + linkType: hard + +"minipass-flush@npm:^1.0.5": + version: 1.0.5 + resolution: "minipass-flush@npm:1.0.5" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd + languageName: node + linkType: hard + +"minipass-pipeline@npm:^1.2.4": + version: 1.2.4 + resolution: "minipass-pipeline@npm:1.2.4" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2 + languageName: node + linkType: hard + +"minipass-sized@npm:^2.0.0": + version: 2.0.0 + resolution: "minipass-sized@npm:2.0.0" + dependencies: + minipass: "npm:^7.1.2" + checksum: 10c0/f9201696a6f6d68610d04c9c83e3d2e5cb9c026aae1c8cbf7e17f386105cb79c1bb088dbc21bf0b1eb4f3fb5df384fd1e7aa3bf1f33868c416ae8c8a92679db8 + languageName: node + linkType: hard + +"minipass@npm:^3.0.0": + version: 3.3.6 + resolution: "minipass@npm:3.3.6" + dependencies: + yallist: "npm:^4.0.0" + checksum: 10c0/a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c + languageName: node + linkType: hard + +"minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": + version: 7.1.2 + resolution: "minipass@npm:7.1.2" + checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 + languageName: node + linkType: hard + +"minizlib@npm:^3.0.1, minizlib@npm:^3.1.0": + version: 3.1.0 + resolution: "minizlib@npm:3.1.0" + dependencies: + minipass: "npm:^7.1.2" + checksum: 10c0/5aad75ab0090b8266069c9aabe582c021ae53eb33c6c691054a13a45db3b4f91a7fb1bd79151e6b4e9e9a86727b522527c0a06ec7d45206b745d54cd3097bcec + languageName: node + linkType: hard + +"ms@npm:^2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 + languageName: node + linkType: hard + +"negotiator@npm:^1.0.0": + version: 1.0.0 + resolution: "negotiator@npm:1.0.0" + checksum: 10c0/4c559dd52669ea48e1914f9d634227c561221dd54734070791f999c52ed0ff36e437b2e07d5c1f6e32909fc625fe46491c16e4a8f0572567d4dd15c3a4fda04b + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 12.2.0 + resolution: "node-gyp@npm:12.2.0" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + graceful-fs: "npm:^4.2.6" + make-fetch-happen: "npm:^15.0.0" + nopt: "npm:^9.0.0" + proc-log: "npm:^6.0.0" + semver: "npm:^7.3.5" + tar: "npm:^7.5.4" + tinyglobby: "npm:^0.2.12" + which: "npm:^6.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/3ed046746a5a7d90950cd8b0547332b06598443f31fe213ef4332a7174c7b7d259e1704835feda79b87d3f02e59d7791842aac60642ede4396ab25fdf0f8f759 + languageName: node + linkType: hard + +"nopt@npm:^9.0.0": + version: 9.0.0 + resolution: "nopt@npm:9.0.0" + dependencies: + abbrev: "npm:^4.0.0" + bin: + nopt: bin/nopt.js + checksum: 10c0/1822eb6f9b020ef6f7a7516d7b64a8036e09666ea55ac40416c36e4b2b343122c3cff0e2f085675f53de1d2db99a2a89a60ccea1d120bcd6a5347bf6ceb4a7fd + languageName: node + linkType: hard + +"openapi-typescript@npm:^7.10.1": + version: 7.10.1 + resolution: "openapi-typescript@npm:7.10.1" + dependencies: + "@redocly/openapi-core": "npm:^1.34.5" + ansi-colors: "npm:^4.1.3" + change-case: "npm:^5.4.4" + parse-json: "npm:^8.3.0" + supports-color: "npm:^10.2.2" + yargs-parser: "npm:^21.1.1" + peerDependencies: + typescript: ^5.x + bin: + openapi-typescript: bin/cli.js + checksum: 10c0/85d2075c27a3802cca50838d4ead52d6a3443f964b0d1601e82cdea5dc317c62a2452f23caafd11991101c8420f326f2977cda2fa126e517c59f57b73017ccff + languageName: node + linkType: hard + +"p-map@npm:^7.0.2": + version: 7.0.4 + resolution: "p-map@npm:7.0.4" + checksum: 10c0/a5030935d3cb2919d7e89454d1ce82141e6f9955413658b8c9403cfe379283770ed3048146b44cde168aa9e8c716505f196d5689db0ae3ce9a71521a2fef3abd + languageName: node + linkType: hard + +"parse-json@npm:^8.3.0": + version: 8.3.0 + resolution: "parse-json@npm:8.3.0" + dependencies: + "@babel/code-frame": "npm:^7.26.2" + index-to-position: "npm:^1.1.0" + type-fest: "npm:^4.39.1" + checksum: 10c0/0eb5a50f88b8428c8f7a9cf021636c16664f0c62190323652d39e7bdf62953e7c50f9957e55e17dc2d74fc05c89c11f5553f381dbc686735b537ea9b101c7153 + languageName: node + linkType: hard + +"path-scurry@npm:^2.0.0": + version: 2.0.1 + resolution: "path-scurry@npm:2.0.1" + dependencies: + lru-cache: "npm:^11.0.0" + minipass: "npm:^7.1.2" + checksum: 10c0/2a16ed0e81fbc43513e245aa5763354e25e787dab0d539581a6c3f0f967461a159ed6236b2559de23aa5b88e7dc32b469b6c47568833dd142a4b24b4f5cd2620 + languageName: node + linkType: hard + +"picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 + languageName: node + linkType: hard + +"picomatch@npm:^4.0.3": + version: 4.0.3 + resolution: "picomatch@npm:4.0.3" + checksum: 10c0/9582c951e95eebee5434f59e426cddd228a7b97a0161a375aed4be244bd3fe8e3a31b846808ea14ef2c8a2527a6eeab7b3946a67d5979e81694654f939473ae2 + languageName: node + linkType: hard + +"pluralize@npm:^8.0.0": + version: 8.0.0 + resolution: "pluralize@npm:8.0.0" + checksum: 10c0/2044cfc34b2e8c88b73379ea4a36fc577db04f651c2909041b054c981cd863dd5373ebd030123ab058d194ae615d3a97cfdac653991e499d10caf592e8b3dc33 + languageName: node + linkType: hard + +"proc-log@npm:^6.0.0": + version: 6.1.0 + resolution: "proc-log@npm:6.1.0" + checksum: 10c0/4f178d4062733ead9d71a9b1ab24ebcecdfe2250916a5b1555f04fe2eda972a0ec76fbaa8df1ad9c02707add6749219d118a4fc46dc56bdfe4dde4b47d80bb82 + languageName: node + linkType: hard + +"promise-retry@npm:^2.0.1": + version: 2.0.1 + resolution: "promise-retry@npm:2.0.1" + dependencies: + err-code: "npm:^2.0.2" + retry: "npm:^0.12.0" + checksum: 10c0/9c7045a1a2928094b5b9b15336dcd2a7b1c052f674550df63cc3f36cd44028e5080448175b6f6ca32b642de81150f5e7b1a98b728f15cb069f2dd60ac2616b96 + languageName: node + linkType: hard + +"require-from-string@npm:^2.0.2": + version: 2.0.2 + resolution: "require-from-string@npm:2.0.2" + checksum: 10c0/aaa267e0c5b022fc5fd4eef49d8285086b15f2a1c54b28240fdf03599cbd9c26049fee3eab894f2e1f6ca65e513b030a7c264201e3f005601e80c49fb2937ce2 + languageName: node + linkType: hard + +"resolve-pkg-maps@npm:^1.0.0": + version: 1.0.0 + resolution: "resolve-pkg-maps@npm:1.0.0" + checksum: 10c0/fb8f7bbe2ca281a73b7ef423a1cbc786fb244bd7a95cbe5c3fba25b27d327150beca8ba02f622baea65919a57e061eb5005204daa5f93ed590d9b77463a567ab + languageName: node + linkType: hard + +"retry@npm:^0.12.0": + version: 0.12.0 + resolution: "retry@npm:0.12.0" + checksum: 10c0/59933e8501727ba13ad73ef4a04d5280b3717fd650408460c987392efe9d7be2040778ed8ebe933c5cbd63da3dcc37919c141ef8af0a54a6e4fca5a2af177bfe + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 + languageName: node + linkType: hard + +"semver@npm:^7.3.5": + version: 7.7.4 + resolution: "semver@npm:7.7.4" + bin: + semver: bin/semver.js + checksum: 10c0/5215ad0234e2845d4ea5bb9d836d42b03499546ddafb12075566899fc617f68794bb6f146076b6881d755de17d6c6cc73372555879ec7dce2c2feee947866ad2 + languageName: node + linkType: hard + +"smart-buffer@npm:^4.2.0": + version: 4.2.0 + resolution: "smart-buffer@npm:4.2.0" + checksum: 10c0/a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539 + languageName: node + linkType: hard + +"socks-proxy-agent@npm:^8.0.3": + version: 8.0.5 + resolution: "socks-proxy-agent@npm:8.0.5" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:^4.3.4" + socks: "npm:^2.8.3" + checksum: 10c0/5d2c6cecba6821389aabf18728325730504bf9bb1d9e342e7987a5d13badd7a98838cc9a55b8ed3cb866ad37cc23e1086f09c4d72d93105ce9dfe76330e9d2a6 + languageName: node + linkType: hard + +"socks@npm:^2.8.3": + version: 2.8.7 + resolution: "socks@npm:2.8.7" + dependencies: + ip-address: "npm:^10.0.1" + smart-buffer: "npm:^4.2.0" + checksum: 10c0/2805a43a1c4bcf9ebf6e018268d87b32b32b06fbbc1f9282573583acc155860dc361500f89c73bfbb157caa1b4ac78059eac0ef15d1811eb0ca75e0bdadbc9d2 + languageName: node + linkType: hard + +"ssri@npm:^13.0.0": + version: 13.0.0 + resolution: "ssri@npm:13.0.0" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/405f3a531cd98b013cecb355d63555dca42fd12c7bc6671738aaa9a82882ff41cdf0ef9a2b734ca4f9a760338f114c29d01d9238a65db3ccac27929bd6e6d4b2 + languageName: node + linkType: hard + +"supports-color@npm:^10.2.2": + version: 10.2.2 + resolution: "supports-color@npm:10.2.2" + checksum: 10c0/fb28dd7e0cdf80afb3f2a41df5e068d60c8b4f97f7140de2eaed5b42e075d82a0e980b20a2c0efd2b6d73cfacb55555285d8cc719fa0472220715aefeaa1da7c + languageName: node + linkType: hard + +"tar@npm:^7.5.4": + version: 7.5.7 + resolution: "tar@npm:7.5.7" + dependencies: + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.1.0" + yallist: "npm:^5.0.0" + checksum: 10c0/51f261afc437e1112c3e7919478d6176ea83f7f7727864d8c2cce10f0b03a631d1911644a567348c3063c45abdae39718ba97abb073d22aa3538b9a53ae1e31c + languageName: node + linkType: hard + +"tinyglobby@npm:^0.2.12": + version: 0.2.15 + resolution: "tinyglobby@npm:0.2.15" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.3" + checksum: 10c0/869c31490d0d88eedb8305d178d4c75e7463e820df5a9b9d388291daf93e8b1eb5de1dad1c1e139767e4269fe75f3b10d5009b2cc14db96ff98986920a186844 + languageName: node + linkType: hard + +"tslib@npm:^2.6.2, tslib@npm:^2.8.1": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 + languageName: node + linkType: hard + +"tsx@npm:^4.21.0": + version: 4.21.0 + resolution: "tsx@npm:4.21.0" + dependencies: + esbuild: "npm:~0.27.0" + fsevents: "npm:~2.3.3" + get-tsconfig: "npm:^4.7.5" + dependenciesMeta: + fsevents: + optional: true + bin: + tsx: dist/cli.mjs + checksum: 10c0/f5072923cd8459a1f9a26df87823a2ab5754641739d69df2a20b415f61814322b751fa6be85db7c6ec73cf68ba8fac2fd1cfc76bdb0aa86ded984d84d5d2126b + languageName: node + linkType: hard + +"type-fest@npm:^4.39.1": + version: 4.41.0 + resolution: "type-fest@npm:4.41.0" + checksum: 10c0/f5ca697797ed5e88d33ac8f1fec21921839871f808dc59345c9cf67345bfb958ce41bd821165dbf3ae591cedec2bf6fe8882098dfdd8dc54320b859711a2c1e4 + languageName: node + linkType: hard + +"typescript@npm:^5.9.3": + version: 5.9.3 + resolution: "typescript@npm:5.9.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/6bd7552ce39f97e711db5aa048f6f9995b53f1c52f7d8667c1abdc1700c68a76a308f579cd309ce6b53646deb4e9a1be7c813a93baaf0a28ccd536a30270e1c5 + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^5.9.3#optional!builtin": + version: 5.9.3 + resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/ad09fdf7a756814dce65bc60c1657b40d44451346858eea230e10f2e95a289d9183b6e32e5c11e95acc0ccc214b4f36289dcad4bf1886b0adb84d711d336a430 + languageName: node + linkType: hard + +"undici-types@npm:~7.16.0": + version: 7.16.0 + resolution: "undici-types@npm:7.16.0" + checksum: 10c0/3033e2f2b5c9f1504bdc5934646cb54e37ecaca0f9249c983f7b1fc2e87c6d18399ebb05dc7fd5419e02b2e915f734d872a65da2e3eeed1813951c427d33cc9a + languageName: node + linkType: hard + +"unique-filename@npm:^5.0.0": + version: 5.0.0 + resolution: "unique-filename@npm:5.0.0" + dependencies: + unique-slug: "npm:^6.0.0" + checksum: 10c0/afb897e9cf4c2fb622ea716f7c2bb462001928fc5f437972213afdf1cc32101a230c0f1e9d96fc91ee5185eca0f2feb34127145874975f347be52eb91d6ccc2c + languageName: node + linkType: hard + +"unique-slug@npm:^6.0.0": + version: 6.0.0 + resolution: "unique-slug@npm:6.0.0" + dependencies: + imurmurhash: "npm:^0.1.4" + checksum: 10c0/da7ade4cb04eb33ad0499861f82fe95ce9c7c878b7139dc54d140ecfb6a6541c18a5c8dac16188b8b379fe62c0c1f1b710814baac910cde5f4fec06212126c6a + languageName: node + linkType: hard + +"which@npm:^6.0.0": + version: 6.0.0 + resolution: "which@npm:6.0.0" + dependencies: + isexe: "npm:^3.1.1" + bin: + node-which: bin/which.js + checksum: 10c0/fe9d6463fe44a76232bb6e3b3181922c87510a5b250a98f1e43a69c99c079b3f42ddeca7e03d3e5f2241bf2d334f5a7657cfa868b97c109f3870625842f4cc15 + languageName: node + linkType: hard + +"ws@npm:^8.0.0, ws@npm:^8.18.0": + version: 8.19.0 + resolution: "ws@npm:8.19.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/4741d9b9bc3f9c791880882414f96e36b8b254e34d4b503279d6400d9a4b87a033834856dbdd94ee4b637944df17ea8afc4bce0ff4a1560d2166be8855da5b04 + languageName: node + linkType: hard + +"yallist@npm:^4.0.0": + version: 4.0.0 + resolution: "yallist@npm:4.0.0" + checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a + languageName: node + linkType: hard + +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 + languageName: node + linkType: hard + +"yaml-ast-parser@npm:0.0.43": + version: 0.0.43 + resolution: "yaml-ast-parser@npm:0.0.43" + checksum: 10c0/4d2f1e761067b2c6abdd882279a406f879258787af470a6d4a659cb79cb2ab056b870b25f1f80f46ed556e8b499d611d247806376f53edf3412f72c0a8ea2e98 + languageName: node + linkType: hard + +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 + languageName: node + linkType: hard