@morgan-stanley/composeui-fdc3 0.1.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +57 -0
  3. package/dist/fdc3-iife-bundle.js +2028 -0
  4. package/package.json +45 -0
  5. package/rollup.config.js +16 -0
  6. package/src/ComposeUIChannel.spec.ts +251 -0
  7. package/src/ComposeUIContextListener.spec.ts +116 -0
  8. package/src/ComposeUIDesktopAgent.spec.ts +189 -0
  9. package/src/ComposeUIDesktopAgent.ts +259 -0
  10. package/src/ComposeUIIntentHandling.spec.ts +218 -0
  11. package/src/ComposeUIMessagingChannelFactory.spec.ts +114 -0
  12. package/src/ComposeUIMessagingMetadataClient.spec.ts +132 -0
  13. package/src/ComposeUIMessagingOpenClient.spec.ts +146 -0
  14. package/src/index.ts +70 -0
  15. package/src/infrastructure/ChannelFactory.ts +25 -0
  16. package/src/infrastructure/ChannelItem.ts +20 -0
  17. package/src/infrastructure/ChannelType.ts +14 -0
  18. package/src/infrastructure/ComposeUIChannel.ts +86 -0
  19. package/src/infrastructure/ComposeUIContextListener.ts +148 -0
  20. package/src/infrastructure/ComposeUIErrors.ts +21 -0
  21. package/src/infrastructure/ComposeUIIntentListener.ts +100 -0
  22. package/src/infrastructure/ComposeUIIntentResolution.ts +61 -0
  23. package/src/infrastructure/ComposeUIPrivateChannel.ts +204 -0
  24. package/src/infrastructure/ComposeUITopic.ts +160 -0
  25. package/src/infrastructure/IntentsClient.ts +22 -0
  26. package/src/infrastructure/MessagingChannelFactory.ts +196 -0
  27. package/src/infrastructure/MessagingIntentsClient.ts +119 -0
  28. package/src/infrastructure/MessagingMetadataClient.ts +72 -0
  29. package/src/infrastructure/MessagingOpenClient.ts +103 -0
  30. package/src/infrastructure/MetadataClient.ts +19 -0
  31. package/src/infrastructure/OpenAppIdentifier.ts +18 -0
  32. package/src/infrastructure/OpenClient.ts +18 -0
  33. package/src/infrastructure/PrivateChannelContextListenerEventListener.ts +45 -0
  34. package/src/infrastructure/PrivateChannelDisconnectEventListener.ts +45 -0
  35. package/src/infrastructure/messages/Fdc3AddContextListenerRequest.ts +22 -0
  36. package/src/infrastructure/messages/Fdc3AddContextListenerResponse.ts +17 -0
  37. package/src/infrastructure/messages/Fdc3CreateAppChannelRequest.ts +17 -0
  38. package/src/infrastructure/messages/Fdc3CreateAppChannelResponse.ts +17 -0
  39. package/src/infrastructure/messages/Fdc3CreatePrivateChannelRequest.ts +16 -0
  40. package/src/infrastructure/messages/Fdc3CreatePrivateChannelResponse.ts +17 -0
  41. package/src/infrastructure/messages/Fdc3FindChannelRequest.ts +24 -0
  42. package/src/infrastructure/messages/Fdc3FindChannelResponse.ts +17 -0
  43. package/src/infrastructure/messages/Fdc3FindInstancesRequest.ts +17 -0
  44. package/src/infrastructure/messages/Fdc3FindInstancesResponse.ts +18 -0
  45. package/src/infrastructure/messages/Fdc3FindIntentRequest.ts +24 -0
  46. package/src/infrastructure/messages/Fdc3FindIntentResponse.ts +19 -0
  47. package/src/infrastructure/messages/Fdc3FindIntentsByContextRequest.ts +23 -0
  48. package/src/infrastructure/messages/Fdc3FindIntentsByContextResponse.ts +19 -0
  49. package/src/infrastructure/messages/Fdc3GetAppMetadataRequest.ts +17 -0
  50. package/src/infrastructure/messages/Fdc3GetAppMetadataResponse.ts +18 -0
  51. package/src/infrastructure/messages/Fdc3GetCurrentContextRequest.ts +18 -0
  52. package/src/infrastructure/messages/Fdc3GetInfoRequest.ts +17 -0
  53. package/src/infrastructure/messages/Fdc3GetInfoResponse.ts +18 -0
  54. package/src/infrastructure/messages/Fdc3GetIntentResultRequest.ts +23 -0
  55. package/src/infrastructure/messages/Fdc3GetIntentResultResponse.ts +23 -0
  56. package/src/infrastructure/messages/Fdc3GetOpenedAppContextRequest.ts +15 -0
  57. package/src/infrastructure/messages/Fdc3GetOpenedAppContextResponse.ts +18 -0
  58. package/src/infrastructure/messages/Fdc3GetUserChannelsRequest.ts +15 -0
  59. package/src/infrastructure/messages/Fdc3GetUserChannelsResponse.ts +18 -0
  60. package/src/infrastructure/messages/Fdc3IntentListenerRequest.ts +22 -0
  61. package/src/infrastructure/messages/Fdc3IntentListenerResponse.ts +17 -0
  62. package/src/infrastructure/messages/Fdc3JoinPrivateChannelRequest.ts +17 -0
  63. package/src/infrastructure/messages/Fdc3JoinPrivateChannelResponse.ts +16 -0
  64. package/src/infrastructure/messages/Fdc3JoinUserChannelRequest.ts +15 -0
  65. package/src/infrastructure/messages/Fdc3JoinUserChannelResponse.ts +20 -0
  66. package/src/infrastructure/messages/Fdc3OpenRequest.ts +21 -0
  67. package/src/infrastructure/messages/Fdc3OpenResponse.ts +18 -0
  68. package/src/infrastructure/messages/Fdc3PrivateChannelInternalEvent.ts +20 -0
  69. package/src/infrastructure/messages/Fdc3RaiseIntentForContextRequest.ts +20 -0
  70. package/src/infrastructure/messages/Fdc3RaiseIntentRequest.ts +24 -0
  71. package/src/infrastructure/messages/Fdc3RaiseIntentResolutionRequest.ts +20 -0
  72. package/src/infrastructure/messages/Fdc3RaiseIntentResponse.ts +21 -0
  73. package/src/infrastructure/messages/Fdc3RemoveContextListenerRequest.ts +19 -0
  74. package/src/infrastructure/messages/Fdc3RemoveContextListenerResponse.ts +17 -0
  75. package/src/infrastructure/messages/Fdc3StoreIntentResultRequest.ts +29 -0
  76. package/src/infrastructure/messages/Fdc3StoreIntentResultResponse.ts +17 -0
  77. package/tsconfig.json +15 -0
  78. package/vite.config.ts +7 -0
