chrome-in-iframe 2.0.0 → 2.0.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 +164 -0
- package/CHANGELOG.zh-CN.md +132 -1
- package/README.md +19 -3
- package/README.zh-CN.md +25 -9
- package/dist/channel/types.d.ts +10 -5
- package/dist/channel/utils.d.ts +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +360 -147
- package/dist/log.d.ts +3 -0
- package/dist/processor/lifecycle.d.ts +1 -1
- package/package.json +6 -7
package/dist/index.js
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import { nanoid } from 'nanoid';
|
|
2
2
|
import { LRUCache } from 'lru-cache';
|
|
3
3
|
|
|
4
|
+
const PREFIX = 'chrome-in-iframe';
|
|
5
|
+
const defaultLogger = (scope, ...args) => {
|
|
6
|
+
if (typeof console !== 'undefined' && typeof console.warn === 'function') {
|
|
7
|
+
console.warn(`[${PREFIX}] ${scope}`, ...args);
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
let activeLogger = defaultLogger;
|
|
11
|
+
function setLogger(logger) {
|
|
12
|
+
activeLogger = logger;
|
|
13
|
+
}
|
|
14
|
+
function warn(scope, ...args) {
|
|
15
|
+
if (!activeLogger)
|
|
16
|
+
return;
|
|
17
|
+
activeLogger(scope, ...args);
|
|
18
|
+
}
|
|
19
|
+
|
|
4
20
|
const WELL_KNOWN_SYMBOLS = {
|
|
5
21
|
asyncIterator: Symbol.asyncIterator,
|
|
6
22
|
hasInstance: Symbol.hasInstance,
|
|
@@ -16,12 +32,9 @@ const WELL_KNOWN_SYMBOLS = {
|
|
|
16
32
|
toStringTag: Symbol.toStringTag,
|
|
17
33
|
unscopables: Symbol.unscopables,
|
|
18
34
|
};
|
|
35
|
+
const WELL_KNOWN_SYMBOL_NAMES = new Map(Object.entries(WELL_KNOWN_SYMBOLS).map(([name, symbol]) => [symbol, name]));
|
|
19
36
|
function getWellKnownSymbolName(value) {
|
|
20
|
-
|
|
21
|
-
if (symbol === value)
|
|
22
|
-
return name;
|
|
23
|
-
}
|
|
24
|
-
return undefined;
|
|
37
|
+
return WELL_KNOWN_SYMBOL_NAMES.get(value);
|
|
25
38
|
}
|
|
26
39
|
function getWellKnownSymbol(name) {
|
|
27
40
|
return WELL_KNOWN_SYMBOLS[name];
|
|
@@ -32,6 +45,11 @@ function serializeThrownError(err) {
|
|
|
32
45
|
}
|
|
33
46
|
return { message: String(err) };
|
|
34
47
|
}
|
|
48
|
+
function isPromiseLike(value) {
|
|
49
|
+
return (value !== null &&
|
|
50
|
+
(typeof value === 'object' || typeof value === 'function') &&
|
|
51
|
+
typeof value.then === 'function');
|
|
52
|
+
}
|
|
35
53
|
|
|
36
54
|
function serializePath(path) {
|
|
37
55
|
return path.map((key) => {
|
|
@@ -117,15 +135,23 @@ function serializeMessageData(type, data) {
|
|
|
117
135
|
|
|
118
136
|
function createMessageChannel(poster, key, instanceId, context, processorRegistry) {
|
|
119
137
|
const sender = createMessageSender(poster, key, instanceId);
|
|
138
|
+
const keyMatch = `"key":${JSON.stringify(key)}`;
|
|
120
139
|
const listener = (event) => {
|
|
140
|
+
if (typeof event.data !== 'string')
|
|
141
|
+
return;
|
|
142
|
+
if (event.data.length === 0 || event.data.charCodeAt(0) !== 123 /* '{' */)
|
|
143
|
+
return;
|
|
144
|
+
if (event.data.indexOf(keyMatch) === -1)
|
|
145
|
+
return;
|
|
121
146
|
let body;
|
|
122
147
|
try {
|
|
123
148
|
body = JSON.parse(event.data);
|
|
124
149
|
}
|
|
125
|
-
catch {
|
|
150
|
+
catch (err) {
|
|
151
|
+
warn('createMessageChannel', 'failed to parse incoming message as JSON', err);
|
|
126
152
|
return;
|
|
127
153
|
}
|
|
128
|
-
if (!
|
|
154
|
+
if (!isMessageEnvelope(body))
|
|
129
155
|
return;
|
|
130
156
|
if (body.key !== key)
|
|
131
157
|
return;
|
|
@@ -136,6 +162,8 @@ function createMessageChannel(poster, key, instanceId, context, processorRegistr
|
|
|
136
162
|
const handler = processorRegistry.get(body.type);
|
|
137
163
|
if (!handler)
|
|
138
164
|
return;
|
|
165
|
+
if (!isValidMessagePayload(body))
|
|
166
|
+
return;
|
|
139
167
|
const meta = { senderInstanceId: body.senderInstanceId };
|
|
140
168
|
try {
|
|
141
169
|
handler(deserializeMessageData(body), context, meta);
|
|
@@ -149,34 +177,25 @@ function createMessageChannel(poster, key, instanceId, context, processorRegistr
|
|
|
149
177
|
getSender() {
|
|
150
178
|
return sender;
|
|
151
179
|
},
|
|
152
|
-
getContext() {
|
|
153
|
-
return context;
|
|
154
|
-
},
|
|
155
|
-
getPoster() {
|
|
156
|
-
return poster;
|
|
157
|
-
},
|
|
158
|
-
getKey() {
|
|
159
|
-
return key;
|
|
160
|
-
},
|
|
161
|
-
getInstanceId() {
|
|
162
|
-
return instanceId;
|
|
163
|
-
},
|
|
164
180
|
destroy() {
|
|
165
181
|
poster.removeEventListener('message', listener);
|
|
166
182
|
},
|
|
167
183
|
};
|
|
168
184
|
}
|
|
169
|
-
function
|
|
185
|
+
function isMessageEnvelope(value) {
|
|
170
186
|
if (!isRecord(value))
|
|
171
187
|
return false;
|
|
172
|
-
if (typeof value.type !== 'string')
|
|
188
|
+
if (typeof value.type !== 'string' || value.type.length === 0)
|
|
173
189
|
return false;
|
|
174
190
|
if (typeof value.key !== 'string')
|
|
175
191
|
return false;
|
|
176
|
-
if (typeof value.senderInstanceId !== 'string')
|
|
192
|
+
if (typeof value.senderInstanceId !== 'string' || value.senderInstanceId.length === 0)
|
|
177
193
|
return false;
|
|
178
194
|
if ('targetInstanceId' in value && typeof value.targetInstanceId !== 'string' && value.targetInstanceId !== undefined)
|
|
179
195
|
return false;
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
function isValidMessagePayload(value) {
|
|
180
199
|
switch (value.type) {
|
|
181
200
|
case 'invokeRequest':
|
|
182
201
|
return isInvokeRequest(value.data);
|
|
@@ -265,7 +284,7 @@ function sendProcessorError(body, sender, err) {
|
|
|
265
284
|
if (body.type === 'invokeFunctionByIdRequest') {
|
|
266
285
|
const data = body.data;
|
|
267
286
|
const error = serializeThrownError(err);
|
|
268
|
-
|
|
287
|
+
warn('sendProcessorError', `callback '${data.id}' failed: ${error.message}`);
|
|
269
288
|
sender.sendMessage('invokeFunctionByIdResponse', {
|
|
270
289
|
id: data.callId,
|
|
271
290
|
error,
|
|
@@ -570,8 +589,8 @@ function deserializeError(source, generateCallback, getRemoteCallback) {
|
|
|
570
589
|
try {
|
|
571
590
|
error.name = name;
|
|
572
591
|
}
|
|
573
|
-
catch {
|
|
574
|
-
|
|
592
|
+
catch (err) {
|
|
593
|
+
warn('deserializeError', 'failed to set error.name', name, err);
|
|
575
594
|
}
|
|
576
595
|
}
|
|
577
596
|
if (typeof source.stack === 'string')
|
|
@@ -587,10 +606,10 @@ function deserializeError(source, generateCallback, getRemoteCallback) {
|
|
|
587
606
|
if (typeof rawKey !== 'string')
|
|
588
607
|
continue;
|
|
589
608
|
try {
|
|
590
|
-
error
|
|
609
|
+
setOwnProperty(error, rawKey, deserialize0(rawValue, generateCallback, getRemoteCallback));
|
|
591
610
|
}
|
|
592
|
-
catch {
|
|
593
|
-
|
|
611
|
+
catch (err) {
|
|
612
|
+
warn('deserializeError', 'failed to set error property', rawKey, err);
|
|
594
613
|
}
|
|
595
614
|
}
|
|
596
615
|
}
|
|
@@ -607,7 +626,7 @@ function deserializeWrappedObject(entries, generateCallback, getRemoteCallback)
|
|
|
607
626
|
const value = deserialize0(entry[1], generateCallback, getRemoteCallback);
|
|
608
627
|
const key = resolveObjectKey(rawKey);
|
|
609
628
|
if (key === undefined) {
|
|
610
|
-
|
|
629
|
+
warn('deserializeWrappedObject', 'dropping wrapped-object entry with unresolvable key', rawKey);
|
|
611
630
|
continue;
|
|
612
631
|
}
|
|
613
632
|
Object.defineProperty(result, key, {
|
|
@@ -628,7 +647,8 @@ function resolveObjectKey(raw) {
|
|
|
628
647
|
try {
|
|
629
648
|
return deserializeSymbol(tagged.v);
|
|
630
649
|
}
|
|
631
|
-
catch {
|
|
650
|
+
catch (err) {
|
|
651
|
+
warn('resolveObjectKey', 'failed to deserialize symbol key', tagged.v, err);
|
|
632
652
|
return undefined;
|
|
633
653
|
}
|
|
634
654
|
}
|
|
@@ -673,7 +693,7 @@ function setOwnProperty(target, key, value) {
|
|
|
673
693
|
|
|
674
694
|
function isListenerRegistrationPath(path) {
|
|
675
695
|
const last = path[path.length - 1];
|
|
676
|
-
return last === 'addListener'
|
|
696
|
+
return last === 'addListener';
|
|
677
697
|
}
|
|
678
698
|
function isListenerRemovalPath(path) {
|
|
679
699
|
const last = path[path.length - 1];
|
|
@@ -694,23 +714,6 @@ function readProperty(target, key) {
|
|
|
694
714
|
return target[key];
|
|
695
715
|
}
|
|
696
716
|
|
|
697
|
-
const boundCache = new WeakMap();
|
|
698
|
-
function bindMethod(fn, owner) {
|
|
699
|
-
if (owner === null || typeof owner !== 'object') {
|
|
700
|
-
return (...args) => Reflect.apply(fn, owner, args);
|
|
701
|
-
}
|
|
702
|
-
let perOwner = boundCache.get(fn);
|
|
703
|
-
if (!perOwner) {
|
|
704
|
-
perOwner = new WeakMap();
|
|
705
|
-
boundCache.set(fn, perOwner);
|
|
706
|
-
}
|
|
707
|
-
const cached = perOwner.get(owner);
|
|
708
|
-
if (cached)
|
|
709
|
-
return cached;
|
|
710
|
-
const bound = (...args) => Reflect.apply(fn, owner, args);
|
|
711
|
-
perOwner.set(owner, bound);
|
|
712
|
-
return bound;
|
|
713
|
-
}
|
|
714
717
|
function handleAccessPropertyRequest(data, ctx, meta) {
|
|
715
718
|
const target = ctx.getDelegateTarget();
|
|
716
719
|
const channel = ctx.getMessageChannel();
|
|
@@ -721,7 +724,7 @@ function handleAccessPropertyRequest(data, ctx, meta) {
|
|
|
721
724
|
}, meta.senderInstanceId);
|
|
722
725
|
return;
|
|
723
726
|
}
|
|
724
|
-
if (
|
|
727
|
+
if (target === undefined || target === null) {
|
|
725
728
|
channel.getSender().sendMessage('accessPropertyResponse', {
|
|
726
729
|
id: data.id,
|
|
727
730
|
error: { message: 'No delegate target is configured' },
|
|
@@ -731,18 +734,24 @@ function handleAccessPropertyRequest(data, ctx, meta) {
|
|
|
731
734
|
try {
|
|
732
735
|
let current = target;
|
|
733
736
|
let owner = target;
|
|
734
|
-
for (
|
|
737
|
+
for (let i = 0; i < data.path.length; i++) {
|
|
738
|
+
const key = data.path[i];
|
|
735
739
|
if (current === undefined || current === null) {
|
|
736
740
|
channel.getSender().sendMessage('accessPropertyResponse', {
|
|
737
741
|
id: data.id,
|
|
738
|
-
|
|
742
|
+
error: {
|
|
743
|
+
message: `Cannot read property '${String(key)}' of ${current === null ? 'null' : 'undefined'} (at '${data.path
|
|
744
|
+
.slice(0, i)
|
|
745
|
+
.map(String)
|
|
746
|
+
.join('.')}')`,
|
|
747
|
+
},
|
|
739
748
|
}, meta.senderInstanceId);
|
|
740
749
|
return;
|
|
741
750
|
}
|
|
742
751
|
owner = current;
|
|
743
752
|
current = readProperty(current, key);
|
|
744
753
|
}
|
|
745
|
-
const value = typeof current === 'function' ? bindMethod(current, owner) : current;
|
|
754
|
+
const value = typeof current === 'function' ? ctx.bindMethod(current, owner) : current;
|
|
746
755
|
channel.getSender().sendMessage('accessPropertyResponse', {
|
|
747
756
|
id: data.id,
|
|
748
757
|
data: serialize(value, ctx.registerFunction, { persistent: isLikelyListenerPath(data.path) }),
|
|
@@ -761,17 +770,21 @@ function handleAccessPropertyResponse(data, ctx, meta) {
|
|
|
761
770
|
return;
|
|
762
771
|
if (data.error) {
|
|
763
772
|
const err = new Error(data.error.message);
|
|
764
|
-
|
|
773
|
+
if (data.error.stack)
|
|
774
|
+
err.stack = data.error.stack;
|
|
775
|
+
pending.onReject?.();
|
|
765
776
|
pending.reject(err);
|
|
766
777
|
return;
|
|
767
778
|
}
|
|
768
779
|
const scopedRemoteCallback = createScopedRemoteCallback$2(ctx, meta.senderInstanceId);
|
|
769
780
|
try {
|
|
781
|
+
pending.onResolve?.();
|
|
770
782
|
pending.resolve(deserialize(data.data, (id, args, options) => {
|
|
771
783
|
return ctx.invokeFunctionById(id, args, options);
|
|
772
784
|
}, scopedRemoteCallback));
|
|
773
785
|
}
|
|
774
786
|
catch (err) {
|
|
787
|
+
pending.onReject?.();
|
|
775
788
|
pending.reject(err instanceof Error ? err : new Error(String(err)));
|
|
776
789
|
}
|
|
777
790
|
}
|
|
@@ -783,13 +796,20 @@ function handleInvokeRequest(data, ctx, meta) {
|
|
|
783
796
|
const target = ctx.getDelegateTarget();
|
|
784
797
|
const channel = ctx.getMessageChannel();
|
|
785
798
|
const scopedRemoteCallback = createScopedRemoteCallback$1(ctx, meta.senderInstanceId);
|
|
786
|
-
if (
|
|
799
|
+
if (target === undefined || target === null) {
|
|
787
800
|
channel.getSender().sendMessage('invokeResponse', {
|
|
788
801
|
id: data.id,
|
|
789
802
|
error: { message: 'No delegate target is configured' },
|
|
790
803
|
}, meta.senderInstanceId);
|
|
791
804
|
return;
|
|
792
805
|
}
|
|
806
|
+
if (data.path.length === 0) {
|
|
807
|
+
channel.getSender().sendMessage('invokeResponse', {
|
|
808
|
+
id: data.id,
|
|
809
|
+
error: { message: 'Invocation path must not be empty' },
|
|
810
|
+
}, meta.senderInstanceId);
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
793
813
|
let current = target;
|
|
794
814
|
for (let i = 0; i < data.path.length - 1; i++) {
|
|
795
815
|
current = readProperty(current, data.path[i]);
|
|
@@ -797,7 +817,10 @@ function handleInvokeRequest(data, ctx, meta) {
|
|
|
797
817
|
channel.getSender().sendMessage('invokeResponse', {
|
|
798
818
|
id: data.id,
|
|
799
819
|
error: {
|
|
800
|
-
message: `Cannot read property '${String(data.path[i + 1])}' of ${
|
|
820
|
+
message: `Cannot read property '${String(data.path[i + 1])}' of ${current === null ? 'null' : 'undefined'} (at '${data.path
|
|
821
|
+
.slice(0, i + 1)
|
|
822
|
+
.map(String)
|
|
823
|
+
.join('.')}')`,
|
|
801
824
|
},
|
|
802
825
|
}, meta.senderInstanceId);
|
|
803
826
|
return;
|
|
@@ -836,8 +859,8 @@ function handleInvokeRequest(data, ctx, meta) {
|
|
|
836
859
|
}, meta.senderInstanceId);
|
|
837
860
|
return;
|
|
838
861
|
}
|
|
839
|
-
if (result
|
|
840
|
-
result
|
|
862
|
+
if (isPromiseLike(result)) {
|
|
863
|
+
Promise.resolve(result)
|
|
841
864
|
.then((value) => {
|
|
842
865
|
sendInvokeSuccess(data.id, value, ctx, meta.senderInstanceId);
|
|
843
866
|
})
|
|
@@ -847,10 +870,9 @@ function handleInvokeRequest(data, ctx, meta) {
|
|
|
847
870
|
error: serializeThrownError(err),
|
|
848
871
|
}, meta.senderInstanceId);
|
|
849
872
|
});
|
|
873
|
+
return;
|
|
850
874
|
}
|
|
851
|
-
|
|
852
|
-
sendInvokeSuccess(data.id, result, ctx, meta.senderInstanceId);
|
|
853
|
-
}
|
|
875
|
+
sendInvokeSuccess(data.id, result, ctx, meta.senderInstanceId);
|
|
854
876
|
}
|
|
855
877
|
function handleInvokeResponse(data, ctx, meta) {
|
|
856
878
|
const pending = ctx.getAndRemovePendingPromise(data.id);
|
|
@@ -858,17 +880,21 @@ function handleInvokeResponse(data, ctx, meta) {
|
|
|
858
880
|
return;
|
|
859
881
|
if (data.error) {
|
|
860
882
|
const err = new Error(data.error.message);
|
|
861
|
-
|
|
883
|
+
if (data.error.stack)
|
|
884
|
+
err.stack = data.error.stack;
|
|
885
|
+
pending.onReject?.();
|
|
862
886
|
pending.reject(err);
|
|
863
887
|
}
|
|
864
888
|
else {
|
|
865
889
|
const scopedRemoteCallback = createScopedRemoteCallback$1(ctx, meta.senderInstanceId);
|
|
866
890
|
try {
|
|
891
|
+
pending.onResolve?.();
|
|
867
892
|
pending.resolve(deserialize(data.data, (id, args, options) => {
|
|
868
893
|
return ctx.invokeFunctionById(id, args, options);
|
|
869
894
|
}, scopedRemoteCallback));
|
|
870
895
|
}
|
|
871
896
|
catch (err) {
|
|
897
|
+
pending.onReject?.();
|
|
872
898
|
pending.reject(err instanceof Error ? err : new Error(String(err)));
|
|
873
899
|
}
|
|
874
900
|
}
|
|
@@ -898,7 +924,7 @@ function handleCallbackInvoke(data, ctx, meta) {
|
|
|
898
924
|
const entry = persistentEntry ?? ctx.getFunctionCache().get(data.id);
|
|
899
925
|
if (!entry) {
|
|
900
926
|
const error = { message: `Callback '${data.id}' is not available or has expired` };
|
|
901
|
-
|
|
927
|
+
warn('handleCallbackInvoke', error.message);
|
|
902
928
|
channel.getSender().sendMessage('invokeFunctionByIdResponse', {
|
|
903
929
|
id: data.callId,
|
|
904
930
|
error,
|
|
@@ -918,8 +944,8 @@ function handleCallbackInvoke(data, ctx, meta) {
|
|
|
918
944
|
}
|
|
919
945
|
try {
|
|
920
946
|
const result = Reflect.apply(entry.fn, entry.thisArg, deserializedArgs);
|
|
921
|
-
if (result
|
|
922
|
-
result
|
|
947
|
+
if (isPromiseLike(result)) {
|
|
948
|
+
Promise.resolve(result)
|
|
923
949
|
.then((value) => {
|
|
924
950
|
sendCallbackSuccess(data.callId, value, ctx, meta.senderInstanceId);
|
|
925
951
|
})
|
|
@@ -940,17 +966,21 @@ function handleCallbackInvokeResponse(data, ctx, meta) {
|
|
|
940
966
|
return;
|
|
941
967
|
if (data.error) {
|
|
942
968
|
const err = new Error(data.error.message);
|
|
943
|
-
|
|
969
|
+
if (data.error.stack)
|
|
970
|
+
err.stack = data.error.stack;
|
|
971
|
+
pending.onReject?.();
|
|
944
972
|
pending.reject(err);
|
|
945
973
|
return;
|
|
946
974
|
}
|
|
947
975
|
const scopedRemoteCallback = createScopedRemoteCallback(ctx, meta.senderInstanceId);
|
|
948
976
|
try {
|
|
977
|
+
pending.onResolve?.();
|
|
949
978
|
pending.resolve(deserialize(data.data, (id, args, options) => {
|
|
950
979
|
return ctx.invokeFunctionById(id, args, options);
|
|
951
980
|
}, scopedRemoteCallback));
|
|
952
981
|
}
|
|
953
982
|
catch (err) {
|
|
983
|
+
pending.onReject?.();
|
|
954
984
|
pending.reject(err instanceof Error ? err : new Error(String(err)));
|
|
955
985
|
}
|
|
956
986
|
}
|
|
@@ -979,13 +1009,17 @@ function createScopedRemoteCallback(ctx, remoteInstanceId) {
|
|
|
979
1009
|
return (id, invoke, options) => ctx.getRemoteCallback(id, invoke, options, remoteInstanceId);
|
|
980
1010
|
}
|
|
981
1011
|
|
|
982
|
-
const
|
|
983
|
-
function handleReleaseCallbacks(data, ctx,
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1012
|
+
const MAX_RELEASE_IDS = 100000;
|
|
1013
|
+
function handleReleaseCallbacks(data, ctx, meta) {
|
|
1014
|
+
const ids = data.ids;
|
|
1015
|
+
const total = ids.length > MAX_RELEASE_IDS ? MAX_RELEASE_IDS : ids.length;
|
|
1016
|
+
if (ids.length > MAX_RELEASE_IDS) {
|
|
1017
|
+
warn('handleReleaseCallbacks', `releaseCallbacks payload exceeds cap (${ids.length} > ${MAX_RELEASE_IDS}); only the first ${MAX_RELEASE_IDS} ids will be released`);
|
|
1018
|
+
}
|
|
1019
|
+
const effectiveIds = total === ids.length ? ids : ids.slice(0, total);
|
|
1020
|
+
ctx.releaseRemoteCallbacks(meta.senderInstanceId, effectiveIds);
|
|
1021
|
+
for (let i = 0; i < total; i++) {
|
|
1022
|
+
ctx.dropLocalCallback(ids[i]);
|
|
989
1023
|
}
|
|
990
1024
|
}
|
|
991
1025
|
function handleDestroyEndpoint(_data, ctx, meta) {
|
|
@@ -1009,24 +1043,60 @@ const DEFAULT_FUNCTION_CACHE_MAX = 100;
|
|
|
1009
1043
|
const DEFAULT_FUNCTION_CACHE_TTL = 5 * 60 * 1000;
|
|
1010
1044
|
const DEFAULT_REMOTE_CALLBACK_CACHE_MAX = 500;
|
|
1011
1045
|
const DEFAULT_REMOTE_CALLBACK_CACHE_TTL = 5 * 60 * 1000;
|
|
1046
|
+
function resolveTimeout(raw) {
|
|
1047
|
+
if (raw === undefined)
|
|
1048
|
+
return DEFAULT_TIMEOUT;
|
|
1049
|
+
if (typeof raw !== 'number' || !Number.isFinite(raw) || raw <= 0)
|
|
1050
|
+
return DEFAULT_TIMEOUT;
|
|
1051
|
+
return raw;
|
|
1052
|
+
}
|
|
1012
1053
|
function createClientContext(getMessageChannel, instanceId, options = {}) {
|
|
1013
1054
|
const remoteCacheMax = options.remoteCallbackCacheMax ?? DEFAULT_REMOTE_CALLBACK_CACHE_MAX;
|
|
1014
1055
|
const remoteCacheTtl = options.remoteCallbackCacheTtl ?? DEFAULT_REMOTE_CALLBACK_CACHE_TTL;
|
|
1056
|
+
const callTimeout = resolveTimeout(options.timeout);
|
|
1015
1057
|
const pending = new Map();
|
|
1016
1058
|
const functionIds = new Map();
|
|
1059
|
+
const persistentFunctionCache = new Map();
|
|
1060
|
+
const persistentRefcount = new Map();
|
|
1017
1061
|
const functionCache = new LRUCache({
|
|
1018
1062
|
max: options.functionCacheMax ?? DEFAULT_FUNCTION_CACHE_MAX,
|
|
1019
1063
|
ttl: options.functionCacheTtl ?? DEFAULT_FUNCTION_CACHE_TTL,
|
|
1020
|
-
dispose: (value,
|
|
1064
|
+
dispose: (value, key, reason) => {
|
|
1021
1065
|
if (reason === 'set' || reason === 'delete')
|
|
1022
1066
|
return;
|
|
1023
|
-
|
|
1067
|
+
if (persistentFunctionCache.has(key))
|
|
1068
|
+
return;
|
|
1069
|
+
removeFunctionIdMapping(value.fn, value.thisArg);
|
|
1024
1070
|
},
|
|
1025
1071
|
});
|
|
1026
|
-
const persistentFunctionCache = new Map();
|
|
1027
1072
|
const remoteCallbacksByOwner = new Map();
|
|
1028
1073
|
const persistentRemoteCallbacksByOwner = new Map();
|
|
1074
|
+
const boundMethodCache = new WeakMap();
|
|
1029
1075
|
let destroyed = false;
|
|
1076
|
+
const bindMethod = (fn, owner) => {
|
|
1077
|
+
if (owner === null || typeof owner !== 'object') {
|
|
1078
|
+
return (...args) => Reflect.apply(fn, owner, args);
|
|
1079
|
+
}
|
|
1080
|
+
let perOwner = boundMethodCache.get(fn);
|
|
1081
|
+
if (!perOwner) {
|
|
1082
|
+
perOwner = new WeakMap();
|
|
1083
|
+
boundMethodCache.set(fn, perOwner);
|
|
1084
|
+
}
|
|
1085
|
+
const cached = perOwner.get(owner);
|
|
1086
|
+
if (cached)
|
|
1087
|
+
return cached;
|
|
1088
|
+
const bound = (...args) => Reflect.apply(fn, owner, args);
|
|
1089
|
+
perOwner.set(owner, bound);
|
|
1090
|
+
return bound;
|
|
1091
|
+
};
|
|
1092
|
+
const removeFunctionIdMapping = (fn, thisArg) => {
|
|
1093
|
+
const perOwner = functionIds.get(fn);
|
|
1094
|
+
if (!perOwner)
|
|
1095
|
+
return;
|
|
1096
|
+
perOwner.delete(thisArg);
|
|
1097
|
+
if (perOwner.size === 0)
|
|
1098
|
+
functionIds.delete(fn);
|
|
1099
|
+
};
|
|
1030
1100
|
const getOrCreateRemoteLru = (remoteId) => {
|
|
1031
1101
|
let lru = remoteCallbacksByOwner.get(remoteId);
|
|
1032
1102
|
if (!lru) {
|
|
@@ -1044,21 +1114,51 @@ function createClientContext(getMessageChannel, instanceId, options = {}) {
|
|
|
1044
1114
|
return map;
|
|
1045
1115
|
};
|
|
1046
1116
|
const registerFunction = (fn, thisArg, callbackOptions = {}) => {
|
|
1047
|
-
let
|
|
1117
|
+
let perOwner = functionIds.get(fn);
|
|
1118
|
+
if (!perOwner) {
|
|
1119
|
+
perOwner = new Map();
|
|
1120
|
+
functionIds.set(fn, perOwner);
|
|
1121
|
+
}
|
|
1122
|
+
let id = perOwner.get(thisArg);
|
|
1048
1123
|
if (!id) {
|
|
1049
1124
|
id = createId('cb');
|
|
1050
|
-
|
|
1125
|
+
perOwner.set(thisArg, id);
|
|
1051
1126
|
}
|
|
1052
1127
|
const entry = { fn, thisArg };
|
|
1053
1128
|
if (callbackOptions.persistent) {
|
|
1054
1129
|
persistentFunctionCache.set(id, entry);
|
|
1055
1130
|
functionCache.delete(id);
|
|
1131
|
+
persistentRefcount.set(id, (persistentRefcount.get(id) ?? 0) + 1);
|
|
1056
1132
|
}
|
|
1057
1133
|
else if (!persistentFunctionCache.has(id)) {
|
|
1058
1134
|
functionCache.set(id, entry);
|
|
1059
1135
|
}
|
|
1060
1136
|
return id;
|
|
1061
1137
|
};
|
|
1138
|
+
const releasePersistentRegistrationById = (id) => {
|
|
1139
|
+
if (!persistentRefcount.has(id))
|
|
1140
|
+
return undefined;
|
|
1141
|
+
const next = (persistentRefcount.get(id) ?? 0) - 1;
|
|
1142
|
+
if (next > 0) {
|
|
1143
|
+
persistentRefcount.set(id, next);
|
|
1144
|
+
return undefined;
|
|
1145
|
+
}
|
|
1146
|
+
persistentRefcount.delete(id);
|
|
1147
|
+
const entry = persistentFunctionCache.get(id);
|
|
1148
|
+
persistentFunctionCache.delete(id);
|
|
1149
|
+
functionCache.delete(id);
|
|
1150
|
+
if (entry)
|
|
1151
|
+
removeFunctionIdMapping(entry.fn, entry.thisArg);
|
|
1152
|
+
return id;
|
|
1153
|
+
};
|
|
1154
|
+
const dropLocalCallback = (id) => {
|
|
1155
|
+
const entry = persistentFunctionCache.get(id) ?? functionCache.get(id);
|
|
1156
|
+
persistentRefcount.delete(id);
|
|
1157
|
+
persistentFunctionCache.delete(id);
|
|
1158
|
+
functionCache.delete(id);
|
|
1159
|
+
if (entry)
|
|
1160
|
+
removeFunctionIdMapping(entry.fn, entry.thisArg);
|
|
1161
|
+
};
|
|
1062
1162
|
const getRemoteCallback = (id, invoke, callbackOptions, remoteInstanceId) => {
|
|
1063
1163
|
const opts = callbackOptions ?? {};
|
|
1064
1164
|
const lru = remoteCallbacksByOwner.get(remoteInstanceId);
|
|
@@ -1085,10 +1185,19 @@ function createClientContext(getMessageChannel, instanceId, options = {}) {
|
|
|
1085
1185
|
try {
|
|
1086
1186
|
getMessageChannel().getSender().sendMessage('releaseCallbacks', { ids });
|
|
1087
1187
|
}
|
|
1088
|
-
catch {
|
|
1089
|
-
|
|
1188
|
+
catch (err) {
|
|
1189
|
+
warn('notifyReleaseCallbacks', 'failed to release callbacks', err);
|
|
1090
1190
|
}
|
|
1091
1191
|
};
|
|
1192
|
+
const rejectPending = (id, error) => {
|
|
1193
|
+
const callbacks = pending.get(id);
|
|
1194
|
+
if (!callbacks)
|
|
1195
|
+
return;
|
|
1196
|
+
clearTimeout(callbacks.timer);
|
|
1197
|
+
pending.delete(id);
|
|
1198
|
+
callbacks.onReject?.();
|
|
1199
|
+
callbacks.reject(error);
|
|
1200
|
+
};
|
|
1092
1201
|
return {
|
|
1093
1202
|
getDelegateTarget() {
|
|
1094
1203
|
return options.delegateTarget;
|
|
@@ -1119,9 +1228,6 @@ function createClientContext(getMessageChannel, instanceId, options = {}) {
|
|
|
1119
1228
|
}
|
|
1120
1229
|
return callbacks;
|
|
1121
1230
|
},
|
|
1122
|
-
registerPendingPromise(id, callbacks) {
|
|
1123
|
-
pending.set(id, callbacks);
|
|
1124
|
-
},
|
|
1125
1231
|
invoke(path, args) {
|
|
1126
1232
|
return new Promise((resolve, reject) => {
|
|
1127
1233
|
if (destroyed) {
|
|
@@ -1129,35 +1235,63 @@ function createClientContext(getMessageChannel, instanceId, options = {}) {
|
|
|
1129
1235
|
return;
|
|
1130
1236
|
}
|
|
1131
1237
|
const id = createId('req');
|
|
1132
|
-
const
|
|
1238
|
+
const persistent = isListenerRegistrationPath(path);
|
|
1239
|
+
const registeredIds = [];
|
|
1240
|
+
const collectingRegister = (fn, thisArg, opts) => {
|
|
1241
|
+
const cbId = registerFunction(fn, thisArg, opts);
|
|
1242
|
+
registeredIds.push(cbId);
|
|
1243
|
+
return cbId;
|
|
1244
|
+
};
|
|
1245
|
+
const releaseRegisteredPersistents = () => {
|
|
1246
|
+
const released = [];
|
|
1247
|
+
for (const cbId of registeredIds) {
|
|
1248
|
+
const releasedId = releasePersistentRegistrationById(cbId);
|
|
1249
|
+
if (releasedId)
|
|
1250
|
+
released.push(releasedId);
|
|
1251
|
+
}
|
|
1252
|
+
return released;
|
|
1253
|
+
};
|
|
1133
1254
|
const timer = setTimeout(() => {
|
|
1255
|
+
rejectPending(id, new Error(`Call timed out: ${String(path.join('.'))}`));
|
|
1256
|
+
}, callTimeout);
|
|
1257
|
+
try {
|
|
1258
|
+
const channel = getMessageChannel();
|
|
1259
|
+
const serializedArgs = args.map((arg) => serialize(arg, collectingRegister, { persistent }));
|
|
1260
|
+
pending.set(id, {
|
|
1261
|
+
resolve,
|
|
1262
|
+
reject,
|
|
1263
|
+
timer,
|
|
1264
|
+
onResolve: () => {
|
|
1265
|
+
if (isListenerRemovalPath(path)) {
|
|
1266
|
+
const released = releaseRegisteredPersistents();
|
|
1267
|
+
if (released.length > 0)
|
|
1268
|
+
notifyReleaseCallbacks(released);
|
|
1269
|
+
}
|
|
1270
|
+
},
|
|
1271
|
+
onReject: () => {
|
|
1272
|
+
if (isListenerRegistrationPath(path)) {
|
|
1273
|
+
const released = releaseRegisteredPersistents();
|
|
1274
|
+
if (released.length > 0)
|
|
1275
|
+
notifyReleaseCallbacks(released);
|
|
1276
|
+
}
|
|
1277
|
+
},
|
|
1278
|
+
});
|
|
1279
|
+
channel.getSender().sendMessage('invokeRequest', {
|
|
1280
|
+
id,
|
|
1281
|
+
path,
|
|
1282
|
+
args: serializedArgs,
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
catch (err) {
|
|
1286
|
+
clearTimeout(timer);
|
|
1134
1287
|
pending.delete(id);
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
const persistent = isListenerRegistrationPath(path);
|
|
1140
|
-
const serializedArgs = args.map((arg) => serialize(arg, registerFunction, { persistent }));
|
|
1141
|
-
if (isListenerRemovalPath(path)) {
|
|
1142
|
-
const releasedIds = [];
|
|
1143
|
-
for (const original of args) {
|
|
1144
|
-
if (typeof original !== 'function')
|
|
1145
|
-
continue;
|
|
1146
|
-
const fnId = functionIds.get(original);
|
|
1147
|
-
if (fnId) {
|
|
1148
|
-
releasedIds.push(fnId);
|
|
1149
|
-
functionCache.delete(fnId);
|
|
1150
|
-
persistentFunctionCache.delete(fnId);
|
|
1151
|
-
functionIds.delete(original);
|
|
1152
|
-
}
|
|
1288
|
+
if (isListenerRegistrationPath(path)) {
|
|
1289
|
+
const released = releaseRegisteredPersistents();
|
|
1290
|
+
if (released.length > 0)
|
|
1291
|
+
notifyReleaseCallbacks(released);
|
|
1153
1292
|
}
|
|
1154
|
-
|
|
1293
|
+
reject(err);
|
|
1155
1294
|
}
|
|
1156
|
-
channel.getSender().sendMessage('invokeRequest', {
|
|
1157
|
-
id,
|
|
1158
|
-
path,
|
|
1159
|
-
args: serializedArgs,
|
|
1160
|
-
});
|
|
1161
1295
|
});
|
|
1162
1296
|
},
|
|
1163
1297
|
accessProperty(path) {
|
|
@@ -1167,16 +1301,21 @@ function createClientContext(getMessageChannel, instanceId, options = {}) {
|
|
|
1167
1301
|
return;
|
|
1168
1302
|
}
|
|
1169
1303
|
const id = createId('req');
|
|
1170
|
-
const timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
1171
1304
|
const timer = setTimeout(() => {
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
}, timeout);
|
|
1305
|
+
rejectPending(id, new Error(`Property access timed out: ${String(path.join('.'))}`));
|
|
1306
|
+
}, callTimeout);
|
|
1175
1307
|
pending.set(id, { resolve, reject, timer });
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1308
|
+
try {
|
|
1309
|
+
getMessageChannel().getSender().sendMessage('accessPropertyRequest', {
|
|
1310
|
+
id,
|
|
1311
|
+
path,
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
catch (err) {
|
|
1315
|
+
clearTimeout(timer);
|
|
1316
|
+
pending.delete(id);
|
|
1317
|
+
reject(err);
|
|
1318
|
+
}
|
|
1180
1319
|
});
|
|
1181
1320
|
},
|
|
1182
1321
|
invokeFunctionById(callbackId, args, callbackOptions = {}) {
|
|
@@ -1186,35 +1325,77 @@ function createClientContext(getMessageChannel, instanceId, options = {}) {
|
|
|
1186
1325
|
return;
|
|
1187
1326
|
}
|
|
1188
1327
|
const callId = createId('cbcall');
|
|
1189
|
-
const timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
1190
1328
|
const timer = setTimeout(() => {
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
}, timeout);
|
|
1329
|
+
rejectPending(callId, new Error(`Callback call timed out: ${callbackId}`));
|
|
1330
|
+
}, callTimeout);
|
|
1194
1331
|
pending.set(callId, { resolve, reject, timer });
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1332
|
+
try {
|
|
1333
|
+
getMessageChannel()
|
|
1334
|
+
.getSender()
|
|
1335
|
+
.sendMessage('invokeFunctionByIdRequest', {
|
|
1336
|
+
id: callbackId,
|
|
1337
|
+
callId,
|
|
1338
|
+
args: args.map((arg) => serialize(arg, registerFunction, callbackOptions)),
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
catch (err) {
|
|
1342
|
+
clearTimeout(timer);
|
|
1343
|
+
pending.delete(callId);
|
|
1344
|
+
reject(err);
|
|
1345
|
+
}
|
|
1202
1346
|
});
|
|
1203
1347
|
},
|
|
1204
1348
|
handleRemoteDestroy(remoteInstanceId) {
|
|
1205
1349
|
remoteCallbacksByOwner.delete(remoteInstanceId);
|
|
1206
1350
|
persistentRemoteCallbacksByOwner.delete(remoteInstanceId);
|
|
1207
1351
|
},
|
|
1352
|
+
releaseRemoteCallbacks(remoteInstanceId, ids) {
|
|
1353
|
+
const lru = remoteCallbacksByOwner.get(remoteInstanceId);
|
|
1354
|
+
const pmap = persistentRemoteCallbacksByOwner.get(remoteInstanceId);
|
|
1355
|
+
if (!lru && !pmap)
|
|
1356
|
+
return;
|
|
1357
|
+
for (const id of ids) {
|
|
1358
|
+
lru?.delete(id);
|
|
1359
|
+
pmap?.delete(id);
|
|
1360
|
+
}
|
|
1361
|
+
},
|
|
1362
|
+
dropLocalCallback(id) {
|
|
1363
|
+
dropLocalCallback(id);
|
|
1364
|
+
},
|
|
1365
|
+
bindMethod(fn, owner) {
|
|
1366
|
+
return bindMethod(fn, owner);
|
|
1367
|
+
},
|
|
1368
|
+
getRemoteCallbackCounts(remoteInstanceId) {
|
|
1369
|
+
return {
|
|
1370
|
+
lru: remoteCallbacksByOwner.get(remoteInstanceId)?.size ?? 0,
|
|
1371
|
+
persistent: persistentRemoteCallbacksByOwner.get(remoteInstanceId)?.size ?? 0,
|
|
1372
|
+
};
|
|
1373
|
+
},
|
|
1208
1374
|
destroy(notifyRemote = true) {
|
|
1209
1375
|
if (destroyed)
|
|
1210
1376
|
return;
|
|
1211
1377
|
destroyed = true;
|
|
1212
1378
|
if (notifyRemote) {
|
|
1379
|
+
const heldRemoteIds = [];
|
|
1380
|
+
for (const lru of remoteCallbacksByOwner.values()) {
|
|
1381
|
+
for (const id of lru.keys())
|
|
1382
|
+
heldRemoteIds.push(id);
|
|
1383
|
+
}
|
|
1384
|
+
for (const map of persistentRemoteCallbacksByOwner.values()) {
|
|
1385
|
+
for (const id of map.keys())
|
|
1386
|
+
heldRemoteIds.push(id);
|
|
1387
|
+
}
|
|
1213
1388
|
try {
|
|
1214
|
-
getMessageChannel().getSender()
|
|
1389
|
+
const sender = getMessageChannel().getSender();
|
|
1390
|
+
if (heldRemoteIds.length > 0) {
|
|
1391
|
+
sender.sendMessage('releaseCallbacks', { ids: heldRemoteIds });
|
|
1392
|
+
}
|
|
1393
|
+
sender.sendMessage('destroyEndpoint', { instanceId });
|
|
1215
1394
|
}
|
|
1216
|
-
catch {
|
|
1217
|
-
|
|
1395
|
+
catch (err) {
|
|
1396
|
+
if (!isExpectedDestroyNotifyError(err)) {
|
|
1397
|
+
warn('destroy', 'failed to notify remote of destroy', err);
|
|
1398
|
+
}
|
|
1218
1399
|
}
|
|
1219
1400
|
}
|
|
1220
1401
|
for (const [id, callbacks] of pending) {
|
|
@@ -1224,6 +1405,7 @@ function createClientContext(getMessageChannel, instanceId, options = {}) {
|
|
|
1224
1405
|
pending.clear();
|
|
1225
1406
|
functionCache.clear();
|
|
1226
1407
|
persistentFunctionCache.clear();
|
|
1408
|
+
persistentRefcount.clear();
|
|
1227
1409
|
functionIds.clear();
|
|
1228
1410
|
remoteCallbacksByOwner.clear();
|
|
1229
1411
|
persistentRemoteCallbacksByOwner.clear();
|
|
@@ -1233,6 +1415,9 @@ function createClientContext(getMessageChannel, instanceId, options = {}) {
|
|
|
1233
1415
|
function createId(prefix) {
|
|
1234
1416
|
return `${prefix}-${nanoid()}`;
|
|
1235
1417
|
}
|
|
1418
|
+
function isExpectedDestroyNotifyError(err) {
|
|
1419
|
+
return err instanceof Error && err.message.includes('contentWindow is not available');
|
|
1420
|
+
}
|
|
1236
1421
|
|
|
1237
1422
|
const EMPTY_TARGET = () => undefined;
|
|
1238
1423
|
const INTERCEPTED_STRING_PROPS = new Set([
|
|
@@ -1355,10 +1540,14 @@ function createEndpoint(options) {
|
|
|
1355
1540
|
processorRegistry.register('releaseCallbacks', handleReleaseCallbacks);
|
|
1356
1541
|
processorRegistry.register('destroyEndpoint', handleDestroyEndpoint);
|
|
1357
1542
|
const proxy = createClientProxy((path, args) => context.invoke(path, args), (path) => context.accessProperty(path));
|
|
1543
|
+
let destroyed = false;
|
|
1358
1544
|
return {
|
|
1359
1545
|
proxy,
|
|
1360
1546
|
getContext: () => context,
|
|
1361
1547
|
destroy() {
|
|
1548
|
+
if (destroyed)
|
|
1549
|
+
return;
|
|
1550
|
+
destroyed = true;
|
|
1362
1551
|
context.destroy(true);
|
|
1363
1552
|
channel.destroy();
|
|
1364
1553
|
},
|
|
@@ -1367,6 +1556,7 @@ function createEndpoint(options) {
|
|
|
1367
1556
|
|
|
1368
1557
|
function setupInMainWindow(options) {
|
|
1369
1558
|
const resolveTargetOrigin = createTargetOriginResolver(() => options.iframe, options.targetOrigin);
|
|
1559
|
+
const allowedOrigin = createAllowedOrigin(options.allowedOrigin, resolveTargetOrigin);
|
|
1370
1560
|
const poster = createWindowPoster({
|
|
1371
1561
|
postMessage: (message) => {
|
|
1372
1562
|
const contentWindow = options.iframe.contentWindow;
|
|
@@ -1376,7 +1566,7 @@ function setupInMainWindow(options) {
|
|
|
1376
1566
|
contentWindow.postMessage(message, resolveTargetOrigin());
|
|
1377
1567
|
},
|
|
1378
1568
|
getExpectedSource: () => options.iframe.contentWindow,
|
|
1379
|
-
allowedOrigin
|
|
1569
|
+
allowedOrigin,
|
|
1380
1570
|
});
|
|
1381
1571
|
const endpoint = createEndpoint({
|
|
1382
1572
|
poster,
|
|
@@ -1399,12 +1589,13 @@ function setupInMainWindow(options) {
|
|
|
1399
1589
|
}
|
|
1400
1590
|
function setupInIframe(options = {}) {
|
|
1401
1591
|
const resolveTargetOrigin = createParentTargetOriginResolver(options.targetOrigin);
|
|
1592
|
+
const allowedOrigin = createAllowedOrigin(options.allowedOrigin, resolveTargetOrigin);
|
|
1402
1593
|
const poster = createWindowPoster({
|
|
1403
1594
|
postMessage: (message) => {
|
|
1404
1595
|
window.parent.postMessage(message, resolveTargetOrigin());
|
|
1405
1596
|
},
|
|
1406
1597
|
getExpectedSource: () => window.parent,
|
|
1407
|
-
allowedOrigin
|
|
1598
|
+
allowedOrigin,
|
|
1408
1599
|
});
|
|
1409
1600
|
const endpoint = createEndpoint({
|
|
1410
1601
|
poster,
|
|
@@ -1439,11 +1630,23 @@ function connectChromeInIframe(options = {}) {
|
|
|
1439
1630
|
}
|
|
1440
1631
|
function createWindowPoster(options) {
|
|
1441
1632
|
const listenerMap = new Map();
|
|
1633
|
+
const getOrCreatePerName = (name) => {
|
|
1634
|
+
let perName = listenerMap.get(name);
|
|
1635
|
+
if (!perName) {
|
|
1636
|
+
perName = new Map();
|
|
1637
|
+
listenerMap.set(name, perName);
|
|
1638
|
+
}
|
|
1639
|
+
return perName;
|
|
1640
|
+
};
|
|
1442
1641
|
return {
|
|
1443
1642
|
postMessage(message) {
|
|
1444
1643
|
options.postMessage(message);
|
|
1445
1644
|
},
|
|
1446
1645
|
addEventListener(name, callback) {
|
|
1646
|
+
const perName = getOrCreatePerName(name);
|
|
1647
|
+
const existing = perName.get(callback);
|
|
1648
|
+
if (existing)
|
|
1649
|
+
window.removeEventListener(name, existing);
|
|
1447
1650
|
const listener = (event) => {
|
|
1448
1651
|
const messageEvent = event;
|
|
1449
1652
|
if (messageEvent.source !== options.getExpectedSource())
|
|
@@ -1452,20 +1655,25 @@ function createWindowPoster(options) {
|
|
|
1452
1655
|
return;
|
|
1453
1656
|
callback(messageEvent);
|
|
1454
1657
|
};
|
|
1455
|
-
|
|
1658
|
+
perName.set(callback, listener);
|
|
1456
1659
|
window.addEventListener(name, listener);
|
|
1457
1660
|
},
|
|
1458
1661
|
removeEventListener(name, callback) {
|
|
1459
|
-
const
|
|
1662
|
+
const perName = listenerMap.get(name);
|
|
1663
|
+
if (!perName)
|
|
1664
|
+
return;
|
|
1665
|
+
const listener = perName.get(callback);
|
|
1460
1666
|
if (!listener)
|
|
1461
1667
|
return;
|
|
1462
1668
|
window.removeEventListener(name, listener);
|
|
1463
|
-
|
|
1669
|
+
perName.delete(callback);
|
|
1670
|
+
if (perName.size === 0)
|
|
1671
|
+
listenerMap.delete(name);
|
|
1464
1672
|
},
|
|
1465
1673
|
};
|
|
1466
1674
|
}
|
|
1467
1675
|
function isAllowedOrigin(origin, allowedOrigin) {
|
|
1468
|
-
if (
|
|
1676
|
+
if (allowedOrigin === undefined)
|
|
1469
1677
|
return true;
|
|
1470
1678
|
if (typeof allowedOrigin === 'function')
|
|
1471
1679
|
return allowedOrigin(origin);
|
|
@@ -1473,6 +1681,14 @@ function isAllowedOrigin(origin, allowedOrigin) {
|
|
|
1473
1681
|
return allowedOrigin.includes(origin);
|
|
1474
1682
|
return allowedOrigin === origin;
|
|
1475
1683
|
}
|
|
1684
|
+
function createAllowedOrigin(explicit, resolveTargetOrigin) {
|
|
1685
|
+
if (explicit !== undefined)
|
|
1686
|
+
return explicit;
|
|
1687
|
+
return (origin) => {
|
|
1688
|
+
const targetOrigin = resolveTargetOrigin();
|
|
1689
|
+
return targetOrigin === '*' || targetOrigin === origin;
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1476
1692
|
function getGlobalChrome() {
|
|
1477
1693
|
const chromeApi = globalThis.chrome;
|
|
1478
1694
|
if (!chromeApi) {
|
|
@@ -1502,13 +1718,11 @@ function deriveOriginFromIframe(iframe) {
|
|
|
1502
1718
|
return loc.origin;
|
|
1503
1719
|
}
|
|
1504
1720
|
}
|
|
1505
|
-
catch {
|
|
1506
|
-
|
|
1507
|
-
}
|
|
1508
|
-
if (typeof console !== 'undefined') {
|
|
1509
|
-
console.warn("chrome-in-iframe: unable to derive iframe origin automatically; falling back to '*'. " +
|
|
1510
|
-
'Pass `targetOrigin` explicitly to lock the destination.');
|
|
1721
|
+
catch (err) {
|
|
1722
|
+
warn('deriveOriginFromIframe', 'unable to access cross-origin contentWindow.location', err);
|
|
1511
1723
|
}
|
|
1724
|
+
warn('deriveOriginFromIframe', "unable to derive iframe origin automatically; falling back to '*'. " +
|
|
1725
|
+
'Pass `targetOrigin` explicitly to lock the destination.');
|
|
1512
1726
|
return '*';
|
|
1513
1727
|
}
|
|
1514
1728
|
function deriveParentOrigin() {
|
|
@@ -1523,10 +1737,8 @@ function deriveParentOrigin() {
|
|
|
1523
1737
|
if (first && first !== 'null')
|
|
1524
1738
|
return first;
|
|
1525
1739
|
}
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
'Pass `targetOrigin` explicitly to lock the destination.');
|
|
1529
|
-
}
|
|
1740
|
+
warn('deriveParentOrigin', "unable to derive parent origin automatically; falling back to '*'. " +
|
|
1741
|
+
'Pass `targetOrigin` explicitly to lock the destination.');
|
|
1530
1742
|
return '*';
|
|
1531
1743
|
}
|
|
1532
1744
|
function parseOrigin(url) {
|
|
@@ -1537,10 +1749,11 @@ function parseOrigin(url) {
|
|
|
1537
1749
|
if (parsed.origin && parsed.origin !== 'null')
|
|
1538
1750
|
return parsed.origin;
|
|
1539
1751
|
}
|
|
1540
|
-
catch {
|
|
1752
|
+
catch (err) {
|
|
1753
|
+
warn('parseOrigin', 'failed to parse URL', url, err);
|
|
1541
1754
|
return null;
|
|
1542
1755
|
}
|
|
1543
1756
|
return null;
|
|
1544
1757
|
}
|
|
1545
1758
|
|
|
1546
|
-
export { connectChromeInIframe, exposeChromeInIframe, setupInIframe, setupInMainWindow };
|
|
1759
|
+
export { connectChromeInIframe, exposeChromeInIframe, setLogger, setupInIframe, setupInMainWindow };
|