@sumit_yewale/angular 1.1.18 → 1.1.21-fix.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.
@@ -1,6 +1,6 @@
1
- import { SecurityContext, Component, ViewChild, Input, NgModule } from '@angular/core';
1
+ import { SecurityContext, Component, ChangeDetectorRef, NgZone, Input, NgModule } from '@angular/core';
2
2
  import { DomSanitizer } from '@angular/platform-browser';
3
- import { generateUuid, buildImportUrl, classifyStructuredMessage, buildInitPayload } from '@sumit_yewale/adapter-core';
3
+ import { createMessageGate, createImportTracker, createFileSender, classifyLegacyMessage, classifyStructuredMessage, generateUuid, buildImportUrl, buildInitPayload } from '@sumit_yewale/adapter-core';
4
4
 
5
5
  /**
6
6
  * @fileoverview added by tsickle
@@ -51,14 +51,133 @@ const insertCSS = (/**
51
51
  * Generated from: lib/csvbox-button.component.ts
52
52
  * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
53
53
  */
54
+ // How long we wait for the importer to confirm a file it was sent before giving up on it.
54
55
  /** @type {?} */
55
- const appVersion = '1.1.16';
56
+ const FILE_ACK_TIMEOUT_MS = 8000;
57
+ /**
58
+ * @record
59
+ */
60
+ function OpenImporterEntry() { }
61
+ if (false) {
62
+ /** @type {?} */
63
+ OpenImporterEntry.prototype.getHolder;
64
+ }
65
+ /**
66
+ * @record
67
+ */
68
+ function OpenImporters() { }
69
+ if (false) {
70
+ /** @type {?} */
71
+ OpenImporters.prototype.open;
72
+ /** @type {?} */
73
+ OpenImporters.prototype.listeners;
74
+ }
75
+ /** @type {?} */
76
+ const OPEN_IMPORTERS_KEY = '__csvboxOpenImporters';
77
+ /** @type {?} */
78
+ const TOP_Z_INDEX = 2147483647;
79
+ /**
80
+ * @return {?}
81
+ */
82
+ function openImporters() {
83
+ /** @type {?} */
84
+ const w = (/** @type {?} */ (window));
85
+ if (!w[OPEN_IMPORTERS_KEY])
86
+ w[OPEN_IMPORTERS_KEY] = { open: [], listeners: [] };
87
+ return w[OPEN_IMPORTERS_KEY];
88
+ }
89
+ /**
90
+ * @param {?} reg
91
+ * @return {?}
92
+ */
93
+ function restackOpenImporters(reg) {
94
+ reg.open.forEach((/**
95
+ * @param {?} entry
96
+ * @param {?} i
97
+ * @return {?}
98
+ */
99
+ (entry, i) => {
100
+ /** @type {?} */
101
+ const holder = entry.getHolder();
102
+ if (holder)
103
+ holder.style.zIndex = String(TOP_Z_INDEX - (reg.open.length - 1 - i));
104
+ }));
105
+ }
106
+ /**
107
+ * @param {?} entry
108
+ * @return {?}
109
+ */
110
+ function registerOpenImporter(entry) {
111
+ /** @type {?} */
112
+ const reg = openImporters();
113
+ if (reg.open.indexOf(entry) >= 0)
114
+ return;
115
+ reg.open.push(entry);
116
+ restackOpenImporters(reg);
117
+ reg.listeners.slice().forEach((/**
118
+ * @param {?} fn
119
+ * @return {?}
120
+ */
121
+ fn => fn()));
122
+ }
123
+ /**
124
+ * @param {?} entry
125
+ * @return {?}
126
+ */
127
+ function unregisterOpenImporter(entry) {
128
+ /** @type {?} */
129
+ const reg = openImporters();
130
+ /** @type {?} */
131
+ const i = reg.open.indexOf(entry);
132
+ if (i < 0)
133
+ return;
134
+ reg.open.splice(i, 1);
135
+ restackOpenImporters(reg);
136
+ reg.listeners.slice().forEach((/**
137
+ * @param {?} fn
138
+ * @return {?}
139
+ */
140
+ fn => fn()));
141
+ }
142
+ /**
143
+ * @param {?} entry
144
+ * @return {?}
145
+ */
146
+ function isBlockedByOpenImporter(entry) {
147
+ /** @type {?} */
148
+ const reg = openImporters();
149
+ return reg.open.length > 0 && reg.open.indexOf(entry) < 0;
150
+ }
151
+ /**
152
+ * @param {?} fn
153
+ * @return {?}
154
+ */
155
+ function subscribeOpenImporters(fn) {
156
+ /** @type {?} */
157
+ const reg = openImporters();
158
+ reg.listeners.push(fn);
159
+ return (/**
160
+ * @return {?}
161
+ */
162
+ () => {
163
+ /** @type {?} */
164
+ const i = reg.listeners.indexOf(fn);
165
+ if (i >= 0)
166
+ reg.listeners.splice(i, 1);
167
+ });
168
+ }
169
+ /** @type {?} */
170
+ const appVersion = '1.1.20';
56
171
  class CSVBoxButtonComponent {
57
172
  /**
58
173
  * @param {?} sanitizer
174
+ * @param {?} cdr
175
+ * @param {?} zone
59
176
  */
60
- constructor(sanitizer) {
177
+ constructor(sanitizer, cdr, zone) {
61
178
  this.sanitizer = sanitizer;
179
+ this.cdr = cdr;
180
+ this.zone = zone;
62
181
  this.isModalShown = false;
63
182
  this.licenseKey = null;
64
183
  this.uuid = null;
@@ -71,58 +190,138 @@ class CSVBoxButtonComponent {
71
190
  this.lazy = false;
72
191
  this.iframe = null;
73
192
  this.disabled = true;
74
- }
75
- /**
76
- * @return {?}
77
- */
78
- ngOnInit() {
79
- this.uuid = generateUuid();
80
- /** @type {?} */
81
- let iframeUrl = buildImportUrl({
82
- licenseKey: this.licenseKey,
83
- customDomain: this.customDomain,
84
- dataLocation: this.dataLocation,
85
- language: this.language,
86
- theme: this.theme,
87
- environment: this.environment
88
- }, "angular", appVersion);
89
- this.safeUrl = this.sanitizer.sanitize(SecurityContext.RESOURCE_URL, this.sanitizer.bypassSecurityTrustResourceUrl(iframeUrl));
90
- }
91
- /**
92
- * @param {?} changes
93
- * @return {?}
94
- */
95
- ngOnChanges(changes) {
96
- if (changes["user"] && changes['user'].currentValue != changes['user'].previousValue) {
97
- this.updateUserVariabe(changes['user'].currentValue);
98
- }
99
- }
100
- /**
101
- * @param {?} data
102
- * @return {?}
103
- */
104
- updateUserVariabe(data) {
105
- this.user = data;
106
- if (this.iframe && this.iframe.contentWindow) {
107
- this.iframe.contentWindow.postMessage({
108
- "customer": data
109
- }, "*");
110
- }
111
- }
112
- /**
113
- * @return {?}
114
- */
115
- ngAfterContentInit() {
116
- window.addEventListener("message", (/**
193
+ // True while a different importer on the page is open: the button stays disabled.
194
+ this.blockedByOther = false;
195
+ this.messageGate = createMessageGate();
196
+ this.importTracker = createImportTracker();
197
+ this.registryEntry = { getHolder: (/**
198
+ * @return {?}
199
+ */
200
+ () => this.holder) };
201
+ this.fileSender = createFileSender({
202
+ getIframe: (/**
203
+ * @return {?}
204
+ */
205
+ () => this.iframe),
206
+ getToken: (/**
207
+ * @return {?}
208
+ */
209
+ () => (/** @type {?} */ (this.uuid))),
210
+ getTargetOrigin: (/**
211
+ * @return {?}
212
+ */
213
+ () => (/** @type {?} */ (this.targetOrigin))),
214
+ getBaseDomain: (/**
215
+ * @return {?}
216
+ */
217
+ () => this.baseDomain),
218
+ takeFile: (/**
219
+ * @return {?}
220
+ */
221
+ () => {
222
+ /** @type {?} */
223
+ const file = this.pendingFile;
224
+ this.pendingFile = null;
225
+ return file;
226
+ }),
227
+ onSent: (/**
228
+ * @return {?}
229
+ */
230
+ () => this.startFileAckTimer())
231
+ });
232
+ this.unsubscribeOpenImporters = null;
233
+ this.pendingFile = null;
234
+ // Data-location sheets redirect to another host, so the iframe origin can differ from
235
+ // targetOrigin. baseDomain is used to check the real origin before trusting it.
236
+ this.baseDomain = "app.csvbox.io";
237
+ this.fileAckTimer = null;
238
+ this.messageListener = (/**
117
239
  * @param {?} event
118
240
  * @return {?}
119
241
  */
120
242
  (event) => {
243
+ // Older importers send these as bare strings, without a token.
244
+ /** @type {?} */
245
+ const legacy = classifyLegacyMessage(event.data);
246
+ if (legacy) {
247
+ // Legacy messages carry no token, so the only way to tell which importer sent one is its
248
+ // window. Ignore those that came from another instance's iframe (or anywhere else).
249
+ if (!this.iframe || event.source !== this.iframe.contentWindow) {
250
+ return;
251
+ }
252
+ // A newer importer sends the typed message as well; act on that one only.
253
+ if (!this.messageGate.allowLegacy()) {
254
+ return;
255
+ }
256
+ /** @type {?} */
257
+ const ended = this.importTracker.observe(legacy);
258
+ if (legacy.type === "mainModalHidden") {
259
+ this.handleModalClosed();
260
+ }
261
+ else if (legacy.type === "uploadSuccessful") {
262
+ if (this.onImport)
263
+ this.onImport(true);
264
+ if (this.isImported)
265
+ this.isImported(true);
266
+ if (this.imported)
267
+ this.imported(true);
268
+ }
269
+ else if (legacy.type === "uploadFailed") {
270
+ if (this.onImport)
271
+ this.onImport(false);
272
+ if (this.isImported)
273
+ this.isImported(false);
274
+ if (this.imported)
275
+ this.imported(false);
276
+ }
277
+ this.finishImport(ended);
278
+ return;
279
+ }
280
+ // Set-file handshake messages. The shared core does not classify these, so they are
281
+ // read here, behind the same unique_token check the core applies.
282
+ /** @type {?} */
283
+ const raw = event.data;
284
+ if (raw && typeof raw === "object" && raw.data && raw.data.unique_token == this.uuid) {
285
+ this.fileSender.learnOrigin(event);
286
+ if (raw.type === "csvbox-set-file-rejected" && raw.data.reason === "not-a-file") {
287
+ // Reply to our probe (it has no file). It only tells us the origin, so do nothing.
288
+ return;
289
+ }
290
+ if (raw.type === "csvbox-modal-shown") {
291
+ this.clearFileAckTimer();
292
+ registerOpenImporter(this.registryEntry);
293
+ // What the file path in openModal() is waiting for. display as well as
294
+ // pointer-events: a second file declined while the first was still opening the
295
+ // modal has put the holder away in the meantime.
296
+ this.isModalShown = true;
297
+ if (this.holder) {
298
+ this.holder.style.display = 'block';
299
+ this.holder.style.pointerEvents = 'auto';
300
+ }
301
+ return;
302
+ }
303
+ if (raw.type === "csvbox-set-file-rejected") {
304
+ this.clearFileAckTimer();
305
+ // The importer has the file and will not take it. Put the holder away only if the
306
+ // modal was never confirmed up: a file declined while it is open leaves that modal
307
+ // where it is, and its own 'csvbox-modal-hidden' will put the holder away.
308
+ if (!this.isModalShown && this.holder) {
309
+ this.holder.style.display = 'none';
310
+ this.holder.style.pointerEvents = 'auto';
311
+ unregisterOpenImporter(this.registryEntry);
312
+ }
313
+ console.warn("[csvbox] importer declined the supplied file: " + raw.data.reason);
314
+ return;
315
+ }
316
+ }
121
317
  /** @type {?} */
122
318
  let message = classifyStructuredMessage(event.data, (/** @type {?} */ (this.uuid)));
123
319
  if (!message) {
124
320
  return;
125
321
  }
322
+ this.messageGate.markTyped();
323
+ /** @type {?} */
324
+ const ended = this.importTracker.observe(message);
126
325
  if (message.type === "data-on-submit") {
127
326
  if (this.onSubmit)
128
327
  this.onSubmit(message.metadata);
@@ -132,9 +331,6 @@ class CSVBoxButtonComponent {
132
331
  this.submitted(message.metadata);
133
332
  }
134
333
  else if (message.type === "data-push-status") {
135
- if (!message.success) {
136
- delete message.metadata["unique_token"];
137
- }
138
334
  if (this.onImport)
139
335
  this.onImport(message.success, message.metadata);
140
336
  if (this.isImported)
@@ -161,7 +357,59 @@ class CSVBoxButtonComponent {
161
357
  if (this.imported)
162
358
  this.imported(false);
163
359
  }
164
- }), false);
360
+ this.finishImport(ended);
361
+ });
362
+ }
363
+ /**
364
+ * @return {?}
365
+ */
366
+ ngOnInit() {
367
+ this.uuid = generateUuid();
368
+ /** @type {?} */
369
+ let iframeUrl = buildImportUrl({
370
+ licenseKey: this.licenseKey,
371
+ customDomain: this.customDomain,
372
+ dataLocation: this.dataLocation,
373
+ language: this.language,
374
+ theme: this.theme,
375
+ environment: this.environment
376
+ }, "angular", appVersion);
377
+ // Default place to send the file. A file is only ever sent to a named origin, never "*".
378
+ this.targetOrigin = new URL(iframeUrl).origin;
379
+ this.baseDomain = this.customDomain || "app.csvbox.io";
380
+ this.safeUrl = this.sanitizer.sanitize(SecurityContext.RESOURCE_URL, this.sanitizer.bypassSecurityTrustResourceUrl(iframeUrl));
381
+ }
382
+ /**
383
+ * @param {?} changes
384
+ * @return {?}
385
+ */
386
+ ngOnChanges(changes) {
387
+ if (changes["user"] && changes['user'].currentValue != changes['user'].previousValue) {
388
+ this.updateUserVariabe(changes['user'].currentValue);
389
+ }
390
+ }
391
+ /**
392
+ * @param {?} data
393
+ * @return {?}
394
+ */
395
+ updateUserVariabe(data) {
396
+ this.user = data;
397
+ if (this.iframe && this.iframe.contentWindow) {
398
+ this.iframe.contentWindow.postMessage({
399
+ "customer": data
400
+ }, "*");
401
+ }
402
+ }
403
+ /**
404
+ * @return {?}
405
+ */
406
+ ngAfterContentInit() {
407
+ window.addEventListener("message", this.messageListener, false);
408
+ this.syncBlocked();
409
+ this.unsubscribeOpenImporters = subscribeOpenImporters((/**
410
+ * @return {?}
411
+ */
412
+ () => this.syncBlocked()));
165
413
  if (this.lazy) {
166
414
  this.disabled = false;
167
415
  }
@@ -174,15 +422,22 @@ class CSVBoxButtonComponent {
174
422
  * @return {?}
175
423
  */
176
424
  handleModalClosed() {
177
- if (this.holder)
178
- this.holder.style.display = 'none';
179
- this.isModalShown = false;
180
- this.isIframeLoaded = false;
181
- this.openModalOnIframeLoad = false;
182
- if (this.holder)
183
- this.holder.remove();
184
- this.holder = null;
185
- this.iframe = null;
425
+ if (this.importTracker.isInFlight()) {
426
+ // Closed after submitting and before the result arrived: keep the iframe and its listener
427
+ // so the late result still reaches onImport. It is removed once that result has been
428
+ // delivered (see finishImport), so the next open is a fresh load.
429
+ if (this.holder) {
430
+ this.holder.style.display = 'none';
431
+ this.holder.style.pointerEvents = 'auto';
432
+ }
433
+ this.isModalShown = false;
434
+ this.openModalOnIframeLoad = false;
435
+ this.clearFileAckTimer();
436
+ unregisterOpenImporter(this.registryEntry);
437
+ }
438
+ else {
439
+ this.destroyImporter();
440
+ }
186
441
  if (this.onClose)
187
442
  this.onClose();
188
443
  if (this.isClosed)
@@ -190,6 +445,40 @@ class CSVBoxButtonComponent {
190
445
  if (this.closed)
191
446
  this.closed();
192
447
  }
448
+ // Called after the result of an import has been delivered. If the modal was closed in the
449
+ // meantime the iframe was only hidden, so remove it now.
450
+ /**
451
+ * @private
452
+ * @param {?} ended
453
+ * @return {?}
454
+ */
455
+ finishImport(ended) {
456
+ if (ended && !this.isModalShown && this.iframe) {
457
+ this.destroyImporter();
458
+ }
459
+ }
460
+ // Puts the importer away: the next open builds a new iframe with a new token.
461
+ /**
462
+ * @private
463
+ * @return {?}
464
+ */
465
+ destroyImporter() {
466
+ if (this.holder) {
467
+ this.holder.style.display = 'none';
468
+ this.holder.style.pointerEvents = 'auto';
469
+ this.holder.remove();
470
+ }
471
+ this.holder = null;
472
+ this.iframe = null;
473
+ this.isModalShown = false;
474
+ this.isIframeLoaded = false;
475
+ this.openModalOnIframeLoad = false;
476
+ this.messageGate.reset();
477
+ this.importTracker.reset();
478
+ // A new iframe is built on the next open, so the origin is learned again.
479
+ this.clearProbeTimer();
480
+ unregisterOpenImporter(this.registryEntry);
481
+ }
193
482
  /**
194
483
  * @return {?}
195
484
  */
@@ -197,11 +486,14 @@ class CSVBoxButtonComponent {
197
486
  if (this.loadStarted)
198
487
  this.loadStarted();
199
488
  this.uuid = generateUuid();
489
+ this.messageGate.reset();
490
+ this.importTracker.reset();
200
491
  insertCSS();
201
492
  /** @type {?} */
202
493
  let iframe = document.createElement("iframe");
203
494
  this.iframe = iframe;
204
495
  iframe.setAttribute("src", this.safeUrl);
496
+ iframe.setAttribute("allow", "clipboard-read; clipboard-write *");
205
497
  iframe.frameBorder = "0";
206
498
  /** @type {?} */
207
499
  let self = this;
@@ -209,17 +501,19 @@ class CSVBoxButtonComponent {
209
501
  * @return {?}
210
502
  */
211
503
  function () {
504
+ self.disabled = false;
505
+ self.isIframeLoaded = true;
506
+ // The handshake goes first: a consumer that calls openModalWithFile() from the ready
507
+ // callback needs the importer to already have its customer, columns, options and token.
508
+ if (self.iframe && self.iframe.contentWindow) {
509
+ self.iframe.contentWindow.postMessage(buildInitPayload(self.user, self.dynamicColumns, self.options, (/** @type {?} */ (self.uuid))), "*");
510
+ }
212
511
  if (self.onReady)
213
512
  self.onReady();
214
513
  if (self.isReady)
215
514
  self.isReady();
216
515
  if (self.importerReady)
217
516
  self.importerReady();
218
- self.disabled = false;
219
- self.isIframeLoaded = true;
220
- if (self.iframe && self.iframe.contentWindow) {
221
- self.iframe.contentWindow.postMessage(buildInitPayload(self.user, self.dynamicColumns, self.options, (/** @type {?} */ (self.uuid))), "*");
222
- }
223
517
  if (self.openModalOnIframeLoad) {
224
518
  self.openModal();
225
519
  }
@@ -229,11 +523,16 @@ class CSVBoxButtonComponent {
229
523
  this.holder.setAttribute('id', `csvbox-embed-${this.uuid}`);
230
524
  this.holder.appendChild(iframe);
231
525
  document.body.insertAdjacentElement('beforeend', this.holder);
526
+ // The holder did not exist yet if openModal() asked for the importer before it was built.
527
+ restackOpenImporters(openImporters());
232
528
  }
233
529
  /**
234
530
  * @return {?}
235
531
  */
236
532
  openModal() {
533
+ // Counts as open from the moment it is requested, so the other buttons are disabled while
534
+ // this importer is still loading, and so it is stacked above any importer opened earlier.
535
+ registerOpenImporter(this.registryEntry);
237
536
  if (!this.iframe) {
238
537
  this.openModalOnIframeLoad = true;
239
538
  this.initImporter();
@@ -241,11 +540,36 @@ class CSVBoxButtonComponent {
241
540
  }
242
541
  if (!this.isModalShown) {
243
542
  if (this.isIframeLoaded) {
244
- this.isModalShown = true;
245
- if (this.holder)
246
- this.holder.style.display = 'block';
247
- if (this.iframe && this.iframe.contentWindow) {
248
- this.iframe.contentWindow.postMessage('openModal', '*');
543
+ if (this.pendingFile) {
544
+ // The importer opens its own modal once it has the file, so that it can inject on
545
+ // 'shown.bs.modal'. Sending 'openModal' as well would race that.
546
+ //
547
+ // It can also decline: wrong step, an import file URL configured on the sheet, or
548
+ // an importer too old to know the message at all. So the holder goes up
549
+ // click-through and stays that way until 'csvbox-modal-shown' confirms the modal
550
+ // is really up. It covers the viewport at z-index 2147483647 and the importer
551
+ // renders transparent with its modal closed, so arming it on an unanswered message
552
+ // leaves an invisible sheet over the host page eating every click.
553
+ //
554
+ // display:block regardless, so the iframe lays out and animates normally; only
555
+ // pointer-events is held back. isModalShown likewise waits for the confirmation,
556
+ // or a declined file would latch the importer shut for good.
557
+ if (this.holder) {
558
+ this.holder.style.pointerEvents = 'none';
559
+ this.holder.style.display = 'block';
560
+ }
561
+ this.fileSender.send();
562
+ }
563
+ else {
564
+ this.isModalShown = true;
565
+ if (this.holder) {
566
+ // Explicit: a previously declined file leaves the holder click-through.
567
+ this.holder.style.pointerEvents = 'auto';
568
+ this.holder.style.display = 'block';
569
+ }
570
+ if (this.iframe && this.iframe.contentWindow) {
571
+ this.iframe.contentWindow.postMessage('openModal', '*');
572
+ }
249
573
  }
250
574
  }
251
575
  else {
@@ -253,13 +577,119 @@ class CSVBoxButtonComponent {
253
577
  }
254
578
  }
255
579
  }
580
+ /**
581
+ * Open the importer on a File the host page already has, instead of the file picker.
582
+ *
583
+ * Reachable through a template reference (`#importer` then `importer.openModalWithFile(file)`)
584
+ * or `\@ViewChild(CSVBoxButtonComponent)`.
585
+ *
586
+ * The File crosses to the iframe by structured clone, so the importer receives the real
587
+ * object and applies its own extension, worksheet and size rules to it -- this does not
588
+ * bypass any of them. Pass a File; a Blob has no name for the importer to read an
589
+ * extension from.
590
+ * @param {?} file
591
+ * @return {?}
592
+ */
593
+ openModalWithFile(file) {
594
+ if (typeof File === 'undefined' || !(file instanceof File)) {
595
+ return;
596
+ }
597
+ this.pendingFile = file;
598
+ // Already open: hand it over now rather than holding it for the next open. The importer
599
+ // takes it only while it is still on the upload step, and ignores it once the user has a
600
+ // dataset and a mapping in progress.
601
+ if (this.isModalShown && this.iframe && this.isIframeLoaded) {
602
+ this.fileSender.send();
603
+ return;
604
+ }
605
+ this.openModal();
606
+ }
607
+ /**
608
+ * @return {?}
609
+ */
610
+ clearProbeTimer() {
611
+ this.clearFileAckTimer();
612
+ this.fileSender.reset();
613
+ }
614
+ /**
615
+ * @return {?}
616
+ */
617
+ startFileAckTimer() {
618
+ this.clearFileAckTimer();
619
+ this.fileAckTimer = setTimeout((/**
620
+ * @return {?}
621
+ */
622
+ () => {
623
+ this.fileAckTimer = null;
624
+ this.giveUpOnFile();
625
+ }), FILE_ACK_TIMEOUT_MS);
626
+ }
627
+ /**
628
+ * @return {?}
629
+ */
630
+ clearFileAckTimer() {
631
+ if (this.fileAckTimer) {
632
+ clearTimeout(this.fileAckTimer);
633
+ this.fileAckTimer = null;
634
+ }
635
+ }
636
+ // The importer never confirmed the file it was sent (an origin we do not trust, or an importer too
637
+ // old to know the message). Without this the holder would stay up, invisible and click-through,
638
+ // and the other instances' buttons would stay disabled for good.
639
+ /**
640
+ * @return {?}
641
+ */
642
+ giveUpOnFile() {
643
+ if (this.isModalShown) {
644
+ return;
645
+ }
646
+ /** @type {?} */
647
+ const holder = this.holder;
648
+ if (holder) {
649
+ holder.style.display = 'none';
650
+ holder.style.pointerEvents = 'auto';
651
+ }
652
+ unregisterOpenImporter(this.registryEntry);
653
+ console.warn("[csvbox] the importer did not respond to the supplied file");
654
+ }
655
+ /**
656
+ * @private
657
+ * @return {?}
658
+ */
659
+ syncBlocked() {
660
+ /** @type {?} */
661
+ const blocked = isBlockedByOpenImporter(this.registryEntry);
662
+ if (blocked !== this.blockedByOther) {
663
+ // The registry can call back from outside Angular's zone (another instance, another app).
664
+ this.zone.run((/**
665
+ * @return {?}
666
+ */
667
+ () => {
668
+ this.blockedByOther = blocked;
669
+ this.cdr.markForCheck();
670
+ }));
671
+ }
672
+ }
673
+ /**
674
+ * @return {?}
675
+ */
676
+ ngOnDestroy() {
677
+ window.removeEventListener("message", this.messageListener);
678
+ // Takes the holder out of document.body: left there, it would keep covering the page with
679
+ // an invisible full-screen layer that nothing is left to hide.
680
+ this.destroyImporter();
681
+ if (this.unsubscribeOpenImporters) {
682
+ this.unsubscribeOpenImporters();
683
+ this.unsubscribeOpenImporters = null;
684
+ }
685
+ }
256
686
  }
257
687
  CSVBoxButtonComponent.decorators = [
258
688
  { type: Component, args: [{
259
689
  selector: 'csvbox-button',
260
690
  template: `
261
691
  <div>
262
- <button [disabled]="disabled" #initiator (click)="openModal()" [attr.data-csvbox-token]="uuid">
692
+ <button [disabled]="disabled || blockedByOther" (click)="openModal()" [attr.data-csvbox-token]="uuid">
263
693
  <ng-content></ng-content>
264
694
  </button>
265
695
  </div>
@@ -268,10 +698,11 @@ CSVBoxButtonComponent.decorators = [
268
698
  ];
269
699
  /** @nocollapse */
270
700
  CSVBoxButtonComponent.ctorParameters = () => [
271
- { type: DomSanitizer }
701
+ { type: DomSanitizer },
702
+ { type: ChangeDetectorRef },
703
+ { type: NgZone }
272
704
  ];
273
705
  CSVBoxButtonComponent.propDecorators = {
274
- initiator: [{ type: ViewChild, args: ['initiator', { static: false },] }],
275
706
  onImport: [{ type: Input }],
276
707
  onReady: [{ type: Input }],
277
708
  onClose: [{ type: Input }],
@@ -304,8 +735,6 @@ if (false) {
304
735
  /** @type {?} */
305
736
  CSVBoxButtonComponent.prototype.isModalShown;
306
737
  /** @type {?} */
307
- CSVBoxButtonComponent.prototype.initiator;
308
- /** @type {?} */
309
738
  CSVBoxButtonComponent.prototype.onImport;
310
739
  /** @type {?} */
311
740
  CSVBoxButtonComponent.prototype.onReady;
@@ -364,9 +793,62 @@ if (false) {
364
793
  /** @type {?} */
365
794
  CSVBoxButtonComponent.prototype.disabled;
366
795
  /** @type {?} */
796
+ CSVBoxButtonComponent.prototype.blockedByOther;
797
+ /**
798
+ * @type {?}
799
+ * @private
800
+ */
801
+ CSVBoxButtonComponent.prototype.messageGate;
802
+ /**
803
+ * @type {?}
804
+ * @private
805
+ */
806
+ CSVBoxButtonComponent.prototype.importTracker;
807
+ /**
808
+ * @type {?}
809
+ * @private
810
+ */
811
+ CSVBoxButtonComponent.prototype.registryEntry;
812
+ /**
813
+ * @type {?}
814
+ * @private
815
+ */
816
+ CSVBoxButtonComponent.prototype.fileSender;
817
+ /**
818
+ * @type {?}
819
+ * @private
820
+ */
821
+ CSVBoxButtonComponent.prototype.unsubscribeOpenImporters;
822
+ /** @type {?} */
367
823
  CSVBoxButtonComponent.prototype.holder;
368
824
  /** @type {?} */
825
+ CSVBoxButtonComponent.prototype.pendingFile;
826
+ /** @type {?} */
827
+ CSVBoxButtonComponent.prototype.targetOrigin;
828
+ /** @type {?} */
829
+ CSVBoxButtonComponent.prototype.baseDomain;
830
+ /**
831
+ * @type {?}
832
+ * @private
833
+ */
834
+ CSVBoxButtonComponent.prototype.fileAckTimer;
835
+ /**
836
+ * @type {?}
837
+ * @private
838
+ */
839
+ CSVBoxButtonComponent.prototype.messageListener;
840
+ /** @type {?} */
369
841
  CSVBoxButtonComponent.prototype.sanitizer;
842
+ /**
843
+ * @type {?}
844
+ * @private
845
+ */
846
+ CSVBoxButtonComponent.prototype.cdr;
847
+ /**
848
+ * @type {?}
849
+ * @private
850
+ */
851
+ CSVBoxButtonComponent.prototype.zone;
370
852
  }
371
853
 
372
854
  /**