@archilogic/extension-sdk 0.2.1

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,23 @@
1
+ # @archilogic/extension-sdk
2
+
3
+ ## 0.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 5c5bf7b: fix(extension-sdk): bundle all types
8
+
9
+ ## 0.2.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 52b7f4d: feat(extension-sdk): allow to select return fields in layout query methods
14
+
15
+ ## 0.1.0
16
+
17
+ ### Minor Changes
18
+
19
+ - a2d70e5: feat(extension-sdk): add plugin and host api with method types
20
+
21
+ ### Patch Changes
22
+
23
+ - a2d70e5: chore: init package
package/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # @archilogic/extension-sdk
2
+
3
+ ## Description
4
+
5
+ The Plugin SDK allows to create plugins for the Archilogic editor.
6
+
7
+ ## Installation
8
+
9
+ ```
10
+ npm install
11
+ ```
12
+
13
+ ## Run tests
14
+
15
+ ```
16
+ npm run test
17
+ ```
18
+
19
+ ## Type check, formatting and linting
20
+
21
+ ```
22
+ npm run codecheck
23
+ ```
24
+
25
+ ## Build
26
+
27
+ ```
28
+ npm run build
29
+ ```
@@ -0,0 +1,584 @@
1
+ var we = Object.defineProperty;
2
+ var Q = (e) => {
3
+ throw TypeError(e);
4
+ };
5
+ var Ee = (e, t, r) => t in e ? we(e, t, { enumerable: !0, configurable: !0, writable: !0, value: r }) : e[t] = r;
6
+ var P = (e, t, r) => Ee(e, typeof t != "symbol" ? t + "" : t, r), Z = (e, t, r) => t.has(e) || Q("Cannot " + r);
7
+ var s = (e, t, r) => (Z(e, t, "read from private field"), r ? r.call(e) : t.get(e)), f = (e, t, r) => t.has(e) ? Q("Cannot add the same private member more than once") : t instanceof WeakSet ? t.add(e) : t.set(e, r), I = (e, t, r, a) => (Z(e, t, "write to private field"), a ? a.call(e, r) : t.set(e, r), r);
8
+ var Pe = class extends Error {
9
+ constructor(t, r) {
10
+ super(r);
11
+ P(this, "code");
12
+ this.name = "PenpalError", this.code = t;
13
+ }
14
+ }, w = Pe, Ie = (e) => ({
15
+ name: e.name,
16
+ message: e.message,
17
+ stack: e.stack,
18
+ penpalCode: e instanceof w ? e.code : void 0
19
+ }), Ae = ({
20
+ name: e,
21
+ message: t,
22
+ stack: r,
23
+ penpalCode: a
24
+ }) => {
25
+ const n = a ? new w(a, t) : new Error(t);
26
+ return n.name = e, n.stack = r, n;
27
+ }, Se = Symbol("Reply"), B, ae, Ce = (ae = class {
28
+ constructor(e, t) {
29
+ P(this, "value");
30
+ P(this, "transferables");
31
+ // Allows TypeScript to distinguish between an actual instance of this
32
+ // class versus an object that looks structurally similar.
33
+ // eslint-disable-next-line no-unused-private-class-members
34
+ f(this, B, Se);
35
+ this.value = e, this.transferables = t == null ? void 0 : t.transferables;
36
+ }
37
+ }, B = new WeakMap(), ae), me = Ce, M = "penpal", W = (e) => typeof e == "object" && e !== null, ce = (e) => typeof e == "function", Re = (e) => W(e) && e.namespace === M, j = (e) => e.type === "SYN", X = (e) => e.type === "ACK1", z = (e) => e.type === "ACK2", de = (e) => e.type === "CALL", le = (e) => e.type === "REPLY", Ne = (e) => e.type === "DESTROY", ue = (e, t = []) => {
38
+ const r = [];
39
+ for (const a of Object.keys(e)) {
40
+ const n = e[a];
41
+ ce(n) ? r.push([...t, a]) : W(n) && r.push(
42
+ ...ue(n, [...t, a])
43
+ );
44
+ }
45
+ return r;
46
+ }, _e = (e, t) => {
47
+ const r = e.reduce(
48
+ (a, n) => W(a) ? a[n] : void 0,
49
+ t
50
+ );
51
+ return ce(r) ? r : void 0;
52
+ }, C = (e) => e.join("."), ee = (e, t, r) => ({
53
+ namespace: M,
54
+ channel: e,
55
+ type: "REPLY",
56
+ callId: t,
57
+ isError: !0,
58
+ ...r instanceof Error ? { value: Ie(r), isSerializedErrorInstance: !0 } : { value: r }
59
+ }), Te = (e, t, r, a) => {
60
+ let n = !1;
61
+ const d = async (l) => {
62
+ if (n || !de(l))
63
+ return;
64
+ a == null || a(`Received ${C(l.methodPath)}() call`, l);
65
+ const { methodPath: v, args: h, id: o } = l;
66
+ let i, E;
67
+ try {
68
+ const u = _e(v, t);
69
+ if (!u)
70
+ throw new w(
71
+ "METHOD_NOT_FOUND",
72
+ `Method \`${C(v)}\` is not found.`
73
+ );
74
+ let p = await u(...h);
75
+ p instanceof me && (E = p.transferables, p = await p.value), i = {
76
+ namespace: M,
77
+ channel: r,
78
+ type: "REPLY",
79
+ callId: o,
80
+ value: p
81
+ };
82
+ } catch (u) {
83
+ i = ee(r, o, u);
84
+ }
85
+ if (!n)
86
+ try {
87
+ a == null || a(`Sending ${C(v)}() reply`, i), e.sendMessage(i, E);
88
+ } catch (u) {
89
+ throw u.name === "DataCloneError" && (i = ee(r, o, u), a == null || a(`Sending ${C(v)}() reply`, i), e.sendMessage(i)), u;
90
+ }
91
+ };
92
+ return e.addMessageHandler(d), () => {
93
+ n = !0, e.removeMessageHandler(d);
94
+ };
95
+ }, De = Te, se, he = ((se = crypto.randomUUID) == null ? void 0 : se.bind(crypto)) ?? (() => new Array(4).fill(0).map(
96
+ () => Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16)
97
+ ).join("-")), Le = Symbol("CallOptions"), J, ie, be = (ie = class {
98
+ constructor(e) {
99
+ P(this, "transferables");
100
+ P(this, "timeout");
101
+ // Allows TypeScript to distinguish between an actual instance of this
102
+ // class versus an object that looks structurally similar.
103
+ // eslint-disable-next-line no-unused-private-class-members
104
+ f(this, J, Le);
105
+ this.transferables = e == null ? void 0 : e.transferables, this.timeout = e == null ? void 0 : e.timeout;
106
+ }
107
+ }, J = new WeakMap(), ie), ke = be, Oe = /* @__PURE__ */ new Set(["apply", "call", "bind"]), pe = (e, t, r = []) => new Proxy(
108
+ r.length ? () => {
109
+ } : /* @__PURE__ */ Object.create(null),
110
+ {
111
+ get(a, n) {
112
+ if (n !== "then")
113
+ return r.length && Oe.has(n) ? Reflect.get(a, n) : pe(e, t, [...r, n]);
114
+ },
115
+ apply(a, n, d) {
116
+ return e(r, d);
117
+ }
118
+ }
119
+ ), te = (e) => new w(
120
+ "CONNECTION_DESTROYED",
121
+ `Method call ${C(
122
+ e
123
+ )}() failed due to destroyed connection`
124
+ ), He = (e, t, r) => {
125
+ let a = !1;
126
+ const n = /* @__PURE__ */ new Map(), d = (h) => {
127
+ if (!le(h))
128
+ return;
129
+ const { callId: o, value: i, isError: E, isSerializedErrorInstance: u } = h, p = n.get(o);
130
+ p && (n.delete(o), r == null || r(
131
+ `Received ${C(p.methodPath)}() call`,
132
+ h
133
+ ), E ? p.reject(
134
+ u ? Ae(i) : i
135
+ ) : p.resolve(i));
136
+ };
137
+ return e.addMessageHandler(d), {
138
+ remoteProxy: pe((h, o) => {
139
+ if (a)
140
+ throw te(h);
141
+ const i = he(), E = o[o.length - 1], u = E instanceof ke, { timeout: p, transferables: g } = u ? E : {}, F = u ? o.slice(0, -1) : o;
142
+ return new Promise((Y, O) => {
143
+ const K = p !== void 0 ? window.setTimeout(() => {
144
+ n.delete(i), O(
145
+ new w(
146
+ "METHOD_CALL_TIMEOUT",
147
+ `Method call ${C(
148
+ h
149
+ )}() timed out after ${p}ms`
150
+ )
151
+ );
152
+ }, p) : void 0;
153
+ n.set(i, { methodPath: h, resolve: Y, reject: O, timeoutId: K });
154
+ try {
155
+ const T = {
156
+ namespace: M,
157
+ channel: t,
158
+ type: "CALL",
159
+ id: i,
160
+ methodPath: h,
161
+ args: F
162
+ };
163
+ r == null || r(`Sending ${C(h)}() call`, T), e.sendMessage(T, g);
164
+ } catch (T) {
165
+ O(
166
+ new w("TRANSMISSION_FAILED", T.message)
167
+ );
168
+ }
169
+ });
170
+ }, r),
171
+ destroy: () => {
172
+ a = !0, e.removeMessageHandler(d);
173
+ for (const { methodPath: h, reject: o, timeoutId: i } of n.values())
174
+ clearTimeout(i), o(te(h));
175
+ n.clear();
176
+ }
177
+ };
178
+ }, xe = He, Fe = () => {
179
+ let e, t;
180
+ return {
181
+ promise: new Promise((a, n) => {
182
+ e = a, t = n;
183
+ }),
184
+ resolve: e,
185
+ reject: t
186
+ };
187
+ }, Ye = Fe, $e = class extends Error {
188
+ constructor(e) {
189
+ super(
190
+ `You've hit a bug in Penpal. Please file an issue with the following information: ${e}`
191
+ );
192
+ }
193
+ }, U = $e, G = "deprecated-penpal", je = (e) => W(e) && "penpal" in e, Ue = (e) => e.split("."), re = (e) => e.join("."), fe = (e) => new U(
194
+ `Unexpected message to translate: ${JSON.stringify(e)}`
195
+ ), Ve = (e) => {
196
+ if (e.penpal === "syn")
197
+ return {
198
+ namespace: M,
199
+ channel: void 0,
200
+ type: "SYN",
201
+ participantId: G
202
+ };
203
+ if (e.penpal === "ack")
204
+ return {
205
+ namespace: M,
206
+ channel: void 0,
207
+ type: "ACK2"
208
+ };
209
+ if (e.penpal === "call")
210
+ return {
211
+ namespace: M,
212
+ channel: void 0,
213
+ type: "CALL",
214
+ // Actually converting the ID to a string would break communication.
215
+ id: e.id,
216
+ methodPath: Ue(e.methodName),
217
+ args: e.args
218
+ };
219
+ if (e.penpal === "reply")
220
+ return e.resolution === "fulfilled" ? {
221
+ namespace: M,
222
+ channel: void 0,
223
+ type: "REPLY",
224
+ // Actually converting the ID to a string would break communication.
225
+ callId: e.id,
226
+ value: e.returnValue
227
+ } : {
228
+ namespace: M,
229
+ channel: void 0,
230
+ type: "REPLY",
231
+ // Actually converting the ID to a string would break communication.
232
+ callId: e.id,
233
+ isError: !0,
234
+ ...e.returnValueIsError ? {
235
+ value: e.returnValue,
236
+ isSerializedErrorInstance: !0
237
+ } : {
238
+ value: e.returnValue
239
+ }
240
+ };
241
+ throw fe(e);
242
+ }, We = (e) => {
243
+ if (X(e))
244
+ return {
245
+ penpal: "synAck",
246
+ methodNames: e.methodPaths.map(re)
247
+ };
248
+ if (de(e))
249
+ return {
250
+ penpal: "call",
251
+ // Actually converting the ID to a number would break communication.
252
+ id: e.id,
253
+ methodName: re(e.methodPath),
254
+ args: e.args
255
+ };
256
+ if (le(e))
257
+ return e.isError ? {
258
+ penpal: "reply",
259
+ // Actually converting the ID to a number would break communication.
260
+ id: e.callId,
261
+ resolution: "rejected",
262
+ ...e.isSerializedErrorInstance ? {
263
+ returnValue: e.value,
264
+ returnValueIsError: !0
265
+ } : { returnValue: e.value }
266
+ } : {
267
+ penpal: "reply",
268
+ // Actually converting the ID to a number would break communication.
269
+ id: e.callId,
270
+ resolution: "fulfilled",
271
+ returnValue: e.value
272
+ };
273
+ throw fe(e);
274
+ }, ge = ({
275
+ messenger: e,
276
+ methods: t,
277
+ timeout: r,
278
+ channel: a,
279
+ log: n
280
+ }) => {
281
+ const d = he();
282
+ let l;
283
+ const v = [];
284
+ let h = !1;
285
+ const o = ue(t), { promise: i, resolve: E, reject: u } = Ye(), p = r !== void 0 ? setTimeout(() => {
286
+ u(
287
+ new w(
288
+ "CONNECTION_TIMEOUT",
289
+ `Connection timed out after ${r}ms`
290
+ )
291
+ );
292
+ }, r) : void 0, g = () => {
293
+ for (const c of v)
294
+ c();
295
+ }, F = () => {
296
+ if (h)
297
+ return;
298
+ v.push(De(e, t, a, n));
299
+ const { remoteProxy: c, destroy: m } = xe(e, a, n);
300
+ v.push(m), clearTimeout(p), h = !0, E({
301
+ remoteProxy: c,
302
+ destroy: g
303
+ });
304
+ }, Y = () => {
305
+ const c = {
306
+ namespace: M,
307
+ type: "SYN",
308
+ channel: a,
309
+ participantId: d
310
+ };
311
+ n == null || n("Sending handshake SYN", c);
312
+ try {
313
+ e.sendMessage(c);
314
+ } catch (m) {
315
+ u(new w("TRANSMISSION_FAILED", m.message));
316
+ }
317
+ }, O = (c) => {
318
+ if (n == null || n("Received handshake SYN", c), c.participantId === l && // TODO: Used for backward-compatibility. Remove in next major version.
319
+ l !== G || (l = c.participantId, Y(), !(d > l || // TODO: Used for backward-compatibility. Remove in next major version.
320
+ l === G)))
321
+ return;
322
+ const $ = {
323
+ namespace: M,
324
+ channel: a,
325
+ type: "ACK1",
326
+ methodPaths: o
327
+ };
328
+ n == null || n("Sending handshake ACK1", $);
329
+ try {
330
+ e.sendMessage($);
331
+ } catch (Me) {
332
+ u(new w("TRANSMISSION_FAILED", Me.message));
333
+ return;
334
+ }
335
+ }, K = (c) => {
336
+ n == null || n("Received handshake ACK1", c);
337
+ const m = {
338
+ namespace: M,
339
+ channel: a,
340
+ type: "ACK2"
341
+ };
342
+ n == null || n("Sending handshake ACK2", m);
343
+ try {
344
+ e.sendMessage(m);
345
+ } catch ($) {
346
+ u(new w("TRANSMISSION_FAILED", $.message));
347
+ return;
348
+ }
349
+ F();
350
+ }, T = (c) => {
351
+ n == null || n("Received handshake ACK2", c), F();
352
+ }, q = (c) => {
353
+ j(c) && O(c), X(c) && K(c), z(c) && T(c);
354
+ };
355
+ return e.addMessageHandler(q), v.push(() => e.removeMessageHandler(q)), Y(), i;
356
+ }, Ke = ge, ze = (e) => {
357
+ let t = !1, r;
358
+ return (...a) => (t || (t = !0, r = e(...a)), r);
359
+ }, Ge = ze, ne = /* @__PURE__ */ new WeakSet(), Be = ({
360
+ messenger: e,
361
+ methods: t = {},
362
+ timeout: r,
363
+ channel: a,
364
+ log: n
365
+ }) => {
366
+ if (!e)
367
+ throw new w("INVALID_ARGUMENT", "messenger must be defined");
368
+ if (ne.has(e))
369
+ throw new w(
370
+ "INVALID_ARGUMENT",
371
+ "A messenger can only be used for a single connection"
372
+ );
373
+ ne.add(e);
374
+ const d = [e.destroy], l = Ge((o) => {
375
+ if (o) {
376
+ const i = {
377
+ namespace: M,
378
+ channel: a,
379
+ type: "DESTROY"
380
+ };
381
+ try {
382
+ e.sendMessage(i);
383
+ } catch {
384
+ }
385
+ }
386
+ for (const i of d)
387
+ i();
388
+ n == null || n("Connection destroyed");
389
+ }), v = (o) => Re(o) && o.channel === a;
390
+ return {
391
+ promise: (async () => {
392
+ try {
393
+ e.initialize({ log: n, validateReceivedMessage: v }), e.addMessageHandler((E) => {
394
+ Ne(E) && l(!1);
395
+ });
396
+ const { remoteProxy: o, destroy: i } = await Ke({
397
+ messenger: e,
398
+ methods: t,
399
+ timeout: r,
400
+ channel: a,
401
+ log: n
402
+ });
403
+ return d.push(i), o;
404
+ } catch (o) {
405
+ throw l(!0), o;
406
+ }
407
+ })(),
408
+ // Why we don't reject the connection promise when consumer calls destroy():
409
+ // https://github.com/Aaronius/penpal/issues/51
410
+ destroy: () => {
411
+ l(!0);
412
+ }
413
+ };
414
+ }, ve = Be, A, R, D, L, N, S, y, _, V, b, H, x, k, oe, Je = (oe = class {
415
+ constructor({ remoteWindow: e, allowedOrigins: t }) {
416
+ f(this, A);
417
+ f(this, R);
418
+ f(this, D);
419
+ f(this, L);
420
+ f(this, N);
421
+ f(this, S, /* @__PURE__ */ new Set());
422
+ f(this, y);
423
+ // TODO: Used for backward-compatibility. Remove in next major version.
424
+ f(this, _, !1);
425
+ P(this, "initialize", ({
426
+ log: e,
427
+ validateReceivedMessage: t
428
+ }) => {
429
+ I(this, D, e), I(this, L, t), window.addEventListener("message", s(this, x));
430
+ });
431
+ P(this, "sendMessage", (e, t) => {
432
+ if (j(e)) {
433
+ const r = s(this, b).call(this, e);
434
+ s(this, A).postMessage(e, {
435
+ targetOrigin: r,
436
+ transfer: t
437
+ });
438
+ return;
439
+ }
440
+ if (X(e) || // If the child is using a previous version of Penpal, we need to
441
+ // downgrade the message and send it through the window rather than
442
+ // the port because older versions of Penpal don't use MessagePorts.
443
+ s(this, _)) {
444
+ const r = s(this, _) ? We(e) : e, a = s(this, b).call(this, e);
445
+ s(this, A).postMessage(r, {
446
+ targetOrigin: a,
447
+ transfer: t
448
+ });
449
+ return;
450
+ }
451
+ if (z(e)) {
452
+ const { port1: r, port2: a } = new MessageChannel();
453
+ I(this, y, r), r.addEventListener("message", s(this, k)), r.start();
454
+ const n = [a, ...t || []], d = s(this, b).call(this, e);
455
+ s(this, A).postMessage(e, {
456
+ targetOrigin: d,
457
+ transfer: n
458
+ });
459
+ return;
460
+ }
461
+ if (s(this, y)) {
462
+ s(this, y).postMessage(e, {
463
+ transfer: t
464
+ });
465
+ return;
466
+ }
467
+ throw new U("Port is undefined");
468
+ });
469
+ P(this, "addMessageHandler", (e) => {
470
+ s(this, S).add(e);
471
+ });
472
+ P(this, "removeMessageHandler", (e) => {
473
+ s(this, S).delete(e);
474
+ });
475
+ P(this, "destroy", () => {
476
+ window.removeEventListener("message", s(this, x)), s(this, H).call(this), s(this, S).clear();
477
+ });
478
+ f(this, V, (e) => s(this, R).some(
479
+ (t) => t instanceof RegExp ? t.test(e) : t === e || t === "*"
480
+ ));
481
+ f(this, b, (e) => {
482
+ if (j(e))
483
+ return "*";
484
+ if (!s(this, N))
485
+ throw new U("Concrete remote origin not set");
486
+ return s(this, N) === "null" && s(this, R).includes("*") ? "*" : s(this, N);
487
+ });
488
+ f(this, H, () => {
489
+ var e, t;
490
+ (e = s(this, y)) == null || e.removeEventListener("message", s(this, k)), (t = s(this, y)) == null || t.close(), I(this, y, void 0);
491
+ });
492
+ f(this, x, ({
493
+ source: e,
494
+ origin: t,
495
+ ports: r,
496
+ data: a
497
+ }) => {
498
+ var n, d, l;
499
+ if (e === s(this, A) && (je(a) && ((n = s(this, D)) == null || n.call(
500
+ this,
501
+ "Please upgrade the child window to the latest version of Penpal."
502
+ ), I(this, _, !0), a = Ve(a)), !!((d = s(this, L)) != null && d.call(this, a)))) {
503
+ if (!s(this, V).call(this, t)) {
504
+ (l = s(this, D)) == null || l.call(
505
+ this,
506
+ `Received a message from origin \`${t}\` which did not match allowed origins \`[${s(this, R).join(", ")}]\``
507
+ );
508
+ return;
509
+ }
510
+ if (j(a) && (s(this, H).call(this), I(this, N, t)), z(a) && // Previous versions of Penpal don't use MessagePorts and do all
511
+ // communication through the window.
512
+ !s(this, _)) {
513
+ if (I(this, y, r[0]), !s(this, y))
514
+ throw new U("No port received on ACK2");
515
+ s(this, y).addEventListener("message", s(this, k)), s(this, y).start();
516
+ }
517
+ for (const v of s(this, S))
518
+ v(a);
519
+ }
520
+ });
521
+ f(this, k, ({ data: e }) => {
522
+ var t;
523
+ if ((t = s(this, L)) != null && t.call(this, e))
524
+ for (const r of s(this, S))
525
+ r(e);
526
+ });
527
+ if (!e)
528
+ throw new w("INVALID_ARGUMENT", "remoteWindow must be defined");
529
+ I(this, A, e), I(this, R, t != null && t.length ? t : [window.origin]);
530
+ }
531
+ }, A = new WeakMap(), R = new WeakMap(), D = new WeakMap(), L = new WeakMap(), N = new WeakMap(), S = new WeakMap(), y = new WeakMap(), _ = new WeakMap(), V = new WeakMap(), b = new WeakMap(), H = new WeakMap(), x = new WeakMap(), k = new WeakMap(), oe), ye = Je;
532
+ const Qe = (e, t) => {
533
+ const r = new ye({
534
+ remoteWindow: e.contentWindow,
535
+ // Defaults to the current origin.
536
+ allowedOrigins: ["http://localhost:3000", "https://archilogic-com.github.io"]
537
+ // Alternatively,
538
+ // allowedOrigins: [new Url(iframe.src).origin]
539
+ });
540
+ return ve({
541
+ messenger: r,
542
+ // Methods the parent window is exposing to the iframe window.
543
+ methods: t
544
+ });
545
+ }, Xe = {
546
+ onPluginConnect: () => {
547
+ },
548
+ onPluginDestroy: () => {
549
+ },
550
+ onLayoutChange: () => {
551
+ },
552
+ onSelectionChange: () => {
553
+ },
554
+ onGraphicDrag: () => {
555
+ },
556
+ onCanvasClick: () => {
557
+ }
558
+ }, Ze = (e = {}) => {
559
+ const t = new ye({
560
+ remoteWindow: window.parent,
561
+ // Defaults to the current origin.
562
+ allowedOrigins: [
563
+ "http://localhost:4000",
564
+ "https://app.archilogic.com",
565
+ "https://testing-app.archilogic.com"
566
+ ]
567
+ }), r = ve({
568
+ messenger: t,
569
+ // Methods the iframe window is exposing to the parent window.
570
+ methods: { ...Xe, ...e }
571
+ });
572
+ return new Proxy(
573
+ {},
574
+ {
575
+ get(n, d) {
576
+ return async (...l) => (await r.promise)[d](...l);
577
+ }
578
+ }
579
+ );
580
+ };
581
+ export {
582
+ Qe as createExtensionConnection,
583
+ Ze as createHostConnection
584
+ };