@@ -0,0 +1,132 @@
1
+ /*
2
+ * Morgan Stanley makes this available to you under the Apache License,
3
+ * Version 2.0 (the "License"). You may obtain a copy of the License at
4
+ * http://www.apache.org/licenses/LICENSE-2.0.
5
+ * See the NOTICE file distributed with this work for additional information
6
+ * regarding copyright ownership. Unless required by applicable law or agreed
7
+ * to in writing, software distributed under the License is distributed on an
8
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
9
+ * or implied. See the License for the specific language governing permissions
10
+ * and limitations under the License.
11
+ */
12
+
13
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
14
+ import { ImplementationMetadata } from '@finos/fdc3';
15
+ import { ComposeUIDesktopAgent } from './ComposeUIDesktopAgent';
16
+ import { ComposeUIErrors } from './infrastructure/ComposeUIErrors';
17
+ import { Fdc3GetInfoResponse } from './infrastructure/messages/Fdc3GetInfoResponse';
18
+ import { Fdc3FindInstancesResponse } from './infrastructure/messages/Fdc3FindInstancesResponse';
19
+ import { Fdc3GetAppMetadataResponse } from './infrastructure/messages/Fdc3GetAppMetadataResponse';
20
+ import { IMessaging, JsonMessaging } from '@morgan-stanley/composeui-messaging-abstractions';
21
+
22
+ describe('MessagingMetadataClient tests', () => {
23
+ beforeEach(() => {
24
+ // @ts-ignore
25
+ window.composeui = {
26
+ fdc3: {
27
+ config: { appId: 'testAppId', instanceId: 'testInstanceId' },
28
+ channelId: 'test',
29
+ openAppIdentifier: { openedAppContextId: 'test' }
30
+ }
31
+ };
32
+ });
33
+
34
+ const baseMessagingMock = (): IMessaging => ({
35
+ subscribe: vi.fn(() => Promise.resolve({ unsubscribe: () => {} })),
36
+ publish: vi.fn(() => Promise.resolve()),
37
+ registerService: vi.fn(() =>
38
+ Promise.resolve({
39
+ unsubscribe: () => {},
40
+ [Symbol.asyncDispose]: () => Promise.resolve()
41
+ })
42
+ ),
43
+ invokeService: vi.fn(() => Promise.resolve(null))
44
+ });
45
+
46
+ it('getInfo throws NoAnswerWasProvided', async () => {
47
+ const messagingMock = baseMessagingMock();
48
+ const agent = new ComposeUIDesktopAgent(new JsonMessaging(messagingMock));
49
+ await expect(agent.getInfo()).rejects.toThrow(ComposeUIErrors.NoAnswerWasProvided);
50
+ });
51
+
52
+ it('getInfo throws backend error', async () => {
53
+ const response = { error: 'dummyError' };
54
+ const messagingMock = baseMessagingMock();
55
+ messagingMock.invokeService = vi.fn(() => Promise.resolve(JSON.stringify(response)));
56
+ const agent = new ComposeUIDesktopAgent(new JsonMessaging(messagingMock));
57
+ await expect(agent.getInfo()).rejects.toThrow('dummyError');
58
+ });
59
+
60
+ it('getInfo returns ImplementationMetadata', async () => {
61
+ const implementationMetadata: ImplementationMetadata = {
62
+ fdc3Version: '2.0.0',
63
+ provider: 'ComposeUI',
64
+ providerVersion: '1.0.0',
65
+ optionalFeatures: {
66
+ OriginatingAppMetadata: false,
67
+ UserChannelMembershipAPIs: true
68
+ },
69
+ appMetadata: { appId: 'dummyAppId' }
70
+ };
71
+ const response: Fdc3GetInfoResponse = { implementationMetadata };
72
+ const messagingMock = baseMessagingMock();
73
+ messagingMock.invokeService = vi.fn(() => Promise.resolve(JSON.stringify(response)));
74
+ const agent = new ComposeUIDesktopAgent(new JsonMessaging(messagingMock));
75
+ const result = await agent.getInfo();
76
+ expect(result).toMatchObject(implementationMetadata);
77
+ });
78
+
79
+ it('findInstances throws NoAnswerWasProvided', async () => {
80
+ const messagingMock = baseMessagingMock();
81
+ const agent = new ComposeUIDesktopAgent(new JsonMessaging(messagingMock));
82
+ await expect(agent.findInstances({ appId: 'test' })).rejects.toThrow(
83
+ ComposeUIErrors.NoAnswerWasProvided
84
+ );
85
+ });
86
+
87
+ it('findInstances throws backend error', async () => {
88
+ const messagingMock = baseMessagingMock();
89
+ messagingMock.invokeService = vi.fn(() =>
90
+ Promise.resolve(JSON.stringify({ error: 'dummyError' }))
91
+ );
92
+ const agent = new ComposeUIDesktopAgent(new JsonMessaging(messagingMock));
93
+ await expect(agent.findInstances({ appId: 'test' })).rejects.toThrow('dummyError');
94
+ });
95
+
96
+ it('findInstances returns instances', async () => {
97
+ const response: Fdc3FindInstancesResponse = {
98
+ instances: [{ appId: 'test', instanceId: 'id' }]
99
+ };
100
+ const messagingMock = baseMessagingMock();
101
+ messagingMock.invokeService = vi.fn(() => Promise.resolve(JSON.stringify(response)));
102
+ const agent = new ComposeUIDesktopAgent(new JsonMessaging(messagingMock));
103
+ const result = await agent.findInstances({ appId: 'test' });
104
+ expect(result).toMatchObject([{ appId: 'test', instanceId: 'id' }]);
105
+ });
106
+
107
+ it('getAppMetadata throws NoAnswerWasProvided', async () => {
108
+ const messagingMock = baseMessagingMock();
109
+ const agent = new ComposeUIDesktopAgent(new JsonMessaging(messagingMock));
110
+ await expect(agent.getAppMetadata({ appId: 'test' })).rejects.toThrow(
111
+ ComposeUIErrors.NoAnswerWasProvided
112
+ );
113
+ });
114
+
115
+ it('getAppMetadata throws backend error', async () => {
116
+ const messagingMock = baseMessagingMock();
117
+ messagingMock.invokeService = vi.fn(() =>
118
+ Promise.resolve(JSON.stringify({ error: 'dummyError' }))
119
+ );
120
+ const agent = new ComposeUIDesktopAgent(new JsonMessaging(messagingMock));
121
+ await expect(agent.getAppMetadata({ appId: 'test' })).rejects.toThrow('dummyError');
122
+ });
123
+
124
+ it('getAppMetadata returns appMetadata', async () => {
125
+ const response: Fdc3GetAppMetadataResponse = { appMetadata: { appId: 'test' } };
126
+ const messagingMock = baseMessagingMock();
127
+ messagingMock.invokeService = vi.fn(() => Promise.resolve(JSON.stringify(response)));
128
+ const agent = new ComposeUIDesktopAgent(new JsonMessaging(messagingMock));
129
+ const result = await agent.getAppMetadata({ appId: 'test' });
130
+ expect(result).toMatchObject({ appId: 'test' });
131
+ });
132
+ });
@@ -0,0 +1,146 @@
1
+ /*
2
+ * Morgan Stanley makes this available to you under the Apache License,
3
+ * Version 2.0 (the "License"). You may obtain a copy of the License at
4
+ * http://www.apache.org/licenses/LICENSE-2.0.
5
+ * See the NOTICE file distributed with this work for additional information
6
+ * regarding copyright ownership. Unless required by applicable law or agreed
7
+ * to in writing, software distributed under the License is distributed on an
8
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
9
+ * or implied. See the License for the specific language governing permissions
10
+ * and limitations under the License.
11
+ */
12
+
13
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
14
+ import { OpenError } from '@finos/fdc3';
15
+ import { MessagingOpenClient } from './infrastructure/MessagingOpenClient';
16
+ import { ComposeUIErrors } from './infrastructure/ComposeUIErrors';
17
+ import { Fdc3OpenResponse } from './infrastructure/messages/Fdc3OpenResponse';
18
+ import { Fdc3GetOpenedAppContextResponse } from './infrastructure/messages/Fdc3GetOpenedAppContextResponse';
19
+ import { OpenAppIdentifier } from './infrastructure/OpenAppIdentifier';
20
+ import { IMessaging, JsonMessaging } from '@morgan-stanley/composeui-messaging-abstractions';
21
+
22
+ describe('MessagingOpenClient tests', () => {
23
+ let client: MessagingOpenClient;
24
+ let jsonMessaging: JsonMessaging;
25
+ const openAppIdentifier: OpenAppIdentifier = { openedAppContextId: 'contextId' };
26
+
27
+ const baseMessagingMock = (): IMessaging => ({
28
+ subscribe: vi.fn(() => Promise.resolve({ unsubscribe: () => {} })),
29
+ publish: vi.fn(() => Promise.resolve()),
30
+ registerService: vi.fn(() =>
31
+ Promise.resolve({
32
+ unsubscribe: () => {},
33
+ [Symbol.asyncDispose]: () => Promise.resolve()
34
+ })
35
+ ),
36
+ invokeService: vi.fn(() => Promise.resolve(null))
37
+ });
38
+
39
+ beforeEach(() => {
40
+ // @ts-ignore
41
+ window.composeui = {
42
+ fdc3: {
43
+ config: { appId: 'testAppId', instanceId: 'testInstanceId' },
44
+ channelId: 'test',
45
+ openAppIdentifier: { openedAppContextId: 'contextId' }
46
+ }
47
+ };
48
+ const messagingMock = baseMessagingMock();
49
+ jsonMessaging = new JsonMessaging(messagingMock);
50
+ client = new MessagingOpenClient('fdc3InstanceId', jsonMessaging, openAppIdentifier);
51
+ });
52
+
53
+ it('open throws AppNotFound when no AppIdentifier provided', async () => {
54
+ await expect(client.open()).rejects.toThrow(OpenError.AppNotFound);
55
+ });
56
+
57
+ it('open throws NoAnswerWasProvided when backend returns undefined', async () => {
58
+ await expect(client.open('appId1')).rejects.toThrow(ComposeUIErrors.NoAnswerWasProvided);
59
+ });
60
+
61
+ it('open throws backend error from error response', async () => {
62
+ const response: Fdc3OpenResponse = { error: 'testError' };
63
+ const messagingMock = baseMessagingMock();
64
+ messagingMock.invokeService = vi.fn(() =>
65
+ Promise.resolve(JSON.stringify(response))
66
+ );
67
+ jsonMessaging = new JsonMessaging(messagingMock);
68
+ client = new MessagingOpenClient('fdc3InstanceId', jsonMessaging, openAppIdentifier);
69
+ await expect(client.open('appId1')).rejects.toThrow('testError');
70
+ });
71
+
72
+ it('open returns AppIdentifier on success', async () => {
73
+ const response: Fdc3OpenResponse = {
74
+ appIdentifier: { appId: 'appId1', instanceId: 'instanceId1' }
75
+ };
76
+ const messagingMock = baseMessagingMock();
77
+ messagingMock.invokeService = vi.fn(() =>
78
+ Promise.resolve(JSON.stringify(response))
79
+ );
80
+ jsonMessaging = new JsonMessaging(messagingMock);
81
+ client = new MessagingOpenClient('fdc3InstanceId', jsonMessaging, openAppIdentifier);
82
+ const result = await client.open({ appId: 'appId1' }, { type: 'fdc3.instrument' });
83
+ expect(result).toMatchObject({ appId: 'appId1', instanceId: 'instanceId1' });
84
+ });
85
+
86
+ it('getOpenedAppContext throws when context id not set', async () => {
87
+ client = new MessagingOpenClient('fdc3InstanceId', jsonMessaging);
88
+ await expect(client.getOpenedAppContext()).rejects.toThrow(
89
+ 'Context id is not defined on the window object.'
90
+ );
91
+ });
92
+
93
+ it('getOpenedAppContext throws NoAnswerWasProvided on undefined response', async () => {
94
+ await expect(client.getOpenedAppContext()).rejects.toThrow(
95
+ ComposeUIErrors.NoAnswerWasProvided
96
+ );
97
+ });
98
+
99
+ it('getOpenedAppContext throws NoAnswerWasProvided on wrong payload shape', async () => {
100
+ const messagingMock = baseMessagingMock();
101
+ messagingMock.invokeService = vi.fn(() =>
102
+ Promise.resolve(JSON.stringify({ payload: 'wrongPayload' }))
103
+ );
104
+ jsonMessaging = new JsonMessaging(messagingMock);
105
+ client = new MessagingOpenClient('fdc3InstanceId', jsonMessaging, openAppIdentifier);
106
+ await expect(client.getOpenedAppContext()).rejects.toThrow(
107
+ ComposeUIErrors.NoAnswerWasProvided
108
+ );
109
+ });
110
+
111
+ it('getOpenedAppContext throws backend error', async () => {
112
+ const response: Fdc3GetOpenedAppContextResponse = { error: 'testGetOpenedAppContextError' };
113
+ const messagingMock = baseMessagingMock();
114
+ messagingMock.invokeService = vi.fn(() =>
115
+ Promise.resolve(JSON.stringify(response))
116
+ );
117
+ jsonMessaging = new JsonMessaging(messagingMock);
118
+ client = new MessagingOpenClient('fdc3InstanceId', jsonMessaging, openAppIdentifier);
119
+ await expect(client.getOpenedAppContext()).rejects.toThrow('testGetOpenedAppContextError');
120
+ });
121
+
122
+ it('getOpenedAppContext throws NoAnswerWasProvided when context missing', async () => {
123
+ const response: Fdc3GetOpenedAppContextResponse = {};
124
+ const messagingMock = baseMessagingMock();
125
+ messagingMock.invokeService = vi.fn(() =>
126
+ Promise.resolve(JSON.stringify(response))
127
+ );
128
+ jsonMessaging = new JsonMessaging(messagingMock);
129
+ client = new MessagingOpenClient('fdc3InstanceId', jsonMessaging, openAppIdentifier);
130
+ await expect(client.getOpenedAppContext()).rejects.toThrow(
131
+ ComposeUIErrors.NoAnswerWasProvided
132
+ );
133
+ });
134
+
135
+ it('getOpenedAppContext returns context', async () => {
136
+ const response: Fdc3GetOpenedAppContextResponse = { context: { type: 'fdc3.instrument' } };
137
+ const messagingMock = baseMessagingMock();
138
+ messagingMock.invokeService = vi.fn(() =>
139
+ Promise.resolve(JSON.stringify(response))
140
+ );
141
+ jsonMessaging = new JsonMessaging(messagingMock);
142
+ client = new MessagingOpenClient('fdc3InstanceId', jsonMessaging, openAppIdentifier);
143
+ const result = await client.getOpenedAppContext();
144
+ expect(result.type).toBe('fdc3.instrument');
145
+ });
146
+ });
package/src/index.ts ADDED
@@ -0,0 +1,70 @@
1
+ /*
2
+ * Morgan Stanley makes this available to you under the Apache License,
3
+ * Version 2.0 (the "License"). You may obtain a copy of the License at
4
+ * http://www.apache.org/licenses/LICENSE-2.0.
5
+ * See the NOTICE file distributed with this work for additional information
6
+ * regarding copyright ownership. Unless required by applicable law or agreed
7
+ * to in writing, software distributed under the License is distributed on an
8
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
9
+ * or implied. See the License for the specific language governing permissions
10
+ * and limitations under the License.
11
+ *
12
+ */
13
+
14
+
15
+ import { AppIdentifier, DesktopAgent } from "@finos/fdc3";
16
+ import { ComposeUIDesktopAgent } from "./ComposeUIDesktopAgent";
17
+ import { IMessaging } from "@morgan-stanley/composeui-messaging-abstractions";
18
+ import { OpenAppIdentifier } from "./infrastructure/OpenAppIdentifier";
19
+
20
+ declare global {
21
+ interface Window {
22
+ composeui: {
23
+ fdc3: {
24
+ config: AppIdentifier | undefined;
25
+ channelId : string | undefined;
26
+ openAppIdentifier: OpenAppIdentifier | undefined;
27
+ },
28
+ messaging: {
29
+ communicator: IMessaging | undefined;
30
+ }
31
+ }
32
+ fdc3: DesktopAgent;
33
+ }
34
+ }
35
+
36
+ async function initialize(): Promise<void> {
37
+ //TODO: decide if we want to join to a channel by default.
38
+ let channelId: string | undefined = window.composeui.fdc3.channelId;
39
+ const openAppIdentifier: OpenAppIdentifier | undefined = window.composeui.fdc3.openAppIdentifier;
40
+ const messaging = window.composeui.messaging.communicator as IMessaging;
41
+ const fdc3 = new ComposeUIDesktopAgent(messaging);
42
+
43
+ if (channelId) {
44
+ await fdc3.joinUserChannel(channelId)
45
+ .then(async() => {
46
+ if (openAppIdentifier) {
47
+ await fdc3.getOpenedAppContext()
48
+ .then(() => {
49
+ window.fdc3 = fdc3;
50
+ window.dispatchEvent(new Event("fdc3Ready"));
51
+ })
52
+ } else {
53
+ window.fdc3 = fdc3;
54
+ window.dispatchEvent(new Event("fdc3Ready"));
55
+ }
56
+ });
57
+ } else {
58
+ if (openAppIdentifier) {
59
+ await fdc3.getOpenedAppContext().then(() => {
60
+ window.fdc3 = fdc3;
61
+ window.dispatchEvent(new Event("fdc3Ready"));
62
+ })
63
+ } else {
64
+ window.fdc3 = fdc3;
65
+ window.dispatchEvent(new Event("fdc3Ready"));
66
+ }
67
+ }
68
+ }
69
+
70
+ initialize();
@@ -0,0 +1,25 @@
1
+ /*
2
+ * Morgan Stanley makes this available to you under the Apache License,
3
+ * Version 2.0 (the "License"). You may obtain a copy of the License at
4
+ * http://www.apache.org/licenses/LICENSE-2.0.
5
+ * See the NOTICE file distributed with this work for additional information
6
+ * regarding copyright ownership. Unless required by applicable law or agreed
7
+ * to in writing, software distributed under the License is distributed on an
8
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
9
+ * or implied. See the License for the specific language governing permissions
10
+ * and limitations under the License.
11
+ *
12
+ */
13
+
14
+ import { Channel, ContextHandler, IntentHandler, Listener, PrivateChannel } from "@finos/fdc3";
15
+ import { ChannelType } from "./ChannelType";
16
+
17
+ export interface ChannelFactory {
18
+ getChannel(channelId: string, channelType: ChannelType): Promise<Channel>;
19
+ createPrivateChannel(): Promise<PrivateChannel>;
20
+ createAppChannel(channelId: string): Promise<Channel>;
21
+ joinUserChannel(channelId: string): Promise<Channel>;
22
+ getUserChannels(): Promise<Channel[]>;
23
+ getIntentListener(intent: string, handler: IntentHandler): Promise<Listener>;
24
+ getContextListener(openHandled: boolean, channel?: Channel, handler?: ContextHandler, contextType?: string | null): Promise<Listener>;
25
+ }
@@ -0,0 +1,20 @@
1
+ /*
2
+ * Morgan Stanley makes this available to you under the Apache License,
3
+ * Version 2.0 (the "License"). You may obtain a copy of the License at
4
+ * http://www.apache.org/licenses/LICENSE-2.0.
5
+ * See the NOTICE file distributed with this work for additional information
6
+ * regarding copyright ownership. Unless required by applicable law or agreed
7
+ * to in writing, software distributed under the License is distributed on an
8
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
9
+ * or implied. See the License for the specific language governing permissions
10
+ * and limitations under the License.
11
+ */
12
+
13
+ import { DisplayMetadata } from "@finos/fdc3";
14
+ import { ChannelType } from "./ChannelType";
15
+
16
+ export interface ChannelItem {
17
+ id: string;
18
+ type: ChannelType;
19
+ displayMetadata?: DisplayMetadata;
20
+ }
@@ -0,0 +1,14 @@
1
+ /*
2
+ * Morgan Stanley makes this available to you under the Apache License,
3
+ * Version 2.0 (the "License"). You may obtain a copy of the License at
4
+ * http://www.apache.org/licenses/LICENSE-2.0.
5
+ * See the NOTICE file distributed with this work for additional information
6
+ * regarding copyright ownership. Unless required by applicable law or agreed
7
+ * to in writing, software distributed under the License is distributed on an
8
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
9
+ * or implied. See the License for the specific language governing permissions
10
+ * and limitations under the License.
11
+ *
12
+ */
13
+
14
+ export type ChannelType = "user" | "app" | "private"
@@ -0,0 +1,86 @@
1
+ /*
2
+ * Morgan Stanley makes this available to you under the Apache License,
3
+ * Version 2.0 (the "License"). You may obtain a copy of the License at
4
+ * http://www.apache.org/licenses/LICENSE-2.0.
5
+ * See the NOTICE file distributed with this work for additional information
6
+ * regarding copyright ownership. Unless required by applicable law or agreed
7
+ * to in writing, software distributed under the License is distributed on an
8
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
9
+ * or implied. See the License for the specific language governing permissions
10
+ * and limitations under the License.
11
+ *
12
+ */
13
+
14
+ import { Channel, Context, ContextHandler, DisplayMetadata, Listener } from "@finos/fdc3";
15
+ import { JsonMessaging } from "@morgan-stanley/composeui-messaging-abstractions";
16
+ import { ChannelType } from "./ChannelType";
17
+ import { ComposeUIContextListener } from "./ComposeUIContextListener";
18
+ import { Fdc3GetCurrentContextRequest } from "./messages/Fdc3GetCurrentContextRequest";
19
+ import { ComposeUITopic } from "./ComposeUITopic";
20
+
21
+ export class ComposeUIChannel implements Channel {
22
+ id: string;
23
+ type: "user" | "app" | "private";
24
+ displayMetadata?: DisplayMetadata;
25
+
26
+ protected jsonMessaging: JsonMessaging;
27
+ private lastContexts: Map<string, Context> = new Map<string, Context>();
28
+ private lastContext?: Context;
29
+ private openHandled: boolean = true; //by default true so if a channel was created then it has no effect
30
+
31
+ constructor(id: string, type: ChannelType, jsonMessaging: JsonMessaging, displayMetadata?: DisplayMetadata) {
32
+ this.id = id;
33
+ this.type = type;
34
+ this.jsonMessaging = jsonMessaging;
35
+ this.displayMetadata = displayMetadata;
36
+ }
37
+
38
+ //Broadcasting on the composeui/fdc3/v2.0/broadcast topic
39
+ public async broadcast(context: Context): Promise<void> {
40
+ //Setting the last published context message.
41
+ this.lastContexts.set(context.type, context);
42
+ this.lastContext = context;
43
+ const topic = ComposeUITopic.broadcast(this.id, this.type);
44
+ await this.jsonMessaging.publishJson(topic, context);
45
+ }
46
+
47
+ public async getCurrentContext(contextType?: string | undefined): Promise<Context | null> {
48
+ const message = new Fdc3GetCurrentContextRequest(contextType);
49
+ const response = await this.jsonMessaging.invokeJsonService<Fdc3GetCurrentContextRequest, Context>(ComposeUITopic.getCurrentContext(this.id, this.type), message);
50
+ if (response) {
51
+ if (response) {
52
+ this.lastContext = response;
53
+ this.lastContexts.set(response.type, response);
54
+ }
55
+ }
56
+ return this.retrieveCurrentContext(contextType);
57
+ }
58
+
59
+ private retrieveCurrentContext(contextType?: string): Context | null {
60
+ let context;
61
+ if (contextType) {
62
+ context = this.lastContexts.get(contextType);
63
+ } else {
64
+ context = this.lastContext;
65
+ }
66
+
67
+ return context ?? null;
68
+ }
69
+
70
+ public addContextListener(contextType: string | null, handler: ContextHandler): Promise<Listener>;
71
+ public addContextListener(handler: ContextHandler): Promise<Listener>;
72
+ public async addContextListener(contextType: any, handler?: any): Promise<Listener> {
73
+ if (contextType != null && typeof contextType != 'string') {
74
+ handler = contextType;
75
+ contextType = null;
76
+ }
77
+
78
+ const listener = new ComposeUIContextListener(this.openHandled, this.jsonMessaging, handler, contextType);
79
+ await listener.subscribe(this.id, this.type);
80
+ return listener;
81
+ }
82
+
83
+ public setOpenHandled(openHandled: boolean): void {
84
+ this.openHandled = openHandled;
85
+ }
86
+ }
@@ -0,0 +1,148 @@
1
+ /*
2
+ * Morgan Stanley makes this available to you under the Apache License,
3
+ * Version 2.0 (the "License"). You may obtain a copy of the License at
4
+ * http://www.apache.org/licenses/LICENSE-2.0.
5
+ * See the NOTICE file distributed with this work for additional information
6
+ * regarding copyright ownership. Unless required by applicable law or agreed
7
+ * to in writing, software distributed under the License is distributed on an
8
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
9
+ * or implied. See the License for the specific language governing permissions
10
+ * and limitations under the License.
11
+ *
12
+ */
13
+
14
+ import { Context, ContextHandler, Listener } from "@finos/fdc3";
15
+ import { JsonMessaging } from "@morgan-stanley/composeui-messaging-abstractions";
16
+ import { ChannelType } from "./ChannelType";
17
+ import { Unsubscribable } from "rxjs";
18
+ import { ComposeUITopic } from "./ComposeUITopic";
19
+ import { Fdc3RemoveContextListenerRequest } from "./messages/Fdc3RemoveContextListenerRequest";
20
+ import { Fdc3RemoveContextListenerResponse } from "./messages/Fdc3RemoveContextListenerResponse";
21
+ import { ComposeUIErrors } from "./ComposeUIErrors";
22
+ import { Fdc3AddContextListenerRequest } from "./messages/Fdc3AddContextListenerRequest";
23
+ import { Fdc3AddContextListenerResponse } from "./messages/Fdc3AddContextListenerResponse";
24
+
25
+ export class ComposeUIContextListener implements Listener {
26
+ private readonly jsonMessaging: JsonMessaging;
27
+ private unsubscribable?: Unsubscribable;
28
+ private readonly handler: ContextHandler;
29
+ public readonly contextType?: string;
30
+ private isSubscribed: boolean = false;
31
+ private id?: string;
32
+ private unsubscribeCallback?: (x: ComposeUIContextListener) => void;
33
+ private openHandled: boolean;
34
+ private contexts: Context[] = [];
35
+
36
+ constructor(openHandled: boolean, jsonMessaging: JsonMessaging, handler: ContextHandler, contextType?: string) {
37
+ this.openHandled = openHandled;
38
+ this.jsonMessaging = jsonMessaging;
39
+ this.handler = handler;
40
+ this.contextType = contextType;
41
+ }
42
+
43
+ public async subscribe(channelId: string, channelType: ChannelType): Promise<void> {
44
+ await this.registerContextListener(channelId, channelType);
45
+ const subscribeTopic = ComposeUITopic.broadcast(channelId, channelType);
46
+
47
+ this.unsubscribable = await this.jsonMessaging.subscribeJson<Context>(subscribeTopic, async (context: Context) => {
48
+ if (!this.contextType || this.contextType == context!.type) {
49
+ if (this.openHandled === true) {
50
+ this.handler!(context!);
51
+ } else {
52
+ this.contexts.push(context);
53
+ }
54
+ }
55
+ });
56
+
57
+ this.isSubscribed = true;
58
+ }
59
+
60
+ public async handleContextMessage(context: Context): Promise<void> {
61
+ if (!this.isSubscribed) {
62
+ throw new Error("The current listener is not subscribed.");
63
+ }
64
+
65
+ if (this.contextType && this.contextType != null && this.contextType != context.type) {
66
+ throw new Error(`The current listener is not able to handle context type ${context.type}. It is registered to handle ${this.contextType}.`)
67
+ }
68
+
69
+ //If the opened app did not resolved the context that was received by the fdc3.open call, we cache the item.
70
+ if (this.openHandled !== true) {
71
+ this.contexts.push(context);
72
+ return;
73
+ }
74
+
75
+ this.handler(context);
76
+ }
77
+
78
+ public setUnsubscribeCallback(unsubscribeCallback: (x: ComposeUIContextListener) => void): void {
79
+ this.unsubscribeCallback = unsubscribeCallback;
80
+ }
81
+
82
+ //TODO: Decide if we want to have this functionality as the standard does not include it. Currently this method is not used.
83
+ public setOpenHandled(openHandled: boolean): void {
84
+ this.openHandled = openHandled;
85
+
86
+ if (this.openHandled === true) {
87
+ this.contexts.forEach(context => {
88
+ this.handler(context)
89
+ });
90
+
91
+ this.contexts = [];
92
+ }
93
+ }
94
+
95
+ public async unsubscribe(): Promise<void> {
96
+ if (!this.unsubscribable || !this.isSubscribed) {
97
+ return;
98
+ }
99
+
100
+ try {
101
+ await this.leaveChannel();
102
+ } catch(err) {
103
+ console.log(err);
104
+ }
105
+
106
+ this.unsubscribable.unsubscribe();
107
+ this.isSubscribed = false;
108
+
109
+ if (this.unsubscribeCallback) {
110
+ this.unsubscribeCallback(this);
111
+ }
112
+ }
113
+
114
+ private async registerContextListener(channelId: string, channelType: ChannelType) :Promise<void>{
115
+ const request = new Fdc3AddContextListenerRequest(window.composeui.fdc3.config?.instanceId!, this.contextType, channelId, channelType);
116
+ const response = await this.jsonMessaging.invokeJsonService<Fdc3AddContextListenerRequest, Fdc3AddContextListenerResponse>(ComposeUITopic.addContextListener(), request);
117
+
118
+ if (!response) {
119
+ throw new Error(ComposeUIErrors.NoAnswerWasProvided);
120
+ }
121
+
122
+ if (response.error) {
123
+ throw new Error(response.error);
124
+ } else if (!response.success) {
125
+ throw new Error(ComposeUIErrors.SubscribeFailure);
126
+ }
127
+
128
+ this.id = response.id!;
129
+ }
130
+
131
+ private async leaveChannel() : Promise<void> {
132
+ const request = new Fdc3RemoveContextListenerRequest(window.composeui.fdc3.config?.instanceId!, this.id!, this.contextType);
133
+ const response = await this.jsonMessaging.invokeJsonService<Fdc3RemoveContextListenerRequest, Fdc3RemoveContextListenerResponse>(ComposeUITopic.removeContextListener(), request);
134
+ if (!response) {
135
+ throw new Error(ComposeUIErrors.NoAnswerWasProvided);
136
+ }
137
+
138
+ if (response.error) {
139
+ throw new Error(response.error);
140
+ }
141
+
142
+ if (!response.success) {
143
+ throw new Error(ComposeUIErrors.UnsubscribeFailure);
144
+ }
145
+
146
+ return;
147
+ }
148
+ }
@@ -0,0 +1,21 @@
1
+ /*
2
+ * Morgan Stanley makes this available to you under the Apache License,
3
+ * Version 2.0 (the "License"). You may obtain a copy of the License at
4
+ * http://www.apache.org/licenses/LICENSE-2.0.
5
+ * See the NOTICE file distributed with this work for additional information
6
+ * regarding copyright ownership. Unless required by applicable law or agreed
7
+ * to in writing, software distributed under the License is distributed on an
8
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
9
+ * or implied. See the License for the specific language governing permissions
10
+ * and limitations under the License.
11
+ *
12
+ */
13
+
14
+ export enum ComposeUIErrors {
15
+ NoAnswerWasProvided = 'No answer was provided by the DesktopAgent backend.',
16
+ InstanceIdNotFound = 'InstanceId was not found on window object. To run Fdc3\'s ComposeUI implementation instance config should be set on window config.',
17
+ CurrentChannelNotSet = 'The current channel has not been set.',
18
+ UnsubscribeFailure = 'The Listener could not unsubscribe.',
19
+ SubscribeFailure = 'The Listener could not subscribe.',
20
+ AppIdentifierTypeFailure = 'Using string type for app argument is not supported. Please use undefined | AppIdentifier types!'
21
+ }