feat(p8): /v1/calendar REST + kernel-client methods + @insignia/iios-meeting-web
Task 8.6: CalendarController exposes schedule/request(+fromCallback/fromClaim)/ list/get/consent/transcript/summarize/action-items/providers(+sync) (session-auth). kernel-client RestClient gains scheduleMeeting/requestMeeting/listMeetings/ getMeeting/setConsent/generateTranscript/summarizeMeeting/listActionItems/ connect+syncCalendarProvider + Meeting/MeetingParticipant/MeetingActionItem types. New @insignia/iios-meeting-web (MeetingProvider + useMeetings/useMeeting/useSchedule/ useConsent/useSummarize/useActionItems). Boundary: meeting-web -> contracts + kernel-client (fails on meeting-web->service). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import type { IngestInteractionRequest } from '@insignia/iios-contracts';
|
import type { IngestInteractionRequest } from '@insignia/iios-contracts';
|
||||||
import type { Message, InboxItem, InboxState, Ticket, TicketState, CallbackRequest, RouteBinding, RouteDecision, AiArtifact, AiJobResult } from './types';
|
import type { Message, InboxItem, InboxState, Ticket, TicketState, CallbackRequest, RouteBinding, RouteDecision, AiArtifact, AiJobResult, Meeting, MeetingActionItem } from './types';
|
||||||
|
|
||||||
export interface RestConfig {
|
export interface RestConfig {
|
||||||
serviceUrl: string;
|
serviceUrl: string;
|
||||||
@@ -149,6 +149,55 @@ export class RestClient {
|
|||||||
return this.post<AiArtifact>(`/v1/ai/artifacts/${id}/reject`, {});
|
return this.post<AiArtifact>(`/v1/ai/artifacts/${id}/reject`, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Calendar & Meeting (P8) ──────────────────────────────────
|
||||||
|
async scheduleMeeting(input: {
|
||||||
|
meetingType: string;
|
||||||
|
title: string;
|
||||||
|
startAt: string;
|
||||||
|
endAt?: string;
|
||||||
|
timezone?: string;
|
||||||
|
attendees?: { userId: string; displayName?: string; role?: string; visibility?: string }[];
|
||||||
|
}): Promise<Meeting> {
|
||||||
|
return this.post<Meeting>('/v1/calendar/meetings', input);
|
||||||
|
}
|
||||||
|
async requestMeeting(input: { requestedWindow: Record<string, unknown>; meetingType?: string }, opts?: { fromCallback?: string; fromClaim?: string }): Promise<unknown> {
|
||||||
|
const q = new URLSearchParams();
|
||||||
|
if (opts?.fromCallback) q.set('fromCallback', opts.fromCallback);
|
||||||
|
if (opts?.fromClaim) q.set('fromClaim', opts.fromClaim);
|
||||||
|
const qs = q.toString();
|
||||||
|
return this.post(`/v1/calendar/requests${qs ? `?${qs}` : ''}`, input);
|
||||||
|
}
|
||||||
|
async listMeetings(): Promise<Meeting[]> {
|
||||||
|
const r = await fetch(this.url('/v1/calendar/meetings'), { headers: this.headers() });
|
||||||
|
if (!r.ok) throw new Error(`listMeetings ${r.status}`);
|
||||||
|
return (await r.json()) as Meeting[];
|
||||||
|
}
|
||||||
|
async getMeeting(id: string): Promise<Meeting> {
|
||||||
|
const r = await fetch(this.url(`/v1/calendar/meetings/${id}`), { headers: this.headers() });
|
||||||
|
if (!r.ok) throw new Error(`getMeeting ${r.status}`);
|
||||||
|
return (await r.json()) as Meeting;
|
||||||
|
}
|
||||||
|
async setConsent(meetingId: string, actorRefId: string, status: string): Promise<unknown> {
|
||||||
|
return this.post(`/v1/calendar/meetings/${meetingId}/consent`, { actorRefId, status });
|
||||||
|
}
|
||||||
|
async generateTranscript(meetingId: string): Promise<unknown> {
|
||||||
|
return this.post(`/v1/calendar/meetings/${meetingId}/transcript`, {});
|
||||||
|
}
|
||||||
|
async summarizeMeeting(meetingId: string): Promise<Meeting> {
|
||||||
|
return this.post<Meeting>(`/v1/calendar/meetings/${meetingId}/summarize`, {});
|
||||||
|
}
|
||||||
|
async listActionItems(meetingId: string): Promise<MeetingActionItem[]> {
|
||||||
|
const r = await fetch(this.url(`/v1/calendar/meetings/${meetingId}/action-items`), { headers: this.headers() });
|
||||||
|
if (!r.ok) throw new Error(`listActionItems ${r.status}`);
|
||||||
|
return (await r.json()) as MeetingActionItem[];
|
||||||
|
}
|
||||||
|
async connectCalendarProvider(): Promise<{ id: string }> {
|
||||||
|
return this.post<{ id: string }>('/v1/calendar/providers', {});
|
||||||
|
}
|
||||||
|
async syncCalendarProvider(id: string): Promise<{ pulled: number; cursor: string }> {
|
||||||
|
return this.post(`/v1/calendar/providers/${id}/sync`, {});
|
||||||
|
}
|
||||||
|
|
||||||
private async post<T>(path: string, body: unknown): Promise<T> {
|
private async post<T>(path: string, body: unknown): Promise<T> {
|
||||||
const r = await fetch(this.url(path), { method: 'POST', headers: this.headers(), body: JSON.stringify(body) });
|
const r = await fetch(this.url(path), { method: 'POST', headers: this.headers(), body: JSON.stringify(body) });
|
||||||
if (!r.ok) throw new Error(`POST ${path} ${r.status}`);
|
if (!r.ok) throw new Error(`POST ${path} ${r.status}`);
|
||||||
|
|||||||
@@ -150,6 +150,36 @@ export interface AiJobResult {
|
|||||||
artifact: AiArtifact | null;
|
artifact: AiArtifact | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MeetingParticipant {
|
||||||
|
id: string;
|
||||||
|
actorRefId?: string | null;
|
||||||
|
role: string;
|
||||||
|
attendanceStatus: string;
|
||||||
|
recordingConsent: string;
|
||||||
|
visibility: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Meeting {
|
||||||
|
id: string;
|
||||||
|
meetingType: string;
|
||||||
|
status: string;
|
||||||
|
title: string;
|
||||||
|
organizerActorId: string;
|
||||||
|
startAt?: string | null;
|
||||||
|
endAt?: string | null;
|
||||||
|
timezone: string;
|
||||||
|
calendarEventId?: string | null;
|
||||||
|
participants?: MeetingParticipant[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MeetingActionItem {
|
||||||
|
id: string;
|
||||||
|
meetingId: string;
|
||||||
|
actionItemId: string;
|
||||||
|
status: string;
|
||||||
|
actionItem?: { id: string; title: string; status: string; ownerActorId?: string | null };
|
||||||
|
}
|
||||||
|
|
||||||
/** Minimal socket surface so the facade can be unit-tested with a fake. */
|
/** Minimal socket surface so the facade can be unit-tested with a fake. */
|
||||||
export interface SocketLike {
|
export interface SocketLike {
|
||||||
on(event: string, handler: (...args: unknown[]) => void): unknown;
|
on(event: string, handler: (...args: unknown[]) => void): unknown;
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "@insignia/iios-meeting-web",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"module": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" } },
|
||||||
|
"files": ["dist"],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsup",
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@insignia/iios-kernel-client": "workspace:*"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=18"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^19.0.0",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"tsup": "^8.3.5",
|
||||||
|
"typescript": "^5.7.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { MeetingProvider, useMeetings, useMeeting, useSchedule, useConsent, useSummarize, useActionItems } from './react';
|
||||||
|
export type { Meeting, MeetingParticipant, MeetingActionItem } from '@insignia/iios-kernel-client';
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { RestClient, type Meeting, type MeetingActionItem } from '@insignia/iios-kernel-client';
|
||||||
|
|
||||||
|
const MeetingContext = createContext<RestClient | null>(null);
|
||||||
|
|
||||||
|
export function MeetingProvider({
|
||||||
|
serviceUrl,
|
||||||
|
token,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
serviceUrl: string;
|
||||||
|
token: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}): React.ReactElement {
|
||||||
|
const client = useMemo(() => new RestClient({ serviceUrl, token }), [serviceUrl, token]);
|
||||||
|
return <MeetingContext.Provider value={client}>{children}</MeetingContext.Provider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function useClient(): RestClient {
|
||||||
|
const c = useContext(MeetingContext);
|
||||||
|
if (!c) throw new Error('meeting hooks must be used within <MeetingProvider>');
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useMeetings(opts?: { pollMs?: number }): { meetings: Meeting[]; refresh: () => Promise<void> } {
|
||||||
|
const client = useClient();
|
||||||
|
const [meetings, setMeetings] = useState<Meeting[]>([]);
|
||||||
|
const refresh = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setMeetings(await client.listMeetings());
|
||||||
|
} catch {
|
||||||
|
/* transient */
|
||||||
|
}
|
||||||
|
}, [client]);
|
||||||
|
useEffect(() => {
|
||||||
|
void refresh();
|
||||||
|
if (!opts?.pollMs) return;
|
||||||
|
const t = setInterval(() => void refresh(), opts.pollMs);
|
||||||
|
return () => clearInterval(t);
|
||||||
|
}, [refresh, opts?.pollMs]);
|
||||||
|
return { meetings, refresh };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useMeeting(id: string | null): { meeting: Meeting | null; refresh: () => Promise<void> } {
|
||||||
|
const client = useClient();
|
||||||
|
const [meeting, setMeeting] = useState<Meeting | null>(null);
|
||||||
|
const refresh = useCallback(async () => {
|
||||||
|
if (!id) return setMeeting(null);
|
||||||
|
try {
|
||||||
|
setMeeting(await client.getMeeting(id));
|
||||||
|
} catch {
|
||||||
|
/* transient */
|
||||||
|
}
|
||||||
|
}, [client, id]);
|
||||||
|
useEffect(() => {
|
||||||
|
void refresh();
|
||||||
|
}, [refresh]);
|
||||||
|
return { meeting, refresh };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSchedule(): (input: Parameters<RestClient['scheduleMeeting']>[0]) => Promise<Meeting> {
|
||||||
|
const client = useClient();
|
||||||
|
return (input) => client.scheduleMeeting(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useConsent(): {
|
||||||
|
setConsent: (meetingId: string, actorRefId: string, status: string) => Promise<unknown>;
|
||||||
|
generateTranscript: (meetingId: string) => Promise<unknown>;
|
||||||
|
} {
|
||||||
|
const client = useClient();
|
||||||
|
return {
|
||||||
|
setConsent: (meetingId, actorRefId, status) => client.setConsent(meetingId, actorRefId, status),
|
||||||
|
generateTranscript: (meetingId) => client.generateTranscript(meetingId),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSummarize(): (meetingId: string) => Promise<Meeting> {
|
||||||
|
const client = useClient();
|
||||||
|
return (meetingId) => client.summarizeMeeting(meetingId);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useActionItems(meetingId: string | null): { actionItems: MeetingActionItem[]; refresh: () => Promise<void> } {
|
||||||
|
const client = useClient();
|
||||||
|
const [actionItems, setActionItems] = useState<MeetingActionItem[]>([]);
|
||||||
|
const refresh = useCallback(async () => {
|
||||||
|
if (!meetingId) return setActionItems([]);
|
||||||
|
try {
|
||||||
|
setActionItems(await client.listActionItems(meetingId));
|
||||||
|
} catch {
|
||||||
|
/* transient */
|
||||||
|
}
|
||||||
|
}, [client, meetingId]);
|
||||||
|
useEffect(() => {
|
||||||
|
void refresh();
|
||||||
|
}, [refresh]);
|
||||||
|
return { actionItems, refresh };
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"lib": ["ES2022", "DOM"],
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"types": ["react"]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
||||||
|
"exclude": ["src/**/*.test.ts", "src/**/*.test.tsx"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { defineConfig } from 'tsup';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
entry: ['src/index.ts'],
|
||||||
|
format: ['esm'],
|
||||||
|
dts: true,
|
||||||
|
clean: true,
|
||||||
|
external: ['react', 'react-dom', '@insignia/iios-kernel-client'],
|
||||||
|
});
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { BadRequestException, Body, Controller, Get, Headers, Param, Post, Query } from '@nestjs/common';
|
||||||
|
import { IiosConsentStatus, IiosMeetingType } from '@prisma/client';
|
||||||
|
import { CalendarService } from './calendar.service';
|
||||||
|
import { CalendarAdapter } from './calendar-adapter';
|
||||||
|
import { SessionVerifier } from '../platform/session.verifier';
|
||||||
|
import type { MessagePrincipal } from '../identity/actor.resolver';
|
||||||
|
import { ConsentDto, RequestMeetingDto, ScheduleMeetingDto } from './calendar.dto';
|
||||||
|
|
||||||
|
@Controller('v1/calendar')
|
||||||
|
export class CalendarController {
|
||||||
|
constructor(
|
||||||
|
private readonly calendar: CalendarService,
|
||||||
|
private readonly adapter: CalendarAdapter,
|
||||||
|
private readonly session: SessionVerifier,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Post('meetings')
|
||||||
|
async schedule(@Body() body: ScheduleMeetingDto, @Headers('authorization') auth?: string) {
|
||||||
|
return this.calendar.scheduleDirect(this.principal(auth), { ...body, meetingType: body.meetingType as IiosMeetingType });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('requests')
|
||||||
|
async request(
|
||||||
|
@Body() body: RequestMeetingDto,
|
||||||
|
@Query('fromCallback') fromCallback?: string,
|
||||||
|
@Query('fromClaim') fromClaim?: string,
|
||||||
|
@Headers('authorization') auth?: string,
|
||||||
|
) {
|
||||||
|
const principal = this.principal(auth);
|
||||||
|
if (fromCallback) return this.calendar.fromCallback(principal, fromCallback, body.requestedWindow);
|
||||||
|
if (fromClaim) return this.calendar.fromEventClaim(principal, fromClaim, body.requestedWindow);
|
||||||
|
return this.calendar.requestMeeting(principal, {
|
||||||
|
requestedWindow: body.requestedWindow,
|
||||||
|
meetingType: body.meetingType as IiosMeetingType | undefined,
|
||||||
|
interactionId: body.interactionId,
|
||||||
|
targetActorId: body.targetActorId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('meetings')
|
||||||
|
async list(@Headers('authorization') auth?: string) {
|
||||||
|
return this.calendar.listMeetings(this.principal(auth));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('meetings/:id')
|
||||||
|
async get(@Param('id') id: string, @Headers('authorization') auth?: string) {
|
||||||
|
this.principal(auth);
|
||||||
|
return this.calendar.getMeeting(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('meetings/:id/consent')
|
||||||
|
async consent(@Param('id') id: string, @Body() body: ConsentDto, @Headers('authorization') auth?: string) {
|
||||||
|
this.principal(auth);
|
||||||
|
return this.calendar.setConsent(id, body.actorRefId, body.status as IiosConsentStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('meetings/:id/transcript')
|
||||||
|
async transcript(@Param('id') id: string, @Headers('authorization') auth?: string) {
|
||||||
|
return this.calendar.generateTranscript(id, this.principal(auth));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('meetings/:id/summarize')
|
||||||
|
async summarize(@Param('id') id: string, @Headers('authorization') auth?: string) {
|
||||||
|
return this.calendar.summarize(id, this.principal(auth));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('meetings/:id/action-items')
|
||||||
|
async actionItems(@Param('id') id: string, @Headers('authorization') auth?: string) {
|
||||||
|
this.principal(auth);
|
||||||
|
return this.calendar.listActionItems(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('providers')
|
||||||
|
async connect(@Headers('authorization') auth?: string) {
|
||||||
|
return this.adapter.connectProvider(this.principal(auth));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('providers/:id/sync')
|
||||||
|
async sync(@Param('id') id: string, @Headers('authorization') auth?: string) {
|
||||||
|
this.principal(auth);
|
||||||
|
return this.adapter.syncOnce(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private principal(authorization?: string): MessagePrincipal {
|
||||||
|
const token = (authorization ?? '').replace(/^Bearer\s+/i, '');
|
||||||
|
if (!token) throw new BadRequestException('Authorization bearer token is required');
|
||||||
|
return this.session.verify(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { IsArray, IsIn, IsObject, IsOptional, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
const MEETING_TYPES = ['ZOOM', 'PHONE', 'IN_PERSON', 'CALLBACK', 'INTERNAL'] as const;
|
||||||
|
const CONSENT = ['UNKNOWN', 'GRANTED', 'DENIED', 'REVOKED'] as const;
|
||||||
|
|
||||||
|
export class ScheduleMeetingDto {
|
||||||
|
@IsIn(MEETING_TYPES) meetingType!: (typeof MEETING_TYPES)[number];
|
||||||
|
@IsString() title!: string;
|
||||||
|
@IsString() startAt!: string;
|
||||||
|
@IsOptional() @IsString() endAt?: string;
|
||||||
|
@IsOptional() @IsString() timezone?: string;
|
||||||
|
@IsOptional() @IsArray() attendees?: { userId: string; displayName?: string; role?: 'REQUIRED' | 'OPTIONAL'; visibility?: 'FULL' | 'LIMITED' | 'NONE' }[];
|
||||||
|
@IsOptional() @IsString() requestId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RequestMeetingDto {
|
||||||
|
@IsObject() requestedWindow!: Record<string, unknown>;
|
||||||
|
@IsOptional() @IsIn(MEETING_TYPES) meetingType?: (typeof MEETING_TYPES)[number];
|
||||||
|
@IsOptional() @IsString() interactionId?: string;
|
||||||
|
@IsOptional() @IsString() targetActorId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ConsentDto {
|
||||||
|
@IsString() actorRefId!: string;
|
||||||
|
@IsIn(CONSENT) status!: (typeof CONSENT)[number];
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import { AiModule } from '../ai/ai.module';
|
|||||||
import { CalendarService } from './calendar.service';
|
import { CalendarService } from './calendar.service';
|
||||||
import { CalendarProjector } from './calendar.projector';
|
import { CalendarProjector } from './calendar.projector';
|
||||||
import { CalendarAdapter } from './calendar-adapter';
|
import { CalendarAdapter } from './calendar-adapter';
|
||||||
|
import { CalendarController } from './calendar.controller';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calendar & Meeting SDK (P8). Imports OutboxModule (projector/events),
|
* Calendar & Meeting SDK (P8). Imports OutboxModule (projector/events),
|
||||||
@@ -13,6 +14,7 @@ import { CalendarAdapter } from './calendar-adapter';
|
|||||||
*/
|
*/
|
||||||
@Module({
|
@Module({
|
||||||
imports: [OutboxModule, AdaptersModule, AiModule],
|
imports: [OutboxModule, AdaptersModule, AiModule],
|
||||||
|
controllers: [CalendarController],
|
||||||
providers: [CalendarService, CalendarProjector, CalendarAdapter],
|
providers: [CalendarService, CalendarProjector, CalendarAdapter],
|
||||||
exports: [CalendarService, CalendarAdapter],
|
exports: [CalendarService, CalendarAdapter],
|
||||||
})
|
})
|
||||||
|
|||||||
Generated
+19
@@ -257,6 +257,25 @@ importers:
|
|||||||
specifier: ^5.7.3
|
specifier: ^5.7.3
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
|
|
||||||
|
packages/iios-meeting-web:
|
||||||
|
dependencies:
|
||||||
|
'@insignia/iios-kernel-client':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../iios-kernel-client
|
||||||
|
devDependencies:
|
||||||
|
'@types/react':
|
||||||
|
specifier: ^19.0.0
|
||||||
|
version: 19.2.17
|
||||||
|
react:
|
||||||
|
specifier: ^19.0.0
|
||||||
|
version: 19.2.7
|
||||||
|
tsup:
|
||||||
|
specifier: ^8.3.5
|
||||||
|
version: 8.5.1(jiti@2.7.0)(postcss@8.5.16)(typescript@5.9.3)
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.7.3
|
||||||
|
version: 5.9.3
|
||||||
|
|
||||||
packages/iios-message-web:
|
packages/iios-message-web:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@insignia/iios-kernel-client':
|
'@insignia/iios-kernel-client':
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ const ALLOWED = {
|
|||||||
'@insignia/iios-inbox-web': ['@insignia/iios-contracts', '@insignia/iios-kernel-client'],
|
'@insignia/iios-inbox-web': ['@insignia/iios-contracts', '@insignia/iios-kernel-client'],
|
||||||
'@insignia/iios-community-web': ['@insignia/iios-contracts', '@insignia/iios-kernel-client'],
|
'@insignia/iios-community-web': ['@insignia/iios-contracts', '@insignia/iios-kernel-client'],
|
||||||
'@insignia/iios-ai-web': ['@insignia/iios-contracts', '@insignia/iios-kernel-client'],
|
'@insignia/iios-ai-web': ['@insignia/iios-contracts', '@insignia/iios-kernel-client'],
|
||||||
|
'@insignia/iios-meeting-web': ['@insignia/iios-contracts', '@insignia/iios-kernel-client'],
|
||||||
'@insignia/iios-support-web': [
|
'@insignia/iios-support-web': [
|
||||||
'@insignia/iios-contracts',
|
'@insignia/iios-contracts',
|
||||||
'@insignia/iios-kernel-client',
|
'@insignia/iios-kernel-client',
|
||||||
|
|||||||
Reference in New Issue
Block a user