@leapdev/app-platform 0.0.10-beta.0 → 0.1.0-beta.4

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 (133) hide show
  1. package/.eslintrc.json +25 -0
  2. package/package.json +15 -15
  3. package/project.json +27 -0
  4. package/src/index.ts +44 -0
  5. package/src/lib/app-platform.ts +270 -0
  6. package/src/lib/environment/environment.dictionary.ts +28 -0
  7. package/src/lib/environment/environment.ts +44 -0
  8. package/src/lib/environment/{index.d.ts → index.ts} +2 -2
  9. package/src/lib/environment/leap/env.dev.au.config.ts +33 -0
  10. package/src/lib/environment/leap/env.live.au.config.ts +32 -0
  11. package/src/lib/environment/leap/env.live.ca.config.ts +32 -0
  12. package/src/lib/environment/leap/env.live.nz.config.ts +32 -0
  13. package/src/lib/environment/leap/env.live.uk.config.ts +32 -0
  14. package/src/lib/environment/leap/env.live.us.config.ts +32 -0
  15. package/src/lib/environment/leap/env.liveb.au.config.ts +32 -0
  16. package/src/lib/environment/leap/env.test.au.config.ts +33 -0
  17. package/src/lib/environment/leap/env.test.ca.config.ts +33 -0
  18. package/src/lib/environment/leap/env.test.nz.config.ts +33 -0
  19. package/src/lib/environment/leap/env.test.uk.config.ts +33 -0
  20. package/src/lib/environment/leap/env.test.us.config.ts +33 -0
  21. package/src/lib/functions/helpers.ts +100 -0
  22. package/src/lib/functions/xml.ts +41 -0
  23. package/src/lib/models/calc-api.model.ts +74 -0
  24. package/src/lib/models/constants/index.ts +29 -0
  25. package/src/lib/models/{index.d.ts → index.ts} +1 -1
  26. package/src/lib/models/leap-events.model.ts +79 -0
  27. package/src/lib/models/leap.model.ts +78 -0
  28. package/src/lib/models/signalr.model.ts +22 -0
  29. package/src/lib/services/auth/auth.service.ts +219 -0
  30. package/src/lib/services/calc-api/calc-api-service.interface.ts +12 -0
  31. package/src/lib/services/calc-api/calc-word.service.ts +117 -0
  32. package/src/lib/services/calc-api/promise-helper.function.ts +26 -0
  33. package/src/lib/services/custom-xml-part.service.ts +340 -0
  34. package/src/lib/services/document/document.service.ts +33 -0
  35. package/src/lib/services/{index.d.ts → index.ts} +10 -5
  36. package/src/lib/services/leap-events.service.ts +489 -0
  37. package/src/lib/services/logger/logger.service.ts +42 -0
  38. package/src/lib/services/signalr/signalr.service.ts +88 -0
  39. package/tsconfig.json +20 -0
  40. package/tsconfig.lib.json +12 -0
  41. package/src/index.d.ts +0 -16
  42. package/src/index.js +0 -26
  43. package/src/index.js.map +0 -1
  44. package/src/lib/app-platform.d.ts +0 -25
  45. package/src/lib/app-platform.js +0 -163
  46. package/src/lib/app-platform.js.map +0 -1
  47. package/src/lib/environment/environment.d.ts +0 -37
  48. package/src/lib/environment/environment.dictionary.d.ts +0 -4
  49. package/src/lib/environment/environment.dictionary.js +0 -30
  50. package/src/lib/environment/environment.dictionary.js.map +0 -1
  51. package/src/lib/environment/environment.js +0 -17
  52. package/src/lib/environment/environment.js.map +0 -1
  53. package/src/lib/environment/index.js +0 -9
  54. package/src/lib/environment/index.js.map +0 -1
  55. package/src/lib/environment/leap/env.dev.au.config.d.ts +0 -32
  56. package/src/lib/environment/leap/env.dev.au.config.js +0 -36
  57. package/src/lib/environment/leap/env.dev.au.config.js.map +0 -1
  58. package/src/lib/environment/leap/env.live.au.config.d.ts +0 -32
  59. package/src/lib/environment/leap/env.live.au.config.js +0 -36
  60. package/src/lib/environment/leap/env.live.au.config.js.map +0 -1
  61. package/src/lib/environment/leap/env.live.ca.config.d.ts +0 -32
  62. package/src/lib/environment/leap/env.live.ca.config.js +0 -36
  63. package/src/lib/environment/leap/env.live.ca.config.js.map +0 -1
  64. package/src/lib/environment/leap/env.live.nz.config.d.ts +0 -32
  65. package/src/lib/environment/leap/env.live.nz.config.js +0 -36
  66. package/src/lib/environment/leap/env.live.nz.config.js.map +0 -1
  67. package/src/lib/environment/leap/env.live.uk.config.d.ts +0 -32
  68. package/src/lib/environment/leap/env.live.uk.config.js +0 -36
  69. package/src/lib/environment/leap/env.live.uk.config.js.map +0 -1
  70. package/src/lib/environment/leap/env.live.us.config.d.ts +0 -32
  71. package/src/lib/environment/leap/env.live.us.config.js +0 -36
  72. package/src/lib/environment/leap/env.live.us.config.js.map +0 -1
  73. package/src/lib/environment/leap/env.liveb.au.config.d.ts +0 -32
  74. package/src/lib/environment/leap/env.liveb.au.config.js +0 -36
  75. package/src/lib/environment/leap/env.liveb.au.config.js.map +0 -1
  76. package/src/lib/environment/leap/env.test.au.config.d.ts +0 -32
  77. package/src/lib/environment/leap/env.test.au.config.js +0 -36
  78. package/src/lib/environment/leap/env.test.au.config.js.map +0 -1
  79. package/src/lib/environment/leap/env.test.ca.config.d.ts +0 -32
  80. package/src/lib/environment/leap/env.test.ca.config.js +0 -36
  81. package/src/lib/environment/leap/env.test.ca.config.js.map +0 -1
  82. package/src/lib/environment/leap/env.test.nz.config.d.ts +0 -32
  83. package/src/lib/environment/leap/env.test.nz.config.js +0 -36
  84. package/src/lib/environment/leap/env.test.nz.config.js.map +0 -1
  85. package/src/lib/environment/leap/env.test.uk.config.d.ts +0 -32
  86. package/src/lib/environment/leap/env.test.uk.config.js +0 -36
  87. package/src/lib/environment/leap/env.test.uk.config.js.map +0 -1
  88. package/src/lib/environment/leap/env.test.us.config.d.ts +0 -32
  89. package/src/lib/environment/leap/env.test.us.config.js +0 -36
  90. package/src/lib/environment/leap/env.test.us.config.js.map +0 -1
  91. package/src/lib/functions/helpers.d.ts +0 -5
  92. package/src/lib/functions/helpers.js +0 -95
  93. package/src/lib/functions/helpers.js.map +0 -1
  94. package/src/lib/functions/xml.d.ts +0 -2
  95. package/src/lib/functions/xml.js +0 -41
  96. package/src/lib/functions/xml.js.map +0 -1
  97. package/src/lib/models/calc-api.model.d.ts +0 -57
  98. package/src/lib/models/calc-api.model.js +0 -39
  99. package/src/lib/models/calc-api.model.js.map +0 -1
  100. package/src/lib/models/constants/index.d.ts +0 -36
  101. package/src/lib/models/constants/index.js +0 -43
  102. package/src/lib/models/constants/index.js.map +0 -1
  103. package/src/lib/models/index.js +0 -5
  104. package/src/lib/models/index.js.map +0 -1
  105. package/src/lib/models/leap-events.model.d.ts +0 -56
  106. package/src/lib/models/leap-events.model.js +0 -71
  107. package/src/lib/models/leap-events.model.js.map +0 -1
  108. package/src/lib/models/leap.model.d.ts +0 -54
  109. package/src/lib/models/leap.model.js +0 -29
  110. package/src/lib/models/leap.model.js.map +0 -1
  111. package/src/lib/services/calc-api/calc-api-service.interface.d.ts +0 -6
  112. package/src/lib/services/calc-api/calc-api-service.interface.js +0 -3
  113. package/src/lib/services/calc-api/calc-api-service.interface.js.map +0 -1
  114. package/src/lib/services/calc-api/calc-word.service.d.ts +0 -12
  115. package/src/lib/services/calc-api/calc-word.service.js +0 -99
  116. package/src/lib/services/calc-api/calc-word.service.js.map +0 -1
  117. package/src/lib/services/calc-api/promise-helper.function.d.ts +0 -2
  118. package/src/lib/services/calc-api/promise-helper.function.js +0 -32
  119. package/src/lib/services/calc-api/promise-helper.function.js.map +0 -1
  120. package/src/lib/services/custom-xml-part.service.d.ts +0 -5
  121. package/src/lib/services/custom-xml-part.service.js +0 -269
  122. package/src/lib/services/custom-xml-part.service.js.map +0 -1
  123. package/src/lib/services/document/document.service.d.ts +0 -16
  124. package/src/lib/services/document/document.service.js +0 -32
  125. package/src/lib/services/document/document.service.js.map +0 -1
  126. package/src/lib/services/index.js +0 -9
  127. package/src/lib/services/index.js.map +0 -1
  128. package/src/lib/services/leap-events.service.d.ts +0 -26
  129. package/src/lib/services/leap-events.service.js +0 -357
  130. package/src/lib/services/leap-events.service.js.map +0 -1
  131. package/src/lib/services/logger/logger.service.d.ts +0 -10
  132. package/src/lib/services/logger/logger.service.js +0 -39
  133. package/src/lib/services/logger/logger.service.js.map +0 -1
