feat(apps): experimental member room IDs read bridge (#37057)

Co-authored-by: Douglas Gubert <1810309+d-gubert@users.noreply.github.com>
pull/37254/head
デワンシュ 8 months ago committed by GitHub
parent b85e96a2b7
commit c253db3ece
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      .changeset/chatty-foxes-attend.md
  2. 6
      apps/meteor/app/apps/server/bridges/bridges.js
  3. 17
      apps/meteor/app/apps/server/bridges/experimental.ts
  4. 1
      packages/apps-engine/deno-runtime/lib/accessors/mod.ts
  5. 16
      packages/apps-engine/src/definition/accessors/IExperimentalRead.ts
  6. 3
      packages/apps-engine/src/definition/accessors/IRead.ts
  7. 1
      packages/apps-engine/src/definition/accessors/index.ts
  8. 13
      packages/apps-engine/src/server/accessors/ExperimentalRead.ts
  9. 7
      packages/apps-engine/src/server/accessors/Reader.ts
  10. 4
      packages/apps-engine/src/server/bridges/AppBridges.ts
  11. 40
      packages/apps-engine/src/server/bridges/ExperimentalBridge.ts
  12. 2
      packages/apps-engine/src/server/bridges/index.ts
  13. 21
      packages/apps-engine/src/server/managers/AppAccessorManager.ts
  14. 3
      packages/apps-engine/src/server/permissions/AppPermissions.ts
  15. 6
      packages/apps-engine/tests/server/accessors/Reader.spec.ts
  16. 9
      packages/apps-engine/tests/test-data/bridges/appBridges.ts
  17. 7
      packages/apps-engine/tests/test-data/bridges/experimentalBridge.ts

@ -0,0 +1,6 @@
---
'@rocket.chat/apps-engine': minor
'@rocket.chat/meteor': minor
---
Adds an experimental API to the apps-engine that retrieves the ids of rooms the user is a member of

@ -8,6 +8,7 @@ import { AppContactBridge } from './contact';
import { AppDetailChangesBridge } from './details';
import { AppEmailBridge } from './email';
import { AppEnvironmentalVariableBridge } from './environmental';
import { AppExperimentalBridge } from './experimental';
import { AppHttpBridge } from './http';
import { AppInternalBridge } from './internal';
import { AppInternalFederationBridge } from './internalFederation';
@ -59,6 +60,7 @@ export class RealAppBridges extends AppBridges {
this._emailBridge = new AppEmailBridge(orch);
this._contactBridge = new AppContactBridge(orch);
this._outboundMessageBridge = new OutboundCommunicationBridge(orch);
this._experimentalBridge = new AppExperimentalBridge(orch);
}
getCommandBridge() {
@ -168,4 +170,8 @@ export class RealAppBridges extends AppBridges {
getContactBridge() {
return this._contactBridge;
}
getExperimentalBridge() {
return this._experimentalBridge;
}
}

@ -0,0 +1,17 @@
import type { IAppServerOrchestrator } from '@rocket.chat/apps';
import { ExperimentalBridge } from '@rocket.chat/apps-engine/server/bridges';
import { Subscriptions } from '@rocket.chat/models';
export class AppExperimentalBridge extends ExperimentalBridge {
constructor(private readonly orch: IAppServerOrchestrator) {
super();
}
protected async getUserRoomIds(userId: string, appId: string): Promise<string[] | undefined> {
this.orch.debugLog(`The App ${appId} is getting the room ids for the user: "${userId}"`);
const subscriptions = await Subscriptions.findByUserId(userId, { projection: { rid: 1 } }).toArray();
return subscriptions.map((subscription) => subscription.rid);
}
}

@ -253,6 +253,7 @@ export class AppAccessors {
getThreadReader: () => this.proxify('getReader:getThreadReader'),
getRoleReader: () => this.proxify('getReader:getRoleReader'),
getContactReader: () => this.proxify('getReader:getContactReader'),
getExperimentalReader: () => this.proxify('getReader:getExperimentalReader'),
};
}

@ -0,0 +1,16 @@
/**
* @description
* Experimental bridge for experimental features.
* Methods in this class are not guaranteed to be stable between updates as the
* team evaluates the proper signature, underlying implementation and performance
* impact of candidates for future APIs
*/
export interface IExperimentalRead {
/**
* Fetches the IDs of the rooms that the user is a member of.
*
* @returns an array of room ids or undefined if the app doesn't have the proper permission
* @experimental
*/
getUserRoomIds(userId: string): Promise<string[] | undefined>;
}

@ -1,6 +1,7 @@
import type { ICloudWorkspaceRead } from './ICloudWorkspaceRead';
import type { IContactRead } from './IContactRead';
import type { IEnvironmentRead } from './IEnvironmentRead';
import type { IExperimentalRead } from './IExperimentalRead';
import type { ILivechatRead } from './ILivechatRead';
import type { IMessageRead } from './IMessageRead';
import type { INotifier } from './INotifier';
@ -51,4 +52,6 @@ export interface IRead {
getRoleReader(): IRoleRead;
getContactReader(): IContactRead;
getExperimentalReader(): IExperimentalRead;
}

@ -11,6 +11,7 @@ export * from './IEnvironmentalVariableRead';
export * from './IEnvironmentRead';
export * from './IEnvironmentWrite';
export * from './IExternalComponentsExtend';
export * from './IExperimentalRead';
export * from './IHttp';
export * from './ILivechatCreator';
export * from './ILivechatMessageBuilder';

@ -0,0 +1,13 @@
import type { IExperimentalRead } from '../../definition/accessors';
import type { ExperimentalBridge } from '../bridges';
export class ExperimentalRead implements IExperimentalRead {
constructor(
private experimentalBridge: ExperimentalBridge,
private appId: string,
) {}
public async getUserRoomIds(userId: string): Promise<string[] | undefined> {
return this.experimentalBridge.doGetUserRoomIds(userId, this.appId);
}
}

@ -1,6 +1,7 @@
import type {
ICloudWorkspaceRead,
IEnvironmentRead,
IExperimentalRead,
ILivechatRead,
IMessageRead,
INotifier,
@ -29,10 +30,10 @@ export class Reader implements IRead {
private cloud: ICloudWorkspaceRead,
private videoConf: IVideoConferenceRead,
private contactRead: IContactRead,
private oauthApps: IOAuthAppsReader,
private thread: IThreadRead,
private role: IRoleRead,
private experimental: IExperimentalRead,
) {}
public getEnvironmentReader(): IEnvironmentRead {
@ -90,4 +91,8 @@ export class Reader implements IRead {
public getContactReader(): IContactRead {
return this.contactRead;
}
public getExperimentalReader(): IExperimentalRead {
return this.experimental;
}
}

@ -6,6 +6,7 @@ import type { CommandBridge } from './CommandBridge';
import type { ContactBridge } from './ContactBridge';
import type { EmailBridge } from './EmailBridge';
import type { EnvironmentalVariableBridge } from './EnvironmentalVariableBridge';
import type { ExperimentalBridge } from './ExperimentalBridge';
import type { HttpBridge } from './HttpBridge';
import type { IInternalBridge } from './IInternalBridge';
import type { IInternalFederationBridge } from './IInternalFederationBridge';
@ -42,6 +43,7 @@ export type Bridge =
| IInternalBridge
| ServerSettingBridge
| EmailBridge
| ExperimentalBridge
| UploadBridge
| UserBridge
| UiInteractionBridge
@ -106,4 +108,6 @@ export abstract class AppBridges {
public abstract getRoleBridge(): RoleBridge;
public abstract getOutboundMessageBridge(): OutboundMessageBridge;
public abstract getExperimentalBridge(): ExperimentalBridge;
}

@ -0,0 +1,40 @@
import { BaseBridge } from './BaseBridge';
import { PermissionDeniedError } from '../errors/PermissionDeniedError';
import { AppPermissionManager } from '../managers/AppPermissionManager';
import { AppPermissions } from '../permissions/AppPermissions';
/**
* @description
* Experimental bridge for experimental features.
* Methods in this class are not guaranteed to be stable between updates as the
* team evaluates the proper signature, underlying implementation and performance
* impact of candidates for future APIs
*/
export abstract class ExperimentalBridge extends BaseBridge {
/**
*
* Candidate bridge: User bridge
*/
public async doGetUserRoomIds(userId: string, appId: string): Promise<string[] | undefined> {
if (this.hasPermission('getUserRoomIds', appId)) {
return this.getUserRoomIds(userId, appId);
}
}
protected abstract getUserRoomIds(userId: string, appId: string): Promise<string[] | undefined>;
private hasPermission(feature: keyof typeof AppPermissions.experimental, appId: string): boolean {
if (AppPermissionManager.hasPermission(appId, AppPermissions.experimental[feature])) {
return true;
}
AppPermissionManager.notifyAboutError(
new PermissionDeniedError({
appId,
missingPermissions: [AppPermissions.experimental[feature]],
}),
);
return false;
}
}

@ -7,6 +7,7 @@ import { CommandBridge } from './CommandBridge';
import { ContactBridge } from './ContactBridge';
import { EmailBridge } from './EmailBridge';
import { EnvironmentalVariableBridge } from './EnvironmentalVariableBridge';
import { ExperimentalBridge } from './ExperimentalBridge';
import { HttpBridge, IHttpBridgeRequestInfo } from './HttpBridge';
import { IInternalBridge } from './IInternalBridge';
import { IInternalFederationBridge } from './IInternalFederationBridge';
@ -45,6 +46,7 @@ export {
UserBridge,
UploadBridge,
EmailBridge,
ExperimentalBridge,
UiInteractionBridge,
SchedulerBridge,
AppBridges,

@ -48,6 +48,7 @@ import {
} from '../accessors';
import { CloudWorkspaceRead } from '../accessors/CloudWorkspaceRead';
import { ContactRead } from '../accessors/ContactRead';
import { ExperimentalRead } from '../accessors/ExperimentalRead';
import { ThreadRead } from '../accessors/ThreadRead';
import { UIExtend } from '../accessors/UIExtend';
import type { AppBridges } from '../bridges/AppBridges';
@ -188,12 +189,28 @@ export class AppAccessorManager {
const oauthApps = new OAuthAppsReader(this.bridges.getOAuthAppsBridge(), appId);
const contactReader = new ContactRead(this.bridges, appId);
const thread = new ThreadRead(this.bridges.getThreadBridge(), appId);
const role = new RoleRead(this.bridges.getRoleBridge(), appId);
const experimental = new ExperimentalRead(this.bridges.getExperimentalBridge(), appId);
this.readers.set(
appId,
new Reader(env, msg, persist, room, user, noti, livechat, upload, cloud, videoConf, contactReader, oauthApps, thread, role),
new Reader(
env,
msg,
persist,
room,
user,
noti,
livechat,
upload,
cloud,
videoConf,
contactReader,
oauthApps,
thread,
role,
experimental,
),
);
}

@ -122,6 +122,9 @@ export const AppPermissions = {
'outboundComms': {
provide: { name: 'outbound-communication.provide' },
},
'experimental': {
getUserRoomIds: { name: 'experimental.getUserRoomIds' },
},
};
/**

@ -3,6 +3,7 @@ import { Expect, SetupFixture, Test } from 'alsatian';
import type {
ICloudWorkspaceRead,
IEnvironmentRead,
IExperimentalRead,
ILivechatRead,
IMessageRead,
INotifier,
@ -47,6 +48,8 @@ export class ReaderAccessorTestFixture {
private contact: IContactRead;
private experimental: IExperimentalRead;
@SetupFixture
public setupFixture() {
this.env = {} as IEnvironmentRead;
@ -63,6 +66,7 @@ export class ReaderAccessorTestFixture {
this.thread = {} as IThreadRead;
this.role = {} as IRoleRead;
this.contact = {} as IContactRead;
this.experimental = {} as IExperimentalRead;
}
@Test()
@ -84,6 +88,7 @@ export class ReaderAccessorTestFixture {
this.oauthApps,
this.thread,
this.role,
this.experimental,
),
).not.toThrow();
@ -102,6 +107,7 @@ export class ReaderAccessorTestFixture {
this.oauthApps,
this.thread,
this.role,
this.experimental,
);
Expect(rd.getEnvironmentReader()).toBeDefined();

@ -7,6 +7,7 @@ import { TestsCommandBridge } from './commandBridge';
import { TestContactBridge } from './contactBridge';
import { TestsEmailBridge } from './emailBridge';
import { TestsEnvironmentalVariableBridge } from './environmentalVariableBridge';
import { TestExperimentalBridge } from './experimentalBridge';
import { TestsHttpBridge } from './httpBridge';
import { TestsInternalBridge } from './internalBridge';
import { TestsInternalFederationBridge } from './internalFederationBridge';
@ -30,6 +31,7 @@ import type {
AppDetailChangesBridge,
ContactBridge,
EnvironmentalVariableBridge,
ExperimentalBridge,
HttpBridge,
IInternalBridge,
IListenerBridge,
@ -106,6 +108,8 @@ export class TestsAppBridges extends AppBridges {
private readonly outboundCommsBridge: TestOutboundCommunicationBridge;
private readonly experimentalBridge: TestExperimentalBridge;
constructor() {
super();
this.appDetails = new TestsAppDetailChangesBridge();
@ -134,6 +138,7 @@ export class TestsAppBridges extends AppBridges {
this.emailBridge = new TestsEmailBridge();
this.contactBridge = new TestContactBridge();
this.outboundCommsBridge = new TestOutboundCommunicationBridge();
this.experimentalBridge = new TestExperimentalBridge();
}
public getCommandBridge(): TestsCommandBridge {
@ -243,4 +248,8 @@ export class TestsAppBridges extends AppBridges {
public getOutboundMessageBridge(): OutboundMessageBridge {
return this.outboundCommsBridge;
}
public getExperimentalBridge(): ExperimentalBridge {
return this.experimentalBridge;
}
}

@ -0,0 +1,7 @@
import { ExperimentalBridge } from '../../../src/server/bridges';
export class TestExperimentalBridge extends ExperimentalBridge {
protected getUserRoomIds(userId: string, appId: string): Promise<string[] | undefined> {
throw new Error('Method not implemented.');
}
}
Loading…
Cancel
Save