|
1 | 1 | import { AppConfig } from '../models/AppConfig'; |
2 | 2 | import { EnvironmentKind } from '../models/EnvironmentKind'; |
3 | 3 | import ProxyFrameHost from '../modules/frames/ProxyFrameHost'; |
4 | | -import { contains } from '../utils'; |
5 | 4 | import SdkEnvironment from './SdkEnvironment'; |
| 5 | +import { Utils } from '../context/shared/utils/Utils'; |
6 | 6 |
|
7 | 7 | export default class AltOriginManager { |
8 | 8 |
|
9 | 9 | constructor() { |
10 | 10 |
|
11 | 11 | } |
12 | 12 |
|
13 | | - static async discoverAltOrigin(appConfig): Promise<ProxyFrameHost> { |
| 13 | + /* |
| 14 | + * This loads all possible iframes that a site could be subscribed to |
| 15 | + * (os.tc & onesignal.com) then checks to see we are subscribed to any. |
| 16 | + * If we find what we are subscribed to both unsubscribe from onesignal.com. |
| 17 | + * This method prefers os.tc over onesignal.com where possible. |
| 18 | + */ |
| 19 | + static async discoverAltOrigin(appConfig: AppConfig): Promise<ProxyFrameHost> { |
14 | 20 | const iframeUrls = AltOriginManager.getOneSignalProxyIframeUrls(appConfig); |
| 21 | + |
15 | 22 | const allProxyFrameHosts: ProxyFrameHost[] = []; |
16 | | - let targetProxyFrameHost; |
17 | 23 | for (const iframeUrl of iframeUrls) { |
18 | 24 | const proxyFrameHost = new ProxyFrameHost(iframeUrl); |
19 | 25 | // A TimeoutError could happen here; it gets rejected out of this entire loop |
20 | 26 | await proxyFrameHost.load(); |
21 | 27 | allProxyFrameHosts.push(proxyFrameHost); |
22 | 28 | } |
23 | | - const nonDuplicatedAltOriginSubscriptions = await AltOriginManager.removeDuplicatedAltOriginSubscription(allProxyFrameHosts); |
24 | 29 |
|
25 | | - if (nonDuplicatedAltOriginSubscriptions) { |
26 | | - targetProxyFrameHost = nonDuplicatedAltOriginSubscriptions[0]; |
| 30 | + const subscribedProxyFrameHosts = await AltOriginManager.subscribedProxyFrameHosts(allProxyFrameHosts); |
| 31 | + await AltOriginManager.removeDuplicatedAltOriginSubscription(subscribedProxyFrameHosts); |
| 32 | + |
| 33 | + let preferredProxyFrameHost: ProxyFrameHost; |
| 34 | + if (subscribedProxyFrameHosts.length === 0) { |
| 35 | + // Use the first (preferred) host (os.tc in this case) if not subscribed to any |
| 36 | + preferredProxyFrameHost = allProxyFrameHosts[0]; |
27 | 37 | } else { |
28 | | - for (const proxyFrameHost of allProxyFrameHosts) { |
29 | | - if (await proxyFrameHost.isSubscribed()) { |
30 | | - // If we're subscribed, we're done searching for the iframe |
31 | | - targetProxyFrameHost = proxyFrameHost; |
32 | | - } else { |
33 | | - if (contains(proxyFrameHost.url.host, '.os.tc')) { |
34 | | - if (!targetProxyFrameHost) { |
35 | | - // We've already loaded .onesignal.com and they're not subscribed |
36 | | - // There's no other frames to check; the user is completely not subscribed |
37 | | - targetProxyFrameHost = proxyFrameHost; |
38 | | - } else { |
39 | | - // Already subscribed to .onesignal.com; remove os.tc frame |
40 | | - proxyFrameHost.dispose(); |
41 | | - } |
42 | | - } else { |
43 | | - // We've just loaded .onesignal.com and they're not subscribed |
44 | | - // Load the .os.tc frame next to check |
45 | | - // Remove the .onesignal.com frame; there's no need to keep it around anymore |
46 | | - // Actually don't dispose it, so we can check for duplicate subscriptions |
47 | | - proxyFrameHost.dispose(); |
48 | | - continue; |
49 | | - } |
50 | | - } |
| 38 | + // Otherwise if their was one or more use the highest preferred one in the list |
| 39 | + preferredProxyFrameHost = subscribedProxyFrameHosts[0]; |
| 40 | + } |
| 41 | + |
| 42 | + // Remove all other unneeded iframes from the page |
| 43 | + for (const proxyFrameHost of allProxyFrameHosts) { |
| 44 | + if (preferredProxyFrameHost !== proxyFrameHost) { |
| 45 | + proxyFrameHost.dispose(); |
51 | 46 | } |
52 | 47 | } |
53 | 48 |
|
54 | | - return targetProxyFrameHost; |
| 49 | + return preferredProxyFrameHost; |
55 | 50 | } |
56 | 51 |
|
57 | | - static async removeDuplicatedAltOriginSubscription(proxyFrameHosts: ProxyFrameHost[]): Promise<void | ProxyFrameHost[]> { |
58 | | - const subscribedProxyFrameHosts = []; |
| 52 | + static async subscribedProxyFrameHosts(proxyFrameHosts: ProxyFrameHost[]): Promise<ProxyFrameHost[]> { |
| 53 | + const subscribed: ProxyFrameHost[] = []; |
59 | 54 | for (const proxyFrameHost of proxyFrameHosts) { |
60 | 55 | if (await proxyFrameHost.isSubscribed()) { |
61 | | - subscribedProxyFrameHosts.push(proxyFrameHost); |
| 56 | + subscribed.push(proxyFrameHost); |
62 | 57 | } |
63 | 58 | } |
| 59 | + return subscribed; |
| 60 | + } |
| 61 | + |
| 62 | + /* |
| 63 | + * If the user is subscribed to more than OneSignal subdomain (AKA HTTP setup) |
| 64 | + * unsubscribe them from ones lower in the list. |
| 65 | + * Example: Such as being being subscribed to mysite.os.tc & mysite.onesignal.com |
| 66 | + */ |
| 67 | + static async removeDuplicatedAltOriginSubscription( |
| 68 | + subscribedProxyFrameHosts: ProxyFrameHost[] |
| 69 | + ): Promise<void> { |
| 70 | + // Not subscribed or subscribed to just one domain, nothing to do, no duplicates |
64 | 71 | if (subscribedProxyFrameHosts.length < 2) { |
65 | | - // If the user is only subscribed on one host, or not subscribed at all, |
66 | | - // they don't have duplicate subscriptions |
67 | | - return null; |
| 72 | + return; |
68 | 73 | } |
69 | | - if (SdkEnvironment.getBuildEnv() == EnvironmentKind.Development) { |
70 | | - var hostToCheck = '.localhost:3001'; |
71 | | - } else if (SdkEnvironment.getBuildEnv() == EnvironmentKind.Production) { |
72 | | - var hostToCheck = '.onesignal.com'; |
73 | | - } |
74 | | - var oneSignalComProxyFrameHost: ProxyFrameHost = (subscribedProxyFrameHosts as any).find(proxyFrameHost => contains(proxyFrameHost.url.host, hostToCheck)); |
75 | | - if (!oneSignalComProxyFrameHost) { |
76 | | - // They aren't subscribed to the .onesignal.com frame; shouldn't happen |
77 | | - // unless we have 2 other frames in the future they can subscribe to |
78 | | - return null; |
79 | | - } else { |
80 | | - await oneSignalComProxyFrameHost.unsubscribeFromPush(); |
81 | | - oneSignalComProxyFrameHost.dispose(); |
82 | 74 |
|
83 | | - const indexToRemove = proxyFrameHosts.indexOf(oneSignalComProxyFrameHost); |
84 | | - proxyFrameHosts.splice(indexToRemove, 1); |
85 | | - return proxyFrameHosts; |
| 75 | + // At this point we have 2+ subscriptions |
| 76 | + // Keep only the first (highest priority) domain and unsubscribe the rest. |
| 77 | + const toUnsubscribeProxyFrameHosts = subscribedProxyFrameHosts.slice(1); |
| 78 | + for (const dupSubscribedHost of toUnsubscribeProxyFrameHosts) { |
| 79 | + await dupSubscribedHost.unsubscribeFromPush(); |
86 | 80 | } |
87 | 81 | } |
88 | 82 |
|
89 | 83 | /** |
| 84 | + * Only used for sites using a OneSignal subdomain (AKA HTTP setup). |
| 85 | + * |
90 | 86 | * Returns the array of possible URL in which the push subscription and |
91 | 87 | * IndexedDb site data will be stored. |
92 | 88 | * |
93 | | - * For native HTTPS sites not using a subdomain of our service, this is the |
94 | | - * top-level URL. |
95 | | - * |
96 | | - * For sites using a subdomain of our service, this URL was typically |
97 | | - * subdomain.onesignal.com, until we switched to subdomain.os.tc for a shorter |
98 | | - * origin to fit into Mac's native notifications on Chrome 59+. |
| 89 | + * This URL was typically subdomain.onesignal.com, until we switched |
| 90 | + * to subdomain.os.tc for a shorter origin to fit into Mac's native |
| 91 | + * notifications on Chrome 59+. |
99 | 92 | * |
100 | 93 | * Because a user may be subscribed to subdomain.onesignal.com or |
101 | 94 | * subdomain.os.tc, we have to load both in certain scenarios to determine |
102 | 95 | * which the user is subscribed to; hence, this method returns an array of |
103 | 96 | * possible URLs. |
| 97 | + * |
| 98 | + * Order of URL is in priority order of one should be used. |
104 | 99 | */ |
105 | 100 | static getCanonicalSubscriptionUrls(config: AppConfig, |
106 | 101 | buildEnv: EnvironmentKind = SdkEnvironment.getApiEnv() |
107 | 102 | ): Array<URL> { |
108 | | - let urls = []; |
| 103 | + const subscriptionDomain = AltOriginManager.getWildcardLegacySubscriptionDomain(buildEnv); |
| 104 | + const legacyDomainUrl = new URL(`https://${config.subdomain}.${subscriptionDomain}`); |
109 | 105 |
|
110 | | - if (config.httpUseOneSignalCom) { |
111 | | - let legacyDomainUrl = SdkEnvironment.getOneSignalApiUrl(buildEnv); |
112 | | - // Add subdomain.onesignal.com |
113 | | - legacyDomainUrl.host = [config.subdomain, legacyDomainUrl.host].join('.'); |
114 | | - urls.push(legacyDomainUrl); |
| 106 | + // Staging and Dev don't support going through the os.tc domain |
| 107 | + if (buildEnv !== EnvironmentKind.Production) { |
| 108 | + return [legacyDomainUrl]; |
115 | 109 | } |
116 | 110 |
|
117 | | - let osTcDomainUrl = SdkEnvironment.getOneSignalApiUrl(buildEnv); |
118 | | - // Always add subdomain.os.tc |
119 | | - osTcDomainUrl.host = [config.subdomain, 'os.tc'].join('.'); |
120 | | - urls.push(osTcDomainUrl); |
| 111 | + // Use os.tc as a first pick |
| 112 | + const urls = [new URL(`https://${config.subdomain}.os.tc`)]; |
121 | 113 |
|
122 | | - for (const url of urls) { |
123 | | - url.pathname = ''; |
| 114 | + if (config.httpUseOneSignalCom) { |
| 115 | + urls.push(legacyDomainUrl); |
124 | 116 | } |
125 | 117 |
|
126 | 118 | return urls; |
127 | 119 | } |
128 | 120 |
|
129 | 121 | /** |
130 | | - * Returns the URL of the OneSignal proxy iFrame helper. |
| 122 | + * Get the wildcard part of the legacy subscription domain. |
| 123 | + * Examples: onesignal.com, staging.onesignal.com, or localhost |
131 | 124 | */ |
132 | | - static getOneSignalProxyIframeUrls(config: AppConfig): Array<URL> { |
133 | | - const urls = AltOriginManager.getCanonicalSubscriptionUrls(config); |
134 | | - |
135 | | - for (const url of urls) { |
136 | | - url.pathname = 'webPushIframe'; |
| 125 | + static getWildcardLegacySubscriptionDomain(buildEnv: EnvironmentKind): string { |
| 126 | + const apiUrl = SdkEnvironment.getOneSignalApiUrl(buildEnv); |
| 127 | + |
| 128 | + // Prod and Dev support domains like *.onesignal.com and *.localhost |
| 129 | + let envSubdomainParts: number = 2; |
| 130 | + if (buildEnv === EnvironmentKind.Staging) { |
| 131 | + // Allow up to 3 parts so *.staging.onesignal.com works. |
| 132 | + envSubdomainParts = 3; |
137 | 133 | } |
138 | 134 |
|
139 | | - return urls; |
| 135 | + return Utils.lastParts(apiUrl.host, ".", envSubdomainParts); |
140 | 136 | } |
141 | 137 |
|
142 | 138 | /** |
143 | | - * Returns the URL of the OneSignal subscription popup. |
| 139 | + * Returns the URL of the OneSignal proxy iFrame helper. |
144 | 140 | */ |
145 | | - static getOneSignalSubscriptionPopupUrls(config: AppConfig): Array<URL> { |
| 141 | + static getOneSignalProxyIframeUrls(config: AppConfig): Array<URL> { |
146 | 142 | const urls = AltOriginManager.getCanonicalSubscriptionUrls(config); |
147 | 143 |
|
148 | 144 | for (const url of urls) { |
149 | | - url.pathname = 'subscribe'; |
| 145 | + url.pathname = 'webPushIframe'; |
150 | 146 | } |
151 | 147 |
|
152 | 148 | return urls; |
|
0 commit comments