@@ -0,0 +1,33 @@
1
+ import { LeapEventAction, LeapEventType } from '../../models/leap-events.model';
2
+ import { LeapEventsService } from '../leap-events.service';
3
+ import { LoggerService } from '../logger/logger.service';
4
+
5
+ export class DocumentService {
6
+ constructor(
7
+ private leapEventsService: LeapEventsService,
8
+ private loggerService: LoggerService
9
+ ) {}
10
+
11
+ createDocument(params: {precedentId: string, body: string, closeExisting?: boolean}): Promise<{precedentId: string, body: string, closeExisting?: boolean}> {
12
+ return new Promise((resolve) => {
13
+ Promise.resolve(() => {
14
+ this.loggerService.log(
15
+ '[DocumentService] (createDocument) Creating a document based on the precedent and generated body'
16
+ );
17
+ })
18
+ .then(() => {
19
+ const id = new Date().getTime().toString();
20
+ return this.leapEventsService.addEvent({
21
+ id,
22
+ correlationId: null,
23
+ eventType: LeapEventType.WordAppEvent,
24
+ action: LeapEventAction.WordApp_CreateDocument,
25
+ data: params
26
+ });
27
+ })
28
+ .then(() =>{
29
+ return resolve(params)
30
+ })
31
+ })
32
+ }
33
+ }
@@ -1,5 +1,10 @@
1
- export * from './calc-api/calc-word.service';
2
- export * from './custom-xml-part.service';
3
- export * from './leap-events.service';
4
- export * from './logger/logger.service';
5
- export * from './document/document.service';
1
+
2
+ export * from './calc-api/calc-word.service';
3
+
4
+ export * from './custom-xml-part.service';
5
+
6
+ export * from './leap-events.service';
7
+
8
+ export * from './logger/logger.service';
9
+
10
+ export * from './document/document.service';
@@ -0,0 +1,489 @@
1
+ /// <reference types="office-js" />
2
+ import {
3
+ Observable,
4
+ Subject,
5
+ Subscription,
6
+ catchError,
7
+ filter,
8
+ of,
9
+ } from 'rxjs';
10
+ import {
11
+ LeapEvent,
12
+ LeapEventAction,
13
+ LeapEventType,
14
+ LeapEvents,
15
+ } from '../models/leap-events.model';
16
+ import {
17
+ addHandlerForCustomXmlPart,
18
+ getChildNodes,
19
+ getDataByNamespace,
20
+ getNodeText,
21
+ updateXmlDataByNamespace,
22
+ } from './custom-xml-part.service';
23
+ import { b64DecodeUnicode, b64EncodeUnicode } from '../functions/helpers';
24
+ import { LoggerService } from './logger/logger.service';
25
+ import { constants } from '../models/constants';
26
+ import { toXml } from '../functions/xml';
27
+
28
+ interface IAddEventQueueItem {
29
+ data: LeapEvent;
30
+ resolve: (res: LeapEvent) => void;
31
+ reject: (err: any) => void;
32
+ }
33
+
34
+ export class LeapEventsService {
35
+ private eventSubject$: Subject<LeapEvent> = new Subject<LeapEvent>();
36
+ private readonly event$: Observable<LeapEvent> =
37
+ this.eventSubject$.asObservable();
38
+ private addEventLock = false;
39
+ private addEventQueue: Array<IAddEventQueueItem> = [];
40
+ private subscriptions: { id: string; subscription: Subscription }[] = [];
41
+ private loggerService: LoggerService;
42
+
43
+ constructor() {
44
+ this.loggerService = new LoggerService(false);
45
+ }
46
+
47
+ public addHandler<
48
+ TEventArgs extends
49
+ | Office.NodeInsertedEventArgs
50
+ | Office.NodeDeletedEventArgs
51
+ | Office.NodeReplacedEventArgs
52
+ >(eventType: Office.EventType, handler: (result: TEventArgs) => void) {
53
+ return addHandlerForCustomXmlPart(
54
+ constants.LeapEventsNamespace,
55
+ eventType,
56
+ handler
57
+ );
58
+ }
59
+
60
+ // A default handler. The caller MUST wrap this in a new Promise
61
+ // NOTE: some voodoo passing over the encompassing Promise's resolve/reject functions
62
+ public handleLeapEventResponse<T>(event: LeapEvent, { resolve, reject }): T {
63
+ this.loggerService.log(
64
+ `[LeapEventsService] (handleLeapEventResponse) Handling ${
65
+ LeapEventType[event.eventType]
66
+ }::${LeapEventAction[event.action]}`
67
+ );
68
+ this.loggerService.log(
69
+ `[LeapEventsService] (handleLeapEventResponse): ${JSON.stringify(
70
+ event.data
71
+ )}`
72
+ );
73
+
74
+ type responseObj = {
75
+ status: string;
76
+ error: string;
77
+ };
78
+
79
+ let response: responseObj = {} as responseObj;
80
+ if (Array.isArray(event.data)) {
81
+ response = event.data as any;
82
+ resolve(response);
83
+ } else {
84
+ Object.assign(response, event.data);
85
+ if (response.status === 'ok') {
86
+ resolve(response);
87
+ } else {
88
+ reject(response.error);
89
+ }
90
+ }
91
+
92
+ return response as T;
93
+ }
94
+
95
+ public handleCustomXmlPartNodeEvent(
96
+ eventType: Office.EventType,
97
+ eventArgs: Office.NodeInsertedEventArgs | Office.NodeReplacedEventArgs
98
+ ): Promise<void> {
99
+ return Promise.resolve(`Handle custom XML part event`)
100
+ .then(() => {
101
+ this.loggerService.log(
102
+ `[LeapEventsService] (handleCustomXmlPartNodeEvent) Handling ${eventType} event on the LEAP Events custom XML part`
103
+ );
104
+ })
105
+ .then(() => {
106
+ return this.getLeapEventFromNode(eventType, eventArgs);
107
+ })
108
+ .then((result: LeapEvent | null) => {
109
+ const leapEvent: LeapEvent | null = result;
110
+
111
+ if (!leapEvent || !leapEvent.eventType) {
112
+ this.loggerService.log(
113
+ `[LeapEventsService] (handleCustomXmlPartNodeEvent) This LeapEvent of type ${eventType} is no longer valid: ${eventArgs} `
114
+ );
115
+ return;
116
+ }
117
+
118
+ switch (leapEvent.eventType) {
119
+ case LeapEventType.CalcApiRequest:
120
+ case LeapEventType.DocumentBuilderRequest:
121
+ this.loggerService.log(
122
+ `[LeapEventsService] (handleCustomXmlPartNodeEvent) This LeapEvent is a request: ` +
123
+ `${leapEvent.id} | ${LeapEventType[leapEvent.eventType]} | ${
124
+ LeapEventAction[leapEvent.action]
125
+ }`
126
+ );
127
+ break;
128
+
129
+ case LeapEventType.VstoEvent:
130
+ this.loggerService.log(
131
+ `[LeapEventsService] (handleCustomXmlPartNodeEvent) This LeapEvent is fire-and-forget: ` +
132
+ `${leapEvent.id} | ${LeapEventType[leapEvent.eventType]} | ${
133
+ LeapEventAction[leapEvent.action]
134
+ }`
135
+ );
136
+ this.notify(leapEvent);
137
+ break;
138
+
139
+ case LeapEventType.WordAppEvent:
140
+ this.loggerService.log(
141
+ `[LeapEventsService] (handleCustomXmlPartNodeEvent) This LeapEvent is fire-and-forget: ` +
142
+ `${leapEvent.id} | ${LeapEventType[leapEvent.eventType]} | ${
143
+ LeapEventAction[leapEvent.action]
144
+ }`
145
+ );
146
+ if (leapEvent.action === LeapEventAction.WordApp_ResetTemplate) {
147
+ this.notify(leapEvent);
148
+ }
149
+ break;
150
+
151
+ case LeapEventType.CalcApiResponse:
152
+ case LeapEventType.DocumentBuilderResponse:
153
+ case LeapEventType.WordAppEventResponse:
154
+ this.loggerService.log(
155
+ `[LeapEventsService] (handleCustomXmlPartNodeEvent) This LeapEvent is a response: ` +
156
+ `${leapEvent.id} | ${LeapEventType[leapEvent.eventType]} | ${
157
+ LeapEventAction[leapEvent.action]
158
+ }`
159
+ );
160
+ this.notify(leapEvent);
161
+ break;
162
+ }
163
+ })
164
+ .catch((error) => {
165
+ this.loggerService.error(
166
+ `[LeapEventsService] (handleCustomXmlPartNodeEvent) Could not handle ${eventType} event on the LEAP Events custom XML part: ${error}`
167
+ );
168
+ });
169
+ }
170
+
171
+ public addEvent(event: LeapEvent): Promise<LeapEvent> {
172
+ return new Promise<LeapEvent>((resolve, reject) => {
173
+ this.addEventQueue.push({ data: event, resolve, reject });
174
+ const e = this.addEventQueue.shift();
175
+ if (!this.addEventLock && e) {
176
+ this.processAddEvent(e);
177
+ }
178
+ });
179
+ }
180
+
181
+ public subscribe(
182
+ correlationId: string,
183
+ eventType: LeapEventType,
184
+ action: LeapEventAction,
185
+ handler
186
+ ): string {
187
+ const subscriptionId = [
188
+ LeapEventType[eventType],
189
+ LeapEventAction[action],
190
+ correlationId,
191
+ ]
192
+ .filter((e) => !!e)
193
+ .join('::');
194
+ const subscription = this.event$
195
+ .pipe(
196
+ catchError((error: any) => {
197
+ this.loggerService.error(
198
+ `[LeapEventsService] (subscribe) Subscription observable error: ${error}`
199
+ );
200
+ return of(null);
201
+ }),
202
+ filter((event) => {
203
+ return (
204
+ event !== null &&
205
+ event.eventType === eventType &&
206
+ (Array.isArray(action)
207
+ ? action.includes(event.action)
208
+ : event.action === action) &&
209
+ (!event.correlationId || event.correlationId === correlationId)
210
+ );
211
+ })
212
+ )
213
+ .subscribe((event) => {
214
+ handler(event);
215
+ });
216
+
217
+ if (this.subscriptions.find((s) => s.id === subscriptionId)) {
218
+ this.loggerService.warn(
219
+ `[LeapEventsService] (subscribe) ${subscriptionId} already subscribed (handler already exists)`
220
+ );
221
+ } else {
222
+ this.subscriptions.push({ id: subscriptionId, subscription });
223
+ }
224
+
225
+ return subscriptionId;
226
+ }
227
+
228
+ public unsubscribe(subscriptionId: string): Promise<boolean> {
229
+ return new Promise((resolve) => {
230
+ const idx = this.subscriptions.findIndex((s) => s.id === subscriptionId);
231
+ if (idx >= 0) {
232
+ this.loggerService.log(
233
+ `[LeapEventsService] (unsubscribe) Removing ${subscriptionId} subscription (handler)`
234
+ );
235
+
236
+ const matchingSubscription = this.subscriptions[idx];
237
+ matchingSubscription.subscription.unsubscribe();
238
+
239
+ this.subscriptions.splice(idx, 1);
240
+ } else {
241
+ const message = `Could not find subscription ${subscriptionId}`;
242
+ this.loggerService.warn(`[LeapEventsService] (unsubscribe) ${message}`);
243
+ resolve(false);
244
+ }
245
+ resolve(true);
246
+ });
247
+ }
248
+
249
+ public getEvents(): Promise<LeapEvents | null> {
250
+ this.loggerService.log('[LeapEventsService] Get data from custom XML part');
251
+ return getDataByNamespace(constants.LeapEventsNamespace)
252
+ .then((result) => {
253
+ const r = result as LeapEvents;
254
+
255
+ const eventArray: LeapEvent[] = [];
256
+
257
+ if (!r || r.xmlns === null) {
258
+ const events = new LeapEvents({
259
+ xmlns: constants.LeapEventsNamespace,
260
+ currentEventId: '',
261
+ LeapEvent: eventArray,
262
+ });
263
+ return events;
264
+ }
265
+
266
+ if (r.LeapEvent != null) {
267
+ if (Array.isArray(r.LeapEvent)) {
268
+ for (let i = 0; i < r.LeapEvent.length; i++) {
269
+ const eventBase64String = r.LeapEvent[i] as string;
270
+ if (eventBase64String != null && eventBase64String !== '') {
271
+ const eventJsonString = b64DecodeUnicode(
272
+ eventBase64String as string
273
+ );
274
+ if (eventJsonString) {
275
+ eventArray.push(JSON.parse(eventJsonString));
276
+ }
277
+ }
278
+ }
279
+ } else {
280
+ const eventBase64String = r.LeapEvent as string;
281
+ if (eventBase64String != null && eventBase64String !== '') {
282
+ const eventJsonString = b64DecodeUnicode(
283
+ eventBase64String as string
284
+ );
285
+ if (eventJsonString) {
286
+ eventArray.push(JSON.parse(eventJsonString));
287
+ }
288
+ }
289
+ }
290
+ }
291
+
292
+ const events = new LeapEvents({
293
+ xmlns: r.xmlns,
294
+ currentEventId: r.currentEventId,
295
+ LeapEvent: eventArray,
296
+ });
297
+
298
+ return events;
299
+ })
300
+ .catch((error) => {
301
+ this.loggerService.error(
302
+ `[LeapEventsService] (getEvents) Unable to retrieve LeapEvents: ${error}`
303
+ );
304
+ return null;
305
+ });
306
+ }
307
+
308
+ private processAddEvent({
309
+ data: event,
310
+ resolve,
311
+ reject,
312
+ }: IAddEventQueueItem): void {
313
+ this.addEventLock = true;
314
+ let events: LeapEvents;
315
+ this.getEvents()
316
+ .then((result) => {
317
+ if (result != null) {
318
+ events = new LeapEvents({
319
+ xmlns: constants.LeapEventsNamespace,
320
+ currentEventId: event.id,
321
+ LeapEvent: (result.LeapEvent as LeapEvent[]).map(
322
+ (evt: LeapEvent) => b64EncodeUnicode(JSON.stringify(evt)) || ''
323
+ ),
324
+ });
325
+ const e = b64EncodeUnicode(JSON.stringify(event));
326
+ if (e) {
327
+ (events.LeapEvent as string[]).push(e);
328
+ }
329
+
330
+ const leapEventsObject = {
331
+ $: {
332
+ xmlns: constants.LeapEventsNamespace,
333
+ currentEventId: event.id,
334
+ },
335
+ LeapEvent: events.LeapEvent,
336
+ };
337
+ const xml = toXml(leapEventsObject, constants.LeapEventsRootTagName, {
338
+ headless: true,
339
+ });
340
+ return xml;
341
+ } else {
342
+ throw new Error(`LeapEvents array is null`);
343
+ }
344
+ })
345
+ .then((initialXml) => {
346
+ // Rewrite XML to add the id attribute to the newly-added <LeapEvent>
347
+ const xml = this.addIdAttributeToLeapEvent(initialXml, event.id);
348
+
349
+ // Write raw XML string to custom XML part
350
+ return updateXmlDataByNamespace(
351
+ constants.LeapEventsNamespace,
352
+ xml,
353
+ constants.LeapEventsRootTagName
354
+ );
355
+ })
356
+ .then(() => {
357
+ resolve(event);
358
+ })
359
+ .catch((error) => {
360
+ this.loggerService.error(
361
+ `[LeapEventsService] (getEvents) Unable to add LeapEvent: ${error}`
362
+ );
363
+ reject(error);
364
+ })
365
+ .finally(() => {
366
+ this.addEventLock = false; // Release template data lock to allow other mutations to be processed
367
+ if (this.addEventQueue.length > 0) {
368
+ const e = this.addEventQueue.shift();
369
+ if (e) {
370
+ this.processAddEvent(e);
371
+ }
372
+ }
373
+ });
374
+ }
375
+
376
+ private notify(event: LeapEvent) {
377
+ this.eventSubject$.next(event);
378
+ }
379
+
380
+ private getLeapEventFromNode(
381
+ eventType: Office.EventType,
382
+ eventArgs: Office.NodeInsertedEventArgs | Office.NodeReplacedEventArgs
383
+ ): Promise<LeapEvent | null> {
384
+ return Promise.resolve(`Get LeapEvent from node`)
385
+ .then(() => {
386
+ if (eventArgs.isUndoRedo || (eventArgs as any).inUndoRedo) {
387
+ // OfficeJS API runtime quirk/bug
388
+ this.loggerService.log(
389
+ `[LeapEventsService] (getLeapEventFromNode) IGNORING ${eventType} event since this is an Undo/Redo`
390
+ );
391
+ return Promise.resolve('');
392
+ } else {
393
+ if ((eventArgs as any).oldNode != null) {
394
+ // Office.NodeReplacedEventArgs - contains all child nodes
395
+ return getChildNodes((eventArgs as any).newNode)
396
+ .then((childNodes: Array<Office.CustomXmlNode>) => {
397
+ if (Array.isArray(childNodes) && childNodes.length > 0) {
398
+ return childNodes[0];
399
+ } else {
400
+ return null;
401
+ }
402
+ })
403
+ .then((firstNode: Office.CustomXmlNode | null) => {
404
+ return getNodeText(firstNode);
405
+ });
406
+ } else {
407
+ // Office.NodeInsertedEventArgs - contains just the one inserted node
408
+ return getNodeText((eventArgs as any).newNode);
409
+ }
410
+ }
411
+ })
412
+ .then((nodeText: string) => {
413
+ let leapEvent: LeapEvent;
414
+ if (nodeText == null) {
415
+ return Promise.resolve(null);
416
+ } else {
417
+ let jsonString: string;
418
+ // TODO: How to handle an invalid LEAP Event?
419
+ try {
420
+ jsonString = b64DecodeUnicode(nodeText) || '';
421
+ } catch (error) {
422
+ // Just ignore and pass through
423
+ return Promise.resolve(null);
424
+ }
425
+
426
+ const obj = JSON.parse(jsonString, this.leapEventJsonReviver);
427
+ leapEvent = new LeapEvent({
428
+ id: obj.id,
429
+ correlationId: obj.correlationId,
430
+ eventType: obj.eventType as LeapEventType,
431
+ action: obj.action as LeapEventAction,
432
+ data: obj.data,
433
+ });
434
+ }
435
+ return leapEvent;
436
+ })
437
+ .catch((error) => {
438
+ this.loggerService.error(
439
+ `[LeapEventsService] (getLeapEventFromNode) Unable to get LeapEvent from node: ${error}`
440
+ );
441
+ return null;
442
+ });
443
+ }
444
+
445
+ private leapEventJsonReviver(key: any, value: any): any {
446
+ let result: any;
447
+ switch (key) {
448
+ case 'eventType':
449
+ const eventTypeAsNumber = Number.parseInt(value, 10);
450
+ if (!Number.isNaN(eventTypeAsNumber)) {
451
+ result = eventTypeAsNumber;
452
+ } else {
453
+ result = LeapEventType[value];
454
+ }
455
+ break;
456
+ case 'action':
457
+ const actionAsNumber = Number.parseInt(value, 10);
458
+ if (!Number.isNaN(actionAsNumber)) {
459
+ result = actionAsNumber;
460
+ } else {
461
+ result = LeapEventAction[value];
462
+ }
463
+ break;
464
+ default:
465
+ result = value;
466
+ break;
467
+ }
468
+
469
+ return result;
470
+ }
471
+
472
+ // Rewrites the newly-added <LeapEvent> element to include the id attribute
473
+ private addIdAttributeToLeapEvent(initialXml: string, leapEventId: string) {
474
+ // 1. Create an XmlDocument in order to edit the XML
475
+ const parser = new DOMParser();
476
+ const xmlDoc = parser.parseFromString(initialXml, 'text/xml');
477
+
478
+ // 2. Get the newly-added <LeapEvent> element, and append the id attribute to it
479
+ const leapEventElements = xmlDoc.getElementsByTagName('LeapEvent');
480
+ if (leapEventElements.length > 0) {
481
+ const last = leapEventElements[leapEventElements.length - 1];
482
+ last.setAttribute('id', leapEventId);
483
+ }
484
+
485
+ // 3. Serialize updated XmlDocument to string
486
+ const xmlSerializer = new XMLSerializer();
487
+ return xmlSerializer.serializeToString(xmlDoc);
488
+ }
489
+ }
@@ -0,0 +1,42 @@
1
+ import * as log from 'loglevel';
2
+
3
+ export class LoggerService {
4
+ log: (message: any) => void = () => undefined;
5
+ warn: (message: any) => void = () => undefined;
6
+ debug: (message: any) => void = () => undefined;
7
+ error: (message: any) => void = () => undefined;
8
+ isProduction: boolean;
9
+
10
+ constructor(isProduction: boolean) {
11
+ console.log('LoggerService ctor');
12
+ this.isProduction = isProduction;
13
+
14
+ this.setupLogs();
15
+ }
16
+
17
+ init(name: string): void {
18
+ this.setupLogs(name);
19
+ }
20
+
21
+ private setupLogs(name?: string): void {
22
+ console.log('LoggerService init', name);
23
+
24
+ log.setDefaultLevel(this.isProduction ? log.levels.ERROR : log.levels.DEBUG);
25
+
26
+ this.error = log.error.bind(log);
27
+ this.warn = log.warn.bind(log);
28
+
29
+ if (!this.isProduction) {
30
+ this.debug = log.debug.bind(log);
31
+ this.log = log.info.bind(log);
32
+ } else {
33
+ this.debug = () => undefined;
34
+ this.log = () => undefined;
35
+ }
36
+
37
+ // Set logger name (optional)
38
+ if (name) {
39
+ log.getLogger(name);
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,88 @@
1
+ import { v4 as uuid } from 'uuid';
2
+ import * as signalR from '@microsoft/signalr';
3
+ import { SignalRInitData, SignalRMessage } from '../../models/signalr.model';
4
+
5
+ export class SignalRService {
6
+ readonly #sessionId: string;
7
+ readonly #automationHubUrl: string;
8
+ #connection: signalR.HubConnection | undefined;
9
+
10
+ constructor() {
11
+ this.#automationHubUrl = 'http://localhost:7214/automationHub';
12
+ this.#sessionId = uuid();
13
+ }
14
+
15
+ connectToSignalR(): Promise<string> {
16
+ if (!this.#connection) {
17
+ this.#createConnection(this.#sessionId);
18
+ return new Promise((resolve) => {
19
+ if (this.#connection) {
20
+ this.#connection
21
+ .start()
22
+ .then(() => {
23
+ resolve(this.#sessionId);
24
+ })
25
+ .catch((error) => {
26
+ console.log('Unable to connect to signalR', error);
27
+ resolve('');
28
+ });
29
+ } else {
30
+ console.log('Unable to connect to signalR: no connection');
31
+ resolve('');
32
+ }
33
+ });
34
+ } else {
35
+ return new Promise((resolve) => resolve(this.#sessionId));
36
+ }
37
+ }
38
+
39
+ getInitData(params: {clientId: string}): Promise<SignalRInitData | null> {
40
+ if (this.#connection) {
41
+ const { clientId } = params;
42
+ const requestId = uuid();
43
+ const payload = {
44
+ source: 'automationCore',
45
+ sessionId: this.#sessionId,
46
+ requestId,
47
+ action: "init_data",
48
+ payload: {
49
+ clientId
50
+ }
51
+ }
52
+ return new Promise(resolve => {
53
+ if (this.#connection) {
54
+ this.#connection.invoke("ProcessMessage", payload, true).then(() => {
55
+ if (this.#connection) {
56
+ this.#connection.on("ProcessMessage", (message: SignalRMessage<SignalRInitData>) => {
57
+ if (message.sessionId === this.#sessionId && message.requestId === requestId) {
58
+ resolve(message.payload)
59
+ }
60
+ })
61
+ }
62
+ }).catch((e) => {
63
+ console.log('SignalR sendMessage error', e);
64
+ return resolve(null);
65
+ })
66
+ }
67
+ })
68
+
69
+ } else {
70
+ return new Promise(resolve => resolve(null));
71
+ }
72
+ }
73
+
74
+ #createConnection(sessionId: string) {
75
+ if (this.#connection) {
76
+ return;
77
+ }
78
+ const url = `${this.#automationHubUrl}?sessionId=${sessionId}`;
79
+ try {
80
+ this.#connection = new signalR.HubConnectionBuilder()
81
+ .withUrl(url)
82
+ .withAutomaticReconnect()
83
+ .build();
84
+ } catch (_) {
85
+ this.#connection = undefined;
86
+ }
87
+ }
88
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "module": "commonjs",
5
+ "forceConsistentCasingInFileNames": true,
6
+ "strict": true,
7
+ "noImplicitOverride": true,
8
+ "noPropertyAccessFromIndexSignature": true,
9
+ "noImplicitReturns": true,
10
+ "noFallthroughCasesInSwitch": true
11
+ },
12
+ "types": ["node", "office-js"],
13
+ "files": [],
14
+ "include": [],
15
+ "references": [
16
+ {
17
+ "path": "./tsconfig.lib.json"
18
+ }
19
+ ]
20
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "../../dist/out-tsc",
5
+ "declaration": true,
6
+ "esModuleInterop": true,
7
+ "types": ["node", "office-js", "loglevel"]
8
+ },
9
+ "types": ["node", "office-js"],
10
+ "include": ["src/**/*.ts"],
11
+ "exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"]
12
+ }