@applicaster/quick-brick-core 17.0.0-alpha.1984136194 → 17.0.0-alpha.5804334713
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.
|
@@ -12,10 +12,6 @@ import * as ReactNative from "react-native";
|
|
|
12
12
|
import { isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
13
13
|
import { log_debug, log_error, log_info } from "../logger";
|
|
14
14
|
import { parseUrl } from "../helpers";
|
|
15
|
-
import {
|
|
16
|
-
addSamsungAppControlListener,
|
|
17
|
-
getSamsungAppControlUrl,
|
|
18
|
-
} from "../SamsungAppControl";
|
|
19
15
|
import { getAppUrlScheme } from "@applicaster/zapp-react-native-utils/appDataUtils";
|
|
20
16
|
import {
|
|
21
17
|
getZappPlatform,
|
|
@@ -91,17 +87,6 @@ function URLSchemeContextProvider(props: Props) {
|
|
|
91
87
|
const url = await Linking.getInitialURL();
|
|
92
88
|
|
|
93
89
|
if (isWeb()) {
|
|
94
|
-
// Samsung: deep link from the app control that launched the app
|
|
95
|
-
const samsungDeepLink = getSamsungAppControlUrl();
|
|
96
|
-
|
|
97
|
-
if (samsungDeepLink) {
|
|
98
|
-
log_info(
|
|
99
|
-
`URLSchemeContextProvider: Retrieved initial Samsung deep link: ${samsungDeepLink}`
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
return setState(samsungDeepLink);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
90
|
const presentDeepLink = getPresentDeepLink(url);
|
|
106
91
|
|
|
107
92
|
if (presentDeepLink) {
|
|
@@ -195,22 +180,10 @@ function URLSchemeContextProvider(props: Props) {
|
|
|
195
180
|
handleOpenURL
|
|
196
181
|
);
|
|
197
182
|
|
|
198
|
-
// Samsung: deep links sent while the app is running ("appcontrol" event)
|
|
199
|
-
const removeSamsungAppControlListener = addSamsungAppControlListener(
|
|
200
|
-
(appControlUrl) => {
|
|
201
|
-
log_info(
|
|
202
|
-
`URLSchemeContextProvider: Samsung appcontrol deep link received — url=${appControlUrl}`
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
setState(appControlUrl);
|
|
206
|
-
}
|
|
207
|
-
);
|
|
208
|
-
|
|
209
183
|
return () => {
|
|
210
184
|
log_debug("URLSchemeContextProvider: Remove URL event listeners");
|
|
211
185
|
listener.remove();
|
|
212
186
|
linkingListener.remove();
|
|
213
|
-
removeSamsungAppControlListener();
|
|
214
187
|
};
|
|
215
188
|
}
|
|
216
189
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/quick-brick-core",
|
|
3
|
-
"version": "17.0.0-alpha.
|
|
3
|
+
"version": "17.0.0-alpha.5804334713",
|
|
4
4
|
"description": "Core package for Applicaster's Quick Brick App",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -28,13 +28,13 @@
|
|
|
28
28
|
},
|
|
29
29
|
"homepage": "https://github.com/applicaster/quickbrick#readme",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@applicaster/applicaster-types": "17.0.0-alpha.
|
|
32
|
-
"@applicaster/quick-brick-core-plugins": "17.0.0-alpha.
|
|
33
|
-
"@applicaster/zapp-pipes-v2-client": "17.0.0-alpha.
|
|
34
|
-
"@applicaster/zapp-react-native-bridge": "17.0.0-alpha.
|
|
35
|
-
"@applicaster/zapp-react-native-redux": "17.0.0-alpha.
|
|
36
|
-
"@applicaster/zapp-react-native-ui-components": "17.0.0-alpha.
|
|
37
|
-
"@applicaster/zapp-react-native-utils": "17.0.0-alpha.
|
|
31
|
+
"@applicaster/applicaster-types": "17.0.0-alpha.5804334713",
|
|
32
|
+
"@applicaster/quick-brick-core-plugins": "17.0.0-alpha.5804334713",
|
|
33
|
+
"@applicaster/zapp-pipes-v2-client": "17.0.0-alpha.5804334713",
|
|
34
|
+
"@applicaster/zapp-react-native-bridge": "17.0.0-alpha.5804334713",
|
|
35
|
+
"@applicaster/zapp-react-native-redux": "17.0.0-alpha.5804334713",
|
|
36
|
+
"@applicaster/zapp-react-native-ui-components": "17.0.0-alpha.5804334713",
|
|
37
|
+
"@applicaster/zapp-react-native-utils": "17.0.0-alpha.5804334713",
|
|
38
38
|
"atob": "^2.1.2",
|
|
39
39
|
"axios": "^0.28.0",
|
|
40
40
|
"btoa": "^1.2.1",
|
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
actionDataToUrl,
|
|
3
|
-
addSamsungAppControlListener,
|
|
4
|
-
getSamsungAppControlUrl,
|
|
5
|
-
resetSamsungAppControlState,
|
|
6
|
-
} from "..";
|
|
7
|
-
|
|
8
|
-
jest.mock("@applicaster/zapp-react-native-utils/appDataUtils", () => ({
|
|
9
|
-
getAppUrlScheme: () => "self",
|
|
10
|
-
}));
|
|
11
|
-
|
|
12
|
-
const QUERY =
|
|
13
|
-
"feed_locator=https%3A%2F%2Fzapp-pf-axnm.web.app%2Faxinom%2Fmovies%2F488059942906&id=488059942906";
|
|
14
|
-
|
|
15
|
-
const DEEP_LINK = `pureflix://open?${QUERY}`;
|
|
16
|
-
|
|
17
|
-
const mockTizen = (data?: { key: string; value: string[] }[]) => {
|
|
18
|
-
const getRequestedAppControl = jest.fn(() =>
|
|
19
|
-
data
|
|
20
|
-
? {
|
|
21
|
-
appControl: {
|
|
22
|
-
operation: "http://tizen.org/appcontrol/operation/default",
|
|
23
|
-
data,
|
|
24
|
-
},
|
|
25
|
-
}
|
|
26
|
-
: null
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
// @ts-ignore
|
|
30
|
-
window.tizen = {
|
|
31
|
-
application: { getCurrentApplication: () => ({ getRequestedAppControl }) },
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
return getRequestedAppControl;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const payload = (values: unknown) => [
|
|
38
|
-
{ key: "PAYLOAD", value: [JSON.stringify({ values })] },
|
|
39
|
-
];
|
|
40
|
-
|
|
41
|
-
// The RN jest environment has no DOM events on window
|
|
42
|
-
const target = new EventTarget();
|
|
43
|
-
// @ts-ignore
|
|
44
|
-
window.addEventListener = target.addEventListener.bind(target);
|
|
45
|
-
// @ts-ignore
|
|
46
|
-
window.removeEventListener = target.removeEventListener.bind(target);
|
|
47
|
-
// @ts-ignore
|
|
48
|
-
window.dispatchEvent = target.dispatchEvent.bind(target);
|
|
49
|
-
|
|
50
|
-
const documentTarget = new EventTarget();
|
|
51
|
-
let documentHidden = false;
|
|
52
|
-
|
|
53
|
-
// @ts-ignore
|
|
54
|
-
global.document = {
|
|
55
|
-
get hidden() {
|
|
56
|
-
return documentHidden;
|
|
57
|
-
},
|
|
58
|
-
addEventListener: documentTarget.addEventListener.bind(documentTarget),
|
|
59
|
-
removeEventListener: documentTarget.removeEventListener.bind(documentTarget),
|
|
60
|
-
dispatchEvent: documentTarget.dispatchEvent.bind(documentTarget),
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const setVisibility = (hidden: boolean) => {
|
|
64
|
-
documentHidden = hidden;
|
|
65
|
-
document.dispatchEvent(new Event("visibilitychange"));
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
afterEach(() => {
|
|
69
|
-
// @ts-ignore
|
|
70
|
-
delete window.tizen;
|
|
71
|
-
documentHidden = false;
|
|
72
|
-
resetSamsungAppControlState();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
describe("actionDataToUrl", () => {
|
|
76
|
-
it("keeps a full deep link", () => {
|
|
77
|
-
expect(actionDataToUrl(DEEP_LINK)).toBe(DEEP_LINK);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("builds an open deep link from a query string", () => {
|
|
81
|
-
expect(actionDataToUrl(QUERY)).toBe(`self://open?${QUERY}`);
|
|
82
|
-
expect(actionDataToUrl(`?${QUERY}`)).toBe(`self://open?${QUERY}`);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it("reads url / deep_link / values from JSON", () => {
|
|
86
|
-
expect(actionDataToUrl(JSON.stringify({ url: DEEP_LINK }))).toBe(DEEP_LINK);
|
|
87
|
-
expect(actionDataToUrl({ deep_link: DEEP_LINK })).toBe(DEEP_LINK);
|
|
88
|
-
|
|
89
|
-
expect(actionDataToUrl(JSON.stringify({ values: DEEP_LINK }))).toBe(
|
|
90
|
-
DEEP_LINK
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
expect(
|
|
94
|
-
actionDataToUrl(
|
|
95
|
-
JSON.stringify({ values: JSON.stringify({ url: DEEP_LINK }) })
|
|
96
|
-
)
|
|
97
|
-
).toBe(DEEP_LINK);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it("returns null for non deep link data", () => {
|
|
101
|
-
expect(actionDataToUrl("")).toBeNull();
|
|
102
|
-
expect(actionDataToUrl("hello")).toBeNull();
|
|
103
|
-
expect(actionDataToUrl(undefined)).toBeNull();
|
|
104
|
-
expect(actionDataToUrl("{not json")).toBeNull();
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
describe("getSamsungAppControlUrl", () => {
|
|
109
|
-
it("returns null outside Tizen", () => {
|
|
110
|
-
expect(getSamsungAppControlUrl()).toBeNull();
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it("returns null when the app was launched without PAYLOAD", () => {
|
|
114
|
-
mockTizen([]);
|
|
115
|
-
expect(getSamsungAppControlUrl()).toBeNull();
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it("returns null when there is no requested app control", () => {
|
|
119
|
-
mockTizen();
|
|
120
|
-
expect(getSamsungAppControlUrl()).toBeNull();
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it("returns the deep link from PAYLOAD", () => {
|
|
124
|
-
mockTizen(payload(DEEP_LINK));
|
|
125
|
-
expect(getSamsungAppControlUrl()).toBe(DEEP_LINK);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("does not throw when getRequestedAppControl throws", () => {
|
|
129
|
-
// @ts-ignore
|
|
130
|
-
window.tizen = {
|
|
131
|
-
application: {
|
|
132
|
-
getCurrentApplication: () => ({
|
|
133
|
-
getRequestedAppControl: () => {
|
|
134
|
-
throw new Error("NotFoundError");
|
|
135
|
-
},
|
|
136
|
-
}),
|
|
137
|
-
},
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
expect(getSamsungAppControlUrl()).toBeNull();
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
describe("addSamsungAppControlListener", () => {
|
|
145
|
-
it("is a no-op outside Tizen", () => {
|
|
146
|
-
const onUrl = jest.fn();
|
|
147
|
-
const remove = addSamsungAppControlListener(onUrl);
|
|
148
|
-
window.dispatchEvent(new Event("appcontrol"));
|
|
149
|
-
expect(onUrl).not.toHaveBeenCalled();
|
|
150
|
-
remove();
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it("calls onUrl with the deep link on appcontrol event and unsubscribes", () => {
|
|
154
|
-
mockTizen(payload(DEEP_LINK));
|
|
155
|
-
const onUrl = jest.fn();
|
|
156
|
-
const remove = addSamsungAppControlListener(onUrl);
|
|
157
|
-
|
|
158
|
-
window.dispatchEvent(new Event("appcontrol"));
|
|
159
|
-
expect(onUrl).toHaveBeenCalledWith(DEEP_LINK);
|
|
160
|
-
|
|
161
|
-
remove();
|
|
162
|
-
window.dispatchEvent(new Event("appcontrol"));
|
|
163
|
-
expect(onUrl).toHaveBeenCalledTimes(1);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it("ignores appcontrol events without a deep link", () => {
|
|
167
|
-
mockTizen([]);
|
|
168
|
-
const onUrl = jest.fn();
|
|
169
|
-
const remove = addSamsungAppControlListener(onUrl);
|
|
170
|
-
window.dispatchEvent(new Event("appcontrol"));
|
|
171
|
-
expect(onUrl).not.toHaveBeenCalled();
|
|
172
|
-
remove();
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
describe("deduplication", () => {
|
|
177
|
-
it("returns the same PAYLOAD only once until a launch without PAYLOAD", () => {
|
|
178
|
-
const tizenData = payload(DEEP_LINK);
|
|
179
|
-
const getRequested = mockTizen(tizenData);
|
|
180
|
-
|
|
181
|
-
expect(getSamsungAppControlUrl()).toBe(DEEP_LINK);
|
|
182
|
-
expect(getSamsungAppControlUrl()).toBeNull();
|
|
183
|
-
|
|
184
|
-
// App opened from the icon: app control without PAYLOAD
|
|
185
|
-
getRequested.mockReturnValueOnce({
|
|
186
|
-
appControl: { operation: "default", data: [] },
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
expect(getSamsungAppControlUrl()).toBeNull();
|
|
190
|
-
expect(getSamsungAppControlUrl()).toBe(DEEP_LINK);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it("handles a different PAYLOAD right away", () => {
|
|
194
|
-
const getRequested = mockTizen(payload(DEEP_LINK));
|
|
195
|
-
expect(getSamsungAppControlUrl()).toBe(DEEP_LINK);
|
|
196
|
-
|
|
197
|
-
const other = DEEP_LINK.replace(/488059942906/g, "111");
|
|
198
|
-
|
|
199
|
-
getRequested.mockReturnValue({
|
|
200
|
-
appControl: { operation: "default", data: payload(other) },
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
expect(getSamsungAppControlUrl()).toBe(other);
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
describe("visibilitychange (app brought back to foreground)", () => {
|
|
208
|
-
it("calls onUrl when the page becomes visible with a new PAYLOAD", () => {
|
|
209
|
-
mockTizen(payload(DEEP_LINK));
|
|
210
|
-
const onUrl = jest.fn();
|
|
211
|
-
const remove = addSamsungAppControlListener(onUrl);
|
|
212
|
-
|
|
213
|
-
setVisibility(true);
|
|
214
|
-
expect(onUrl).not.toHaveBeenCalled();
|
|
215
|
-
|
|
216
|
-
setVisibility(false);
|
|
217
|
-
expect(onUrl).toHaveBeenCalledWith(DEEP_LINK);
|
|
218
|
-
|
|
219
|
-
remove();
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
it("does not reopen the same deep link on a plain return to the app", () => {
|
|
223
|
-
mockTizen(payload(DEEP_LINK));
|
|
224
|
-
const onUrl = jest.fn();
|
|
225
|
-
const remove = addSamsungAppControlListener(onUrl);
|
|
226
|
-
|
|
227
|
-
setVisibility(false);
|
|
228
|
-
setVisibility(true);
|
|
229
|
-
setVisibility(false);
|
|
230
|
-
expect(onUrl).toHaveBeenCalledTimes(1);
|
|
231
|
-
|
|
232
|
-
remove();
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
it("handles appcontrol + visibilitychange for one launch only once", () => {
|
|
236
|
-
mockTizen(payload(DEEP_LINK));
|
|
237
|
-
const onUrl = jest.fn();
|
|
238
|
-
const remove = addSamsungAppControlListener(onUrl);
|
|
239
|
-
|
|
240
|
-
window.dispatchEvent(new Event("appcontrol"));
|
|
241
|
-
setVisibility(false);
|
|
242
|
-
expect(onUrl).toHaveBeenCalledTimes(1);
|
|
243
|
-
|
|
244
|
-
remove();
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
it("does not react after unsubscribe", () => {
|
|
248
|
-
mockTizen(payload(DEEP_LINK));
|
|
249
|
-
const onUrl = jest.fn();
|
|
250
|
-
addSamsungAppControlListener(onUrl)();
|
|
251
|
-
|
|
252
|
-
setVisibility(false);
|
|
253
|
-
expect(onUrl).not.toHaveBeenCalled();
|
|
254
|
-
});
|
|
255
|
-
});
|
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Samsung (Tizen) deep links.
|
|
3
|
-
*
|
|
4
|
-
* Samsung delivers deep links (Smart Hub Preview, Universal Guide, search, voice,
|
|
5
|
-
* partner launchers) as an app control with a `PAYLOAD` data key:
|
|
6
|
-
* PAYLOAD = '{"values":"<action data>"}'
|
|
7
|
-
*
|
|
8
|
-
* - Cold start: the app control that launched the app is available through
|
|
9
|
-
* tizen.application.getCurrentApplication().getRequestedAppControl().
|
|
10
|
-
* - App already running (multitasking): the platform fires a window "appcontrol"
|
|
11
|
-
* event and getRequestedAppControl() returns the new app control.
|
|
12
|
-
*
|
|
13
|
-
* The action data may be a full deep link (`<scheme>://open?feed_locator=...&id=...`),
|
|
14
|
-
* a bare query string (`feed_locator=...&id=...`) or a JSON object with a `url`,
|
|
15
|
-
* `deep_link` or `deeplink` field. It is normalized to a URL handled by
|
|
16
|
-
* URLSchemeListener (routing is by host, e.g. "open" / "present").
|
|
17
|
-
*/
|
|
18
|
-
import { getAppUrlScheme } from "@applicaster/zapp-react-native-utils/appDataUtils";
|
|
19
|
-
|
|
20
|
-
import { log_debug, log_error, log_info } from "../logger";
|
|
21
|
-
|
|
22
|
-
export const PAYLOAD_KEY = "PAYLOAD";
|
|
23
|
-
|
|
24
|
-
export const APP_CONTROL_EVENT = "appcontrol";
|
|
25
|
-
|
|
26
|
-
type AppControlData = { key: string; value?: string[] };
|
|
27
|
-
|
|
28
|
-
const SCHEME_REGEX = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
29
|
-
|
|
30
|
-
const getTizenApplication = () => {
|
|
31
|
-
try {
|
|
32
|
-
// @ts-ignore tizen is a global injected by the Tizen web runtime
|
|
33
|
-
const tizenApi = typeof window !== "undefined" ? window.tizen : undefined;
|
|
34
|
-
|
|
35
|
-
return tizenApi?.application?.getCurrentApplication?.() ?? null;
|
|
36
|
-
} catch (error) {
|
|
37
|
-
log_debug("SamsungAppControl: Tizen application API is not available", {
|
|
38
|
-
error,
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const parseJSON = (value: string): unknown => {
|
|
46
|
-
try {
|
|
47
|
-
return JSON.parse(value);
|
|
48
|
-
} catch {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Converts Samsung action data into a deep link URL, or null if it is not one.
|
|
55
|
-
*/
|
|
56
|
-
export const actionDataToUrl = (raw: unknown, depth = 0): string | null => {
|
|
57
|
-
if (depth > 6 || raw === null || raw === undefined) return null;
|
|
58
|
-
|
|
59
|
-
if (typeof raw === "object") {
|
|
60
|
-
const data = raw as Record<string, unknown>;
|
|
61
|
-
|
|
62
|
-
return actionDataToUrl(
|
|
63
|
-
data.url ?? data.deep_link ?? data.deeplink ?? data.values,
|
|
64
|
-
depth + 1
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (typeof raw !== "string") return null;
|
|
69
|
-
|
|
70
|
-
const value = raw.trim();
|
|
71
|
-
|
|
72
|
-
if (!value) return null;
|
|
73
|
-
|
|
74
|
-
if (value.startsWith("{")) {
|
|
75
|
-
const parsed = parseJSON(value);
|
|
76
|
-
|
|
77
|
-
if (parsed) return actionDataToUrl(parsed, depth + 1);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (SCHEME_REGEX.test(value)) {
|
|
81
|
-
// Already a deep link (<scheme>://<host>?...). http(s) links are not deep
|
|
82
|
-
// links: only their query string is used below.
|
|
83
|
-
if (!/^https?:\/\//i.test(value)) return value;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const queryIndex = value.indexOf("?");
|
|
87
|
-
const query = queryIndex >= 0 ? value.slice(queryIndex + 1) : value;
|
|
88
|
-
|
|
89
|
-
if (!query.includes("=")) return null;
|
|
90
|
-
|
|
91
|
-
return `${getAppUrlScheme()}://open?${query}`;
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Key of the last app control whose PAYLOAD was handed to the app. Used to avoid
|
|
96
|
-
* opening the same deep link again when the app is merely brought back to the
|
|
97
|
-
* foreground. A launch without PAYLOAD (e.g. from the app icon) replaces the
|
|
98
|
-
* requested app control and resets this key, so the same link can be sent again.
|
|
99
|
-
*/
|
|
100
|
-
let lastHandledPayloadKey: string | null = null;
|
|
101
|
-
|
|
102
|
-
/** Test helper */
|
|
103
|
-
export const resetSamsungAppControlState = () => {
|
|
104
|
-
lastHandledPayloadKey = null;
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Returns the deep link carried by the app control that launched / resumed the
|
|
109
|
-
* app, or null if there is none or it was already handled.
|
|
110
|
-
*/
|
|
111
|
-
export const getSamsungAppControlUrl = (source = "launch"): string | null => {
|
|
112
|
-
const application = getTizenApplication();
|
|
113
|
-
|
|
114
|
-
if (!application) return null;
|
|
115
|
-
|
|
116
|
-
try {
|
|
117
|
-
const requested = application.getRequestedAppControl?.();
|
|
118
|
-
const data: AppControlData[] = requested?.appControl?.data ?? [];
|
|
119
|
-
const payload = data.find((item) => item?.key === PAYLOAD_KEY);
|
|
120
|
-
|
|
121
|
-
if (!payload) {
|
|
122
|
-
lastHandledPayloadKey = null;
|
|
123
|
-
|
|
124
|
-
log_debug(
|
|
125
|
-
`SamsungAppControl[${source}]: no PAYLOAD in requested app control`,
|
|
126
|
-
{
|
|
127
|
-
operation: requested?.appControl?.operation,
|
|
128
|
-
keys: data.map((item) => item?.key),
|
|
129
|
-
}
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
return null;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const payloadKey = JSON.stringify(payload.value ?? null);
|
|
136
|
-
|
|
137
|
-
if (payloadKey === lastHandledPayloadKey) {
|
|
138
|
-
log_debug(
|
|
139
|
-
`SamsungAppControl[${source}]: PAYLOAD already handled, skipping`,
|
|
140
|
-
{
|
|
141
|
-
payload: payload.value,
|
|
142
|
-
}
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
lastHandledPayloadKey = payloadKey;
|
|
149
|
-
|
|
150
|
-
const url = actionDataToUrl(payload.value?.[0]);
|
|
151
|
-
|
|
152
|
-
if (!url) {
|
|
153
|
-
log_error(`SamsungAppControl[${source}]: PAYLOAD is not a deep link`, {
|
|
154
|
-
payload: payload.value,
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
return null;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
log_info(
|
|
161
|
-
`SamsungAppControl[${source}]: deep link from app control: ${url}`,
|
|
162
|
-
{
|
|
163
|
-
payload: payload.value,
|
|
164
|
-
}
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
return url;
|
|
168
|
-
} catch (error) {
|
|
169
|
-
log_error(
|
|
170
|
-
`SamsungAppControl[${source}]: failed to read requested app control`,
|
|
171
|
-
{
|
|
172
|
-
error,
|
|
173
|
-
}
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
return null;
|
|
177
|
-
}
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Listens for deep links sent while the app is already running.
|
|
182
|
-
*
|
|
183
|
-
* On Samsung TVs (verified on a 2023 Tizen 7 model) a running app relaunched with
|
|
184
|
-
* an app control is only brought to the foreground: the new app control is
|
|
185
|
-
* available through getRequestedAppControl(), but no "appcontrol" event is fired.
|
|
186
|
-
* So the app control is checked both on "appcontrol" (fired by some runtimes /
|
|
187
|
-
* configurations) and when the page becomes visible again.
|
|
188
|
-
*
|
|
189
|
-
* Returns an unsubscribe function.
|
|
190
|
-
*/
|
|
191
|
-
export const addSamsungAppControlListener = (
|
|
192
|
-
onUrl: (url: string) => void
|
|
193
|
-
): (() => void) => {
|
|
194
|
-
if (
|
|
195
|
-
typeof window === "undefined" ||
|
|
196
|
-
typeof document === "undefined" ||
|
|
197
|
-
typeof window.addEventListener !== "function" ||
|
|
198
|
-
typeof document.addEventListener !== "function" ||
|
|
199
|
-
!getTizenApplication()
|
|
200
|
-
) {
|
|
201
|
-
return () => {};
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const check = (source: string) => {
|
|
205
|
-
const url = getSamsungAppControlUrl(source);
|
|
206
|
-
|
|
207
|
-
if (url) onUrl(url);
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
const onAppControl = () => check("appcontrol");
|
|
211
|
-
|
|
212
|
-
const onVisibilityChange = () => {
|
|
213
|
-
if (!document.hidden) check("visibilitychange");
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
window.addEventListener(APP_CONTROL_EVENT, onAppControl);
|
|
217
|
-
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
218
|
-
|
|
219
|
-
return () => {
|
|
220
|
-
window.removeEventListener(APP_CONTROL_EVENT, onAppControl);
|
|
221
|
-
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
222
|
-
};
|
|
223
|
-
};
|